Chapter 4: Controlling Components
ZK's Components are not just only for constructing user interface, we even can control them. In this chapter, we continue to use the last chapter's example but we remove 3 items with hyper links in the sidebar and replace them with redirecting action. To achieve this, we should write codes in Java for each item to response a user's clicking and redirect the user to an external site.
Control Components in Zscript
The simplest way to response a user's clicking is to write a event listener method and invoke it in onClick attribute. We could define a event listener in Java inside a <zscript> element and those codes will be interpreted when the ZUL is visited. This element also allows other script language like Javascript, Ruby, or Groovy.
Event listener redirect()
<grid hflex="1" vflex="1" sclass="sidebar">
<zscript><![CDATA[
//zscript code, it runs on server site, use it for fast prototyping
java.util.Map sites = new java.util.HashMap();
sites.put("zk","http://www.zkoss.org/");
sites.put("demo","http://www.zkoss.org/zkdemo");
sites.put("devref","http://books.zkoss.org/wiki/ZK_Developer's_Reference");
void redirect(String name){
String loc = sites.get(name);
if(loc!=null){
execution.sendRedirect(loc);
}
}
]]></zscript>
...
- Line 11: Define a event listener method like normal Java method and it redirects a browser according to passed key.
- Line 14: The execution is a implicit variable which you can use it directly without declaration. It represents an execution of a client request that holds relevant information.
After defining the event listener, we should specify it in a Row's event attribute onClick because we want to invoke the event listener when clicking a Row.
Invoke event listeners at "onClick"
<grid>
...
<rows>
<row sclass="sidebar-fn" onClick='redirect("zk")'>
<image src="/imgs/site.png"/> ZK
</row>
<row sclass="sidebar-fn" onClick='redirect("demo")'>
<image src="/imgs/demo.png"/> ZK Demo
</row>
<row sclass="sidebar-fn" onClick='redirect("devref")'>
<image src="/imgs/doc.png"/> ZK Developer Reference
</row>
</rows>
</grid>
Through above steps, now if you click a Row of the Grid in the sidebar, your browser will be redirected to corresponding site.
This approach is very simple and fast, so it is especially suitable for building prototype. But if you need a better architecture for your application, you had better separate these codes from a ZUL.
Control Components in Controller
In this section, we will demonstrate how to redirect users to external site with a Controller and event listeners when they click an item in the sidebar.
The most commonly-used architecture to divide an web application is MVC (Model-View-Controller) which separates an application into 3 parts. The Model is responsible for exposing data and perform business logic which is usually implemented by users, and the View is responsible for displaying data which is what ZUL does. The Controller can change the View's presentation and handle events from the View. The benefit of designing an application in MVC architecture is that your application is more modularized.
In ZK world, there is a Composer plays the same role as the Controller and you can assign it to a target component. Through the composer, you can listen events of the target component and manipulate target component's child components to change View's presentation according to your requirement. To create a Controller in ZK is simply creating a class that inherits SelectorComposer.
public class SidebarChapter4Controller extends SelectorComposer<Component>{
//other codes...
}
Then you have to "connect" the composer with a component in the zul by specifying full qualified class name in apply attribute.
chapter4/sidebar.zul
<grid hflex="1" vflex="1" sclass="sidebar"
id="fnList"
apply="org.zkoss.tutorial.chapter4.SidebarChapter4Controller">
<columns>
<column width="36px"/>
<column/>
</columns>
<rows/>
</grid>
- Line 3: A component id can be used to retrieve the component in a composer, please see the next section.
- Line 8: Here we don't create 3 Rows in the zul because we need to add an event listener programmatically on each Row in the composer.
Wire Components
To control a component, we must retrieve it first. In SelectorComposer, when you specify a @Wire
annotation on a field or setter method, the SelectorComposer will automatically find the component and assign it to the field or pass it into the setter method. By default SelectorComposer will find the component whose id equals to the variable name.
public class SidebarChapter4Controller extends SelectorComposer<Component>{
//wire components
@Wire
Grid fnList;
...
}
- Line 4: SelectorComposer looks for a Grid whose id is "fnList" and assign it to the variable fnList.
Initialize the View
It is very common that we need to initialize components when a zul is loaded. In our example, we need to create Rows of the Grid for the sidebar, therefore we should override a composer life-cycle method doAfterCompose(Component). The passed argument Component is the component that the composer applies to, that is, Grid in our example. This method will be called after the applied component's all child components are created, so we can change components' attributes or even creates other components in it.
public class SidebarChapter4Controller extends SelectorComposer<Component>{
//wire components
@Wire
Grid fnList;
//services
SidebarPageConfig pageConfig = new SidebarPageConfigChapter4Impl();
@Override
public void doAfterCompose(Component comp) throws Exception{
super.doAfterCompose(comp);
//initialize view after view construction.
Rows rows = fnList.getRows();
for(SidebarPage page:pageConfig.getPages()){
Row row = constructSidebarRow(page.getLabel(),page.getIconUri(),page.getUri());
rows.appendChild(row);
}
}
}
- Line 8: Here we demonstrate a configurable architecture, the SidebarPageConfig stores hyperlink's configuration such as URL, and label and we use this configuration to create and setup components in the sidebar.
- Line 12: You have to call super class doAfterCompose() method , because it performs initialization like wiring components for you.
- Line 15 - 20: These codes involve the concept that we have not talked about yet. All you have to know for now is these codes create Row with event listeners and put them into Grid. We will discuss them in next section.
Create Components & Event Listeners Dynamically
Manipulating components is the most powerful feature of ZK. You can change the user interface as you want by creating, removing, or changing components and all change you made will reflect to clients.
Now we are going to explain how to create components and add event listener to response users' clicking. Basically, there are 3 steps to create a component:
- Create a component object.
- Setup the component's attributes.
- Append to the target parent component.
In constructSidebarRow() method, we create Rows and add an event listener to each of them.
public class SidebarChapter4Controller extends SelectorComposer<Component>{
//...
//wire components
@Wire
Grid fnList;
//services
SidebarPageConfig pageConfig = new SidebarPageConfigChapter4Impl();
@Override
public void doAfterCompose(Component comp) throws Exception{
super.doAfterCompose(comp);
//initialize view after view construction.
Rows rows = fnList.getRows();
for(SidebarPage page:pageConfig.getPages()){
Row row = constructSidebarRow(page.getLabel(),page.getIconUri(),page.getUri());
rows.appendChild(row);
}
}
private Row constructSidebarRow(String name,String label, String imageSrc, final String locationUri) {
//construct component and hierarchy
Row row = new Row();
Image image = new Image(imageSrc);
Label lab = new Label(label);
row.appendChild(image);
row.appendChild(lab);
//set style attribute
row.setSclass("sidebar-fn");
//create and register event listener
EventListener<Event> actionListener = new SerializableEventListener<Event>() {
private static final long serialVersionUID = 1L;
public void onEvent(Event event) throws Exception {
//redirect current url to new location
Executions.getCurrent().sendRedirect(locationUri);
}
};
row.addEventListener(Events.ON_CLICK, actionListener);
return row;
}
}
- Line 21: Append a newly-created Row will make it become a child component of Rows.
- Line 28: The first step to create a component is instantiating its class.
- Line 32: Append a component to establish the parent-child relationship.
- Line 36: You can change a component's attributes by various setter methods and their method names correspond to tag's attribute name.
- Line 39: We create an event listener anonymous class for convenient.
- Line 44: Implement the business logic inonEvent() method, and this method will be called if the listened event is sent to the server. Here we get current execution by Executions and redirect a client to a new URL.
- Line 48: Apply the event listener to a Row for listening Events.ON_CLICK event which is triggered by a mouse clicking action.