ZK框架的分析与应用

时间:2023-06-10 18:21:02

前言:本文是在下的在学习ZK官方文档时整理出来的初稿。本来里面有很多的效果图片和图片代码的。奈何博客园中图片不能粘贴上去,所以感兴趣的筒子们就将就吧。内容中,如有不好的地方,欢迎斧正!

ZK框架的分析与应用

1 ZK框架的引入

1.1 概述

ZK是一套以 AJAX/XUL/Java 为基础的网页应用程序开发框架,用于丰富网页应用程序的使用界面。最大的好处是,在设计AJAX网络应用程序时,轻松简便的操作就像设计桌面程序一样。 ZK包含了一个以AJAX为基础、事件驱动(event-driven)、高互动性的引擎,同时还提供了丰富多样、可重复使用的XUL与HTML组件,以及以 XML 为基础的使用界面设计语言 ZK User-interfaces Markup Language (ZUML)。

通过ZK这个框架,软件开发者基本可以脱离美工,由开发人员自己来布局页面,这样能够大大提高软件开发的效率。

1.2 构建一个简单的ZK应用

如何组建UI

组建UI即创建我们展示给Client端的View层页面。

2.1  使用基于xml的方式

2.2  使用纯Java代码的方式

2.3  xml与java代码混用的方式

2.4  zk中组建UI时需要了解的xml基础

通常情况下,我们都是使用XML的方式来组建UI,XML文件有严格的语法。如:必需要有结束标签;标签之间的嵌套要合理;等等。

XML文件中的<?……?>表示的是处理命令。它位于xml文件的第一行,它和根元素是一个级别的。

在XML文件中是不能使用特殊字符的,如果我们一定要用那么就要将它替换掉:

特殊字符

替换成

<

&lt;

>

&gt;

&

&amp;

&quot;

&apos;

\t(Tab)

     当属性值中要使用时才需要替换

\n

     当属性值中要使用时才需要替换

3  ZK页面中的基本概念:桌面、页面、组件

我们通过浏览器去访问ZK应用的时候,浏览器展示给我们的是ZK的一个Desktop,而在一个Desktop中可以包含多个Page。组成一个Page的基本元素就是ZK Component(ZK组件)。

3.1  Desktop、Page和Component之间的关系

桌面(Desktop)

它是zul页面的集合,即一个Desktop可以包含多个zul页面。

页面(Page)

zul页面是组件的集合。

组件(component)

ZK组件是组成zul页面的最小单位。

组件是一种用户接口(UI),比如一个标签、一个按钮。所有的ZK组件都实现了org.zkoss.zk.ui.Component接口

下面我们通过一幅图来看它们之间的关系:

注:使用Include组件包含一个页面时,如果我们设置它的mode(模式)为defer(延迟),那么,将有两个页面在这个Desktop上被创建

3.2  将Component附加到Page

一个组件只有附加到一个page时才能在client端得到显现。要使一个组件属于一个page的话有两种方式:

1、  将组件添加到一个已经附加到page的组件上。相应的api:Component.appendChild(org.zkoss.zk.ui.Component)、Component.insertBefore(org.zkoss.zk.ui.Component, org.zkoss.zk.ui.Component)、Component.setParent(org.zkoss.zk.ui.Component)

2、  使用Component.setPage(org.zkoss.zk.ui.Page)直接将组件添加到一个page上,这个组件就成为了该页面的根组件。

3.3  销毁Page上的Component

ZK的组件之间是树状结构的,每一组件都只有一个根。

从页面上销毁一个组件可以通过下面两种方式来实现:

1、  组件不是根组件时:Component.setParent(null)

组件是根组件时:Component.setPage(null)

2、  Component.detach()

很显然第二种方式要简便。

销毁一个组件下面的所有组件可以通过如下代码来实现:

Component.getChildren().clear();

它等同于

for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {

it.next();

it.remove();

}

3.4  使用ZK Studio来编辑zul页面

建议使用ZK Studio来编辑zuml页面,它里面有很好的content assist和visual editor功能。我们只需要在eclipse上安装一个ZK Studio插件就可以了。

你也可以直接使用XML Editor来编辑zuml页面,不过最好是在window组件中加上schema,再在eclipse中配置好对应的xsd文件,一样可以实现content assist的功能。

<window xmlns="http://www.zkoss.org/2005/zul"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://www.zkoss.org/2005/zul

http://www.zkoss.org/2005/zul/zul.xsd">

4  ZK常用基本组件

4.1  输入框组件汇总

在ZK中,输入框被细分成了很多种:textbox、intbox、decimalbox、doublebox、datebox、timebox、spinner、combobox、bandbox

组件的常用属性有(属性是可选的):constraint、disabled、maxlength、readonly

组件名

用途

textbox

用于输入文本。

例1:<textbox value="Jerry" width="150px" />

效果图:

例2:<textbox type="password" value="foo" width="150px" />

效果图:

intbox

用于输入整数,其他的按键都会被屏蔽。比如输入字母时不会有任何反应

例:<intbox constraint="no negative,no zero" width="150px" value="12345678" />

效果图:

decimalbox

用于输入小数,其他的按键都会被屏蔽。

例:<decimalbox format="###.##" value="154.326" width="150px" />

效果图:

doublebox

用于输入双精度数,其他的按键都会被屏蔽

datebox

用于输入日期,它自带一个日历控件,且可以对日期进行格式化。

例:<datebox id="db" width="150px" format="yyyy/MM/dd" />

效果图:

未点击时:

点击后:

timebox

用于输入日期,注意:它的value是Date类型的。

例:

<zscript>

Date now = new Date();

</zscript>

<timebox value="${now}"/>

效果图:

spinner

用于输入整数,其他的按键都会被屏蔽,组件上的箭头用于增加或减小输入框中的值。它的value只能是int类型的整数。step属性用于控制增长的歩长

例:<spinner value="123345" step="2"/>

效果图:

combobox

下拉框。默认情况下是可以输入值的,设置了disabled=”true”之后,就只能通过鼠标选择而不能输入值。

例:

<combobox>

<comboitem label="湖南" />

<comboitem label="北京" />

<comboitem label="上海" />

</combobox>

效果图:

未点击时:

点击后:

bandbox

一般用作搜索框。可以使用buttonVisible="false"将组件上的按钮隐藏

例:<bandbox value="123" />

效果图:

4.1.1 对输入框输入内容的约束和验证

输入框通常都有一个constraint属性,它是用来约束用户输入的,使用constraint属性可以控制输入控件接受什么值。

当失去光标的焦点时,如果不符合约束条件,就会弹出出错信息。

在ZK中提供了两种约束用户输入的方式:第一是使用ZK自定义的约束条件;第二是使用正则表达式。

constraint属性可以使用的值有:no positive、no negative、 no zero、no empty、no future、no past、no today 和一个正则表达式的集合。

no positive、no negative、 no zero适用于intbox。

no future、no past和no today 约束仅适用于datebox。

no empty适用于任何组件。

正则表达式约束仅适用于字符串类型组件,例如textbox、combobox和bandbox。

①  ZK自定义的约束条件

constraint中可以使用的约束条件表

约束条件

解析

no empty

输入不能为空

no future

不能是以后的时间。用于datebox

no negtive

不能输入负数。用于intbox

no past

不能是过去的时间。用于datebox

no positive

不能是正数。用于intbox

no today

不能是今天。用于datebox

no zero

不能是0。用于intbox

between yyyyMMdd and yyyyMMdd

控制时间在一个范围内。

例:<datebox constraint="between 20071225 and 20071203"/>

after yyyyMMdd

例:<datebox constraint="after 20071225"/>

before yyyyMMdd

end_before

end_after

after_start

after_end

指定弹出的错误提示框的位置。

例:<textbox constraint="no empty, start_before"/>

效果图:

②  使用正则表达式

例:<textbox constraint="/.+@.+\.[a-z]+/:邮箱地址不正确" />

效果图:

也可以不指定出错信息,这时会使用默认的出错信息。

例:<textbox constraint="/.+@.+\.[a-z]+/" />

效果图:

如果是在java代码中,我们可以这样指定控件的constraint:

new Textbox().setContraint("/.+@.+\\.[a-z]+/");

4.2  容器组件

容器组件可以用来包含其他组件。

4.2.1  Window组件

Window组件是我们在ZK中使用最多的一个组件。

一个Window组件,就像HTML中的div标签一样,用来将组件组合在一起。

和其他组件不同,Window组件具有如下特点:

◆ Window组件是ID空间宿主。Window组件下的任何组件(包括它自身)都可以通过这个Window组件的id来进行检索。使用:id.getFellow(java.lang.String)

◆ Window组件可以设置成overlapped(重叠)、popup(弹出)、embeded(嵌入)

◆ Window可以作为一个模态窗口(modal dialog)

①  Window组件的模式(mode)

zul页面中,使用mode属性来设置Window的模式。

Window组件有5种不同的模式:overlapped、popup、modal、hightlighted、embeded

默认的是embeded模式。

我们可以通过Window.setMode(java.lang.String)来改变Window的模式。

我们也可以通过调用Window.doOverlapped()、Window.doPopup()、Window.doModal()、Window.doHightlighted()、Window.doEmbeded()来改变Window的模式。

例:

<zk>

<window id="win" title="Hi!" border="normal" width="200px">

<caption>

<toolbarbutton label="Help" />

</caption>

<checkbox label="Hello, Wolrd!" />

<button label="CLOSE" onClick='win.setVisible(false)'/>

</window>

<button label="Overlap" onClick="win.doOverlapped();" />

<button label="Popup" onClick="win.doPopup();" />

<button label="Modal" onClick="win.doModal();" />

<button label="Embed" onClick="win.doEmbedded();" />

<button label="Highlighted" onClick="win.doHighlighted();" />

<space bar="true"/>

<button label="SHOW" onClick='win.setVisible(true)'/>

</zk>

效果图:

embeded:这是默认的模式。这种模式下,我们不能改变Window的位置。

overlapped:overlapped模式下的Window与其他的组件是层叠的。用户可以使用鼠标拖拽它。开发人员可以通过Window.setLeft(java.lang.String)和Window.setTop(java.lang.String)来设置它的位置。

popup:popup模式和overlapped模式相似。它们之间不同是:在popup模式下,我们只要点击其他组件的话,这个弹出的Window就会自动消失。

modal:modal与hightlighted模式基本上是相同的。modal模式下,Window之外的组件是不能够操作的(如下图)。

hightlighted:

4.1.2  Window组件的常用属性

window属性表

属性名

说明

border

取值有:normal、none(默认为none)

例:

<window title="bordernone" border="none" width="200px">

border为none的Window

</window>

<window title="bordernone" border="normal" width="200px">

border为normal的Window

</window>

closable

一旦我们关闭了这个Window,那么这个页面上将不会有这个Window存在,也不能对它进行引用,否则会报错,这是也隐藏最大的不同。我们也可以通过Window.detach()来实现相同的效果。

Window关闭的时候会触发onClose事件。

我们可以重写onClose方法来实现我们自己想要的操作。比如,我们想在点击关闭按钮的时候将Window隐藏,而不是detach

例:

<window id="myWin" closable="true" title="Demo" border="normal" width="200px"

onClose="self.visible = false; event.stopPropagation();">

点击关闭按钮,Window将会被隐藏,而非detach

</window>

<button label="SHOW" onClick='myWin.setVisible(true)'/>

效果图:

contentStyle

通过这个属性,我们可以为Window定制样式。

例1:

<window title="My Window" border="normal" width="200px" contentStyle="background:yellow">

Hello, World!

</window>

效果图:

例2:

<window id="win" title="Hi" width="150px" height="100px" contentStyle="overflow:auto" border="normal">

当我们设置了overflow:auto之后,当内容超过了可显示的区域之后,就会出现滚动条

</window>

效果图:

position

我们可以为mode为overlapped、pupup和modal的Window设置位置。

如:<window width="300px" mode="overlapped" position="right,bottom">

可供选择的position有:center、left、right、top、bottom

sizable

用于设置Window是否可以改变大小。

当用户改变Window的大小时会触发onSize事件

title AND caption

用来设置标题。(注:caption是一个组件,而非属性)

例:

<window title="My Window" border="normal" width="200px">

<caption image="/imgs/sml4.gif" label="Hi there!"/>

<checkbox label="Hello, World!"/>

</window>

效果图:

4.2  Grid组件

Grid组件是处理与展现大量数据的组件。与它功能类似的组件有:Grid、Listbox、Tree。

4.2.1  Grid、Listbox、Tree的比较

◆  Grid和Listbox都是用来展现列表数据的。而Tree主要用来展示分层数据。

◆  Listbox和Tree允许用户选中它所展现的一条或多条数据(可以通过ZK提供的方法来获取选中的数据),而Grid则不行。

◆  Grid、Listbox和Tree都支持分页,具体请查阅Paging组件。

4.2.2  Grid概观

相关子组件:grid、columns、colum、rows、row

组件名

说明

grid

使用它来定义一个grid

columns

用来设置grid的标题头。

column

位于columns里面,用来设置列的相关属性

rows

用来展现数据

row

位于rows里面,用来展现一行数据

例1:

<zk>

<grid width="500px">

<columns>

<column label="Column 1"/>

<column label="Column 2"/>

<column label="Column 3"/>

</columns>

<rows>

<row>

<label value="Item A.1" />

<label value="Item A.2" />

<label value="Item A.3" />

</row>

<row>

<label value="Item B.1" />

<label value="Item B.2" />

<label value="Item B.3" />

</row>

<row>

<label value="Item C.1" />

<label value="Item C.2" />

<label value="Item C.3" />

</row>

</rows>

</grid>

</zk>

效果图:

4.2.3  Grid展现大批量数据

在grid中,我们有两种方式来展现大批量数据:一是使用滚动条;二是使用分页。

① 使用滚动条

只需要为grid的hight属性设置一个值,当数据展现超过这个hight时,滚动条会自动出现。

例:

我们在上面的例子中的grid标签上加个属性height=”100px”,即<grid width="500px" height="100px">。得到的效果图如下:

② 使用分页

在grid中添加属性:mold=”paging”  pageSize=”n”即可。当展现的数据量超过n条时,分页控件会自动出现。

(注意:这里的分页只是页面上的分页,而不是后台的分页。有人叫这种分页为假分页)

例:

我们将上面代码中的height去掉,加上属性:mold="paging" pageSize="3"

效果图:

pageSize的默认值为:20

除此指定mold=”paging”之外,我们还可以使用Paging组件来实现分页。详情请参照后续Paging分页的介绍。

4.2.4  使用Paging组件来为Grid分页

使用Paging组件,我们可以自定义的放置paging组件的位置。还可以使用一个Paging组件来控制多个grid,这时需要指定Grid组件的paginal属性为同一个Paging。

例:

<vbox>

<paging id="pg" pageSize="3" />

<hbox>

<grid width="300px" mold="paging" paginal="${pg}">

<columns>

<column label="Left" />

<column label="Right" />

</columns>

<rows>

<row>

<label value="Item 1.1" />

<label value="Item 1.2" />

</row>

<row>

<label value="Item 2.1" />

<label value="Item 2.2" />

</row>

<row>

<label value="Item 3.1" />

<label value="Item 3.2" />

</row>

<row>

<label value="Item 4.1" />

<label value="Item 4.2" />

</row>

</rows>

</grid>

<grid width="300px" mold="paging" paginal="${pg}">

<columns>

<column label="Left" />

<column label="Right" />

</columns>

<rows>

<row>

<label value="Item A.1" />

<label value="Item A.2" />

</row>

<row>

<label value="Item B.1" />

<label value="Item B.2" />

</row>

<row>

<label value="Item C.1" />

<label value="Item C.2" />

</row>

<row>

<label value="Item D.1" />

<label value="Item D.2" />

</row>

<row>

<label value="Item E.1" />

<label value="Item E.2" />

</row>

</rows>

</grid>

</hbox>

</vbox>

效果图:

4.2.5  Paging组件的onPaging事件

当用户点击paging组件的时候,会触发onPaging事件。如果想实现后台自动分页,我们就可以通过这个事件来实现。

例:

<window id="win" title="分页" border="normal" width="400px">

<grid mold="paging" onCreate="init()">

<columns>

<column label="编号" />

<column label="名字" />

</columns>

<rows id="rowsId">

</rows>

</grid>

<paging id="myPaging" pageSize="3" onPaging="nextPage(event)"/>

<zscript>

<![CDATA[

import org.zkoss.zul.event.PagingEvent;

class Student{

private int id;

private String name;

public Student(){}

public Student(int id, String name){

this.id = id;

this.name = name;

}

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

}

List stuList = new ArrayList();

Student stu1 = new Student(1, "张三");

Student stu2 = new Student(2, "李四");

Student stu3 = new Student(3, "王五");

Student stu4 = new Student(4, "朱红");

stuList.add(stu1);

stuList.add(stu2);

stuList.add(stu3);

stuList.add(stu4);

final int PAGE_SIZE = myPaging.getPageSize();

public void nextPage(Event event){

PagingEvent pe = (PagingEvent) event;

int pgno = pe.getActivePage();//页数(从零计算)

int start = pgno * PAGE_SIZE;

int end = start + PAGE_SIZE;

end = end>stuList.size()?stuList.size():end;

System.out.println(start+","+end);

rowsId.getChildren().clear();//清空数据

createData2View(start, end);

}

private void createData2View(int start, int end){//显示数据

for(int i=start; i<end; i++){

Student stu = (Student)stuList.get(i);

Row row = new Row();

Label lab1 = new Label();

lab1.setValue(stu.getId()+"");

Label lab2 = new Label();

lab2.setValue(stu.getName());

lab1.setParent(row);

lab2.setParent(row);

rowsId.appendChild(row);

}

}

public void init(){

createData2View(0, 3);

myPaging.setTotalSize(stuList.size());//总数据量

}

]]>

</zscript>

</window>

效果图:

除了可以在onPaging事件上写函数之外,我们还可以使用下面的代码来为Paging组件添加事件监听器:

pagingId.addEventListener("onPaging", new EventListener() {

public void onEvent(Event event) {

……

……

……

}

};

4.2.6  Grid的排序功能

通过在column上设置属性sort,即可实现排序功能。详情请参见Listbox的排序。

4.2.7  使用grid的Model来动态展现数据

例:

<window title="Live Grid" border="normal" width="150px">

<zscript><![CDATA[

String[] data = new String[10];

for (int j = 0; j < data.length; ++j) {

data[j] = "option " + j;

}

ListModel strset = new SimpleListModel(data);

]]></zscript>

<grid height="100px" model="${strset}">

<columns>

<column label="options" />

</columns>

</grid>

</window>

效果图:

4.2.8  Grid的辅助标题(Auxiliary Headers

除了使用columns来添加表头以外,我们还可以使用辅助表头auxhead来添加表头。

auxhead支持rowspan和colspan属性,这是column所不支持的。

columns和column只能用在grid组件里面,而auxheader、auxhead可以用在Listbox、Grid和Tree组件里面。

例:

<zk>

<grid>

<auxhead>

<auxheader label="H1'07" colspan="6" />

<auxheader label="H2'07" colspan="6" />

</auxhead>

<auxhead>

<auxheader label="Q1" colspan="3" />

<auxheader label="Q2" colspan="3" />

<auxheader label="Q3" colspan="3" />

<auxheader label="Q4" colspan="3" />

</auxhead>

<columns>

<column label="Jan" />

<column label="Feb" />

<column label="Mar" />

<column label="Apr" />

<column label="May" />

<column label="Jun" />

<column label="Jul" />

<column label="Aug" />

<column label="Sep" />

<column label="Oct" />

<column label="Nov" />

<column label="Dec" />

</columns>

<rows>

<row>

<label value="1,000" />

<label value="1,100" />

<label value="1,200" />

<label value="1,300" />

<label value="1,400" />

<label value="1,500" />

<label value="1,600" />

<label value="1,700" />

<label value="1,800" />

<label value="1,900" />

<label value="2,000" />

<label value="2,100" />

</row>

</rows>

</grid>

</zk>

效果图:

4.2.9  使用span属性来合并单元格

例:

<zk>

<grid width="500px">

<columns>

<column label="Left" align="left" width="150px"/>

<column label="Center" align="center" width="150px"/>

<column label="Right" align="right" />

<column label="Column 4" />

</columns>

<rows>

<row>

<label value="Item A.1" />

<label value="Item A.2" />

<label value="Item A.3" />

<label value="Item A.4" />

</row>

<row spans="1,2,1">

<label value="Item B.1" />

<label value="Item B.2" />

<label value="Item B.4" />

</row>

<row spans="3">

<label value="Item C.1" />

<label value="Item C.4" />

</row>

<row spans=",,2">

<label value="Item D.1" />

<label value="Item D.2" />

<label value="Item D.4" />

</row>

<row>

<div>

<label value="Item A.1" />

<button label="BUTTON" />

</div>[微软用户1]

<span>

<label value="Item A.2" />

<button label="BUTTON" />

</span>

<label value="Item A.3" />

<label value="Item A.4" />

</row>

</rows>

</grid>

</zk>

效果图:

4.2.10  Grid组件的常用属性

属性名

说明

sizedByContent

根据内容改变大小。取值:true、false

一般与span=”true”连用。例如:

<grid sizedByContent="true" span="true" width="800px">

span

当设置了sizeByContent后,每一列都只会占用它需要的宽度。如果我们想每一列都看起来宽松一点的话,就可以设置span=”true”

emptyMessage

当展现的内容为空时,就会显示我们设置的emptyMessage中的内容。

例如:<grid id="test1" emptyMessage="没有相关数据">

sizable

columns组件的属性,设置sizable=”true”后,用户就可以使用鼠标来更改列宽。例如:<columns sizable="true">

4.2.11  Grid表头上的弹出菜单:Menuepopup

默认情况下menuepopup被设置成none,我们可以在columns组件上设置menuepopup=”auto”来显示一个默认的弹出菜单。你也可以为组件设置自己定义的弹出菜单。

①  默认的Menuepopup:

例:

<zk>

<grid width="300px">

<columns menupopup="auto">

<column label="第一列" />

<column label="第二列" />

</columns>

<rows>

<row>

<label value="Item 1.1" />

<label value="Item 1.2" />

</row>

<row>

<label value="Item 2.1" />

<label value="Item 2.2" />

</row>

<row>

<label value="Item 3.1" />

<label value="Item 3.2" />

</row>

</rows>

</grid>

</zk>

效果图:

鼠标移上点击→

(勾选的列即为显示出来的列)

②  自定义的Menuepopup

例:

<zk>

<menupopup id="editPopup">

<menuitem label="Group" image="/imgs/sml1.gif"/>

<menuitem label="Sort Ascending" image="/imgs/sml2.gif"/>

<menuitem label="Sort Descending" image="/imgs/sml3.gif"/>

</menupopup>

<grid width="300px">

<columns menupopup="editPopup">

<column label="第一列" />

<column label="第二列" />

</columns>

<rows>

<row>

<label value="Item 1.1" />

<label value="Item 1.2" />

</row>

<row>

<label value="Item 2.1" />

<label value="Item 2.2" />

</row>

<row>

<label value="Item 3.1" />

<label value="Item 3.2" />

</row>

</rows>

</grid>

</zk>

效果图:

4.2.12  Grid中Detail组件的使用

detail组件是用来展示详情部分,主行和详情部分在同一行上。

例:

<grid>

<columns>

<column hflex="min" />

<column label="productName"/>

<column label="Price"/>

<column label="Item Location"/>

</columns>

<rows>

<row>

<detail open="true">

<hlayout>

<image sclass="myimg" width="100px" height="100px"

src="/imgs/tea1.gif" />

这里面可以有其他的组件来展示详细信息。也可以直接使用文字来描述……

</hlayout>

</detail>

<label value="商品1"/>

<label value="$250"/>

<label value="美国"/>

</row>

</rows>

</grid>

效果图:

展开时:

收拢后:

4.2.13  foot组件

用来显示grid的foot。

例:

<grid width="300px">

<columns>

<column label="Type" width="50px" />

<column label="Content" />

</columns>

<rows>

<row>

<label value="File:" />

<textbox width="99%" />

</row>

<row>

<label value="Type:" />

<hbox>

<listbox rows="1" mold="select">

<listitem label="Java Files,(*.java)" />

<listitem label="All Files,(*.*)" />

</listbox>

<button label="Browse..." />

</hbox>

</row>

</rows>

<foot>

<footer>footer1</footer>

<footer>footer2</footer>

</foot>

</grid>

效果图:

4.2.14  Grid中使用Group组件分组

支持groupfoot。

例:

<zk>

<grid width="800px">

<columns sizable="true">

<column label="Brand" />

<column label=" Processor Type " width="150px" />

<column label="Memory (RAM)" width="120px" />

<column label="Price" width="100px" />

<column label="Hard Drive Capacity" width="150px" />

</columns>

<rows>

<group label="Dell" />

<row>

<label style="padding-left:15px" value="Dell E4500 2.2GHz" />

<label value="Intel Core 2 Duo" />

<label value="2GB RAM" />

<label value="$261.00" style="color:green" />

<label value="500GB" />

</row>

<row>

<label style="padding-left:15px" value="XP-Pro Slim Dell-Inspiron" />

<label value="Intel Core 2 Duo " />

<label value="2GB RAM" />

<label value="$498.93" style="color:green" />

<label value="500GB" />

</row>

<row>

<label style="padding-left:15px" value="Dell P4 3.2 GHz" />

<label value="Intel Pentium 4" />

<label value="4GB RAM" />

<label value="$377.99" style="color:green" />

<label value="500GB" />

</row>

<group label="Compaq" />

<row>

<label style="padding-left:15px" value="Compaq SR5113WM" />

<label value="Intel Core Duo" />

<label value="1GB RAM" />

<label value="$279.00" style="color:green" />

<label value="160GB" />

</row>

<row>

<label style="padding-left:15px" value="Compaq HP XW4200" />

<label value="Intel Pentium 4" />

<label value="4GB RAM" />

<label value="$980" style="color:green" />

<label value="500GB" />

</row>

<groupfoot spans="5">

<label value="This a summary about Compaq Desktop PCs" />

</groupfoot>

</rows>

</grid>

</zk>

效果图:

收起来:

4.3  List组件

相关子组件:listbox、listitem、listcell、listhead、listheader

listbox组件可以用来展示列表数据。用户可以选中列表数据中的某一条记录。

listbox的mold:default、select、paging。

如果我们使用mold=”select”,那么它将被解析成HTML的select标签。

4.3.1  List组件概观

例1:

<window title="listbox demo" border="normal" width="250px">

<listbox id="box">

<listhead sizable="true">

<listheader label="name" sort="auto" />

<listheader label="gender" sort="auto" />

</listhead>

<listitem>

<listcell label="Mary" />

<listcell label="" />

</listitem>

<listitem>

<listcell label="John" />

<listcell label="" />

</listitem>

<listitem>

<listcell label="Jane" />

<listcell label="" />

</listitem>

<listfoot>

<listfooter>

<label value="This is footer1" />

</listfooter>

<listfooter>

<label value="This is footer2" />

</listfooter>

</listfoot>

</listbox>

</window>

效果图:

4.3.2  mold为select的lisbox

当listbox的mold属性为select时,可以作为一个下拉框来使用。

例:

<zk>

<listbox mold="select" rows="1" width="70px">

<listitem label="Car" />

<listitem label="Taxi" />

<listitem label="Bus" selected="true" />

<listitem label="Train" />

</listbox>

</zk>

效果图: →

4.3.3  listbox的常用属性

属性名

说明

sizedByContent

根据内容改变大小。取值:true、false

一般与span=”true”连用。例如:

<listbox sizedByContent="true" span="true" width="800px">

span

当设置了sizeByContent后,每一列都只会占用它需要的宽度。如果我们想每一列都看起来宽松一点的话,就可以设置span=”true”

emptyMessage

当展现的内容为空时,就会显示我们设置的emptyMessage中的内容。

如:<listbox id="test1" emptyMessage="没有相关数据">

sizable

listhead组件的属性,设置sizable=”true”后,用户就可以使用鼠标来更改列宽。例如:<listhead sizable="true">

4.3.4  listbox的分页

同grid组件类似,只要在listbox组件上加上属性mold="paging" pageSize="n"即可。如果不指定pageSize,则会使用默认值20。

4.3.5  listbox的自动排序功能

我们通过listheader上的sort属性来实现listbox的自动排序。默认情况下的设置为sort=”auto”。它能对这一列所展示的字符串进行自动排序。如果我们想自定义的控制,根据指定的字段来排序,可以使用sort="auto(field1[,field2...])"的格式来指定。

①  默认的排序功能

例:

<zk>

<listbox width="400px" mold="paging" pageSize="2">

<listhead sizable="true">

<listheader label="序号" sort="auto" hflex="min"/>

<listheader label="名字" sort="auto" />

<listheader label="性别" sort="auto" />

</listhead>

<listitem>

<listcell label="1" />

<listcell label="Mary" />

<listcell label="" />

</listitem>

<listitem>

<listcell label="2" />

<listcell label="John" />

<listcell label="" />

</listitem>

<listitem>

<listcell label="3" />

<listcell label="Jane" />

<listcell label="" />

</listitem>

</listbox>

</zk>

效果:

②  根据指定的字段来排序,使用sort="auto(field1[,field2...])"的格式

例:

<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?>

<window title="Test auto(FIELD_NAME1,FIELD_NAME2)" border="normal" width="300px">

<zscript>

<![CDATA[

class Person {

private String firstName;

private String lastName;

private int age;

public Person(String firstName, String lastName, int age) {

this.firstName = firstName;

this.lastName = lastName;

this.age = age;

}

public String getFirstName() {

return firstName;

}

public String getLastName() {

return lastName;

}

public String getFullName() {

return firstName + " " + lastName;

}

public int getAge() {

return age;

}

}

java.util.List persons = new java.util.ArrayList(8);

persons.add(new Person("Tom", "Yeh", 43));

persons.add(new Person("Henri", "Chen", 43));

persons.add(new Person("Jim", "Yeh", 39));

]]>

</zscript>

<listbox model="@{persons}">

<listhead>

<listheader label="Full Name"

sort="auto(lastName, firstName)" />

<listheader label="Age" sort="auto(age)" />

</listhead>

<listitem self="@{each=person}">

<listcell label="@{person.fullName}" />

<listcell label="@{person.age}" />

</listitem>

</listbox>

</window>

效果图:

4.4  Tree组件

tree组件可以实现与listbox相似的功能。但是我们通常都是用tree组件来做树状的菜单。这里不做过多介绍。

请看下面的例子:

<zk>

<window id="main" width="100%" height="100%" border="none">

<borderlayout>

<west size="200px" splittable="true" flex="true" collapsible="true" title="功能菜单">

<tree id="tree" zclass="z-dottree" width="150px" rows="100">

<treechildren>

<treeitem>

<treerow>

<treecell><image src="/imgs/menu.png"/>信息汇总</treecell>

</treerow>

<treechildren>

<treeitem>

<treerow>

<treecell><image src="/imgs/menu.png"/>基本信息</treecell>

</treerow>

<treechildren>

<treeitem>

<treerow>

<treecell><image src="/imgs/menuitem.png"/>班级信息</treecell>

</treerow>

</treeitem>

<treeitem>

<treerow>

<treecell><image src="/imgs/menuitem.png"/>学生信息</treecell>

</treerow>

</treeitem>

<treeitem>

<treerow>

<treecell><image src="/imgs/menuitem.png"/>我的信息</treecell>

</treerow>

</treeitem>

</treechildren>

</treeitem>

</treechildren>

</treeitem>

</treechildren>

</tree>

</west>

</borderlayout>

</window>

</zk>

效果图:

4.5  开发中的ZK常用组件

4.5.1  A组件

例1:

<a href="http://www.zkoss.org" label="Visit ZK!" image="/imgs/gear1.gif">

<grid>

<rows>

<row>What ever content</row>

</rows>

</grid>

</a>

<a href="http://www.zkoss.org" label="Visit ZK!"/>

<a href="../../grid/paging.zul" label="Visit Grid Demo!"/>

效果图:

a组件中的路径都是相对路径。如果路径是以斜杠(/)开头,那么则表示是当前的context path(上下文路径)。其他的路径都是相对于文件本身的路径而言的。

a组件可以包含其他的组件,但是它的子组件也要能处理鼠标点击事件。

4.5.2  Button组件

按钮组件有两种类型,button和toolbarbutton,它们的主要功能是类似的,只是其外观显示不同。

在解析时,button组件使用html中的button标记,而toolbarbutton则使用的是html中的a标记。

①  button组件的属性

属性名

说明

label

指定按钮上的文字

image

为按钮指定图像

dir

用于控制图像和文字哪个显示在前面,默认是图像显示在前面。

若dir="reverse"则文字显示在前面

orient

控制布局是横向还是纵向,默认是横向布局。

orient="vertical"则为纵向布局

onClick

按钮被点击后进行的响应

href

按钮被点击后跳转的页面

mold

取值有:default、trendy、os

autodisable

通过设置这个属性,可以防止重复提交。

例:

<button label="Btn" autodisable="+self" onClick='text.value="点击了Btn"'/>

<button id="ok" label="确定" autodisable="ok,cancel" onClick='text.value="点击了确定"'/>

<button id="cancel" label="取消" autodisable="+ok,+cancel" onClick='text.value="点击了取消"'/>

<label id="text"/>

效果图:

如果在按钮的id前面加上加号(+),则点击一次后被指定的按钮将变成disabled,需要我们手动变回enabled;如果不加+,则点击时的这次request完成以后,按钮将自动从disabled变成enabled

② 配置button默认的属性

在zk.xml中做如下设置,则可以使按钮的默认mold变成trendy:

<library-property>

<name>org.zkoss.zul.Button.mold</name>

<value>trendy</value>

</library-property>

在zk.xml中做如下设置,则可以使按钮的autodisable指向它自身:

<language-addon>

<component>

<component-name>button</component-name>

<extends>button</extends>

<property>

<property-name>autodisable</property-name>

<property-value>self</property-value>

</property>

</component>

</language-addon>

③  button上面使用图片

例1:

<?page title="button标签"?>

<window title="按钮应用" border="normal" width="40%">

<button label="文字left" image="/imgs/sml1.gif" dir="reverse" onClick="view.value=self.label" href="/index.zul"/>

<button label="文字right" image="/imgs/sml2.gif" onClick="view.value=self.label"/>

<button label="文字below" image="/imgs/sml3.gif" orient="vertical" onClick="view.value=self.label"/>

<button label="文字above" image="/imgs/sml4.gif" orient="vertical" dir="reverse" onClick="view.value=self.label"/>

<separator/>

<label id="view"/>

</window>

效果图:

④  按钮的onClick与href属性

我们可以通过两种方式为一个按钮指定动作:一是通过onClick来监听;二是使用href来指定一个路径。这两种方式中,href的优先级要高于onClick。如果同时指定两者,则onClick不会被发送。

⑤  使用html中的button标签

通过指定xml命名空间为native,则ZK解析器会按照html的格式来解析这个命名空间下的组件。(很少用)

例:

(实际应用中,我们可以使用下面的代码跳转到一个jsp页面,或者一个actoin中,等等)

<n:form action="/foo/my_handler" xmlns:n="native">

<textbox/>

<button type="submit" label="Submit"/>

<button type="reset" label="Reset"/>

</n:form>

4.5.3  验证码Captcha

例:

<vbox>

<span>验证码:<textbox id="tb" value="请输入" onFocus='self.setValue("")' onBlur="check()"/></span>

<captcha id="cpa" length="5" width="200px" height="50px" />

<zscript><![CDATA[

void check(){

if(!tb.getValue().toLowerCase().equals(cpa.getValue().toLowerCase())){

Messagebox.show("请输入正确的验证码");

tb.setValue("请输入");

}

}

]]>

</zscript>

</vbox>

效果图:

4.5.4  combobutton组件

combobutton是一种特殊的按钮,它里面可以包含一个popup或者menuepopup做为child

例:

<zk>

<combobutton label="popup" image="/imgs/sml3.gif" autodrop="true">

<popup>

<vbox>

<hbox>

Search

<textbox />

</hbox>

<listbox width="200px">

<listhead>

<listheader label="Name" />

<listheader label="Description" />

</listhead>

<listitem>

<listcell label="John" />

<listcell label="CEO" />

</listitem>

<listitem>

<listcell label="Joe" />

<listcell label="Engineer" />

</listitem>

</listbox>

</vbox>

</popup>

</combobutton>

<combobutton label="menu popup" image="/imgs/sml2.gif">

<menupopup>

<menuitem label="Index" />

<menu label="Menu">

<menupopup>

<menu label="Color Picker" content="#color=#029BCB" />

</menupopup>

</menu>

</menupopup>

</combobutton>

</zk>

autodrop属性用于设置鼠标滑过时是否自动打开弹出框。

效果图:

→ →

4.5.5  Fisheye组件

例:

<zk>

<div height="450px">

<checkbox label="Attach icon edge at bottom"

onCheck='fsb.attachEdge=self.checked?"bottom":"top"' />

<checkbox label="Vertical orient"

onCheck='fsb.orient=self.checked?"vertical":"horizontal"' />

<separator bar="true" />

<fisheyebar id="fsb"

style="position:absolute;margin:80px 150px;" attachEdge="top"

itemWidth="80" itemHeight="80" itemMaxHeight="160"

itemMaxWidth="160">

<fisheye

image="/imgs/button04.jpg"

label="folder" onClick="alert(self.label)" />

<fisheye

image="/imgs/button05.jpg"

label="search" onClick="alert(self.label)" />

<fisheye

image="/imgs/button06.jpg"

label="project" onClick="alert(self.label)" />

<fisheye

image="/imgs/button08.jpg"

label="Email" onClick="alert(self.label)" />

<fisheye

image="/imgs/button21.gif" label="Globe"

onClick="alert(self.label)" />

<fisheye

image="/imgs/button23.gif"

label="telescope" onClick="alert(self.label)" />

</fisheyebar>

</div>

</zk>

效果图:        →

4.5.6  HTML组件

如果我们要在页面上直接使用html标签而不让zk的解析器来解析,那么我们就可以使用html组件。配合<![CDATA[]]>一起使用。

注意:html组件中的内容不是这个组件的child。我们可以在里面使用EL表达式

例:

<window id="win" title="Html Demo" border="normal">

<html><![CDATA[

<h4>Hi, ${win.title}</h4>

<p>It is the content of the html component.</p>

]]></html>

</window>

效果图:

4.5.7  Iframe组件

iframe组件和HTML中的ifram标签的功能相同。

iframe组件和include组件很相似,但是它们有很大的不同:

◆ include包含进来的子页面是属于当前的页面的,也就是属性当前desktop的一部分。我们可以直接访问include包含进来的组件。包含的动作是在server端,browser对此一无所知。

◆ iframe包含进来的子页面是作为一个单独的页面来加载的。包含的动作发生在browser端。

例:

<window id="win" title="This is an Iframe Demo!">

<iframe style="width:99%; height:400px;border:3px inset;" src="http://www.zkoss.org" />

<iframe style="width:99%; height:400px;border:3px inset;" src="/components/_essentialComponent/006html/html.zul" />

</window>

4.5.8  Include组件

include组件是一个ID空间宿主。所以不用担心两个include组件中的id会重复。

src属性可以指定为一个servlet、jsp、zul等等。

Include可以包含ZUML页面、静态页面、JSP页面或者一个servlet。

当我们使用Include包含的是一个非ZUML的页面时,那么那个页面中的内容将会被作为Include组件的内容。

当我们使用Include包含一个ZUML页面时,那个被包含的ZUML页面中的组件将会作为Include组件的子组件

例:

<include src="another.zul"/>

<include src="another.jsp"/>

简单的include用法我们就不再多言。我们重点讲一下include关于url的参数传递。

①  include中通过路径来传递参数

include.zul页面:

<zk>

<window title="include demo" border="normal" width="300px">

<zscript>

<![CDATA[

class User{

private int id;

private String name;

private String sex;

public User() {

}

public User(int id, String name, String sex) {

this.id = id;

this.name = name;

this.sex = sex;

}

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getSex() {

return sex;

}

public void setSex(String sex) {

this.sex = sex;

}

}

User user = new User(1, "小张", "女");

]]>

</zscript>

Hello, World!

<include src="temp.zul?text=5" />

<include src="temp.zul" some="something" another="${user}" />

使用include时,两种传值的方式

</window>

</zk>

temp.zul页面:

<window title="temp页面" border="normal" width="280px">

<label if="${param.text ne null}" value="1.param.text${param.text}"/>

<separator bar="true"/>

<label if="${requestScope.some ne null}" value="2.equestScope.some${requestScope.some}"/>

<separator bar="true"/>

<label if="${requestScope.another ne null}" value="3.requestScope.another${requestScope.another}${requestScope.another.id}${requestScope.another.name}${requestScope.another.sex}"/>

<separator bar="true" />

</window>

效果图:

通过代码的话,我们可以使用:Include.setDynamicProperty(java.lang.String, java.lang.Object)来传递参数。

接收传递过来的参数时,我们可以使用:Execution.getParameter(java.lang.String)或者使用javax.servlet.ServletRequest的接口,也就是说我们可以通过EL表达式来得到传递的参数值。

通过url,我们只能传递string类型的参数。传递复杂数据类型的参数时请参照上面的例子。

②  刷新include包含进来的页面

例:

<window title="include demo" border="normal" width="300px">

<div style="color:red">Hello, World!刷新include进来的页面</div>

<include id="inner" src="/components/_essentialComponent/003captcha/captcha.zul?text=5" />

<button label="Load" onClick='inner.src = "/components/_essentialComponent/003captcha/captcha.zul?text=10"'/>

<button id="reload1" label="Reload1" onClick="String tmp=inner.src; inner.src=null; inner.src=tmp;"/>

<button id="reload2" label="Reload2" onClick="inner.invalidate();"/>

</window>

解析:

1、Load按钮所设置的的路径是恒定不变的。当第二次再点击时,就不会进行刷新了。

2、通过先设置inner.src=null后,再设置它的值,这样就可以完成刷新同一个页面了。例如:Reload1按钮

3、通过inner.invalidate()也可以实现相同的效果

4.5.9  Image组件

例:

<zk>

Location: <textbox onChange="updateMap(self.value)"/>

Map: <image id="image"/>

<zscript><![CDATA[

void updateMap(String location) throws Exception{

if (location.length() > 0) {

org.zkoss.image.AImage img = new org.zkoss.image.AImage(location);

image.setContent(img);

}

}

]]>

</zscript>

<separator/>

<image src="/imgs/folder.gif" />

</zk>

效果图:

Image组件还支持javax.awt.image.RenderedImage产生的图片:

例:

<window title="Test of Live Image">

<image id="img"/>

<zscript><![CDATA[

import java.awt.*;

import java.awt.image.*;

import java.awt.geom.*;

int x = 10, y = 10;

void draw(int x1, int y1, int x2, int y2) {

BufferedImage bi = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);

Graphics2D g2d = bi.createGraphics();

Line2D line = new Line2D.Double(x1, y1, x2, y2);

g2d.setColor(Color.blue);

g2d.setStroke(new BasicStroke(3));

g2d.draw(line);

img.setContent(bi);

}

draw(x, y, x += 10, y += 10);

]]>

</zscript>

<button label="change" onClick="draw(x, y, x += 10, y += 10)"/>

</window>

4.5.10  Imagemap组件

例:

<window title="Test of Imagemap">

点击图片显示点击的坐标

<imagemap src="/imgs/map.gif" onClick='alert(event.x + "," + event.y)' />

点击我们设置的区域,显示对应的区域id

<imagemap src="/imgs/map.gif" onClick="alert(event.area)">

<area id="First" coords="0, 0, 100, 100" />

<area id="Second" shape="circle" coords="200, 200, 100" />

</imagemap>

</window>

第一个imagemap的效果图:

4.5.11  Label组件

Label组件是一个特别常用的组件。用法也很简单。我们这里主要讲一下它的3个属性:pre、multiline和maxlength

属性名

说明

pre

用来屏蔽多余的空白字符。多余即首尾的空白字符(处理后被去掉),和中间多个空白字符(处理后变成一个)。

multiline

取值:true、false。是否显示多行

maxlength

最多显示几个字符

例:

<window id="win" title="demo" border="normal">

<vbox id="result">

<label id="lbl1" pre="true"></label>

<separator bar="true" />

<label id="lbl2" multiline="false" />

<separator bar="true" />

<label id="lbl3" maxlength="10" />

<zscript><![CDATA[

lbl1.value = "    this   thing   has   spaces.\nnext line.";

lbl2.value = "    this   thing   no   space.\nnext line.";

lbl3.value = "    this is more than 10 chars.";

]]></zscript>

</vbox>

</window>

效果图:

4.5.12  Menue组件

例:

<window>

<menubar id="menubar">

<menu label="File">

<menupopup>

<menuitem label="New" onClick="alert(self.label)" />

<menuitem label="Open" onClick="alert(self.label)" />

<menuitem label="Save" onClick="alert(self.label)" />

<menuseparator />

<menuitem label="Exit" onClick="alert(self.label)" />

</menupopup>

</menu>

<menu label="Help">

<menupopup>

<menuitem label="Index" onClick="alert(self.label)" />

<menu label="About">

<menupopup>

<menuitem label="About ZK" onClick="alert(self.label)" />

<menuitem label="About Potix" onClick="alert(self.label)" />

</menupopup>

</menu>

</menupopup>

</menu>

</menubar>

</window>

效果图:

→  →

根据需求和条件手动控制页面的加载和组件的创建

5.1  根据条件来控制组件的创建

ZK中提供了很多根据条件来创建组件的方式。注意他们和visible的区别。visible是控制组件的显示和隐藏,而这里要介绍的是控制组件是否创建。

5.1.1  if和unless属性(适用于所有组件)

基本上所有的zk组件都有if和unless属性,他们用于控制组件是否被创建。

例1:当param.readonly为true时,我们使用label组件,否则使用textbox组件

<label value="${customer.label}" if="${param.readonly == 'true'}"/>

<textbox value="${customer.value}" unless="${param.readonly == 'true'}"/>

例2:只有当a等于1,b不等于2的时候,这个window才会被创建

<window if="${a==1}" unless="${b==2}">

...

</window>

5.1.2  switch和case(适用于zk标签)

我们一般用switch case组合来控制ZUML页面的创建。第一个匹配成功的条目才会被创建,只要一个匹配成功,就不再做匹配。没有case属性的zk标签是默认的匹配项。

例:

<zk switch="${fruit}">

<zk case="apple">

当${fruit}值为apple时被创建

</zk>

<zk case="${special}">

当${fruit}值为${special}时被创建

</zk>

<zk>

当上面没有一个匹配成功时被创建

</zk>

</zk>

我们也可以为case指定多个值,只要其中的一个能够匹配成功,那么整个表达式就匹配成功,该zk标签中的内容将被创建。

例:

<zk switch="${fruit}">

<zk case="apple, ${special}">

当${fruit}的值为apple 或 ${special} 时被创建

</zk>

</zk>

我们还可以在case中使用正则表达式:

例:

<zk switch="${fruit}">

<zk case="/ap*.e/">

Evaluate if the regular expression, ap*.e"., matches the switch

condition.

</zk>

</zk>

5.1.3  choose和when(适用于zk标签)

choose when的功能与switch case差不多,这里不再过多介绍。

例:

<zk choose="">

<zk when="${fruit == 'apple'}">

Evaluated if the when condition is true.

</zk>

<zk><!-- default -->

Evaluated if none of above cases matches.

</zk>

</zk>

注:你不需要为choose属性指定值,它的作用仅仅是为了标识choose的结束,即:通过它可以找到choose结束的地方

5.2  根据需求加载页面或组件

一个ZUML文件可以被划分成多个页面,一般情况下,这些页面都是同时被加载进来的。这样,在页面首次被加载的时候将会消耗很多带宽,为了提高性能,我们可以通过fulfill属性来延迟页面的加载,也就是我们说的根据需求来加载。

不仅是页面可以延迟加载,组件也是可以延迟加载的。例如下拉框,我们在点击它的时候才加载里面的数据的话,我们也可以为其设置fulfill属性

例1:当combobox收到了onOpen的事件后再加载里面的数据

<combobox fulfill="onOpen">

<comboitem label="First Option"/>

</combobox>

例2:通过事件触发和组件的id来指定组件的加载

<button id="btn" label="show" onClick="content.visible = true"/>

<div id="content" fulfill="btn.onClick">

Any content created automatically when btn is clicked

</div>

不属于同一个id空间的两个组件,我们可以通过路径来指定:

<button id="btn" label="show" onClick="content.visible = true"/>

<window id="content" fulfill="../btn.onClick">

Any content created automatically when btn is clicked

</window>

按需加载一个页面:

我们也可以为fulfill属性指定一个页面。这个延迟加载的页面将会作为相应的组件的children。

<zk>

<button id="btn" label="Click to Load"/>

<div fulfill="btn.onClick=another.zul"/>

</zk>

迭代赋值

默认情况下,ZK加载器为zuml页面中的每个XML元素实例化一个组件。如果我们想生成一个组件集的话,我们可以通过forEach来实现。

例:

<listbox>

<listitem label="${each}" forEach="Apple, Orange, Strawberry"/>

</listbox>

它等同于

<listbox>

<listitem label="Apple"/>

<listitem label="Orange"/>

<listitem label="Strawberry"/>

</listbox>

也等同于

<zscript><![CDATA[

String[] grades = new String[] {"Best", "Better", "Good"};

]]></zscript>

<listbox>

<listitem label="${each}" forEach="${grades}"/>

</listitem>

当ZK加载器通过集合中的项目来迭代的时候,它会更新两个隐含对象:each和forEachStatus。

这种迭代方式依赖于forEach属性的值的类型。

forEach属性的值必须是一个集合类型。大致有以下几种情况:

① java.util.Collection:迭代Collection集合中的每个元素

② java.util.Map:迭代Map中的每一个Map.Entry

③ java.util.Iterator:迭代Iterator中的每个元素

④ java.util.Enumeration:迭代Enumeration中的每个元素

⑤ Object[]、int[]、short[]、byte[]、char[]、float[]、double[]:迭代数组中的每个元素

⑥ null:不会生成任何组件,将会被忽略

看下面这个例子:

<zk>

<hlayout>

<zscript>

String[] classes = new String[] {"College", "Graduate"};

Object[] grades = new Object[] {

new String[] {"Best", "Better"}, new String[] {"A++", "A+", "A"}

};

</zscript>

<listbox width="200px" forEach="${classes}">

<listhead>

<listheader label="${each}"/>

</listhead>

<listitem label="${forEachStatus.previous.each}: ${each}"

forEach="${grades[forEachStatus.index]}"/>

</listbox>

</hlayout>

</zk>

效果图:

将<hlayout></hlayout>去掉后的效果图:

解析:forEach就好比一个for循环,它循环的范围就是forEach所在的标签的开始标签至结束标签处。故,在上例中,listbox处的forEach循环它里面的listhead和listitem组件。我们不难发现,这是一个两重循环。listitem处还有一个forEach,也就是第二重循环。循环第一次开始时,listheader处的each被赋值为”College”。listitem处又有一个forEach,这时each从属于listitem处的forEach,这时我们要引用上一层循环中的each的话,只能通过forEachStatus.previous.each来得到。

注:forEach好比一个for循环,each就好比一个iterator

forEach和each组合在一起可以与下面的代码类比:

while(iterator.hasNext()){//forEach开始处

each = iterator.next();//给each属性赋值

}

我们可以在zscript中直接使用each和forEachStatus:

<window>

<button label="${each}" forEach="apple, orange">

<zscript><![CDATA[

self.parent.appendChild(new Label("" + each));

]]></zscript>

</button>

</window>

效果图:

你不能在事件监听器中获得each和forEachStatus的值,因为当页面加载完成后,each已经不存在了,而这个时候我们再去触发事件想要得到each中的值的话,就不能够实现了。

一个简单的解决办法就是在页面加载的时候就将第一个each的值都用组件的属性存放起来,这样的话,当页面加载完成后,我们就可以通过检索预先存放的属性值来得到我们想要的值了。

例:

<window>

<button label="${each}" forEach='"中国","美国"'

onClick='alert(self.getAttribute("country"))'>

<custom-attributes country="${each}"/>

</button>

</window>

注:用户自定义属性,属性名是随便取的,但是标签名一定是custom-attribute

迭代集合中的一部分:通过指定forEachBegin和forEachEnd

<grid>

<rows>

<row forEach="${foos}" forEachBegin="${param.begin}" forEachEnd="${param.end}">

${each.name} ${each.title}

</row>

</rows>

</grid>

7  XML命名空间简介

在ZK中标准的命名空间有3个:client、native、zul

ZK解析器在加载一个页面的时候,首先通过页面的拓展名来确定页面默认的命名空间。

在ZK工程中一般有两种页面:.zul和.zhtml。.zul的页面默认按zul的命名空间来进行解析。.zhtml的页面默认按native的命名空间来解析(即把它当成一个普通的html页面)。

如果我们想在默认的命名空间下使用不同的命名空间,让zk解析器按指定的命名空间来解析的话,我们就可以通过下面的方式来指定页面的命名空间。

① 指定由客户端(前台)来控制组件,而不是后台

<combobox xmlns:w="client" w:onFocus="this.open()" onChange="doOnChange()"/>

② 使用html的形式来解析组件(在zul页面中)

<n:table xmlns:n="native">

<n:tr>

<n:td>Username</n:td>

<n:td><textbox/></n:td>

</n:tr>

<n:tr>

<n:td>Password</n:td>

<n:td><textbox type="password"/></n:td>

</n:tr>

</n:table>

③ 使用zul的形式来解析组件(在zhtml页面中)

<!-- foo.zhtml -->

<p> <!-- assumed from the XHTML language -->

<u:window xmlns:u="zul"/> <!-- ZK Loader will search the ZUL language instead -->

</p>

8  Zscript的使用

zscript是zuml页面上的页面脚本。

zuml页面中的脚本分两种:1、客户端脚本 2、服务器端脚本

zscript是属于服务器端脚本。<zscript>里面的代码是java代码,它运行在服务器端,所以它能够访问java包,在它里面你也可以定义变量、方法甚至是类,在同一个page下它对EL表达式是可见的。也就是说,EL表达式可以使用<zscript>中的变量、方法、类等。

在zscript中的代码必须是一段有效的xml文本。所以,在用要特殊字符的时候我们需要转义。为了避免转义,我们通常使用CDATA来包围这段代码。<![CDATA[代码]]>

书写zscript的方式大致有三种:

1、标准格式:

<zscript>

<![CDATA[

//代码

]]>

</zscript>

2、将zscript写在事件监听器上

如下面的例子

<button id="reload1" label="Reload1" onClick="String tmp=inner.src; inner.src=null; inner.src=tmp;"/>

3、将zscript写在attribute中

如果处理事件处理器的代码比较复杂,我们可以在attribute元素中指定事件的名字,然后编写代码。上面的代码可以写为:

<button id="reload1" label="Reload1">

<attribute name="onClick"><![CDATA[

String tmp=inner.src;

inner.src=null;

inner.src=tmp;

]]></attribute>

</button>

zscript中的变量对EL表达式是可见的:

例:

<zscript>

<![CDATA[

Date now = new Date();

]]>

</zscript>

<label value='${now}'/>

在ZUML页面上使用tld文件中指定的函数

在页面上,我们经常会对一些内容进行格式化,然后再输出。通常我们都将这些转换函数写成一个公有的函数。在ZK中我们可以通过以下步骤来实现在页面上调用指定的函数:

1、  用一CommonUtils类将所有的公共函数写在一起:

例:

public class CommonUtils {

public static final String TIME_PATTERN_DEFAULT = "yyyy-MM-dd HH:mm:ss";    private static String getPattern(String pattern){

if("".equals(pattern) || null==pattern){

pattern = TIME_PATTERN_DEFAULT;

}

return pattern;

}

public static String parseDateToString(Date date, String pattern){

String redate = null;

pattern = CommonUtils.getPattern(pattern);

SimpleDateFormat sdf = new SimpleDateFormat(pattern);

redate = sdf.format(date);

return redate;

}

}

2、  新建一个tld文件来描述CommonUtils类中的每一个公共函数

<taglib>

<function>

<name>parseDateToString</name>

<function-class>com.cn.developer.common.CommonUtils</function-class>

<function-signature>

java.lang.String parseDateToString(java.util.Date, java.lang.String)

</function-signature>

<description>

Returns a formatted date.

</description>

</function>

<!-- any number of functions are allowed -->

</taglib>

3、  在页面上引用我们的tld文件

<?taglib uri="/tld/commonFunction.tld" prefix="c"?>

<window id="win" title="引用tld文件中的函数" border="normal" width="200px">

<zscript>

<![CDATA[

Date now = new Date();

]]>

</zscript>

<label value='${c:parseDateToString(now, "yyyy-MM-dd") }'/>

</window>

访问上面的页面的效果图:

10  自定义变量解析器

将EL表达式中特定的名字转换成我们指定的值。

下面举一个例子:

<zk>

<listbox>

<listitem label="${each.name}" forEach="${customers}"/>

</listbox>

</zk>

如果直接访问这个页面,肯定会报错。因为zk无法解析customers。下面的操作将会解析出这个页面。

首先,我们定义一个类,这个类要实现VariableResolver接口:

public class CustomerResolver implements VariableResolver {

@Override

public Object resolveVariable(String name) throws XelException {

if ("customers".equals(name))

return Customer.getAll("*");

return null; //not a recognized variable

}

}

在Customer类中加入静态的方法Customer.getAll(java.lang.String)

public class Customer {

private int id;

private String name;

private String sex;

public Customer() {

}

public Customer(int id, String name, String sex) {

this.id = id;

this.name = name;

this.sex = sex;

}

//略去getter和setter

//..........

public static List<Customer> getAll(String str){

List<Customer> list = new ArrayList<Customer>();

list.add(new Customer(1, "张三", "男"));

list.add(new Customer(2, "李四", "女"));

return list;

}

}

resolver.zul:

<?variable-resolver class="com.cn.developer.resolver.CustomerResolver"?>

<zk>

<listbox>

<listitem label="${each.name}" forEach="${customers}"/>

</listbox>

</zk>

最后得到的效果:

我们可以将这个Resolver设置成全局的,而不需要每个页面都引用,可以在zk.xml中加入下面的代码:

<listener>

<listener-class> com.cn.developer.resolver.CustomerResolver </listener-class>

</listener>

10  ZK-MVVM开发模式

ZK6.0之前的版本都是采用MVP(Model-View-Presenter)设计模式。从ZK6.0开始就有了很大的不同,采用MVVM(Model-View-ViewModel)设计模式。

10.1  ZK MVP开发模式

我们通过一个例子来剖析MVP的开发模式。

假如我们要实现下面的功能:Client客户端页面上点击一个按钮,然后从Server服务端返回消息”Hello World”

MVP通过下图所示来实现:

作用过程:

①用户在屏幕上点击按钮(一个动作action),一个通信事件就被触发。

②后台的事件监听器(event listener)处理这个事件(event)。

③访问数据库(access data)并返回

④用户界面元素(UI element)被改变,相应的把视觉反馈(visual feedback)呈现给用户

相关的代码如下:

10.2  MVVM开发模式

实现与2.1中相同的功能,MVVM的实现过程如下图:

作用过程:

①用户在屏幕上点击按钮(action)触发一个事件(event)

②绑定器(Binder)感知到相应的事件(corresponding event)被触发

③绑定器(Binder)在ViewModel中找到相应的动作逻辑(action logic)并调用它

④动作逻辑(action logic)从Model层访问数据库(access data),并更新ViewModel中相应的属性

⑤ViewModel通知(notify chnage)绑定器(Binder) 一些属性被改变

⑥每当有属性被改变,绑定器(Binder)就会从ViewModel中加载数据(load data)

⑦绑定器(Binder)改变相应的UI组件来给用户呈现视觉反馈(visual feedback)

很明显,UI设计者必须至少告诉绑定器(Binder)以下几点:

◆     用到了哪些UI事件(UI event)来触发哪些动作逻辑(action logic)。(这样Binder才知道调用什么方法)

◆     用到了哪些UI属性(UI attribute)来显示哪些数据。(这样Binder才知道加载什么数据,更新什么数据)

◆     用到的哪些UI属性(UI attribute)是用来输入到数据库的。(这样Binder才知道要保存哪些属性)

相关代码如下:

在ZK绑定(ZK Bind)中用到了ZK注解(annotation)来完成了很多工作。

结合代码,我们再一次分析程序运行的过程如下:

① 当用户按下了”Show”按钮,onClick event就被触发到了Binder

② ZK Binder通过指定的ZK 注解 @command('showHello')在ViewModel中找到命令的名字(command name)为showHello的方法。

③ Binder在HelloViewModel.java中调用showHello()方法,并且改变message的属性值为”Hello World”。

注意:@command('showHello')是告诉Binder showHello这个方法是一个command方法,而@NotifyChange("message")则是告诉Binder 如果@command('showHello')中的showHello()方法被调用,那么HelloViewModel中的属性message将会被改变。也就是说当这个方法被调用后,页面上(客户端)可以感知到属性message的改变。

④ Binder找到HelloViewModel.java中与组件标签(component label)相关联的属性message的属性值(这是由于页面上message被指定了ZK注解@load(vm.message)的缘故)。因此它通过调用HelloWorldModel.java的message属性的getMessage()方法从vm.message中加载数据,并改变标签label的属性值。最后”Hello World!”就呈现到了客户端用户。

10.3  为什么要使用MVVM的设计模式

有时用户看了上面的页面之后,说想通过弹出窗(pop window)来显示信息message。这样的改变其实很容易,将信息放到一个模态窗口中:

MVP实现:

对于MVP设计模式而言,view层的zul页面修改了之后,Presenter层的java文件必须完全被重写,这时需要注入弹出窗口popwin、弹出窗口中的标签popwin$lbl

MVVM实现:

当客户改变View层的需求的时候,MVVM设计模式的优势就体现出来了。UI设计者可以独立的处理这些改变,而不用去改变HelloViewModel.java文件,因为客户需要的是改变message的呈现方式而不是message本身。

注意:模态窗口的显示或隐藏是通过判断message的值是否为空来控制的。(visible="@load(not empty vm.message)")

11  ZK数据绑定注解

Client端页面上的数据与Server端的ViewModel中的属性的绑定是通过注解来完成的。

如下图所示:

11.1  单向绑定与双向绑定

@save:将它绑定的标签对应的值保存到ViewModel中,

@load:将ViewModel中的对应的属性值加载到前台页面。

@bind:双向绑定,相当于是@save和@bind的一种简写方式。

双向绑定和我们通常实现的ajax功能类似。

11.2  模板绑定

我们可以将一个集合的值赋给model属性,再将model赋给template组件的name属性,这就是我们所说的模板绑定,其实就是将值绑定在template上。

通过模板绑定,我们可以迭代集合中的每一个元素,从而创建多个组件。template组件就好比一个while循环一样。

11.3  表单绑定

11.4  条件绑定

11.5  数据绑定中使用EL表达式

12  ZK开发中使用的注解

12.1  Client端ZUL页面上使用的注解

语法(syntax)

说明(explanation)

viewModel="@id(name) @init(expression)"

用于设置ViewModel。

◆ 一个拥有apply="org.zkoss.bind.BindComposer"属性的组件可以使用这个注解,如果没有这个注解,那么这个组件将会被设置到一个composer上。

◆ expression:如果它被赋予一个Class,那么这个Class将创建一个ViewModel实例。

comp-attribute="@load(expression)"

单向绑定。用来加载属性的表达式

◆ 用户可以在表达式中指定绑定条件,通过参数before和after来设定。例如@load(vm.filter, after='myCommand'),这表示Binder会在执行完command方法myCommand之后加载这个属性

comp-attribute="@save(expression)"

单向绑定。保存组件的属性值到ViewModel属性中的表达式。

◆ 用户可以在表达式中指定绑定条件,通过参数before和after来设定。例如:@save(vm.filter, before='myCommand'),这表示Binder会在执行command方法myCommand之前保存这个属性到ViewModel

comp-attribute="@bind(expression)"

双向绑定。在组件的属性和ViewModel的属性之间实现双向绑定,它不支持条件绑定,也就是说在绑定时不能使用条件。

◆ 它等同于@load(expression) @save(expression)

◆ 如果组件的属性不支持@save的话,Binder将会自动忽略它

◆ 通知改变(notify change)的例子:对于表达式vm.filter,如果任意一个通知(notification)说vm或vm.filter改变了,那么这个属性将会被重新加载

◆ 复杂一点的例子:对于表达式e.f.g.h,如果任何一个通知(notification)说e.f.g.h、e.f.g、e.f或者e被改变了,那么组件的属性将会被重新加载

@converter(expression, arg = arg-expression)

为绑定提供一个转换器conveter。

◆ 如果expression是一个ZK提供的conveter,那么它将会直接被使用

◆ 如果expression是一串字符,那么它将会从ViewModel中查找相应的getConveter方法,方法的名字为get+expression返回值为Conveter。参照上面提到conveter的代码。

◆ 在执行conveter之前,你可以传递多个参数给conveter。这些参数将会在调用conveter方法之前被赋值。

comp-event="@command(expression, arg =another-expression)"

组件的事件绑定,绑定到相应的事件command方法。

◆     expression必须是ViewModel中的一个command方法的名字(当然,这个方法必须是以@command为注解的)

◆     事件被触发时,它将会按照ZK Bind Lifecycle来执行这个command方法

12.2  Server端ViewMole中使用的注解

语法(syntax)

说明(explanation)

@NotifyChange on setter

它是用于ViewModel类属性的setter方法上的,当调用了属性的setter方法之后,它将会通知(notify)Binder该属性的值被改变了。它是默认开启的,所以,当通知目标(notification target)和属性相同时,在属性的setter方法上你可以省略不写这个注解。

◆ 我们可以为属性properties指定name,通过@NotifyChange(expression)、@NotifyChange({expression1,expression2,……}),当指定的属性改变时调用这个setter方法

@NotifyChange on command method

它可以用于command方法上,当这个command方法被调用时,通知Binder属性值改变了

@NotifyChangeDisabled on setter

这个注解用在属性的setter方法上可以使setter方法默认开启的@NotifyChange关闭

@Command('commanName')

定义一个command方法。commandName是可选的,如果不指定的话,command方法的名字默认就是它所注解的方法的名字。

13  构建第一个MVVM页面

我将用一个查询的例子来展示你怎样利用ZK Bind在ZK 6中使用MVVM设计模式。请假设你正在创建一个查询页面,查询页面上显示的item清单是通过一个字符串条件检索出来的,选中的item会显示它的详细信息。

按照MVVM的设计理念,我们应该抛开视觉效果,首先设计ViewModel。一个ViewModel应该不依赖View层,但是要考虑到与View层交互的数据(data)和行为方法(action)。在这个例子中我们需要一个字符串作为检索条件,一个ListModelList<Item>来保存查询结果,一个doSearch()方法来执行查询命令(command)。此外,我们还需要一个域来保存当前选中的item。

我们可以看出,ViewModel是独立于View层的,这就意味着其他View也可以重复使用这个ViewModel。

通过在一个属性的getter方法上加上@NotifyChange,Binder设置了属性之后,Binder将会被通知属性已经被改变,被绑定属性的组件将会被重新加载。你也可以为command方法加上注解@NotifyChange,这样Binder执行了这个方法后,方法里面如果有指定属性被改变,那么与属性绑定的组件将会被重新加载。定义一个command,我们需要为方法加上Java注解@command,方法的名字就是command默认的名字。

通过为filter的setter方法setFilter()加上@NotifyChange,当Binder保存用户输入的filter值的时候,Binder将会被@NotifyChange通知来重新加载与filter属性相关的ViewModel。我们为doSearch() command方法加上了注解@NotifyChange({"items","selected"}),这是因为每次根据当前filter的值执行完查询后,我们都将创建一个新的items集合,并且需要将选中的属性item(selected)设置为空。当doSearch() command方法被调用后,Binder需要能够感知到这两个属性的改变。

注解@NotifyChange能够通知Binder什么时候自动加载View,和加载什么数据。

注意:

① @NotifyChange是默认激活的,因此你可以忽略它。然而,你想要改变通知的目标,那么你就必须要为不同的属性名加上@NotifyChange注解。

② 如果你想让一个属性默认的@NotifyChange失效,那么你就必须要为该属性的setter方法加上@NotifyChangeDisabled注解。

@id(name) @init(expression)是用于为Binder指定一个ViewModel,其中name是ViewModel的名字,expression是一个EL2.0表达式,用于指定绑定的类。

将filter值绑定到textbox上:

在上面的代码中,我们将vm.filter绑定到了textbox的value属性和button的disabled属性。而且,我们在disabled属性上使用了@load(expression),因为它只需要加载ViewModel中的filter属性的值。当我们在页面上修改textbox中的值的时候,vm.filter的值将会立即改变,相应的,按钮是否可点击状态也会立即改变,这取决于vm.filter是否为空。

将查询结果绑定到listbox上:

在ZK6中有一个新的功能叫template。当我们绑定一个集合的时候,使用它简直是绝配。

下面我们将用listbox和template来显示查询结果:

@load(expressoin)也可以被应用到listbox组件的model属性上。我们需要提供一个名为model(在这里template的name必须为model,与listbox中的model对应)的template来绑定到这个listbox组件的model上,用这个template来显示model中的每一个item。在这个template中,我们还需要给这个template的var属性设置一个值(例如示例代码中的item),这样我们就能用它来表示template中的数据元素了。

绑定当前选中的item:

当我们绑定listbox的时候,我们也将listbox中当前选中的item绑定到了ViewModel中的selected属性上。你不必担心当前选中的项目的数据类型不匹配,因为Binder会将它自动转换成Item(vm.items中存放的元素的类型就是Item,参照上面的代码)。

在上面的代码中我们将groupbox的visible属性与表达式not empty vm.selected绑定,因此,只有当用户选择了一个item之后,这个groupbox才会可见。

在这里,@converter又一次被用到了,但是和之前一次有所不同。现在这个converter来自于ViewModel。当然,我们需要在ViewModel中定义一个名为getTotalPriceConverter的方法,并且返回值的类型为Conveter。

将button的动作绑定到一个command方法:

现在我们需要在点击button按钮的时候执行一个ViewModel中的方法。为了实现这个功能,我们需要在button的onClick事件上添加一个@Command注解。

在组件的事件(event)属性上使用@command(expression)代表将这个事件绑定到ViewModel中的一个command方法上。

它的规则如下:

① 表达式expression必须是一个String字符串

② String字符串必须是ViewModel中的一个command方法的名字

③ 在ViewModel中必须有一个含有注解@Command('commandName')的可执行方法,如果不指定commandName,那么Binder就使用方法的名字作为command方法默认的名字。

实现的最终效果:

14  ZK6 MVVM结合Spring

尽管我们在ZVVM设计模式中使用Spring,但我们并没有把ViewModel设置成一个Spring的bean。主要有以下两个原因:

①在Spring中没有哪一种bean的范围(scope)是能够与ViewModel的生命周期(lifecycle)准确匹配的

②如果我们将ViewModel设置成一个Spring的bean,那么将会有两个变量引用同一个ViewModel,一个是@id(zul页面上指定的ViewModel的id),一个是Spring的bean的名字。这两个变量会很容易让开发者迷惑。

因此,我们就用最原始的方式来使用ViewModel,和激发Spring的变量解析器(variable resolver)

看下面的例子:

由于ViewModel不是Spring的bean,所以我们不能通过Spring的autowire(自动注入)来检索OrderService对象,但是,我们可以用ZK专门的注解@WireVariable来达到相同的效果。

将Conveter设计成一个单例bean:

有时我们想要用到一个Converter来完成类型或格式转换,但是在ZK自带的Conveter里面找不到合适的,这时我们通常是在相应的ViewModel中写一个getXXConveter的方法来返回一个Conveter(参见前面的例子)。 这样的Conveter没有重用性,为了提高组件的重用性,我们可以将我们需要用到的Conveter放到一个独立的类中,并把它们设置成一个bean,这样我们就可以在zul页面上直接使用这个conveter而不必在ViewModel中添加getXXConveter的方法。

代码如下:

由于Conveter不依赖于其他的bean,而且是无状态的,所以我们可以将它声明成单例bean。上面代码中的Conveter都需要接收一个format的参数作为格式化的Date的格式。

页面上使用:

同时,我们也可以将Validator设计成一个单例bean:

Validator和Conveter类似,我们可以将ViewModel中的Validator全部提取出来放到一个单独的类中。

这个验证器验证的域(shippDate)是基于另一个域(creationDate)的,并且将验证消息存入验证消息持有器中。

页面上使用:

这样设置之后,我们就不必在zul页面上使用”vm.”前缀来调用它们,因为它们可以被ZK的DelegatiingVariableResolver检索。任何一个zul页面都可以通过它们的bean name来得到相应的validator。

任何一个可以重复使用的元素都可以被分离开来,放到不同的包中。


[微软用户1]使用div组件(或span组件)将两个组件包含后,它们只占用了一个单元格的位置。