Building Stateless UI"

From Documentation
Line 141: Line 141:
  
 
== Locator ==
 
== Locator ==
When you manipulate stateless components with <code>UiAgent</code> API, you need to pass a <code>Locator</code>. Why? Since ZK doesn't have a reference to a stateless component on the server side, we cannot just call a method to remove it. Hence, we need to tell ZK client engine the target component we want to remove by describing its location with <code>Locator</code>.
+
When you manipulate stateless components with <code>UiAgent</code> API, you need to pass a <code>Locator</code>. Why? Because your Richlet doesn't have any reference to a stateless component on the server side, we don't have any setter to call. Hence, we need to tell ZK client engine the target component we want to manipulate by describing its location with <code>Locator</code>.
  
 
== Remove ==
 
== Remove ==

Revision as of 08:13, 29 November 2023


Building Stateless UI


Setting up

To set up stateless components in a ZK 10 application, you need to include the stateless components module and define a Dispatcher Richlet Filter in your WEB-INF/web.xml file.

Including Required Jar

dependencies {
    implementation "org.zkoss.zk:stateless:${zkVersion}"
    ...
}

Dispatcher Richlet Filter

<filter>
    <filter-name>DispatcherRichletFilter</filter-name>
    <filter-class>org.zkoss.stateless.ui.http.DispatcherRichletFilter</filter-class>
    <init-param>
        <param-name>basePackages</param-name>
        <param-value><!-- your base packages --></param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>DispatcherRichletFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>


Example Application

We will use the simple shopping cart application as an example to introduce basic features of stateless components:

Shoppingcart.png

Building UI with Richlet

Building user interfaces (UI) with stateless components requires creating a StatelessRichlet and mapping a URL to that richlet.

URL Mapping

We use @RichletMapping to compose a URL. When users visit that URL, ZK will invoke the corresponding method.

For example below, the index() URL will be <protocal>:// <host name: port> /shoppingCart.

    @RichletMapping("/shoppingCart")
    public class DemoRichlet implements StatelessRichlet {
        @RichletMapping("")
        public List<IComponent> index() {
            //return ...
        }
    }


Class-Level Mapping

At this level, @RichletMapping defines the base path for all methods in a StatelessRichlet. For example, assigning @RichletMapping("/shoppingCart") to the DemoRichlet class sets a foundational path for all UI components it manages.


Method-Level Mapping

Within the StatelessRichlet, each method can specify further URL mapping. By applying @RichletMapping("") to a method, the specified path appends to the class-level path.

Composing the UI with Stateless Components

Before ZK 10, ZK components are stateful, meaning that the server holds the state. Starting from ZK 10, we provide a set of stateless components as Immutable objects. Immutable objects are constructed once and can not be changed after they are constructed. After Immutable objects are rendered, they will be destroyed. Since the stateless component states will not be saved on the server, they consume less memory.

With stateless components, ZK offers a streamlined, fluent API for building user interfaces.

  • Every classic component has a corresponding stateless version, identified by an "I" prefix, denoting "immutable."
  • Stateless components employ a builder pattern, using methods like of() for initializing properties and withSclass() for setting classes.

Here's a comparison of UI composition between classic and stateless components:

Classic Component in ZK 9

Button button = new Button("add items");
button.setSclass("add-items");

Equivalent Stateless Component in ZK 10

IButton.of("add items")
.withSclass("add-items");


Therefor, for the method with URL mapping, we should return a list of components like:

	@RichletMapping("")
	public List<IComponent> index() {
		return asList(
			IStyle.ofSrc(DEMO_CSS),
			IVlayout.of(
				renderShoppingCart(),
				Boilerplate.ORDER_TEMPLATE
			)
		);
	}

Event Wiring

To wire an action handler method for an event, you need to call withAction(ActionHandler action) with a public method reference:

IButton.of("add item +")
    .withSclass("add-items")
    .withAction(ActionType.onClick(this::addItem))
  • Line 3: it means register addItem() as an action handler for onClick event on IButton. ActionType supports all types of component events.

In stateless components, we use the term action handler, which distinctly separates it from the event listener associated with classic components.

Hence, when a user clicks the button above, ZK will invoke addItem() declared in the Richlet.

Obtain Component State

Since a server no longer holds a component's state (it's on the client side), we provide @ActionVariable to access a UI component's state sent from the client. When ZK invokes an action handler for an event, it will pass the corresponding parameters you specified.

  • @ActionVariable(targetId = ActionTarget.SELF, field = "id") retrieves the value from the field of a component with the targetId on the client.
  • ActionTarget.SELF represents the component associated with the event which is a button. Please see ActionTarget for other targets.
    public void addItem(@ActionVariable(targetId = ActionTarget.SELF, field = "id") String orderId) {
    }
  • in this case, we will get a button's id

Get User Input

If you register an action handler on an input component like ICombobox:

ICombobox.of(initProductSize)
	.withReadonly(true)
	.withAction(ActionType.onChange(this::doSizeChange))

Declare InputData in the handler's signature, ZK will pass user input to you:

public void doSizeChange(InputData data, 
                         @ActionVariable(targetId = ActionTarget.PARENT, field = "id") String uuid){
    String value = data.getValue();
}

Update Component State

ZK provides various APIs on UiAgent to update a component's state. You need to call its method in an action handler method to implement your UI logic. Then those commands to update component states will be sent to the client after executing the method.

Locator

When you manipulate stateless components with UiAgent API, you need to pass a Locator. Why? Because your Richlet doesn't have any reference to a stateless component on the server side, we don't have any setter to call. Hence, we need to tell ZK client engine the target component we want to manipulate by describing its location with Locator.

Remove

The following code removes a component specified by Locator.

	public void doDelete(Self self,
						 @ActionVariable(targetId = ActionTarget.PARENT, field = "id") String id) {
		...
		UiAgent.getCurrent().remove(Locator.ofId(id));
	}


Add Child Components

public void addItem(@ActionVariable(targetId = ActionTarget.SELF, field = "id") String id) {
    UiAgent.getCurrent()
           .appendChild(Locator.ofId(SHOPPING_CART_ROWS),
                        renderShoppingCartOneItem(parseOrderId(id)));
}

Change Component's Property

UiAgent.getCurrent()
    .smartUpdate(Helper.getPriceLocator(self), 
                 new ILabel.Updater().value(String.valueOf(price)))
  • It changes a label'a value with a price

Example Application

You can download the shopping cart example project.




Last Update : 2023/11/29

Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.