The Trilogy of ZK's MVC Adventure

Robbie Cheng, Engineer, Potix Corporation
November 29, 2007

Version

Applicable to ZK 3.0.1 Freshly (zk-3.0.1-FL-2007-11-28 and later)

Introduction

MVC(Model-View-Controller) is a famous and good practice for web application development. Thus, we are always looking for the best practice for ZK to realize MVC approach perfectly. Finally, we come up with the idea of composer which allows you to separate view and controller clearly. Let’s review the history of improvement on realizing MVC approach.

The Day Before - Episode I

In the past, we used to teach developers to create a customized component as a controller, and then register related event listeners to invoke the controller as follows.


<!-- mvc1.zul -->
<window id="win" border="normal" width="350px" sizable="true" title="Demo" use="MyWindow">
 <button label="A" onClick="win.onSayA()"/>
 <button label="B" onClick="win.onSayB()"/>
 <button label="C" onClick="win.onSayC()"/>
 <button label="D" onClick="win.onSayD()"/>
 <button label="E" onClick="win.onSayE()"/>
 <separator/>
</window>

Then, creates a customized window component, and defines required event listener in it.


<!-- MyWindow.java -->
public class MyWindow extends Window{
 public void onSayA(){
  appendChild(new Label("A"));
 }
 public void onSayB(){
  appendChild(new Label("B"));
 }
 public void onSayC(){
  appendChild(new Label("C"));
 }
 public void onSayD(){
  appendChild(new Label("D"));
 }
 public void onSayE(){
  appendChild(new Label("E"));
 }
}

Thought it looks like intuitive, there are two drawbacks.

  • The performance is not good since those event listeners defined in ZUL page require additional execution time of BeanShell.
  • Those events are better to be processed in the window component instead of in those scattered-around button components.

Improve the Performance - Episode II

To overcome the above two problems, you can use the forward property to forward those events to the window component without registering event listeners in the ZUL page. If one of these buttons is clicked by the user, a corresponding event will be forwarded to the window component, and the corresponding event listener defined in the MyWindow Java file will be invoked.


<!-- mvc2.zul -->
<window id="win" border="normal" width="350px" sizable="true" title="Demo" use="MyWindow">
 <button label="A" forward="onSayA"/>
 <button label="B" forward="onSayB"/>
 <button label="C" forward="onSayC"/>
 <button label="D" forward="onSayD"/>
 <button label="E" forward="onSayE"/>
 <separator/>
</window>

Though this way looks much better than the former one, but here comes another problem. Is it appropriate to take the view (UI Components) as a controller? The answer depends on user's preference. Instead of making decision for you, ZK always enabled options for you. In the Episode I and II, we've showed you how to mix these two concepts together. And now, we are going to show you how to separate the controller from the view clearly, we come up the idea of composer.

Separate Controller from View - Episode III

To realize the MVC approach more gracefully, we need the help of composer and apply property.

Create a Composer as the Controller

The first job is to create a composer which must extend org.zkoss.zk.ui.util.GenericComposer, and then move those event listeners defined in window component to the composer as follows.


<!-- MyComposer.java -->
public class MyComposer extends GenericComposer{ 
 public void onSayA(Event evt){
  evt.getTarget().appendChild(new Label("A"));
 }
 public void onSayB(Event evt){
  evt.getTarget().appendChild(new Label("B"));
 }
 public void onSayC(Event evt){
  evt.getTarget().appendChild(new Label("C"));
 }
 public void onSayD(Event evt){
  evt.getTarget().appendChild(new Label("D"));
 }
 public void onSayE(Event evt){
  evt.getTarget().appendChild(new Label("E"));
 }
}

Use apply property

Then, apply property is used to attach the composer to the window component. Those evens forwarded to the window component will be processed in the composer. So far, the job of controller is separated from the view clearly.


<!-- mvc3.zul -->
<window id="win" border="normal" width="350px" sizable="true" title="MVC Demo" apply="MyComposer">
 <button label="A" forward="onSayA"/>
 <button label="B" forward="onSayB"/>
 <button label="C" forward="onSayC"/>
 <button label="D" forward="onSayD"/>
 <button label="E" forward="onSayE"/>
 <separator/>
</window>

Summary

So far, we believe this should be the best practice for ZK to realize MVC approach. However, we welcome your comments to make it better. If you come up with any idea, don’t hesitate to leave a comment.

Download the example code here.

Comments
 
ehols
2007-11-29

where is "GenericComposer"?

robbiecheng
2007-11-29

GenericComposer is only supported in the latest freshly of ZK 3.0.1. Please download the latest freshly of ZK 3.0.1 And here it is, org.zkoss.zk.ui.util.GenericComposer

jj
2007-11-29

Robbie, First of all, I think ZK is a great framework, and one of the reasons that I like it is that it does NOT conform to MVC. In my experience, the MVC is a poor-man's fix for the stateless HTTP protcols used in web applications, and you have to have a controller layer. I'll argue that Episode III above is no better than Episode II, and more code has to be written, and logic is scattered around. If ZK wants to stay as Event-driven framework, I think Episode II is the best approach. Event --> Handler, no middleman (controller), and handler should be defined in the component where the event is generated from. This would make it simple and efficient. Just my two cents. Again, great work. -JJ

robbiecheng
2007-11-30

Hi JJ, I will be glad to know your practice about using ZK. Are you interested in writing a smalltalk to share your experience with other? :) /Robbie

Jeff
2007-11-30

Good thought ! JJ Comment more !

Daniel
2007-11-30

Hi Robbie, I'm a happy user of ZK and so far, I told everybody that one of the things I like most about ZK is the nice and clean implementation of the MVC pattern. Through the 'use' attribute a class can be defined that acts as the controller. Ok, from a purists point of view this controller is not a real controller because it extends a view component (window) and it needs some discipline from the developer not to put any presentation logic into the customized window component and instead to create custom components. So, from my point of view I was already happy with Episode I. If you tell me that for performance reasons Episode II is to be prefered, I have learned something new and I will use Episode II in the future. But as JJ pointed out, I don't see the real advantage that Episode III should bring. What problem does it solve? How does it make my life easier? Maybe I missed the point but if not, I am still happy with Episode II. /Daniel

stealth_nsk
2007-11-30

Hello Everybody

So many people argue with the Scenario III approach, what I wish to speak in defense.

One of the greatest things in the last scenario is what it allows many functional codes to be stored in one place. For example, you have a separate page for logging in and a special login controller on the page header. Both views call the same method, so we could use

stealth_nsk
2007-11-30

...the same composer.

If there is the possibility to add several composers to one view it would allow to group event listeners by functional logic.

robbiecheng
2007-11-30

Hi there,

I think stealth_nsk has pointed out another benefit of separating the functional logic from the view (UI component) is that these functional logic could be more reusable since they are no longer binded with one specific UI component.

And, yes, stealth_ns, multple composers could be applied on one component.

frank
2007-12-04

Is it possible to apply Composers to any component?

ehols
2007-12-04

Our projects,especially "OfficeOnlineSystem", are all developed with ZK already.Since version 3.0 RC,I make the most of "forward" ,like Episode II.But however I have to use Episode I in somewhere ,because "forward" can only be used in the scopt of "window".how to solve ?Episode III can ?

robbiecheng
2007-12-04

Hi Frank,

Yes, composer could be applied to any of ZK componnets.

And ehols,

You could specify the target component directly, ex. MyListbox.onSelect.
If you don't specify it, ZK forwards the event to its SpaceOwner.
Please take a look at this url,
http://www.zkoss.org/doc/devguide/ch02s09s02.html

robbiecheng
2007-12-04

Sorry, the url is wrong. This is the correct one.
http://www.zkoss.org/doc/devguide/ch02s09s03.html

Francisco Ferri Pérez
2007-12-04

Good article.

I learned the forward property. Thanks.

lerf
2007-12-10

Hi , there :
if a button has onClick .onMouseMove... event,how to use forward. and also refered to other components, such as listbox ,how to send it's onSelected event by forward attribute in the same window ?

robbiecheng
2007-12-11

Please refer to the following url,
http://www.zkoss.org/doc/devguide/ch02s09s03.html

PS. If you have any problem about using ZK, please take a look at ZK developer's guide, http://www.zkoss.org/doc/devguide.
Or you could post your problem to ZK forum.

davetam
2007-12-16

The composer looks good and logical (but perhaps you want to change the name to Controller?). I have been implementing my own controllers but the composer ideal looks neater. I am looking forward for it to be added in the next release.

However in terms of performance, how much do you gain by switching from Episode I to Episode II (and possibly III) I dont know the imp. details but if you apply multiple composer wouldnt it mean the vm need to go through all the composers to find the right method, hence hurting performance?

ep
2008-02-11

Lerf's question has not been answered!

I have 2 issues with zk mvc
1) It appears that the MVC III approach is not generic enough to handle other events. It only forwards onclick. Right or wrong?

2) ZK implements only a "push" mvc ability. A "pull" mvc is much more powerful. I don't think ZK supports PULL-MVC on the client or client/server correct?

[Push MVC]

<textbox id="source" onChange="copy.value=source.value" />
<textbox id="copy" readonly="true"/>

[Pull MVC]

<textbox id="source" value="input"/>
<textbox id="copy" value=${source.value} />

With this syntax, copy get.value gets registered with the onChange event or source.value with very little code. Event listeners are setup behind the scene.

deerball
2008-03-10

The first job is to create a composer which must extend org.zkoss.ui.util.GenericComposer, and then move those event listeners defined in window component to the composer as follows.

-- it should be org.zkoss.zk.ui.util.GenericComposer

henrichen
2008-03-19

@ep
(1) It can forward any event to any component. See this http://www.zkoss.org/doc/devguide/ch06s07s09.html

(2) ZK support a databinding mechanism which do the Pull MVC you mentioned.

<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<zk>
<textbox id="source" value="input"/>
<textbox id="copy" value="@{source.value}"/>
</zk>

copy's value changes whenever source's value changes.

ep
2008-06-18

@henrichen
I tried your (2) example by pasting it into the online demo. It displays properly when loaded. However, if I update the "source" textbox with new text, the "copy" textbox does not change. But then if the "copy" textbox is updated with new text....the "source" textbox updates? This is just push behavior with different syntax.

What I am looking for is the ability to declare in object B, that it "pulls" data from object A.

ep
2008-06-18

We need something to cover 3 use cases:

1) <textbox id="B" value="@bindpush{A.value}"/>
This is the current implementation and it allows me to update the model from a single view
2) <textbox id="B" value="@bindpull{A.value}"/>
This is more interesting as it allows me to update the view (or multiple views) when the model changes. Very powerful.
3) <textbox id="B" value="@bindboth{A.value}"/> //bi-direction binding
This defines object being updated (view or model) to be the master and the other the target. Even more powerful.

henrichen
2008-06-26

@ep

ZK's data binding has to be triggered by component event.
we use a "load-when" to tell when to do load (pull).

Regarding push/pull, we use an "access" to distinguish them.

access=load //pull only
access=save //push only
access=both //pull and push

case 1) push only

<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<zk>
A:<textbox id="A" value="input"/>
B:<textbox id="B" value="@{A.value,access=save}"/>
</zk>

case 2) pull only

<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<zk>
<textbox id="A" value="input"/>
<textbox id="B" value="@{A.value,load-when=A.onChange,access=load}"/>
</zk>

case3) push/pull

<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<zk>
<textbox id="A" value="input"/>
<textbox id="B" value="@{A.value,load-when=A.onChange,access=both}"/>
</zk>

ep
2008-06-27

Outstanding! That's what I was looking for. ;-)

mike
2008-08-07

great feature! keep on working!

Jask
2008-12-04

There is a typo in the article, saying that GenericComposer is in:
org.zkoss.ui.util.GenericComposer

where it should be:
org.zkoss.zk.ui.util.GenericComposer

henrichen
2008-12-05

Jask,

Thanks for the information. We have corrected it.

Sudamar
2008-12-23

Care Robbie,

I can´t work databinnding with GenericComposer. I use a extends of the Window and atributes vo´s as person.

Ex:

  class MyWindow extends Window {
   // code to generics view
  }

  class package.foo extends MyWindow {
   Person p = new Person()...
  }

In Zul Pages:

<window title="Testes ZK" sclass="login" sizable="false" id="AcessoWindow"
	use="package.foo">

<textbox id="inLogin" value="@{foo.person.nome, save-when='btEntrar.onClick'}"/>
</window>

How do you to this works with GenericComposer? Thank u for attention...

Until now, i prefer Episode I.

robbie
2008-12-24

@Sudamar,

Please use AutoWireComposer which allows you to access serviced defined in Composer from the ZUML page.

Or you can refer to this discussion.

/robbie

henrichen
2008-12-24

Take a look of this article, you shall find the answer.

http://www.zkoss.org/smalltalks/mvc3/

Sudamar
2009-01-08

Friends,

I think that code in view is very very ugly! :(

I´d like than the url that henrichen send me, but I miss a plugin that encapsules (wraps) the input fields, as struts by form´s. Type:

Class Foo {

Person p;

public void doAfterCompose(Component win) throws Exception {
        super.doAfterCompose(win);
                		
	p = getPersonByForm();
	
    }
    
    public void onFirstName(Event event) { 
        fullName.setValue(firstName.getValue()+" "+lastName.getValue());
    }
    
    public void onLastName(Event event) {
        fullName.setValue(firstName.getValue()+" "+lastName.getValue());
    }

Using a form struts mode:

public Person getPersonByForm() {
   FormZK form = getFormByRequest();
   Person.setName(form.get("txNome"));
   Person.setAge(form.get("age");
}

We could add some features as autoconversion with same names...(eg: Person = form.getBinding("new Person()")).

What do you think?

robbiecheng
2009-01-09

@Sudamar,

We welcome any idea to make ZK better.
Please post your idea to feature request.

thanks!

/robbie

pedro
2009-12-03

adfdsafdcasfed

vsdv
2010-03-29

sdsddsd

vsdv
2010-03-29

zxsxdsadw

Ignacio
2010-04-07

How would you make a properly view-controller as MVC real pattern requires??? Could you post a solution for this please?

Regards,

IS

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