ZK官方的MVC模式建议
来源:互联网 发布:阿里云 修改帐号密码 编辑:程序博客网 时间:2024/06/06 17:30
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.javapublic 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.javapublic 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.javapublic 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.javapublic 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 theGenericComposer
.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 theGenericAutowireComposer
.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 thoseonXxx$id
methods defined in the controller object and adds forward condition to the components as specified inid
so you don't have to specify in zul file theforward
attribute one by one. This helper mehtod provides the same feature of theGenericForwardComposer
.
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.
- ZK官方的MVC模式建议
- ZK官方MVC原理详解
- Sun 官方的 MVC 架构模式蓝图
- ZK MVC入门教程
- 关于ANR的官方建议
- 关于ANR的官方建议
- 关于ANR的官方建议
- PHP官方建议我们不要使用Worker模式下的Apache2来运行PHP
- 基于ZK自动切换模式的实现(六)
- 官方建议的python的书写惯例
- 线上环境部署MongoDB的官方建议
- android枚举的替代(官方建议)
- oracle 官方建议系统参数的配置
- zk框架 官方文档翻译1
- zk框架 官方文档翻译2
- Spring MVC的安全隐患及建议
- ZK-Client的使用
- zk excel的导出
- IE 8不支持支付宝插件问题解决方法
- 清空邮件队列
- 造成HTTP-500错误,可能存在的原因小结
- PO/VO/DAO/BO/POJO是什么(JAVA几种对象的解释)
- ibm创新产品1---紫禁城游览
- ZK官方的MVC模式建议
- 如何在 Windows Server 2003、Windows 2000 和 Windows XP 中备份恢复代理的加密文件系统 (EFS) 私钥
- 用于层次结构数据库批量导入(Insert)的存储过程
- MS-DOS Programming 总结
- 分组后数量问题,分组后得到每组前几行row_number() over (partition by 字段)
- Arm中Cache知识汇总
- 腾讯08年招聘面试题(以狗试毒)(转)
- WINCE下内核模式和用户模式有什么区别
- magento -- 巧用magento的分类实现把特定产品放到首页