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和业务逻辑控制层。

 哎呀!写不完!分两页写!翻译的不好!希望大家拍砖!如果那里不对希望大家帮我纠正!我主要是为了自己不再翻英文看!烦死了!

posted @ 2011-03-29 23:37  小彭博客  阅读(5886)  评论(0编辑  收藏  举报