ZK MVC Made Easy
Henri Chen, Principal Engineer, Potix Corporation
August 28, 2008
Applicable to ZK 3.0.8 and later.
- Applicable to ZK 3.5.0 and later.
Introduction
The ZK Team are always looking for the best practice to realize MVC approaches in ZK. In this article and this article there have been some discussions about MVC programming on ZK. In this article, I am going to discuss in details the three utility classes and three helper methods provided by ZK that will help developers to write applications in Model-View-Controller pattern easily. Besides, they also provides a convenient path for refactoring prototype codes that originally written in zul zscript to pure Java class codes.
I will use a simple example to illustrate the power of these utility classes and helper methods.
The Example
Two input textboxes for the user to input First Name and Last Name. The Full Name will be automatically updated when either input textbox changed.
The Day Before - Implement Own Composer Class
composer1.zul
<window title="composer1 example" border="normal" width="300px" apply="MyComposer1">
<grid>
<rows>
<row>First Name: <textbox id="firstName" forward="onChange=onFirstName"/></row>
<row>Last Name: <textbox id="lastName" forward="onChange=onLastName"/></row>
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
MyComposer1.java
...
public class MyComposer1 implements Composer {
private Textbox firstName;
private Textbox lastName;
private Label fullName;
public void doAfterCompose(Component win) throws Exception {
firstName = (Textbox) win.getFellow("firstName");
lastName = (Textbox) win.getFellow("lastName");
fullName = (Label) win.getFellow("fullName");
//define and register event listeners
win.addEventListener("onFirstName",
new EventListener() {
public void onEvent(Event event) throws Exception {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
});
win.addEventListener("onLastName",
new EventListener() {
public void onEvent(Event event) throws Exception {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
});
}
}
Here the composer1.zul defines the View of the application while the MyComposer1 class intervenes in the life cycle of the window creation and is responsible for the definition and registration of the event listener. However, the more you write these codes, the more you will find the same addEventListener pattern occurs repeatedly. So..., "Can we take away those addEventListener codes?"
The org.zkoss.zk.ui.util.GenericComposer utility class is ZK's response to this question.
Just Write those onXxx Event Listener Codes
composer2.zul
<window title="composer2 example" border="normal" width="300px" apply="MyComposer2">
<grid>
<rows>
<row>First Name: <textbox id="firstName" forward="onChange=onFirstName"/></row>
<row>Last Name: <textbox id="lastName" forward="onChange=onLastName"/></row>
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
MyComposer2.java
...
public class MyComposer2 extends GenericComposer {
private Textbox firstName;
private Textbox lastName;
private Label fullName;
public void doAfterCompose(Component win) throws Exception {
super.doAfterCompose(win);
//locate ZK components
firstName = (Textbox) win.getFellow("firstName");
lastName = (Textbox) win.getFellow("lastName");
fullName = (Label) win.getFellow("fullName");
//all addEventListener and new EventListener() codes are removed
}
public void onFirstName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
public void onLastName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
}
The MyComposer2 class inherits the GenericComposer and write onXxx event listener codes directly. The doAfterCompose method of GenericComposer class will do the tedious addEventListener for you. This is a typical Design by Convention approach. All methods with the onXxx naming pattern would be deemed as an event handling code and is registered automatically.
Auto-wire the Components and Beans
For a typical interactive rich application, the event handling codes usually have to access data model beans and manipulate UI components to interact with the end users. Getting references to such components and data beans are a common practice in event handling codes. That is why we need those getFellow() codes in MyComposer2 class. On the other hand, these are where a framework can play a role. Why not just bind these components and data beans automatically?
So here comes the org.zkoss.zk.ui.util.GenericAutowireComposer utility class which extends GenericComposer class and add the auto-wire features.
composer3.zul
<window title="composer3 example" border="normal" width="300px" apply="MyComposer3">
<grid>
<rows>
<row>First Name: <textbox id="firstName" forward="onChange=onFirstName"/></row>
<row>Last Name: <textbox id="lastName" forward="onChange=onLastName"/></row>
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
MyComposer3.java
...
public class MyComposer3 extends GenericAutowireComposer {
private Textbox firstName; //auto-wired
private Textbox lastName; //auto-wired
private Label fullName; //auto-wired
//all getFellow() codes are removed
public void onFirstName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
public void onLastName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
}
Takes a look of the MyComposer3 class. It extends GenericAutowireComposer and all getFellow() methods can be removed. The doAfterCompose method of GenericAutowireComposer class will inject the proper components to the fields for you. In fact, if you define proper variable resolver for example the Spring variable resolver in your zul file, the GenericAutowireComposer will also inject Spring beans for you.
Following are some snippet codes for that concept. I will discuss ZK + Spring using this approach in another article.
spring-config.xml
...
<bean id="taskDao" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
...
taskEditor.zul
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<window id="taskWnd" title="Task Editor" border="normal" width="500px" apply="TaskEditorComposer">
<grid>
<rows>
<row>Title: <textbox id="title"/></row>
<row>Description: <textbox id="description"/></row>
</rows>
</grid>
<button id="saveBtn" label="Save" forward="onClick=onSaveTask"/>
</window>
TaskEditorComposer.java
public class TaskEditorComposer extends GenericAutowireComposer {
private TransactionProxyFactoryBean taskDao; //Spring bean auto wired
private Textbox title; //auto wired
private Textbox description; //auto wired
private Button saveBtn; //auto wired
public void onSaveTask(Event event) {
Task currentTask = componentScope.get("task");
currentTask.setTitle(title.getValue());
currentTask.setDescription(description.getValue());
taskDao.update(currentTask);
}
...
}
Supporting of Implicit Objects
As the code in zscript can use some implicit objects directly, the GenericAutowrieComposer also supports such implicit objects concept and even the zscript alert() methods. MyComposer4 demonstrates you this feature. Whenever the firstName changed, it will ask self (i.e. the implicit object which is applied the composer; i.e. the Window) to change its title and popup a message alert window. This brings in another benefits. It provides a convenient path for you to easily refactor your prototype zscript codes into product Java class code. You can just copy-and-paste and do some minor modification even if you have used some such implicit objects that used to exists in zscript only. Check the following composer4-1.zul that use zul and zscript only. See how similar of its onFirstName() and onLastName() codes to those of the MyComposer4.java
composer4.zul
<window title="composer4 example" border="normal" width="300px" apply="MyComposer4">
<grid>
<rows>
<row>First Name: <textbox id="firstName" forward="onChange=onFirstName"/></row>
<row>Last Name: <textbox id="lastName" forward="onChange=onLastName"/></row>
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
MyComposer4.java
public class MyComposer4 extends GenericAutowireComposer {
private Textbox firstName;
private Textbox lastName;
private Label fullName;
public void onFirstName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
((Window)self).setTitle("First Name changed");
alert("First Name changed to "+firstName.getValue());
}
public void onLastName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
((Window)self).setTitle("Last Name changed");
alert("Last Name changed to "+lastName.getValue());
}
}
composer4-1.zul (quick prototyping)
<window title="composer4-1 example" border="normal" width="300px">
<attribute name="onFirstName"><![CDATA[
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
((Window)self).setTitle("First Name changed");
alert("First Name changed to "+firstName.getValue());
]]></attribute>
<attribute name="onLastName"><![CDATA[
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
((Window)self).setTitle("Last Name changed");
alert("Last Name changed to "+lastName.getValue());
]]></attribute>
<grid>
<rows>
<row>First Name: <textbox id="firstName" forward="onChange=onFirstName"/></row>
<row>Last Name: <textbox id="lastName" forward="onChange=onLastName"/></row>
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
Get Rid of Those "forward" Attributes
Until now, the previous example codes always use the forward attribute in zul file to forward onChange event of the firstName textbox and lastName textbox to the Window as onFirstName and onLastName event, respectively. No doubt these are also repeat patterns in a ZK MVC practice. "Can we remove such forward attributes?"
The org.zkoss.zk.ui.util.GenericForwardComposer utility class is designed to fulfill this requirement. Note that GenericForwardComposer extends the GenericAutowireComposer class, so it has all features of its parent class(GenericAutowireComposer) and grandparent class(GenericComposer).
composer5.zul
<window title="composer5 example" border="normal" width="300px" apply="MyComposer5">
<grid>
<rows>
<row>First Name: <textbox id="firstName"/></row><!-- forward is removed -->
<row>Last Name: <textbox id="lastName"/></row><!-- forward is removed -->
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
MyComposer5.java
public class MyComposer5 extends GenericForwardComposer {
private Textbox firstName;
private Textbox lastName;
private Label fullName;
//onChange event from firstName component
public void onChange$firstName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
//onChange event from lastName component
public void onChange$lastName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
}
When writing composer that extends GenericForwardComposer, you have to follow some naming rules on your event listener methods. It is another Design by Convention pattern implementation. Check the MyComposer5 class. The naming of onChange$firstName event listener is read as "onChange event from the firstName component" and the GenericForwardComposer will help you to forward the event to the applied component. As you can see, the forward attributes are removed from the composer5.zul without problems. So now we have a clean separation between the View the .zul file and the Controller the composer codes.
What if I Cannot Inherit from These ZK Utility Classes
In some cases, we might have to inherit our Composer class from another class for some reason. Are we restrictive to have the benefits of these GenericXxxComposer? Not at all! The ZK Team provides three corresponding helper methods to do the same features without requiring the inheritance from those GenericXxxComposer classes. (In this article Qamaralzaman Habeek also suggested one way to register the ZK event listeners automatically.)
composer6.zul
<window title="composer6 example" border="normal" width="300px" apply="MyComposer6">
<grid>
<rows>
<row>First Name: <textbox id="firstName"/></row>
<row>Last Name: <textbox id="lastName"/></row>
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
MyComposer6.java
public class MyComposer6 implements Composer {
private Textbox firstName;
private Textbox lastName;
private Label fullName;
public void doAfterCompose(Component win) throws Exception {
//wire variables
Components.wireVariables(win, this);
//register onXxx event listeners
Events.addEventListeners(win, this);
//auto forward
Components.addForwards(win, this);
}
public void onChange$firstName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
public void onChange$lastName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
}
Composer6 example is the refactored codes of the Composer1 example. See the comments on each line. Each helper method provides a feature to remove the boilerplate codes.
- org.zkoss.zk.ui.event.Events.addEventListeners(Component comp, Object controller): Add onXxx event listener defined in controller object to the specified component. This helper method registers those onXxx events to the specified component so you don't have to implement and add EventListener into the component one by one. This helper method provides the same feature of the GenericComposer.
- org.zkoss.zk.ui.Components.wireVariables(Component comp, Object controller): Wire accessible variable objects of the specified component into a controller Java object. This helper method wire the embedded objects, components, and accessible variables into the controller object per the components' id and variables' name automatically for you so you don't have to call getFellows() or Path.getComponents() etc. to get references. This helper method provides the same feature of the GenericAutowireComposer.
- org.zkoss.zk.ui.Components.addForwards(Component comp, Object controller): Adds forward conditions to the source components defined in the controller object so their onXxx events can be forwarded to the specified target component. This helper method searches those onXxx$id methods defined in the controller object and adds forward condition to the components as specified in id so you don't have to specify in zul file the forward attribute one by one. This helper method provides the same feature of the GenericForwardComposer.
How About if I Use the "use" attribute
Since day one, ZK has provided a way to do MVC by using customized component as a controller. "Can we have the benefits of these three helper methods in such conditions?" Sure you can!
mywindow.zul
<window title="mywindow example" border="normal" width="300px" use="MyWindow">
<grid>
<rows>
<row>First Name: <textbox id="firstName"/></row>
<row>Last Name: <textbox id="lastName"/></row>
<row>Full Name: <label id="fullName"/></row>
</rows>
</grid>
</window>
MyWindow.java
...
public class MyWindow extends Window implements AfterCompose {
private Textbox firstName;
private Textbox lastName;
private Label fullName;
//-- AfterCompose --//
public void afterCompose() {
//wire variables
Components.wireVariables(this, this);
//NO need to register onXxx event listeners
//auto forward
Components.addForwards(this, this);
}
public void onChange$firstName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
public void onChange$lastName(Event event) {
fullName.setValue(firstName.getValue()+" "+lastName.getValue());
}
}
As you can see, MyWindow actually looks similar to MyComposer6. The difference is that using the use attribute, the component and the controller is of the same object. We need to find a proper timing to call the auto-wire and add-forword helper methods in MyWindow class. In this example, I choose implementing the org.zkoss.zk.ui.ext.AfterCompose interface and call the auto-wire and add-forward helper methods in afterCompose() interface method. The ZK loader engine will call this afterCompose() method after all child components are properly composed just like the doAfterCompose() to the Composer interface. Note that here we don't have to register onXxx event listeners explicitly since the ZK update engine will call onXxx event listeners defined in component automatically.
Download
Download the example codes(.war file).
Summary
ZK programming is all about event handling and UI components manipulation. Apparently, the event listener is where the business logic is all about and it is the controller codes that coordinates everything. To make MVC programming easier, we have made writing the controller codes much easier. So here are the targets we have achieved:
- Make writing event listener easier.
- Make register event listener easier.
- Make accessing UI components and data beans easier.
And with implicit objects auto-wiring, it makes the refactoring from quick prototyping zscript codes to product performance codes as simple as copy and paste.
We hope you enjoy these ZK new features and write your MVC codes happily with ZK. If you come up with any idea, please feel free to feed back and we can make ZK programming even easier and better.
Fore more information, please refer to ZK Developer's Reference: MVC.
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |