Building User Interface Programmatically with Richlet

From Documentation
DocumentationSmall Talks2013JanuaryBuilding User Interface Programmatically with Richlet
Building User Interface Programmatically with Richlet

Author
Hawk Chen, Engineer, Potix Corporation
Date
January , 2013
Version
ZK 6.0 and later


Overview

A Richlet is a small Java program that composes a user interface programmatically in Java as an alternative to writing a ZUL for serving the user's request. When a user requests an URL that maps to a Richlet, ZK Loader hands over the UI creation process to the Richlet. It provides total control of component creation for developers. The choice between the ZUML pages and Richlets depends on your preference. In this article, we will demonstrate how to use Richlet by building a simple search application. For more explanation, please refer to Richlet in ZK Developer's Reference.

Example Application

The example application we are going to build is a simple car catalog application. This application has two functions:

  1. Search cars.
    Enter a keyword in the input field, click "Search" button and search results will be displayed in the car list below.
  2. View details.
    Click an item from the car list, the area below the car list will show the selected car's details including model, price, description, and preview.


Tutorial-searchexample.png

Implementation

To implement a richlet, your class should implement Richlet interface. However, you usually don't have to implement it from scratch. Rather, you could extend GenericRichlet and only override Richlet.service(Page). The method is called when the richlet is requested by users.

public class SearchRichlet extends GenericRichlet {

	@Override
	public void service(Page page) throws Exception {
		//...
	}

	@Override
	public void init(RichletConfig config) {
		//initialize resources, e.g. get initial parameters
	}
	
	@Override
	public void destroy() {
		//destroy resources
	}
}

To have better control, you can even implement the Richlet.init(RichletConfig) and Richlet.destroy() methods to initialize and to destroy resources required by the richlet.


Composing UI

One of richlet's main task is to compose UI. We suggest users to read basic concept of ZK component-based UI before starting to build UI in Java. ZK UI components constructs a tree structure, a component has one parent at the most while it may have multiple children. Some components accept only certain types of components as children, some doesn't allow to have any children at all. For example, Grid in XUL accepts Columns and Rows as children only. Please refer to ZK Component Reference for detail.


public class SearchRichlet extends GenericRichlet {

	@Override
	public void service(Page page) throws Exception {
		Component rootComponent = buildUserInterface();
		...
	}
	
	private Component buildUserInterface(){

		//build search area
		final Textbox keywordBox = new Textbox();
		Button searchButton = new Button("Search");
		searchButton.setImage("/img/search.png");
		
		Hbox searchArea = new Hbox();
		searchArea.setAlign("center");
		searchArea.appendChild(new Label("Keyword:"));
		searchArea.appendChild(keywordBox);
		searchArea.appendChild(searchButton);
		
		...
	}
...
}
  • Line 12: To create a component, simply just new an instance of a component class.
  • Line 14: Every attribute of a component has a corresponding setter method, we could use it to set a component's attribute.
  • Line 18: To establish a parent-child relationship between components, we can use appendChild(Component). It appends a component to the end of all existing children. There are other similar methods such as setParent(Component), insertBefore(Component , Component ). Please refer to Javadoc for details.

Rendering Model's Data

After providing a Model object for a Listbox, we usually need to specify how to render the Model. To do this, we need to create a renderer.

A renderer is a Java class that is used to render items specified in a component's model. We need to implement different interfaces for different components. For a Listbox we should implement ListitemRenderer. For other components' renderer, please refer to ZK Developer's Reference .

Item renderer for a Listbox

class CarRenderer implements ListitemRenderer<Car>{

	@Override
	public void render(Listitem listitem, Car car, int index) throws Exception {
		listitem.appendChild(new Listcell(car.getModel()));
		listitem.appendChild(new Listcell(car.getMake()));
		Listcell priceCell = new Listcell();
		priceCell.appendChild(new Label("$"));
		priceCell.appendChild(new Label(car.getPrice().toString()));
		listitem.appendChild(priceCell);
	}
}
  • In render() method, we need to compose desired UI by appending child on listitem.


After creating our renderer, we also need to set it to the Listbox.

carListbox.setItemRenderer(new CarRenderer());

Event Listener

In our example application, a user can clicks "Search" button to perform search, so we have to listen to "Search" button's "onClick" event. We can achieve this by invoking a component's method AbstractComponent.addEventListener(String, EventListener). The first parameter is event name, and the second one is an object that implements EventListener interface.

In an event listener, you can manipulate components to implement your application logic such as changing a component's attributes, create a new component, or even remove an existing component. You can realize a component's characteristics in ZK Component Reference to find out which attribute fulfills your requirement.

public class SearchRichlet extends GenericRichlet {

	private Component buildUserInterface(){
		
		...

		searchButton.addEventListener(Events.ON_CLICK, new EventListener<Event>() {
			//search
			@Override
			public void onEvent(Event event) throws Exception {
				String keyword = keywordBox.getValue();
				List<Car> result = carService.search(keyword);
				carListbox.setModel(new ListModelList<Car>(result));
			}
			
		});
		
		...
	}
}
  • Line 7: You can create a separate class to implement EventListener. Here we use anonymous class for simplicity. In a clustering environment, you should implement SerializableEventListener.
  • Line 10: Write your application logic inside onEvent() method, like get value, change a component's attribute, or update data.


Another function of the example application is when a user clicks a car in the Listbox, we display the car's detail in the detail area. To achieve this, we have to listen the select event of the Listbox, then get the selected car's data and set the data to components to display them.

public class SearchRichlet extends GenericRichlet {

	private Component buildUserInterface(){
		
		...

		carListbox.addEventListener(Events.ON_SELECT, new EventListener<SelectEvent>() {
			//show selected item's detail
			@Override
			public void onEvent(SelectEvent event) throws Exception {
				//get selection from listbox's model
				Set<Car> selection = ((Selectable<Car>)carListbox.getModel()).getSelection();
				if (selection!=null && !selection.isEmpty()){
					Car selected = selection.iterator().next();
					previewImage.setSrc(selected.getPreview());
					modelLabel.setValue(selected.getModel());
					makeLabel.setValue(selected.getMake());
					priceLabel.setValue(selected.getPrice().toString());
					descriptionLabel.setValue(selected.getDescription());
				}
			}
		});

		...
	}
}

Configuration

Two steps is required to make a Richlet available to clients.

  1. Turn on the support for Richlets (in WEB-INF/web.xml)
  2. Map an URL pattern to a Richlet (in WEB-INF/zk.xml)

Turn on Richlet Support

By default, richlets are disabled. To enable them, please add the following declaration to WEB-INF/web.xml.

<servlet-mapping>
    <servlet-name>zkLoader</servlet-name>
    <url-pattern>/zk/*</url-pattern>
</servlet-mapping>

You can change /zk/* to other URL pattern you like, such as /do/*. Notice that you cannot map it to an extension (such as *.do) since it will be considered as a ZUML page (rather than a Richlet).


Map an URL pattern to a Richlet

For each Richlet you implement, you should declare it in WEB-INF/zk.xml with the following:

 
	<richlet>
		<richlet-name>SearchRichlet</richlet-name>
		<richlet-class>tutorial.richlet.SearchRichlet</richlet-class>
	</richlet>

	<richlet-mapping>
		<richlet-name>SearchRichlet</richlet-name>
		<url-pattern>/search</url-pattern>
	</richlet-mapping>
  • Line 6: After defining a Richlet, you can map it to any number of URL patterns with richlet-mapping element.


Then, you can visit http://localhost:8080/PROJECT_NAME/zk/search to visit the R ichlet.

The URL specified in the url-pattern element must start with /. If the URL ends with /*, it is matched to all request with the same prefix. To retrieve the request's actual URL, you can check the value returned by the getRequestPath method of the current page.

public class MyRichlet extends GenericRichlet {

	@Override
	public void service(Page page) throws Exception {
		if ("/search/admin".equals(page.getRequestPath())){
			//build admin UI
		}else{
			//build normal UI
		}
		...
	}
}

Summary

ZK presents its flexibility by providing multiple building UI approaches where you can choose one of them or even mix them according to your preferences or environment.

Download

Source code used in the article can be found here.


Comments



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