ZK MVC Made Easy

Henri Chen, Principal Engineer, Potix Corporation
August 28, 2008

Version

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 approachs in ZK. In this article and this article there have been some discusions 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 reponsible 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 repeatly. 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 registed automatically.

Auto-wire the Components and Beans

For a typcial 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 utilty 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 utilty 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 coresponding 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 searchs 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 mehtod 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 benifits 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.

Comments
 
robertpic71
2008-08-28

First, great article, a good summary about MVC. And, of course, good new features.

>> In the early days, we used to teach developers to use customized component as a controller.

I using this technic until today. I did not find a way (or example in the docs) to use the composer as "dataholder" for the @databinding.

My "perfect" controller should:
- ready for Spring injections (DAO, Logindata, Configurationobjects maybe: Requestscope Hibernatesession )
- read (with DAO) and hold the data for databinding
- get the forwardevents from GUI

The composer is very close to my requirements, but i miss the databindig part.

Can i inject Springvariables with org.zkoss.zk.ui.Components.wireVariables(Component comp, Object controller)
for a window?

/Robert

Marcos de Sousa
2008-08-28

Robert,

"Can i inject Springvariables with org.zkoss.zk.ui.Components.wireVariables(Component comp, Object controller)
for a window?"
check at:
http://sourceforge.net/tracker/index.php?func=detail&aid=2054631&group_id=152762&atid=785191

there is archives: Bug2054631.zip and Bug2054631.war showing it.

Maybe Components.wireVariables already support injection of spring or they must give us an way to inject such needs! Check the method if it handle spring.

Henri Chen,

I use the "use" attribute, hope today check onChange$firstName(Event event) but with buttons and other events like onClick$btnAdd, etc

What about onFulfill ?, it is still open, so it means that fufill will fail for those that use the last case. Check the link above.

henrichen
2008-08-29

robertpic71,

Yes, Components.wireVariables() supports injection of Spring bean.

The implementation actually use component.getVariable(id, false) method to find the associated components and/or data beans. The getVariable() method will search variables defined in zscript, component variables, components (by id), and finally ask defined VariableResolver to find the specified "variable", in that order.

So the key is whether you specify the Spring VariableResolver in your zul page. In other words, if you specify the JndiVariableResolver in your zul page, Components.wireVariables method will auto-wire that Jndi object for you, too. So developers can "extend" the power to "auto-wire" other kind of "beans" as long as a proper XxxVariableResolver is written.

henrichen
2008-08-29

robertpic71,

As for the @databinding, I wonder if you can give a simple use case so I can follow your suggestion. Thanks.

henrichen
2008-08-29

Marcos,

onFulfill is already implemented. I think the issue is about "when" the event is fired and processed. See my comment here.

event$id name pattern is a generic name pattern, onFulfill is also an event, so it can be applied to the same name pattern without problem.

robertpic71
2008-08-29

Henri Chen,

>> Yes, Components.wireVariables() supports injection of Spring bean.
Than I'm happy with "MyWindow.java + Utils example".

>> As for the @databinding, I wonder if you can give a simple use case so I can follow your suggestion. Thanks.
With spring injection my controller (extends window) becomes complete. But the sentence "In the early days, we used to teach developers to use customized component as a controller." sounds a little bit like "use-variant is not state of the art". But for databinding, i didn't see a better way.

/Robert

henrichen
2008-08-29

Well, I will see if I can change the "statement" to make it neutral :). Which way to go is really a personal taste.

I don't quite catch your statement regarding the databinding though. May I ask what cannot be done with databinding when use "Composer" approach?

Marcos de Sousa
2008-08-29

Henrichen,

Why not support spring 2.5 beans using annotations?

So, I have not work/test this entry "ZK MVC Made Easy", but it look like spring is suported via variavel resolver when injecting spring beans.

The fact, I we don't need it if we are using spring annotations.

My patch code, which you can found an working example in Bug2054631.zip and Bug2054631.war

In wireVariables(Component comp, Object controller) I append
annotationInjector(comp, fd);

It is enough developer to call:
@Resource
private TransactionProxyFactoryBean taskDao; //Spring bean auto wired

Looking forward to hear from you.

import javax.annotation.Resource;
/*
* Inject @Resource fields from Spring to ZK Components.
*/
private void annotationInjector(Component component, Field field) {
// If Detect field annotated by @Resource
if (field.isAnnotationPresent(Resource.class)) {
String fdname = field.getName();
if (!fdname.equals("")) {
// Inject detected field annotated by @Resource to annotated
// field founded
try {
field.setAccessible(true);
field.set(component, SpringUtil.getBean(fdname));
} catch (IllegalAccessException illegalAccessException) {
log.error("Can't set/inject value to component: "
+ illegalAccessException);
throw UiException.Aide.wrap(illegalAccessException);
} finally {
field.setAccessible(false);
}
}
}
}

Horst Neumann
2008-08-29

Thank you Henri !!!!!!!

the very last example is exactly what I am searching for.
It solves my question posted on the help forum! Thanks!

best regards
/Horst

henrichen
2008-08-29

Marcos,

We have considered using Java annotations to handle such variable wiring. The issue is that ZK is currently by policy sticks to Java 1.4. We will not require Java 5 to run ZK.

And using VariableResolver has a benefit in such case. Developers can write own VariableResolver to extends the "wiring" of different beans rather than just Spring beans.

However, it is good that the community can share different thoughts and ways to make ZK programming easier and faster. I appreciate your input.

Marcos de Sousa
2008-08-29

Thanks, I forgot about it.

robertpic71
2008-08-29

Henri,

>> I don't quite catch your statement regarding the databinding though. May
>> I ask what cannot be done with databinding when use "Composer" approach?

The composer class defined in the apply cannot hold the models and beans for databinding. When i use the composer for events and the extended window for the models and beans i have 2 controllers.

Because i want only one controller, i can't use the new features (autoevent-binding) until now. With the new utils, i can use all new features from the composer. (following the composer approach, but without the composer itself).

There is one difference: the composer itself could injected by Spring.

Macros, Henri
good arguments for both sides

For simple resoucemanagment i'm happy with the SpringResolver. Maybe i need more, for complexer quest (i.e. transactioncontroll for JTA/Hibernate session..).

/Robert

Qamaralzaman Habeek
2008-08-29

Henri,

Thanks a lot for this informative article, which I hope I read before I tried to re-invent the wheel that is already there.

Unfortunately, the 6th example isn't working for me. It throws an "object is not an instance of declaring class" exception at line 414 of Events.java class, which is:

final Method [] mtds = controller.getClass().getMethods();
final EventListener evtl = new EventListener() {
public void onEvent(Event evt) throws Exception {
final Method mtd = ComponentsCtrl.getEventMethod(controller.getClass(), evt.getName());
if (mtd != null) {
if (mtd.getParameterTypes().length == 0)
mtd.invoke(this, null);
else
mtd.invoke(this, new Object[] {evt});
}
}
};


It invokes the the method mtd which belongs to controller on the this object which is the event listener !

am I wrong or should I post a bug report?

Qamaralzaman Habeek

henrichen
2008-08-30

Hi Qamaralzaman,

1. Your approach is different from mine so I don't think it is a "re-invent the wheel" case. You add the event listener directly to the component while mine still use the "forward" approach. However, it was your article that reminded me that I should have given some detail discussions on ZK's MVC approaches. I really appreciate your contribution.

2. That is a bug which I have fixed in ZK 3.0.8 and ZK 3.5.0 code base. You can get the ZK 3.0.8 Freshly from the download example of this article. Or you can download ZK 3.5.0 Freshly from ZK's download area. Sorry for the inconvenient.

henrichen
2008-08-30

Robert,

Now I catch your point. Data binder cannot have access to the data models and beans defined in the Composer.

However, I think it can be arranged. Would you post a new feature for such requirements so developers can choose either approaches (use or apply) without limitation. Thanks.

Dieter Aschenwald
2008-10-21

... adding the interface org.zkoss.zk.ui.util.ComposerExt to Composer subclasses you are able to use data binding.

 
 
Leave a Reply
 
Name (required)
Mail (will not be published) (required)
Website
(Case Insensitive)
Bold textItalic textUnderLine textSource CodeHorizontal rulerExternal Link
Post
Preview