ZK开发手册


ZK 开发手册 chanson 制作 第 1 页 共 265 页 ZK 开发手册 版权 © 2007 Potix Corporation. All rights reserved. Version 3.0.0 本文档内容均来自 http://zh.zkoss.org/doc/devguide 整理人:chanson 北京马甸 2008-2-21 ZK 开发手册 chanson 制作 第 2 页 共 265 页 1. 简介 1.1. 传统的 Web 应用程序 以交换文档简单高效为目标,Web 技术,超文本传输协议(HTTP)和超文本标记语言 (HTML),都来源于单页性(page-based )和无状态(stateless-communication)的模 式。在这种模式中,一个页面是自给自足(self-contained)的,并且是沟通客户端与服 务器端的最小单位。 随着网络俨然成为应用开发的默认平台,这种模式面临着巨大的挑战:对于表现当今应 用程序中复杂性的无能为力。举个例子,为了给客户报价,你或许必须打开另一个页面 来查询此客户的交易记录,再打开一个页面来显示当前的价格,还得开一个页面来存储 当前信息。用户被迫离开他正在工作的页面,并且在几个页面间来回浏览。这很容易迷 失,混淆,结果是把客户弄得不愉快,销售机会的损失和低生产力。 在这种单页性(page-based )的模式上开发一个现代的应用程序也是一个极大的挑战。 在这种模式中,运行在服务器上的应用程序必须处理来自从语法上分析请求,送出回 应 ,连接用户从一个页面到另一个页面路由的一切,并且处理用户的各种错误。 数十 种框架,例如 Struct,Tapestry 和 JSF,随即出现用来简化开发过程。由于单页性 (page-based)模式与现代模式之间的巨大差别,学习和使用这些框架并不是一个愉快 的过程,更不要提直觉感知(intuition)和简化了。 ZK 开发手册 chanson 制作 第 3 页 共 265 页 1.2. 点对点(Ad-hoc)AJAX 应用 经过数十年的演变,Web应用已经从静态HTML发展到动态HTML,applets,Flash, 最后发展到了AJAX[1] (Asynchronous JavaScript and XML ,非同步的JavaScript 和XML )。通过谷歌地图及推荐(Google Maps and Suggest)的说明,AJAX技术通 过提供与桌面应用程序同等水平的互动性和反应能力给Web应用带来了新生命。不同于 applets或Flash,AJAX基于标准的浏览器和JavaScript,并且不需要专门的插件。 AJAX 是新一代的 DHTML,就 像 DHTML,它在很大程度上依赖于 JavaScript 监听用 户活动产生的事件,然后动态的操纵浏览器中一个页面(aka. DOM)的视觉表现。此外, 它更近一步,能够使与服务器的沟通异步进行,即不需要离开或提交整个页面。它通过 引入客户与服务器间轻量级的通信(light-weight communication )打破了基于页面 的模式。妥善设计,AJAX 可以给 Web 应用带来丰富的桌面通用组件,而且这些组件 的内容可以在应用程序的控制下动态更新。 当提供给用户需要的交互性的同时,AJAX 给已经很昂贵的 Web 应用程序开发增加了 复杂性和技术先决条件。开发者不得不在浏览器中操纵 DOM,并且使用不兼容甚至是 错误的 JavaScript 与服务器通信,为了更好的交互性,开发者必须重复复制应用数据 和业务逻辑以便于浏览。这样就增加了维护成本及同步数据的挑战。 底线是在关于处理请求方面,点对点(Ad-hoc)的 AJAX 应用与传统的 Web 应用没有区 别。开发者仍然必须解决由单页性(page-based)和无状态 (stateless-communication)模式造成的隔阂。 [1] Ajax是由Jesse James Garrett於Ajax: A New Approach to Web Applications中所提 出的。 ZK 开发手册 chanson 制作 第 4 页 共 265 页 1.3. ZK: 它是什么 ZK 是一个事件驱动(event-driven)的,基于组件(component-based)的,用以丰富 网络程序中用户界面的框架。ZK 包括一个基于 AJAX 事件驱动的引擎(engine),一套 丰富的 XUL 和 XHTML,以及一种被称为 ZUML(ZK User Interface Markup Language,ZK 用户界面标记语言)的标记语言。 有了 ZK,您可以利用 XUL 和 XHTML 的丰富特性来呈现您的 Web 应用,操纵它们来 处理因用户活动而引发的事件,就像您使用多年的桌面应用程序那样。不同于大多数其 它框架,ZK 是一种幕后(behind-the-scene)技术,组件内容的同步和流水线事件 (pipelining of events)都由 ZK 引擎自动完成。 您的用户获得了如同桌面程序的互动性和反应能力,而您的开发仍然像开发桌面应用程 序那样简单。 除了简单的模型和丰富的组件,ZK也支持一种文本标记语言,称为ZUML。ZUML,如 同HTML,可以让开发人员设计界面而无需编程。通过XML的命名空间,ZUML无缝的 集成了一套不同的标签[2]到同一页面。目前,ZUML支持两套标签,即XUL和HTML。 为了方便快速模型开发(prototyping)和定制,ZK允许开发人员嵌入EL表达式,以及 您喜欢的脚本语言,包括但不限于 Java[3], JavaScript[4], Ruby[5] and Groovy[6]. 开发人员可以选择不嵌入任何脚本语言,如果他们喜欢更严格的要求(discipline)。不 同于JavaScript嵌入在HTML,ZK在服务器端执行所有的嵌入脚本。 注意我们所说的一切运行在服务器端是从应用程序开发者的角度出发的。对于组件开发 人员来说,他们必须平衡互动性与简单性来决定什么任务由浏览器来完成,而什么任务 由服务器来完成。 [2] 标签是XML元素。组件是在当ZUML网页被翻译时所产生出来的。 [3] 使用BeanShell(http://www.beanshell.org)的Java interpreter。 [4] 使用Rhino (http://www.mozilla.org/rhino)的JavaScript interpreter。 [5] 使用JRuby (http://jruby.codehaus.org/)的Ruby interpreter。 [6] 使用Groovy (http://groovy.codehaus.org/)的Groovy interpreter。 ZK 开发手册 chanson 制作 第 5 页 共 265 页 1.4. ZK: 它不是什么 ZK 并没有关注持久化(persistence)或伺服务器之间的沟通(inter-server communication)。ZK 被设计的尽可能的简单,它只针对表示层(presentation tier)。 他并不要求和暗示任何后端技术,所有你喜欢的中间件就像以前一样工作,如 JDBC, Hibernate, Java Mail,EJB 或 JMS。 Zk 并没有为开发人员提供(tunnel),RMI 或其他的 API 用来在客户端与服务器端通信, 因为所有的代码都运行在同一服务器的同一 Java 虚拟机(JVM)上。 ZK 并没有强迫开发人员使用 MVC 或其他设计模式。是否使用它们由开发人员选择。 ZK 并不是旨在把 XUL 带入 Web 应用的框架。它的目标是把桌面编程模式引入 Web 应用。目前,它只支持 XUL 和 XHTML。将来它或许会支持 XAML, Xquery 及其它。 ZK将AJAX嵌入到了现今的应用中(implementation),但它并没有止步于AJAX结束的 地方。在即将来临的ZK Mobile中,您的应用程序可以到达支持J2ME的任何设备,例 如PDA,手机和游戏平台。此外,您根本不用修改您的应用程序[7]。 [7] 根据萤幕大小有时需要做调整。 1.5. ZK: 局限 ZK 不适合在客户端运行多任务的应用程序,例如 3D 动作游戏,除非你写编写一个特殊的组件。 ZK 也不适合需要大量使用客户端计算能力的应用程序。 2. 让我们开始吧 这一章的内容描述了如何写出你的第一个 ZUML 页面,如果你没时间的话建议你至少阅读这一章。 此章使用 ZUL 来说明 ZK 的功能,但是也适合于其他 ZK 支持的语言。 2.1. Hello World 当ZK安装到你最喜爱的Web服务器[8]后, 你就可以直接编写应用程序。仅需在合适的 目录新建一个名为hello.zul的文件[9] 。 ZK 开发手册 chanson 制作 第 6 页 共 265 页 Hello World! 然后输入正确的URL,例如:http://localhost/myapp/hello.zul,得到如下页面 : 在 ZUML 页面中,一个 XML 元素描述了应该创建。在这个例子中,被创建的是 window(org.zkoss.zul.Window),XML 属性(attributes)用来指定 window 组件属性(properties)的值。在这个例子中,创建了 window,并指定了 title 和 border 属性的值分别为 'Hello'和'normal'。XML 元素内的文本(即 Hello World)也可以通过一个称为 Label (org.zkoss.zul.Label)的标签来展 示。所以上面的例子和下面的例子是等价的: 也等价于: [8] 参考Quick Start Guide。 [9] 你也可以试试这些例子的在线示范。 2.2. 互动性 让我们来添加一些互动元素: 你可以决定是否使用 trim 属性来省略属性值开头和末位的值,使用方法如下: 2.5. EL 表达式 就像 JSP 一样,你可以在 ZUML 页面的任何部分使用 EL 表达式,但除了属性的名字 (names of attributes),元素(elements)和处理指令(processing instruction)。 EL 表达式的语法格式为${expr},例如: ${map[entry]} ${3+counter} is ${empty map} [提示]: emp ty 是用来测试一个 map, collection, array 或者 string 是否为 null 或空的。 [提示]: map[entry]是读取 map 元素的一种方法,换句话说,就像 Java 中的 map.get(entry)。 ZK 开发手册 chanson 制作 第 10 页 共 265 页 当一个 EL 表达式作为一个属性值时,它可以返回任何类型的对象,对象的长度限制在 组件可以接受的范围内。在下面的例子中,表达式被赋予一个 Boolean 对象的值: [提示]: + 在 EL 表达坏中是算数操作,并不能用于 string 类型。对于 string 可以使 用 "${expr1} is added with ${expr2}"。 标准的隐含对象(implicit objects),如 param 和 requestScope,还有 ZK 的隐 含对象,如 self 和 page,可以很简单的使用。 为了引入一个方法,你可以按如下方法使用 xel-method 处理指令(processing instruction) 。 通过从 TLD 引入 EL 函数,你可以使用被称为 tablib 的指令,就像下面: Developer's Reference 提供了更多关于 EL 表达式的细节。或者,你可以参考 JSP 2.0 的指南或手册来来获得更多关于 EL 表达式的信息。 2.6. id 属性 为了读取 Java 代码或 EL 表达式中的组件,你可以使用 id 属性来标识它。在下面的 例子中,我们为 label 设置了一个标识,这样当一个按钮被按下时,我们就可以操 纵 label 的值了。 Do you like ZK? 当一个组件被手动创建时,它并没有自动被加到页面。换句话说,它并不在用户的浏览 器中出现。为了将它加到页面,你可以调用 setParent,appendChild 或 insertBefore 方法来为其指定一个父类(parent),如果父类组件是页面的一部分, 那么它也变成了页面的一部分。 组件类并没有destroy 或close方法[16],当一个组件从页面中被拆卸的时候就会从浏览 器中内移除。它表现的就像附着在页面上一样。 Component detached = null; 在上面的例子中,你可以用 setVisible 方法来产生类似的效果。但是 setVisible(false)方法并没有把组件从浏览器中移除,它只是使一个组件(及其 所有的子组件(children))变得不可见。 当一个组件从页面被卸载后,如果应用程序没有涉及到该组件,它所占用的内存会被 Java 虚拟机的垃圾回收机制(JVM's garbage collector)所释放。 2.10.1. 不使用 ZUML 来开发 ZK 应用程序 对于根本不习惯使用 ZUML 的开发人员来说,他们可以使用被称为 richlet 的方法来手 动创建所有的组件。 ZK 开发手册 chanson 制作 第 17 页 共 265 页 import org.zkoss.zul.*; public class TestRichlet extends org.zkoss.zk.ui.GenericRichlet { public void service(Page page) { page.setTitle("Richlet Test"); final Window w = new Window("Richlet Test", "normal", false); new Label("Hello World!").setParent(w); final Label l = new Label(); l.setParent(w); //... w.setPage(page); } } 请参考高级特性一章中 Richlets 一节。 [15] 为了简化,这里不用factory design pattern。 [16] 与W3C DOM的观念相近。而另一方面,Windows API需要程序员管理生命周期。 2.11. 为某一页面定义新的组件 就像所展示的那样,通过使用 XML 属性为组件指定一个属性是很容易的事情。 ZK 开发手册 chanson 制作 第 44 页 共 265 页 注意到我们必须使用 timer 来真正地恢复被挂起的事件监听器(onClick)。这看起来 是多余的,但是由于 HTTP 的限制:为了保持 Web 页面在浏览器端的活跃,当事件处 理程序被挂起时我们必须返回响应。然后,工作线程完成了工作并唤醒了事件监听器, HTTP 请求已经不在了。因此,我们需要一种方式来”捎带(piggyback)”这个结果, 而这就使 timer 为什么会被使用的原因。 更确切地说,当工作线程唤醒一个事件监听器时,ZK 只是把它加到一个等待列表。当 另一个 HTTP 请 求到达时,监听器才真正恢复(如上面例子中的 onTimer 事件)。 在这个简单的事例中,我们并没有为 onTimer 事件作任何事情。对于一个复杂的应用 程序,你可以用它来返回处理状态。 5.4.2.3. 选择 2: Timer(没有挂起/恢复) 无需挂起和恢复来实现一长操作(long operation)是由可能的。这在同步代码 (synchronization codes)对于调试来说太复杂的情况下是很有用的。 注意是很简单的。工作线程将结果保存在一个临时空间,然后使用 onTimer 事件将结 果弹到(pops)桌面。 //WorkingThread2 package test; public class WorkingThread2 extends Thread { private static int _cnt; private final Desktop _desktop; private final List _result; public WorkingThread2(Desktop desktop, List result) { _desktop = desktop; _result = result; } public void run() { _result.add(new Label("Execute "+ ++_cnt)); } } 然后,在 onTimer 事件监听器上附加标签。 int numPending = 0; List result = Collections.synchronizedList(new LinkedList()); while (!result.isEmpty()) { main.appendChild(result.remove(0)); --numPending; } if (numPending == 0) timer.stop(); 5.4.2.4. 选择 3:捎带 (piggyback)(没有挂起/恢复,没有 Timer) 当用户,例如,点击一个按钮或输入一些东西时,你可以将结果捎带(piggyback)到客 户端,而不必循环地检查它们。 为了完成捎带(piggyback),你需要为一个根组件注册一个 onPiggyback 事件监听 器。然后每次 ZK 更新引擎(ZK Update Engine)处理事件时,这个监听器将会被调用。 例如,你可以按如下的方式重写代码。 List result = Collections.synchronizedList(new LinkedList()); void checkResult() { while (!result.isEmpty()) main.appendChild(result.remove(0)); } 捎带(piggyback)方式的优点是客户端与服务器端没有额外的往来。但是,如果用户没 有任何活动(如点击或打字)的话是无法看到更新的。这种方式是否合适取决于应用程序 的要求。 ZK 开发手册 chanson 制作 第 46 页 共 265 页 [注]: 一个延期的事件(deferrable)不会马上被送到客户端,所以,只有一个非延期的 事件被触发后 onPiggyback 事件才会被触发。请参考关于延期事件监听器一节获得 详细信息。 5.5. 初始与清理事件处理线程 5.5.1. 处理每个事件前的初始化 一个事件监听器是在一个事件处理线程中执行的。有时,你必须在处理所有事件前初始 该线程。 一个典型的例子是初始化认证所使用的线程。一些 J2EE 或 Web 容器将认证信息存储 在局部存储器(local storage)线程中。这样,可以在需要的时候自动进行重复验证。 为了初始化事件处理线,必须在WEBINF/zk.xml文件[30]的listener元素中注册一 个是实现了org.zkoss.zk.ui.event.EventThreadInit接口的类。 一旦注册完毕,一个指定类的实例就会在主线程中被创建。然后,在处理其他事情之前, 该实例的 init 方法就会在事件处理线程的上下文中被调用。 请注意构造程序(constructor)和 init 方法是在不同的线程中被调用的,因此开发人 员可以从一个线程取得独立线程数据并传送给另外一个线程。 这里有一个 JBoss[31]的认证机制。在这个例子中,我们在构造程序中获取存储在 Servlet线程中的信息。然后当init方法被调用时初始事件处理线程。 import java.security.Principal; import org.jboss.security.SecurityAssociation; import org.zkoss.zk.ui.Component; import org.zkoss.zk.ui.event.Event; import org.zkoss.zk.ui.event.EventThreadInit; public class JBossEventThreadInit implements EventThreadInit { private final Principal _principal; private final Object _credential; /** Retrieve info at the constructor, which runs at the servlet thread. */ public JBossEventThreadInit() { _principal = SecurityAssociation.getPrincipal(); _credential = SecurityAssociation.getCredential(); } //-- EventThreadInit --// /** Initial the event processing thread at this method. */ ZK 开发手册 chanson 制作 第 47 页 共 265 页 public void init(Component comp, Event evt) { SecurityAssociation.setPrincipal(_principal); SecurityAssociation.setCredential(_credential); } } 然后,在 WEB-INF/zk.xml 文件中,你需要完成如下的配置: JBossEventThreadInit 5.5.2. 处理完每个事件后清理 类似的,在处理完每个事件后你或许必须清理一个事件处理线程。 一个典型的例子是关闭事务处理,如果它没有被适时地关闭。 为了清理一个事件处理线程,你必须在 WEBINF/zk.xml 文件的 listener 元素中 注册一个实现 org.zkoss.zk.ui.event.EventThreadCleanup 接口的监听 类。 my.MyEventThreadCleanup [30] the Developer's Reference 的附录(Appendix B) 进行了详细描述。 [31] http://www.jboss.org 6. ZK 用户界面标记语言 ZK 用户界面标记语言 ZK 用户界面标记语言(ZUML)是基于 XML 的。每一个 XML 元素描述了要创建的组件。 一个 XML 属性描述了被创建组件的初始值。一个 XML 处理指令(processing instruction)如何处理整个页面,如页面的标题。 ZK 开发手册 chanson 制作 第 48 页 共 265 页 不同的组件集通过XML命名空间来区分。例如,XUL的命名空间为 http://www.zkoss.org/2005/zul [32],而XHTML的命名空间为 http://www.w3.org/1999/xhtml 。 6.1. XML 这一章节提供了和ZK一起工作的XML的最基本概念,如果内很熟悉XML,可以跳过这 一章节。如果你想学习更多,在网络上有很多资源,如 http://www.w3schools.com/xml/xml_whatis.asp和 http://www.xml.com/pub/a/98/10/guide0.html。 XML 是一种标记语言,就像 HTML,但是有更严格和简洁(cleaner)的语法,有几点需 要特别注意。 6.1.1. 元素必须格式良好 首先,每个元素必须关闭。有两种元素来关闭一元素,就像下面描述的一样,它们是等 价的。 描述 代码 通过一个结束标签关闭: 不通过一个结束标签关闭: 其次, 元素要被正确的嵌套(nested)。 结果 代码 正确: Hello World! 错误: Hello World! 6.1.2. 特殊字符必须被替换 XML 使用来表示一个元素,所以你必须替换特殊的字符。 ZK 开发手册 chanson 制作 第 49 页 共 265 页 例如,你必须使用<表示<。 特殊字符 替换字符 < < > > & & " " ' ' 另外,你可以使用 CDATA 来使 XML 解析器不要解释其内的文本,如下: 0) { //do something } ]]> 有意思的是反斜杠(\)不是特殊字符,所以你不需要担心。 6.1.3. 属性值必须被指定且用引号包围 结果 代码 正确: width="100%" checked="true" 错误: width=100% checked 6.1.4. 注释 注释通常被用于留下一个说明或暂时便基出一部分 XML 源码(leave a note or to temporarily edit out a portion of XML code)。 ZK 开发手册 chanson 制作 第 50 页 共 265 页 6.1.5. 字符编码 尽管是可选定的,但在你的 XML 中指定编码是个好主意,这样 XML 解析器可以正确的 解释文本。 注意:它必须被放置在文件的第一行。 除了指定正确的编码,同时也要确保你的 XML 编辑器支持这种编码。 6.1.6. 命名空间 命名空间是区分 XML 文档中用到名字的一个简单易懂的办法。ZK 使用 XML 命名空间 来区分组件名称。这样,只要不在同一个命名空间,两个组件有相同的名字是可以的。 ZK 使用 XML 命名空间来表现一个组件集。这样,开发人员可以在同一个页面内混合使 用两个或多个组件集,如下所示 ZHTMLDemo

ZHTML Demo

void addItem() { } 在这里 ZK 开发手册 chanson 制作 第 51 页 共 265 页 1. xmlns:x=" http://www.zkoss.org/2005/zul " 指定一个称为 ttp://www.zkoss.org/2005/zul的命名空间,并且使用x来呈现这个命 名空间。 2. xmlns:="http://www.w3.org/1999/xhtml" 指定一个称为 http://www.w3.org/1999/xhtml 的命名空间,且使用它作为默认的命 名空间。 3. 从默认的命名空间中指定一个称为 html 的元素,在这个例子中也 就是 http://www.w3.org/1999/xhtml 。 4. < x:textbox/> 从 http://www.zkoss.org/2005/zul命名空间中指定 一个称为textbox 的元素。 6.1.6.1. 使用 Schema 自动完成 许多 IDE,如 Ecipse,支持自动完成,如果 XML schema 被按如下方式指定。 除了可以从 http://www.zkoss.org/2005/zul/zul.xsd 下载到,可以在ZK的binary 发 行版中的dist/xsd目录找到zul.xsd 。 [32] 被称为 http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul。但是,添加 了许多非XUL特性,所以最好使用独立的命名空间。 6.2. 条件式流程 创建一个元素的流程可以条件式的。通过只指定 if 或 unless 属性,或都指定,开发人 员可以控制是否创建相关的元素。 在下面的例子中,如果 a 为 1,且 b 不为 2 window 组件就会被创建。如果一个元素 被忽略,那么它所有的组件都会忽略。 ... 下面的例子控制什么时候解释 Java 代码。 ZK 开发手册 chanson 制作 第 52 页 共 265 页 contributor.label = Executions.getCurrent().getParameter("contributor"); 6.3. 反复式流程 创建一个元素的流程可以是反复式的。通过为 forEach 属性一个对象集合,开发人员 可以控制相关元素要被创建多少次。为了描述方便,如果一个元素被 forEach 属性赋 值就称其为迭代元素(iterative element)。 在下面的例子中,列表项目被创建了三次。注意必须使用 EL 表达式来指定集合。 grades = new String[] {"Best", "Better", "Good"}; 迭代(iteration)依赖于 forEach 属性指定值的类型。 1. 如果是 java.util.Collection,就会迭代集合(collection)的每个元素。 2. 如果是 java.util.Map,就会迭代 map 中的每个 Map.Entry。 3. 如果是 java.util.Iterator,就会迭代迭代器(iterator)中的每个元素。 4. 如果是 java.util.Enumeration,就会迭代 enumeration 中的每个元 素。 5. 如果是 Object[],int[],short[],byte[],char[],float[] 或 double[]被指定了,就会迭代数组(array)中的每个元素。 6. 如果是 null,什么也不会产生(被忽略)。 7. 如果被指定的不是以上类型,相关元素仅被赋值(evaluated)一次,就好像一个 集合只指定了一个单 独的项目。 6.3.1. each 变量 在迭代中,一个变量被称为 each,通过指定集合的项目被创建并且赋值。在上面的例 子中,首次迭代中,each 被赋值为"Best",然后是"Better",最后是"Good"。 ZK 开发手册 chanson 制作 第 53 页 共 265 页 注意 each 变量在 EL 表达式和 zscript 中都是可访问的。ZK 将会保留以前定义的 每个变量,并在迭代完每个元素后将其恢复。 6.3.2. forEachStatus 变量 forEachStatus 变量是 org.zkoss.ui.util.ForEachStatus 的一个实例 (instance),用来保存(hold)当前迭代(iteration)的信息。也主要用于取得封闭元素的 项目,这些元素已通过 forEach 属性赋值。 在下面的例子中,我们使用嵌套迭代元素(nested iterative elements)来产生两个 listbox。 classes = new String[] {"College", "Graduate"}; grades = new Object[] { new String[] {"Best", "Better"}, new String[] {"A++", "A+", "A"} }; 注意 forEachStatus 变量在 EL 表达式和 zscript 中都是可访问的。 6.3.3. 如何在事件监听器中使用 each 和 forEachStatus 变量 在事件监听器中使用forEach和forEachStatus变量有点棘手(tricky),因为它们 仅在组件创建阶段(Component Creation Phase)[33]是可用的(available)。因 此 ,下 面的例子是不正确的。当onClick监听器被调用时,each变量就不可用了。 就像按钮(button)标签(label),定制属性(custom attributes)的属性在组件创建阶段 (Component Creation Phase)被赋值(evaluated),所以在这里你可以使用 each。 然后,它被存储在一个定制属性,这个定制属性会在组间存在的期间一直有效(直到组 件被移除)。 ZK 开发手册 chanson 制作 第 55 页 共 265 页 [33] 参考组件活动周期 (Component Lifecycle) 一章获取细节。 6.4. 随机存取(Load on Demand) 默认情况下,当加载页面时,ZK 基于在 ZUML 页面内定义的内容依次创建组件。但是, 我们可以推迟部分组件的创建,直到它们可见。这个特性即为随机存取 (load-on-demand)。如果在初始时有许多非可见组件,随机存取可以提高性能。 6.4.1. 使用 fulfill 属性的随机存取 延迟创建子组件的最简单方式是使用 fulfill 属性。例如,在下面的代码片断中, comboitem 组件将不会被创建,comboitem 组件接收了 onOpen 事件,此事件可 使 comboitem 变为可见。 换句话说,如果一个 ZUML 元素使用了 fulfill 属性,直到 fulfill 指定的事 件发生时这个组件的子组件才会被处理。 如果创建子组件创建事件的目标是另一个组件,你可以按如下描述指定目标组件的标 识。 7.2.8. position 属性 除了 left 和 top 属性,你可以使用 position 属性来控制重叠/弹出/modal window 的位置。例如,下面的代码片断将 window 置于右下角。 ... position 属性可以是下列常量的集合,各常量之间以逗号 (,)隔开。 常量 描述 center 将 winow 组件放置在中间。若制定了 left 和 right 属性,则为垂直中心。 若指定了 top 和 buttom 属性,则为水平中心。若均未指定,则以为着在 两个方向均居中。 left 和 top 属性被忽略。 ZK 开发手册 chanson 制作 第 99 页 共 265 页 常量 描述 left 将 winow 组件在左边。 left 属性被忽略。 right 将 winow 组件在右边。 left 属性被忽略。 top 将 winow 组件在顶部。 top 属性被忽略。 bottom 将 winow 组件在底部。 top 属性被忽略。 默认情况下,其值为 null。也就是,重叠和弹出 window 的位置由 left 和 top 决定, 而 modal window 居中。 7.2.9. 通用对话框 XUL 组件集支持下列通用对话框来简化一些通用任务。 7.2.9.1. 消息框 org.zkoss.zul.Messagebox 类提供了一套功能来显示消息框。典型应用是当发 生错误时警告用户,或促使用户作出决定。 if (Messagebox.show("Remove this file?", "Remove?", Messagebox.YES | Messagebox.NO, Messagebox.QUESTION) == Messagebox.YES) { ...//remove the file } 由于警告用户有误是很平常的,所以一个称为 alert 的全局函数被加进了 zscript。 alert 函数是 Messagebox 类中 show 方法的一个捷径。换言之,下列两条语句是 等价的。 alert("Wrong"); Messagebox.show("Wrong"); 注意 Messagebox 为一个 modal window,所以它也受相同的约束:仅在事件监听 器中是可执行的。因此,下列代码将会失败。参考上面的 Modal 窗口和事件监听器一 节获取更多的描述。 ZK 开发手册 chanson 制作 第 100 页 共 265 页 //failed since show cannot be called in paging loading if (Messagebox.show("Redirect?", "Redirect?", Messagebox.YES | Messagebox.NO, Messagebox.QUESTION) == Messagebox.YES) Executions.sendRedirect("another.zul"); 7.2.9.2. 文件上传对话框 org.zkoss.zul.Fileupload 类提供了一套功能用以帮助用户向服务器上传文 件。一旦调用了 get 方法,浏览器端会显示一个文件上传对话框来促使用户指定要上传 的文件。直到用户已经上传了文件或点击了放弃按钮其才会返回。 ZK 开发手册 chanson 制作 第 101 页 共 265 页 2.7.2.9.1. 一次上传多个文件 如果你允许一次上传多个文件,可以按如下方式 指定允许数字的最大值。 7.2.9.3. fileupload 组件 fileupload 不是一个 modal 对话框。它是一个组件,所以 fileupload 可以和其它组件 一起插入文字之间。 注:除了静态的 get 方法用于打开文件上传对话框, org.zkoss.zul.Fileupload 本身即为一个组件。 即所谓的 fileuplod 组 件。 例如, Upload your hot shot: ZK 开发手册 chanson 制作 第 102 页 共 265 页 3.7.2.9.1. onUpload 事件 当按下上传按钮后,onUpload 事件及 org.zkoss.zk.ui.event.UploadEvent 事件的一个实例被送出。你可以使用 getMedia 或 getMedias 方法获取上传文件的内容。 注意 getMedia 和 getMedias 方法返回 null 即表示没有文件被指定但上传按钮被 按下了。 3.7.2.9.2. OnClose 事件 除了 onUpload,onClose 事件也被送出以通知上传按钮还是放弃按钮被按下。默 认情况下,如果监听了此事件来实现定制行为(have the custom behavior), fileupload 组件会失效,也就是说,所有的域(field)会被清空或重设(redraw)。 7.2.9.4. 文件下载对话框 org.zkoss.zul.Filedownload 类提供了一套功能用以帮助用户从服务器下载 文件。不同于 iframe 组件在浏览器窗口显示文件,如果其中的一个 save 方法被调 用,则文件下载对话框会显示在浏览器端。然后,用户可以指定在本地文件系统中的存 储路径。 ZK 开发手册 chanson 制作 第 103 页 共 265 页 [35] wc 为 window 内容, 而 wt 为 window title. [36] 参考组件活动周期(Component Lifecycle) 一章. [37] 假定使用Tomcat. 7.3. 布局组件 组件: borderlayout, north,south,center,west,east 布局组件是嵌套组件。 父组件为 borderlayout,子组件包括 north,south, center,west,和 east。borderlayout 子组件的组合是任意的。例如,你想 将某一区域分成三个区域(竖直的),则可以试试下面的组合, ZK 开发手册 chanson 制作 第 104 页 共 265 页 The East
The Center
The West
或者你可以将区域水平的分为三部分,如下, The North
The Center
The South
并且,你可以根据需求在这些区域内嵌入任何 ZK 组件。 7.3.1. 嵌套的 borderlayout 组件 此外,你可将布局组件嵌入到另一个布局组件中,以划分更多的区域,如下, Inner West
Inner Center
Inner East
ZK 开发手册 chanson 制作 第 105 页 共 265 页
Inner West
Inner Center
7.3.2. size 和 border 属性 你可以为下列的自组件(north,south,east,west)指定 size 属性以决定其大 小。但是,size 属性的功能依赖于子组件的类型(竖直的或水平的)。对于水平组件 (north, 和 south),size 属性决定了它们的高度。而对于竖直组件, size 属性 决定了它们的宽度 border 属性决定是否为这些布局组件设置边框,包括 borderlayout 的所有子组 件。下面的表格指出了 border 属性的值。 值 描述 none (default) 无边框 normal 有边框 这里有一个例子。 The North The East
The Center
The West ZK 开发手册 chanson 制作 第 106 页 共 265 页 The South
7.3.3. splittable 和 collapsible 属性 若你想使你的布局组件可拆分(splittable),则可以将 splittable 属性设置为 true。 此外,若你想使一个组件可折叠(collapsible),则可以将 collapsible 属性设置为 true。
7.3.3.1. maxsize 和 minisize 属性 当你将一个组件设置为 可拆分时,则 maxsize 和 minisize 属性会决定此组件的变动 范围(re-resizing range)。 7.3.4. flex 属性 若留浏览器的大小改变了,则布局组件会自动调整自身的尺寸来适合浏览器的尺寸。若 你想那些嵌入这些布局组件的 ZK 组件也可以自定调整自身大小,则可以将布局组件的 flex 属性设置为 true。 7.3.5. open 属性 为了获知一个布局组件是否已被折叠,则可以检查属性的值(也就是 isOpen 方法)。 若想在程序中打开或折叠布局组件,则可以设置 open 属性的值(也就是,setOpen 方法)。 ZK 开发手册 chanson 制作 第 107 页 共 265 页 7.3.6. onOpen 属性 当用户打开或折叠一个布局组件时,则 onOpen 事件会被送至应用程序。 7.4. 箱式模型 组件: vbox , hbox 和 box 。 XUL 的箱式模型用于将显示部分分割成一系列的 box。Box 内的组件需要将它们定位 成水平或垂直的。通过一系列的 box 及 separator,可以控制视觉表现的布局(visual representation)。 box 可以将其子组件布局成两种方位,水平的或垂直的。水平 box 可以将其子组件排 成一条线,而垂直 box 可以将其子组件定位成垂直方向。你可以将其想象成 HTML 表 格的行或里列。 下面是一些例子。 1. 一旦组件被克隆了,所有它的子组件也都会被克隆(all its children and descendants)。 2. 被克隆的组件并不属于任何页面和父组件。换言之, src.clone().getParent()会返回 null。 3. ID 并未改变,若你想将被克隆的组件添加回相同的 ID 空间,要记住更改 ID。 1. 组件序列化 所有组件都是可序列化的(serializable),所以你可以为内存或其它存储器序列化组件, 之后再拆解它们(so you can serialize components to the memory or other storage and de-serialize them later)。就像克隆,被拆解的组件不属于另一个页面 (和桌面)(another page (and desktop))。它们也独立于被序列化的组件。如下所述, 序列化可以被用于实现相似的克隆功能。 int cnt = 0; 当然,使用 clone 方法克隆会有更好的性能,而序列化组件可以被用于不同的机器之 间(crossing different machines)。 1. 序列化会话 默认情况下,一个非序列化的实现会被用于表示一个会话 (org.zkoss.zk.ui.Session)。使用非序列化实现的好处是不需要担心存储在一 个组件内的值,例如 Listitem's setValue,是否为可序列化的。 但是,若你想确认存储在组件内的所有值都是可序列化的,可以使用一个序列化的实现 表示一个会话。 为了配置 ZK 使用序列化实现,你需要在 WEB-INF/zk.xml 内配置 factory-class 元素,细节请参考 the Developer's Reference 的附录 B(Appendix B)。 2. 序列化监听器 存储在一个组件,页面,桌面或会话内的属性,变量和监听器也会被序列化,如果它们 是可序列化的(且相应的组件,页面,桌面或会话会被序列化)。 为简化可序列化对象的实现,ZK 会在序列化前和拆解之后调用序列化监听器,若实现 了特定的接口。例如,你可以按如下方式为一个组件实现事件监听器。 public MyListener implements EventListener, java.io.Serializable, ComponentSerializationListener { private transient Component _target; //no need to serialize it //ComponentSerializationListener// public willSerialize(Component comp) { ZK 开发手册 chanson 制作 第 213 页 共 265 页 } public didDeserialize(Component comp) { _target = comp; //restore it back } } org.zkoss.zk.ui.util.ComponentSerializationListener 接口被用 于序列化一个组件时。类似的,PageSerializationListener, DesktopSerializationListen 和 SessionSerializationListener 被分别用于序列化一个页面,桌面和会话时。 10.7. 跨页面通信 在同一桌面内不同页面间通信是很直接的。首先,可以使用事件来互相通知。其次,可 以使用属性共享数据。 10.7.1. 提交和发送事件 你可以在同一桌面的不同页面间通信。通信方式是使用 postEvent 或 sendEvent 通知目标页面的组件。 Events.postEvent(new Event("SomethingHappens", comp.getDesktop().getPage("another").getFellow("main")); 10.7.2. 属性 每个组件,页面,桌面,会话和 Web 应用程序都一个独立的属性映射。这是一个在组 件,页面,桌面,甚至会话间共享数据的好地方。 在 zscript 和 EL 表达式中,你可以使用隐含对象 :componentScope , pageScope , desktopScope , sessionScope , requestScope 和 applicationoScope。 在一个 Java 文件中,你可以使用相应类中的属性相关方法来访问它们。你也可以使用 作用域(scope)参数来标识你想访问的作用域。下面的两条语句是等价的,假定 comp 为一个组件。 comp.getAttribute("some", comp.DESKTOP_SCOPE); comp.getDesktop().getAttribute("some"); ZK 开发手册 chanson 制作 第 214 页 共 265 页 10.8. 跨 Web 应用程序通信 一个 EAR 文件可以包含多个 WAR 文件。每个 WAR 都为一个 Web 应用程序。在两个 Web 应用程序间通信没有标准的方法。 但是,ZK 支持从另一个 Web 应用程序引用文件。例如,假定你想从另一个 Web 应用 程序,例如 app2,包含一个资源,例如/foreign.zul。那么,你可以按如下方式 处理。 类似的,你可以从另一个 Web 应用程序引用一个样式表。