WrapFaces is a component Wrapper solution for Jakarta Server Faces (JSF).
It's transfers the object-oriented discipline of desktop frameworks like Swing to the web.
If you are serious about object-orientation and love the Web?
- Then WrapFaces is exactly the right Horse for you!
It enables you to build web based user interfaces according to the original concepts of object-oriented programming as defined by Alan Kay:
- “OOP means only messaging, local retention, protection and hiding of state-process, with extreme late-binding of all things.”
- Objects should not read or modify the internal state of other objects in unexpected ways.
- An object has an interface that determines how it can be interacted with.
The Object as Domain Model takes full responsibility for its own presentation via the Model::displayFrom() method. This enforces the Single Responsibility Principle (SRP) in the object oriented way.
- An end to anemic data models. The presentation belongs to the object being presented.
It's prevents harmful setter calls from the UI binding. Instead, a new, immutable instance of the Domain Model is created from the UI values via the map() mechanism.
- Guaranteed data integrity and reduction of side effects.
The entire UI structure and logic is defined exclusively in type-safe Java code. The XHTML serves merely as an inactive, empty container.
- 100% refactorability, compile-time validation, and elimination of XML boilerplate.
The view state lives as a long-lived Java object in the JVM Heap (View-Scoped). The statelessness of the HTTP protocol is transparently and completely abstracted.
- A desktop application development experience on the web with focus on OO logic, not protocol details.
The standard Maven or Gradle dependency is required:
Maven Dependency
<dependency>
<groupId>org.wrapfaces</groupId>
<artifactId>wrapfaces-core</artifactId>
<version>[LATEST VERSION]</version>
</dependency>Prevent build your application around Data Transfer Objects (DTOs) and XML Markups.
- 🔎 The focus is on object-oriented, type-safe Java code.
The adapter, as JavaBean, is the minimal contact point to the JSF world. It only serves to hold and provide the component tree generated by WrapFaces.
- ⛔ Business logic is forbidden here.
// Example: The LoginView (Your JSF-Managed-Bean)
@Named @ViewScoped
public class LoginView {
private HtmlForm form;
@PostConstruct
public void init() {
// 1. Creation of the Domain Model
User user = new User("admin", new Credential("secret123"));
// 2. The Model creates the UI
form = user.displayFrom();
}
// JSF requires Getter/Setter for the binding
public HtmlForm getForm() { return form; }
public void setForm(HtmlForm form) { /* empty */ }
}In the corresponding XHTML it use the binding attribute of JSF tags (not value):
<h:form binding="#{loginView.form}"/>only one line is needed.
The domain model takes responsibility for its presentation and persistence with the mapping of UI values into a new, immutable instance:
- 👉 Business logic is only here (Inside the domain Object class).
// UI-Creation (inside the User class)
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private Credential credential;
public User(String name, Credential credential) {
this.name = name;
this.credential = credential;
}
public Credential credential() {
return credential;
}
// Constructor for mapping the values from the Component back into the object
public User(Map<String, Object> map) {
// delegates the mapping to the with nested object
this(map.getOrDefault("txtUser", "").toString(), new Credential(map));
}
// The method that displays itself as UI components
public Form displayFrom() {
// PanelGroup for user input
PanelGrid<User> userGrid = new PanelGrid<User>("userGrid")
// Use a 2-column layout (default for PanelGrid)
.addRow(new Label("lblUser", "User:"), new Text("txtUser", this.name))
.addRow(new Label("lblSecret", "Secret:"), credential.displayInput())
// Defines the mapping function that converts the Map back into a User object
.map(User::new); // Uses the Map constructor above
// PanelGroup for the buttons
PanelGroup buttonGroup = new PanelGroup("btnGrp",
// ... only messaging
new Button("btnCancel", "Abbrechen").onAction(e -> System.out.println("Canceled.")),
new Button("btnSubmit", "Senden").onAction(e -> {
// Here, the new User model is retrieved from the UI values
User updatedUser = userGrid.model(); // Use userGrid here
System.out.println("Submitted. Updated User: " + updatedUser.toString());
updatedUser.credential.authenticate();
}));
return new Form("loginForm", userGrid, buttonGroup);
}
// No!!! Getter/Setter like JSF beans - not OOP conform
// for EL binding present object with toString() and use hashCode for id.
@Override
public String toString() {
return "name:" + name + ", credential:" + credential;
}
@Override
public int hashCode() {
return Objects.hash(getClass().getSimpleName(), name);
}
@Override
public boolean equals(Object obj) {
return obj != null && hashCode() == obj.hashCode();
}
}
public static class Credential implements Serializable {
private static final long serialVersionUID = 1L;
private String value;
public Credential(String value) {
this.value = value;
}
// Constructor for mapping the value from the Component Map back into the object
public Credential(Map<String, Object> map) {
this(map.getOrDefault("txtSecret", "").toString());
}
public void authenticate() {
// Simulate authentication
System.out.println("Authenticating with secret: " + value);
}
// The method that displays itself as a UI component
public Text displayInput() {
return new Text("txtSecret", value);
}
// No!!! Getter/Setter like JSF beans - not OOP conform
// for EL binding present object with toString() and hashCode for id.
@Override
public String toString() {
return "*****";
}
@Override
public int hashCode() {
return Objects.hash(getClass().getSimpleName(), value);
}
@Override
public boolean equals(Object obj) {
return obj != null && hashCode() == obj.hashCode();
}
}WrapFaces enforces discipline through the clear separation of technical inheritance (JSF) and application-specific logic (Hooks).
- The wrapper uses the Decorator Pattern to inject logic surgically.
Purpose of the Decorator
- The wrapper class encloses the native JSF component and serves as a type-safe decorator, offering clear,
overridable entry points(Hooks) into the rendering process.
The Contract: WrapComponent<T>
- Defines the contract for transferring the transient state to the underlying JSF component before rendering.
- (optional) The Architecture: The Abstract Base Class (
*Wrap) - The base class technically inherits from the JSF component but uses Delegation and Composition for all application logic. This is the central control point in
encodeBegin():
// Encapsulation of HtmlInputText (as Text field)
public static class Text extends HtmlInputText implements WrapComponent<HtmlInputText>,
ValueChangeListener, AjaxBehaviorListener, Serializable {
private static final long serialVersionUID = 1L;
private final String id;
private final String initialValue;
private List<Changed<EventObject, String>> events = new ArrayList<>(0);
private boolean initialized = false;
public Text() {
this.id = null;
this.initialValue = null;
}
public Text(String id, String initialValue) {
this.id = id;
this.initialValue = initialValue;
this.setTransient(true);
}
// --- WrapComponent Hook (Called after initialization in encodeBegin) ---
@Override
public void render(FacesContext context, HtmlInputText uiComponent) {
// Can be used for custom renderer logic if needed
}
// --- Component Lifecycle (Initialization during render phase) ---
@Override
public void encodeBegin(FacesContext context) throws IOException {
// ... late-binding
if (!initialized) {
if (this.id != null) {
this.setId(this.id);
}
if (this.initialValue != null) {
this.setValue(this.initialValue);
}
addValueChange(this, this);
initialized = true;
}
render(context, this);
super.encodeBegin(context);
}
private boolean addValueChange(UIComponent e, ValueChangeListener valueChangeListener) {
if (e instanceof EditableValueHolder) {
EditableValueHolder editableValueHolder = (EditableValueHolder) e;
for (ValueChangeListener listener : editableValueHolder.getValueChangeListeners()) {
if (listener.getClass().equals(valueChangeListener.getClass())) {
return true;
}
}
editableValueHolder.addValueChangeListener(valueChangeListener);
}
return false;
}
// Fluent API for event handlers
public Text onChanged(Changed<EventObject, String> event) {
events.add(event);
return this;
}
@Override // ValueChangeListener implementation: delegates to the registered lambdas
public void processValueChange(ValueChangeEvent event) throws AbortProcessingException {
events.forEach(e -> e.accept(event, event.getNewValue() != null ? event.getNewValue().toString() : ""));
}
@Override // AjaxBehaviorListener implementation: delegates to the registered lambdas
public void processAjaxBehavior(AjaxBehaviorEvent event) throws AbortProcessingException {
if (event.getComponent() instanceof EditableValueHolder) {
Object value = ((EditableValueHolder) event.getComponent()).getValue();
events.forEach(e -> e.accept(event, value != null ? value.toString() : null));
}
}
}The logic resides in the hooks, which can be overridden in the concrete implementation or the base classes:
| Hook Method | Use Case (Real Control) | Example |
|---|---|---|
initialize(...) |
Dynamic Visibility: Prevents rendering of the entire component if a condition fails. | if (!isUserAdmin()) uiComponent.setRendered(false); |
render(...) |
Attribute Dominance: Sets dynamic attributes immediately before the renderer call. | uiComponent.getPassThroughAttributes().put("role", "presentation"); |
WrapFaces enforces the MVC pattern from a strictly object-oriented perspective — a clear demarcation from traditional layered architectures with vertical layers and technical responsibility.
| Role | WrapFaces / OOP Approach | Traditional JSF / DTO & Markup Approach |
|---|---|---|
| Model | The pure Value Object. It encapsulates its presentation (displayFrom()). |
Usually a Managed-Bean that violates encapsulation via setter-bindings. |
| View | The dynamic component hierarchy generated in Java. | The .xhtml file, static and hard to abstract. |
| Controller | Behavior is integrated directly into the components or the mapping logic. | The central, overloaded Managed Bean that carries too many responsibilities. |
The design philosophy of WrapFaces is based on measurable OOP metrics (Cohesion/Coupling) rather than subjective layered architectures.
Details
| Source | Concept |
|---|---|
| javadevguy.wordpress.com | Pragmatic SRP & Cohesion: The definition of SRP as maximizing cohesion and minimizing coupling. Object Mechanics & Immutability: The concepts of encapsulation, immutability, and the strict avoidance of getters/setters (Naked Data) |
WrapFaces provides the necessary tools to enforce a clean object-oriented architecture:
100% of UI logic and the component tree is in Java. The XHTML markup serves merely as a simple, non-critical container.
Components live as Java objects in the heap. The framework handles the state management.
The UI tree is defined type-safely in Java. Every component is an encapsulated domain object.
The model concept binds UI values via late-binding to new, immutable domain model instances.
Actions (clicks, changes, etc.) are bound directly to Java Lambda expressions.
WrapFaces is more than a JSF commponent library — it is a manifesto for "UI of Objects" against anemic data containers and leaky abstractions in the Web UI — a puristic approach against the fundamental flaws of data- and markup-centric MVC through consistent, type-safe abstractions.
"UI of Objects" is a design philosophy that consistently adheres to the principles of object-orientation.
Instead of viewing the user interface as a collection of screens, forms, and commands, it is understood as a system of interacting, real objects. The user does not interact with a user interface that manipulates data; the user interacts directly with the objects found in the system.
The user interface is organized around the system's objects (e.g., Customer, Product, Order) and not around the actions one can perform with them. The user first selects an object and then decides what to do with it.
In contrast to the strict separation in classic MVC, the UI is seen as a natural property of the domain objects. An object knows how to display itself. This leads to high cohesion and strong encapsulation.
Domain objects are not simple Data Transfer Objects (DTOs). They encapsulate both data and the behavior necessary to manipulate that data. The "Tell, Don't Ask" principle is consistently applied: The UI component requests the domain object to do something, rather than querying its data and manipulating it from the outside.
Complex user interfaces are created through the composition of smaller, self-contained UI objects. A ProductList consists of several ProductCard objects, each representing its own domain object.
- Intuitive Operation: The UI mirrors the user's way of thinking, who thinks in objects, not tasks. This makes operation more intuitive.
- High Reusability: The UI components are tightly coupled with their respective domain object. This makes them modular and easier to reuse.
- Better Maintainability: The encapsulation of data and behavior within the objects ensures that changes remain localized.
- Alignment of UX and OOP: The concept aligns User Experience (UX) and the principles of Object-Oriented Programming (OOP), as both are based on the idea of objects and their relationships.
WrapFaces was developed to consequently enforce the principles of Object-Orientation (OOP) and Domain-Driven Design (DDD) in the UI layer. It corrects the inherent weaknesses of the traditional, data- and markup-centric MVC pattern:
| Principle | Traditional MVC | UI of Objects with WrapFaces → Solution |
|---|---|---|
| Encapsulation | Controllers violate encapsulation by manipulating internal model data. | The Value Object creates itself anew. No harmful setter-bindings by UI elements. |
| Model Role | The model degenerates into an anemic data container without behavior. | The Model is autonomous: A Value Object with embedded behavior and the ability to display itself (displayFrom()). |
| Controller | The controller acts as a central hub, knows model internals, and violates the "Tell, Don't Ask" principle as well as the Single Responsibility Principle (SRP). | Logic is integrated directly into type-safe lambda handlers. The controller role is reduced to minimal routing. The component decides for itself. |
| State | The developer must manually manage state in the HttpSession and handle HTTP details. | Web pages are stateful Java objects in the JVM Heap. HTTP protocol details are transparently abstracted. |
| Autonomy | Display and manipulation logic is spread across three layers. Low cohesion. | The domain object bundles data, behavior, and presentation. Maximum cohesion. |
Participation is welcome. However, every contribution must adhere to the puristic, object-oriented standards of the framework.
This project is under the MIT License.
Questions, suggestions, or problems? Get in touch!