Skip to content

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.

Notifications You must be signed in to change notification settings

andreas-wagner-dev/wrapfaces

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🎭 WrapFaces (Incubator state!!!)

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.

🔑 Key Facts

Object Autonomy

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.

Immutability

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.

No Markups

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.

True Heap State

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.

📥 Installation

The standard Maven or Gradle dependency is required:

Maven Dependency

<dependency>
    <groupId>org.wrapfaces</groupId>
    <artifactId>wrapfaces-core</artifactId>
    <version>[LATEST VERSION]</version>
</dependency>

🚀 The Code

Prevent build your application around Data Transfer Objects (DTOs) and XML Markups.

  • 🔎 The focus is on object-oriented, type-safe Java code.

The Adapter: No Controller

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 */ }

}

📱 The View Binding

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 Autonomous Component

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();
    }
}

⚙️ The Library: The Implementation Guide

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 Concrete Component: Overriding the Hooks

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: MVC with Encapsulation

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.

Design Philosophy & Deeper Concepts

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)

🛠️ The Purist's Tools

WrapFaces provides the necessary tools to enforce a clean object-oriented architecture:

💥 Markup Destruction

100% of UI logic and the component tree is in Java. The XHTML markup serves merely as a simple, non-critical container.

True State

Components live as Java objects in the heap. The framework handles the state management.

🧩 Pure Composition

The UI tree is defined type-safely in Java. Every component is an encapsulated domain object.

🔒 Type-Safe Binding

The model concept binds UI values via late-binding to new, immutable domain model instances.

🛠️ Immediate Action

Actions (clicks, changes, etc.) are bound directly to Java Lambda expressions.


🛑 No Compromises: "UI of Objects" as Architectural Dogma

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.

🎯 Goal: 100% integrity of Object-Orientation in web development with the "UI of Objects" approach.

"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.

Core Principles of "UI of Objects"

Object at the Center

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.

Tight Coupling between Domain and UI

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.

Behavior instead of just Data

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.

Composition of Objects

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.

Advantages of the Concept

  • 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.

🧠 The TRUTH: About traditional layered architectures with MVC etc.

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.

🤝 CONTRIBUTING

Participation is welcome. However, every contribution must adhere to the puristic, object-oriented standards of the framework.

📄 License

This project is under the MIT License.

📬 Support

Questions, suggestions, or problems? Get in touch!

About

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.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published