ZK官方MVC原理详解(一)
作者:Henri Chen, Principal Engineer
ZK的团队一直试图在zk中实现MVC方法最佳实践,在这篇文章中我们将会讨论zk中MVC编程,我们还详细的介绍在zk中提供三个工具类和三个帮助方法,将为开发者在程序中编写Model-View-Controller模式更方便、更简单。此外、还将最初将Java写在zscript中,提供了最佳途径来重构原型代码。
我将使用一个简单的例子来说明这些工具类和帮助方法实力。
假设:两个文本框:一个是姓、一个是名,当这两个文本框变化时候而另外一个全名要自动更新。
第一种方式:
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()); } }); } } 在这里composer1.zul定义了程序的页面视图部分,而MyComposer1.java在程序生命周期上window创建时候介入,主要负责负定义和注册监听事件。然而,不管怎么样!
当我添加更多事件代码时候,我们会不停的添加addEventListener这样重复的代码。但是我们怎么去除这烦人的代码呢?
org.zkoss.zk.ui.util.GenericComposer
辅助类帮助我们解决这个问题
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); //得到zk组件
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()); } } 该MyComposer2类继承GenericComposer,写事件侦听器的代码直接写onXxx,对GenericComposer类doAfterCompose方法会为你做的繁琐的addEventListener
按照惯例,这是一个典型的Design by Convention方法,与onXxx命名模式的所有方法将会自动被视为事件处理,而且自动帮你注册这些事件。
对于一个典型的互动丰富的应用程序,事件处理中通常需要访问数据模型bean和操纵UI组件与最终用户交互。获得这样的组件引用和数据bean是在事件处理常见的做法。所以为什么在MyComposer2中大量使用getFellow()方法,另一方面,这些都是在一个框架可以发挥作用,为什么不把这些组件和这些数据beans绑定呢?
因此,这里来了org.zkoss.zk.ui.util.GenericAutowireComposer 工具类扩展GenericComposer类并添加了auto-wire的特点。
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()); } } 在来看看MyComposer3中它继承了GenericAutowireComposer ,所有的getFellow方法都去除掉了。
对GenericAutowireComposer类doAfterCompose方法为你自动注入适当的组件,
事实上,如果在你的zul文件中你定义适当的变量解析器(variable resolver),例如Spring变量解析器(variable resolver),则GenericAutowireComposer也会为你的Spring bean注入
以下是这的一些片断代码。讨论的ZK +spring请参考另一篇文章。
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); } ... }
由于在zscript中我们可以直接使用隐式对象,同样的GenericAutowrieComposer 也支持隐式对象甚至zscript中的alert() 方法也支持。下面composer4就是个例子,当firstName变化时,它就会请求self(应用composer时候的隐式对象,这里也就window实例)
改变他的标题并且弹出alert消息框,这也带来另外一个好处,为zscript代码重构到java代码上来提供更方便更轻松的途径。你可以复制和粘贴,并做一些轻微的修改,即使你已经用了一些这样用来隐式对象存在只在zscript。查看一下composer4-1.zul使用zul和zscript,在看看MyComposer4.java中的onFirstName() 和onLastName()方法有什么类似吗?
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>
知道现在!在前面几个例子中zul文件中总是使用forword来分别触发window中firstName和lastName文本框中OnFirstName和OnLastName事件来转向他们的onChang事件,毫无疑问!这个MVC使得每个组件重复forward,我们还能不能消除forward的属性呢?
org.zkoss.zk.ui.util.GenericForwardComposer 这个工具类满足这一需求,请注意,GenericForwardComposer继承GenericAutowireComposer类,因此它有它的父类(GenericAutowireComposer)和祖父类(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());
}
}
当我们写composer继承于GenericForwardComposer
的时候,你必须按照一些命名规则来定义你的事件侦听器方法,
这是另一种Design by Convention模式实现。例如MyComposer5.class
,被命名onchang$firstName事件监听读成为“onChange event from the firstName component”于firstName组件中的onChang事件
GenericForwardComposer会帮助你forward事件到应用组件,正如你可以看到,forword在compser5.zul毫无疑问地移除掉,这样我们可以完全分离了页面层zul和业务逻辑控制层。
哎呀!写不完!分两页写!翻译的不好!希望大家拍砖!如果那里不对希望大家帮我纠正!我主要是为了自己不再翻英文看!烦死了!