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.
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.
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.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.
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.
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
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?
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
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?
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
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