`
Hunter_Wang
  • 浏览: 13014 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

ZK开发学习

 
阅读更多

1. 页面布局

ZK具有非常高的开发效率(以至于可以取代HTML用来完成高质量的Fast Prototyping),最主要缘自它采用的页面布局技术——ZUL;

采用XML语言以声明式的方式创建用户界面——XML UI技术并不是ZK的独创,Android UI、JavaFX、Microsoft Silverlight和Mozilla XUL等开发

框架都采用了这种技术,这是目前最先进的构造用户界面技术;

ZUL只不过是为所有ZK组件指定了一个对应的XML Element,然后通过这些Element以声明式的方式定义页面由哪些组件以什么样的方式构成。

相对于Java编程式方式,这种声明式方式的优点十分明显:

  • 直观:ZUL代码结构与页面布局完全一致(而且必须一致),ZUL元素的嵌套关系就是页面组件的嵌套关系,ZUL元素的前后并列关系就
  • 是页面组件的前后摆放;而Java编程方式与最终页面却没有这种一致性;
  • 代码简洁:由于XML在表达页面布局时语义的先天优势(一致性),同样的页面用ZUL比Java代码量要少得多;

直观、简洁的代码意味着容易理解、容易编写、容易修改维护、不容易出错,因此带来开发效率上的巨大优势。

值得注意的是,上述ZUL相对于Java编程的优势也适用于JS,比如EXT、DOJO等JS UI框架。

1.1. 布局组件

1.1.1. 东西南北中布局Borderlayout

Borderlayout将屏幕划分成东西南北中五个区域,如下图所示,其灵活性可以实现绝大多数系统的首页整体布局。

首先纵向看,要指定N和S的高度,剩下中间部分就是W C E的高度;然后水平看,N S宽度百分百,中间部分指定W E的宽度后,

剩下的部分就是C了。

由于Center大小不是自己决定的,当里面摆放组件过多显示不全时,可以指定autoscroll="true"产生滚动条。

1.1.2. 基本布局

Borderlayout适合于实现大的页面结构布局,在页面局部最常见的需求就是如何将各种组件有序的摆放:有时需要水平摆放有时需要垂直摆放,

还要考虑居中居左居右问题、摆不下的问题(摆放不下时要有滚动条);

ZK提供了更细粒度的布局组件——hbox/vbox/hlayout/vlayout用于实现这些常见需求;

hlayout和hbox(h代表horizon水平)用于水平布局,vlayout和vbox(v代表vertical垂直)用于垂直布局,它们是最常用的的容器组件,里面可以

放任意多的组件;

hbox vbox与hlayout vlayout的区别:

  • Hbox and Vbox provide more functionalities such as splitter, align and pack.

  • However, their performance is slower,

  • so it is suggested to use Hlayout and Vlayout if you'd like to use them a lot in a UI, unless you need the features that only Hbox and Vbox support.

1.2. 各种容器组件

1.2.1. groupbox

企业应用往往需要在一个页面中显示大量信息或组件,如果随意摆放或只是简单的罗列,会让用户感觉很混乱难以使用,用户体验不好。

groupbox顾名思义就是用来分组布局的组件,它就像收纳盒一样可以把页面组件分门别类的摆放,标题栏可以清晰的标识分类名称,而且可收缩。

<groupbox width="50%"hflex="true"closable="true"mold="3d">
    <caption label="基本信息"/>
    <textbox id="userIdLongbox"value="@bind(fx.id)"visible="false"/>
………………

1.2.2. tabbox页签

像ZK这样的RIA框架做出来的系统基本上SinglePage的(整个系统只有一个页面,其它都是组件和AJAX),

同时企业应用不同于网站,用户需要打开很多视图查看各种数据和表单,因此普遍采用“多页签布局”来保证系统的方便易用。

ZK提供了tabbox组件方便的实现多种形式的页签:

默认水平排列页签
<tabboxid="tb"height="300px">
    <tabsid="tabs">
        <tabid="A"label="Tab A"/>
        <tabid="B"label="Tab B"/>
    </tabs>
    <tabpanels>
        <tabpanel>This is panel A</tabpanel>
        <tabpanel>This is panel B</tabpanel>
    </tabpanels>
</tabbox>
纵向排列页签
<tabboxid="tb"height="300px"orient="vertical">
    <tabsid="tabs">
        <tabid="A"label="Tab A"/>
        <tabid="B"label="Tab B"/>
    </tabs>
    <tabpanels>
        <tabpanel>This is panel A</tabpanel>
        <tabpanel>This is panel B</tabpanel>
    </tabpanels>
</tabbox>

最新zk-7支持下方的水平排列页签。

另外只需设置属性 mold="accordion"就可以把页签变成可纵向滑动伸缩的“抽屉”式页签

纵向排列页签
<tabboxid="tb"height="300px"mold="accordion">
    <tabsid="tabs">
        <tabid="A"label="Tab A"/>
        <tabid="B"label="Tab B"/>
    </tabs>
    <tabpanels>
        <tabpanel>This is panel A</tabpanel>
        <tabpanel>This is panel B</tabpanel>
    </tabpanels>
</tabbox>

1.2.3. window与panel

window和panel是GUI最常见的容器形式,可以在里面放置任意多的组件;

它们不同于其它容器之处在于可以关闭、最小化、最大化、模态显示(始终显示在最前面,除非最小化或关闭)、可拖动;

但是window和panel也有两个很小的区别:

  • window是一个独立的idspace,而panel不是;因此panel内部的组件与panel外部的是一样的;
  • panel智能在自己的parent组件范围内移动,而window可以在整个页面移动;

在ZK中创建一个窗口并以模态显示,代码如下:

//create a window programmatically and use it as a modal dialog.       
Window window = (Window)Executions.createComponents("/widgets/window/modal_dialog/employee_dialog.zul",null,null);
window.doModal();

一个简单的窗口页面:

<windowid="modalDialog"title="Coffee Order"border="normal"width="460px"apply="demo.window.modal_dialog.EmployeeDialogController"
   position="center,center"closable="true"action="show: slideDown;hide: slideUp">
    <vlayout>
      ………………
    </vlayout>
</window>

详见 http://www.zkoss.org/zkdemo/window/modal_dialog

1.3. Messagebox对话框

  • Warning :      Messagebox.show("Warning is pressed", "Warning", Messagebox.OK, Messagebox.EXCLAMATION);
  • Question:      Messagebox.show("Question is pressed. Are you sure?", "Question", Messagebox.OK | Messagebox.CANCEL, Messagebox.QUESTION);
  • Information:        Messagebox.show("Information is pressed", "Information", Messagebox.OK, Messagebox.INFORMATION);
  • Error:                 Messagebox.show("Error is pressed", "Error", Messagebox.OK, Messagebox.ERROR);
  • Confirm Dialog:详见http://www.zkoss.org/zkdemo/window/message_box

2. MVC

ZK虽然支持在ZUL脚本语言编程,但显然更正规也更有效的开发模式是把交互逻辑放到后台Java代码中实现,MVC模式正是这样的风格。

ZK MVC很简单:页面apply指定Controller、Controller中注入页面组件、Controller方法监听页面事件并修改操纵页面组件;

详见 http://www.zkoss.org/zkdemo/getting_started/mvc

2.1. MVC基本原理示例

1
2
3
4
5
6
7
8
9
10
11
public class SearchController extendsSelectorComposer<Component> {
    @Wire   privateTextbox keywordBox;//注入页面组件
    @Wire   privateListbox carListbox;//注入页面组件
     
    @Listen("onClick = #searchButton")   //监听页面事件
    publicvoidsearch(Event event){
        Button searchButton = (Button) event.getTarget();
        String keyword = keywordBox.getValue();
        List<Car> result = carService.search(keyword);
        carListbox.setModel(newListModelList<Car>(result));//操纵页面组件(显示数据或改变状态)
    }

其中2、3行代码将页面中id为keywordBox和carListbox的组件注入Controller作为实例变量,后面方法中对它们进行的修改将被ZK框架自动同步到前端页面上去;

第5行代码为方法注册了页面事件监听器——页面中id为searchButton的组件的onClick事件发生时调用此方法,

组件以及事件监听的表达式详见:http://books.zkoss.org/wiki/Small_Talks/2011/January/Envisage_ZK_6:_An_Annotation_Based_Composer_For_MVC

2.2. MVC forward事件处理

当页面组件很多时,如果只用onClick等少数内建事件进行监听会显得混乱。

forward可以用来将某个组件上发生的内建事件转发到外层并取别名,示例如下:

<windowid="mywin">
    <buttonlabel="Save"forward="onSave"/>
    <buttonlabel="Cancel"forward="onCancel"/>
    <listitemself="@{each=p1}"forward="onDoubleClick=mywin.onDetail(each.prop1)">
 </window>
controller事件处理代码
1
2
3
4
5
@Listen("onDetail= #mywin")//监听mywin的onDetail事件
    publicvoidonDetail(ForwardEvent e) {
        MouseEvent me = (MouseEvent) e.getOrigin();//获取源事件
        System.out.println(me.getData());//获取参数
    }

3. MVVM

3.1. MVVM Binding

3.1.1. Binding绑定概述

Binding(绑定)是Web框架最重要特性之一,Binding没有一个统一的定义,通常的Binding是指:

在 页面元素 与 后台(Controller)组件字段 之间建立起链接,使得后台数据(及其变化)可以显示(同步更新)到页面,

同时用户在页面的输入(修改)也可以传递(更新)到后台

从这个定义可以看出,Binding是很常见的需求,如果不采用Binding技术,那么手工完成上述工作(如request.getParameter或setAttribute)

会十分的繁琐无聊,产生大量重复代码;

3.1.2. 复杂类型Binding

幸好ZK的MVVM数据绑定非常强大——支持任意复杂类型,例如枚举类型:

<comboboxselectedItem="@bind(fx.userTypeForCc)"readonly="true"model="@load(vm.userTypeForCcList)"itemRenderer="com.xxx.ctrl.renderer.ComboitemRenderer4UserTypeCc"/>
class ComboitemRenderer4UserTypeCc implements ComboitemRenderer<USER_TYPE_FOR_CC> {
    @Override
    public void render(Comboitem item, USER_TYPE_FOR_CC data, int index) throws Exception {
        item.setLabel(data.getText());
    }
}

3.1.3. Binding标签

而且用起来很简单——只要三个标签(@load、@save、@bind)就可以实现各种类型的数据绑定:

  • @load 用来从后台读数据显示在页面;
  • @save 用来将页面输入的信息传递给后台绑定的组件字段;
  • @bind 是@load加@save;

3.1.4. Binding表达式

标签中还可以运用复杂表达式,例如:

  • 日期格式转换:<label value="@load(vm.modelA.crtDttm) @converter('formatedDate', format='yyyy-MM-dd HH:mm')" />
  • 比较运算:<listcell label="@load(item.quantity)" style="@load(item.quantity lt 3?'color:red':'')"/>
  • 绑定集合<listbox selectedItems="@bind(vm.selected)" model="@load(vm.model)">
  • 根据条件动态选择Template循环:

 

<gridmodel="@bind(vm.orders) @template(vm.type='foo'?'template1':'template2')">
    <templatename="template1">
    <!-- child components -->
    </template>
    <templatename="template2">
    <!-- child components -->
    </template>
</grid>
<gridmodel="@bind(vm.orders) @template(each.type='A'?'templateA':'templateB')">
    <templatename="templateA">
    <!-- child components -->
    </template>
    <templatename="templateB">
    <!-- child components -->
    </template>
</grid>

3.1.5. 表单整体Binding

对于表单提交场景,我们通常不希望表单中的各个字段单独进行Binding(那会导致每输入一个字段都会产生一次后台交互,

而且无法进行整体校验),

更好的做法是把表单所有元素要作为一个整体,在最后提交时才绑定到后台组件(的Model字段上),这样也使得架构更清晰更OO;

ZUL示例如下:

<groupboxwidth="50%"hflex="true"closable="true"mold="3d"
   form="@id('fx') @load(vm.user) @save(vm.user, before='submit') @validator(vm.validator)">
   <textboxvalue="@bind(fx.userName)"readonly="${not empty arg.userId }"/>
   <textboxtype="password"value="@bind(fx.password)"/>
   <textboxtype="password"value="@bind(fx.confirmPassword)"/>
………………
………………
   <buttonid="btn_submit"label="提交"onClick="@command('submit')"/>
………………

更多参考 http://books.zkoss.org/wiki/ZK%20Developer%27s%20Reference/MVVM/Data%20Binding/Property%20Binding

3.2. MVVM前后台通信

binding只是在在前后台之间建立起了一个链接,但是还需要一个命令机制来通知框架什么时候以及如何在前后台同步状态;

3.2.1. 前台触发后台动作

页面使用@command标签调用后台组件,示例:

<menuitemlabel="创建Xxx"onClick="@command('openXxxForm',id=each.id)"/>

后台组件示例:

@Command
public void openXxxForm(@BindingParam("id") String roleId) {

3.2.2. 后台通知前台刷新

只需在后台组件方法上声明@NotifyChange({ "property1" }),页面中的@load(vm.property1)就会刷新获取最新的值;

3.2.3. MVVM跨页面调用

页面中的@command只能触发当前页面对应的后台组件的方法调用,要想通知其它页面的后台组件调用需要使用@GlobalCommand("refreshDataList");

调用也不是发生在页面,而是在后台显式调用:BindUtils.postGlobalCommand(null, null, "refreshDataList", null);

3.3. MVVM Validation

3.3.1. Validation概述

Web框架最重要的职责之一就是Validation校验——对客户端提交的数据进行合法性检查(长度、类型、取值范围等),

如果校验失败,则返回错误信息并在前端界面中友好清晰的显示错误信息。

3.3.2. MVVM Validation典型步骤

前面binding章节的表单整体绑定中已经包含了validation:

<windowid="winEditUser"apply="org.zkoss.bind.BindComposer"
        viewModel="@id('vm') @init('com.xxx.ctrl.UserFormDialogCtrl')"validationMessages="@id('vmsgs')"
<groupbox width="50%"hflex="true"closable="true"mold="3d"
   form="@id('fx') @load(vm.user) @save(vm.user, before='submit') @validator(vm.validator)">
   <textboxtype="password"value="@bind(fx.password)"/>
   <labelvalue="@load(vmsgs['password'])"sclass="red"/>
 
   <textboxvalue="@bind(fx.contactInfo.email)"/>
   <labelvalue="@load(vmsgs['contactInfo.email'])"sclass="red"/>
………………
  • validationMessages="@id('vmsgs')"为校验失败时的错误信息集合指定别名vmsgs
  • 其中@validator(vm.validator)指定了表单提交后用来校验的Validator校验器;
  • <label value="@load(vmsgs['contactInfo.email'])" sclass="red" /> 用来在校验失败时显示错误信息。
ViewModel
public org.zkoss.bind.Validator getValidator() {
        returnvalidator;
}

3.3.3. MVVM Validation集成JSR303

JSR303是专门针对JavaBean Validation的规范,hibernate-validator是它的一个实现:

<dependency org="org.hibernate" name="hibernate-validator" rev="4.3.0.Final" conf="compile;runtime" />

借助这一框架,可以在JavaBean类中添加对应的Annotation声明校验规则,非常简便而强大;

 

详见 http://books.zkoss.org/wiki/ZK_Developer%27s_Reference/MVVM/Data_Binding/Validator

4. 列表与分页

4.1. 列表内存分页

ZK列表listbox组件只需配置mold属性为paging即可实现分页,但这样的分页属于内存分页——数据一次性加载到服务端然后每次翻页把当前页数据显示在前台;

<listboxid="dataListbox"mold="paging"pageSize="20"multiple="true"checkmark="false"emptyMessage="搜索结果为空"width="100%"vflex="true">
      <listheadmenupopup="auto"width="100%"sizable="false">
          ………………
      </listhead>
      <templatename="model">
      <listitemstyle="cursor:hand;cursor:pointer;">
          <labelvalue="${forEachStatus.index+1}"/>
          <labelvalue="${each.userName}"/>

然后Controller中只要设置dataListbox的model即可显示列表数据:

List carsModel = new ListModelList<Car>(carService.findAll());
dataListbox.setModel(carsModel);

Demo见 http://www.zkoss.org/zkdemo/getting_started/listbox

4.2. 列表数据库分页

上面的内存分页无法用于真正的生产系统,因为一次加载出所有数据会耗尽服务器内存;

解决方法是数据库分页,每次请求只查询出一页数据然后显示到页面;但是这就不能简单的通过一个配置实现了。

参考ZK文档(http://books.zkoss.org/wiki/ZK_Developer's_Reference/MVVM/Advanced/Displaying_Huge_Amount_of_Data )可自行设计分页组件,

用于封装分页逻辑:构造查询条件、获取总记录数、查询当前页数据、处理返回结果。

代码详见:

这里有个小问题——有时查询会缓存上次查询的总记录数和页号,可以显式调用防止缓存:

  • dataListbox.getPaginal().setTotalSize(model.getSize());
  • dataListbox.getPaginal().setActivePage(0);

5. Tree

动态树
<hlayoutheight="90%"vflex="true">
                        <!-- 设置vflex="true"否则没有垂直滚动条,导致显示不全;设置hflex="true"否则会出现讨厌的水平滚动条 -->
                        <treeid="orgTree"vflex="true"hflex="true"height="100%"width="100%"
                            model="@bind(vm.organTreeModel)"multiple="false"checkmark="false"zclass="z-tree">
                            <treecols>
                                <treecolhflex="7"label="名称"/>
                                <treecolhflex="3"label="描述"/>
                                <treecolhflex="1"label="用户数"align="center"/>
                            </treecols>
                            <templatename="model">
                                <treeitemopen="@load(each.data.open)"selected="@bind(each.data.selected)">
                                    <treerowonClick="@command('selectOrganNode',organId=each.data.id)">
                                        <treecelllabel="${each.data.organName}"/>
                                        <treecelllabel="${each.data.description}"/>
                                        <treecelllabel="${each.data.userAmount}"/>
                                    </treerow>
                                </treeitem>
                            </template>
                        </tree>
                    </hlayout>
  • 和listbox类似的,只需为tree提供model数据,然后内部通过template循环即可打印出一颗动态的树;
  • 设置treeitem的属性open和selected属性即可控制树节点是否展开以及是否选中;

6. 右键菜单

首先在页面中隐藏一个menupopup如下:

<menupopupid="treeMenupopup">
            <menuitemlabel="展开"onClick="@command('expandDir')"/>
            <menuitemlabel="创建子目录"onClick="@command('newDir')"/>
            <menuitemlabel="修改名称"onClick="@command('editDir')"/>
            <menuitemlabel="删除"onClick="@command('deleteDir')"/>
        </menupopup>

然后为需要右键弹出菜单的组件注册事件:

<treerowonRightClick="@command('openTreeMenu', paramEvent=event, reportId=each.data.id, image=each.data.image)">

处理右键事件:

1
2
3
4
5
6
7
8
@Command
    publicvoidopenTreeMenu(@BindingParam("paramEvent") Event paramEvent,@BindingParam("reportId") String reportId,
            @BindingParam("image") String image) {
        if(StringUtils.isEmpty(image)) {
            treeMenupopup.open(paramEvent.getTarget(),"after_end");//在鼠标光标所在的组件后面弹出菜单
            reportIdForRightClick = reportId;
        }
    }

然后就是处理菜单的点击事件,没什么特别的了。

7. Spring集成

ZK与Spring集成非常简单,只需在MVC的Controller或MVVM的ViewModel类上面声明@VariableResolver(DelegatingVariableResolver.class)即可,

然后就可以通过Annotation声明实例变量注入Spring Bean,代码如下:

@VariableResolver(DelegatingVariableResolver.class)
public class AbcCtrl extendsSelectorComposer<Window> {
    @WireVariable
    privateAbcService    abcService;

8. SpringSecurity集成

8.1. ZK集成SpringSecurity原理

SpringSecurity是最主流的Web安全框架,框架中封装了一个Web应用通用的典型认证与授权流程,以及安全上下文、session管理、cookie管理等服务;

同时框架为那些不通用的部分留下了扩展点和配置点,例如用户信息获取、权限数据获取、登录页面、登录后跳转、出错页面等;

ZK应用也是Web应用,因此可以直接置于SpringSecurity的保护之下。

但ZK应用又有特殊之处:大量采用AJAX交互并且请求URL不规则,因此为了对ZK应用进行细粒度的权限控制需要借助zkspring-security这个库的帮助;

8.2. ZK集成SpringSecurity配置步骤

依赖的第三方lib:zkspring-security

ivy.xml
<dependencyorg="org.zkoss.zk"name="zkspring-security"rev="3.1.1"conf="compile;runtime"transitive="false"/>
 
<dependencyorg="org.springframework.security"name="spring-security-core"rev="3.1.4.RELEASE"conf="compile;runtime"/>
<dependencyorg="org.springframework.security"name="spring-security-acl"rev="3.1.4.RELEASE"conf="compile;runtime"/>
<dependencyorg="org.springframework.security"name="spring-security-taglibs"rev="3.1.4.RELEASE"conf="compile;runtime"/>
<dependencyorg="org.springframework.security"name="spring-security-config"rev="3.1.4.RELEASE"conf="compile;runtime"/>
<dependencyorg="org.springframework.security"name="spring-security-web"rev="3.1.4.RELEASE"conf="compile;runtime"/>

web.xml配置:

<listener>
        <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
    </listener>
……………………
    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

SpringSecurity配置:

<?xmlversion="1.0"encoding="UTF-8"?>
    xsi:schemaLocation="http://www.springframework.org/schema/beans
             http://www.springframework.org/schema/security
             http://www.zkoss.org/2008/zkspring/security
             >
    <httpauto-config='true'access-denied-page="/error.html">
        <intercept-urlpattern="/images/**"access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <intercept-urlpattern="/login.html*"access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <intercept-urlpattern="/pages/admin/**"access="ROLE_ADMIN"/>
        <intercept-urlpattern="/pages/**"access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <intercept-urlpattern="/**"access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <form-loginlogin-page="/login.html"authentication-failure-url="/login.html?login_error=1"
            default-target-url="/main.html"always-use-default-target="true"/>
        <!-- Following is list of ZK Spring Security custom filters. They needs to be exactly in the same order as shown below
            in order to work. -->
        <custom-filterref="zkDesktopReuseFilter"position="FIRST"/>
        <custom-filterref="zkDisableSessionInvalidateFilter"before="FORM_LOGIN_FILTER"/>
        <custom-filterref="zkEnableSessionInvalidateFilter"before="FILTER_SECURITY_INTERCEPTOR"/>
        <custom-filterref="zkLoginOKFilter"after="FILTER_SECURITY_INTERCEPTOR"/>
        <custom-filterref="zkError403Filter"after="LOGOUT_FILTER"/>
    </http>
     
    <authentication-manager>
        <authentication-provider>
            <user-serviceproperties="classpath:/properties/security-users.properties"/>
        </authentication-provider>
    </authentication-manager>
 
    <zksp:zk-eventlogin-template-close-delay="1"path-type="ant">
        <zksp:intercept-eventevent="onClick"path="//**/cmdBtn_*"access="ROLE_ADMIN"/>
        <zksp:intercept-eventevent="onClick"path="//**/menu_*"access="ROLE_ADMIN"/>
        <zksp:intercept-eventevent="onClick"path="//**/treemenu_*"access="ROLE_ADMIN"/>
        <zksp:intercept-eventevent="onClick"path="//**/btn_*"access="ROLE_USER"/>
        <zksp:intercept-eventpath="/**"access="IS_AUTHENTICATED_ANONYMOUSLY"/>
        <zksp:form-loginlogin-page="/login.html"/>
    </zksp:zk-event>
</beans:beans>

详见 http://books.zkoss.org/wiki/Small_Talks/2010/April/Making_Spring_Security_Work_with_ZK

9. ZK全局配置

9.1. 按钮防止连击

在zk.xml配置:

<language-config>
        <addon-uri>/WEB-INF/lang-addon.xml </addon-uri>
</language-config>

在lang-addon.xml添加配置:

<component>
        <component-name>button</component-name>
        <extends>button</extends>
        <property>
            <property-name>autodisable</property-name>
            <property-value>self</property-value>
        </property>
    </component>

9.2. Theme换肤

在lang-addon.xml添加配置:

<library-property>
        <name>org.zkoss.theme.preferred</name>
        <value>sapphire</value>
    </library-property>

10. 国际化i18n

设置当前用户的local
<listbox id="localSelector"mold="select"rows="1"width="80px">
        <listitem label="语言/Local"value=""/>
        <listitem label="English"value="en"/>
        <listitem label="简体中文"value="zh_CN"/>
    </listbox>
 
    @Listen("onSelect = #localSelector")
    publicvoidonSelectLocal(Event event) {
        Object localName = ((Listbox) event.getTarget()).getSelectedItem().getValue();
        logger.debug("选择语言区域【"+ localName +"】");
        CookieUtils.setLocal(Executions.getCurrent(), (String) localName);
        Locale locale = Locales.getLocale((String) localName);
        Executions.getCurrent().getSession().setAttribute(Attributes.PREFERRED_LOCALE, locale);
        Executions.sendRedirect(null);
    }
zk.xml
<listener>
        <listener-class>com.xxx.base.LocalInterceptor</listener-class>
    </listener>
LocalInterceptor.java
public class LocalInterceptor implementsRequestInterceptor {
    @Override
    publicvoidrequest(org.zkoss.zk.ui.Session sess, Object request, Object response) {
        String localName = CookieUtils.getLocal((HttpServletRequest) request);
        Locale locale = Locales.getLocale(localName);
        ((HttpServletRequest) request).getSession().setAttribute(Attributes.PREFERRED_LOCALE, locale);
    }
}
 
public class CookieUtils {
    /**
     * 添加名为zktheme的cookie就可以改变当前用户的theme
     */
    staticString    THEME_COOKIE_KEY    ="zktheme";
    staticString    LOCAL_COOKIE_KEY    ="zkLocal";
    
    publicstaticString getLocal(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if(cookies ==null)
            return"";
        for(inti =0; i < cookies.length; i++) {
            Cookie c = cookies[i];
            if(LOCAL_COOKIE_KEY.equals(c.getName())) {
                String theme = c.getValue();
                if(theme !=null)
                    returntheme;
            }
        }
        return"";
    }
    publicstaticvoidsetLocal(Execution exe, String localName) {
        Cookie cookie =newCookie(LOCAL_COOKIE_KEY, localName);
        cookie.setMaxAge(60*60*24*30);// store 30 days
        String cp = exe.getContextPath();
        // if path is empty, cookie path will be request path, which causes problems
        if(cp.length() ==0) {
            cp ="/";
        }
        cookie.setPath(cp);
        ((HttpServletResponse) exe.getNativeResponse()).addCookie(cookie);
    }
}
资源文件zk-label.properties
welcome=Welcome
theme.sapphire=sapphire
theme.silvertail=silvertail
资源文件zk-label_zh_CN.properties
welcome=欢迎
theme.sapphire=蓝色
theme.silvertail=银灰
页面中用标签显示信息
<listitemlabel="${labels.theme.sapphire}"value="sapphire"/>

11. 文件上传

在ZUL页面中,通过指定upload属性就可以把一个按钮或菜单变成文件上传组件了,并且可以设置附件大小限制,如下:
<menuitemlabel="添加附件"image="/images/attachment_16.png"upload="true,native,maxsize=10240"onUpload="@command('boardUploadFile')"/>
<buttonlabel="添加附件"upload="true,native,maxsize=10240"onUpload="@command('formUploadFile')"/>

然后在ViewModel中处理上传的文件,如下:

1
2
3
4
5
6
7
8
9
10
11
@Command
    @NotifyChange({"boardAttachmentList","boardAuditInfoList"})
    publicvoidboardUploadFile(@ContextParam(ContextType.TRIGGER_EVENT) UploadEvent event)throwsIOException {
        Media media = event.getMedia();
        logger.debug("文件名为【"+ media.getName() +"】");
        logger.debug("文件大小为【"+ media.getStreamData().read() +"】");
        logger.debug("文件类型为【"+ media.getContentType() +"】");
        QualityPlanAttachment qualityPlanAttachment =newQualityPlanAttachment();
        qualityPlanAttachment.setFileName(media.getName());
        qualityPlanAttachment.setFileSize((long) media.getByteData().length);
        qualityPlanAttachment.setContent(media.getByteData());

12. 文件下载

很简单,只需调用ZK相关API Filedownload.save;另外要注意不同浏览器对中文文件名可能产生乱码问题;

@Command
    publicvoidboardDownloadFile(@BindingParam("fileId") String fileId)throwsUnsupportedEncodingException {
        ………………
        String fileName = attachment.getFileName();
        byte[] content=attachment.getByteArray();
        Filedownload.save(content,null, ZkUtils.encodingFileName(fileName));
    }
//解决文件名乱码问题
public static String encodingFileName(String fileName) throws UnsupportedEncodingException {
        HttpServletRequest httpRequest = (HttpServletRequest) Executions.getCurrent().getNativeRequest();
        String browserName = Servlets.getBrowser(httpRequest);
        if(StringUtils.equalsIgnoreCase("gecko", browserName)) {//firefox
            fileName =newString(fileName.getBytes("UTF-8"),"ISO8859-1");
        }else{//ie浏览器
            fileName = URLEncoder.encode(fileName,"UTF-8");
        }
        returnfileName;
    }

13. CKEditor

ZK的子项目ckez实现了对CKEditor(一款流行的在线文本编辑器)的封装,可以方便的集成到ZK应用中实现在线编辑复杂格式文档;

ivy.xml
<dependencyorg="org.zkoss.zkforge"name="ckez"rev="3.6.4.0"conf="runtime"/>

然后zul中只需像textbox一样去用就可以了:

<ckeditortoolbar="Basic"value="@bind(fx.content)"hflex="true"width="90%"height="95px"/>

14. Chart图表

ZK自带的chart图表参考在线demo :http://www.zkoss.org/zkdemo/chart/pie_chart

另外ZK的子项目zhighcharts对higncharts-js(用于绘制各种常见图表的js库)进行了封装,可以方便的集成到ZK应用,详见:

15. 自定义组件

15.1. taglib标签式自定义组件

zul中声明以及使用
<?componentname="progressBar"extends="hlayout"class="com.xxx.component.ProgressBarHlayout"?>
 
<progressBarprogress="@load(vm.plan1.progressPercentageValue)"widthValue="120"height="9px"/>
Java代码
public class ProgressBarHlayout extendsHlayoutimplementsIdSpace {
    privatestaticfinalint   VALUE_LABEL_WIDTH    =0;                                                   //30
    @Wire
    Div                            progressDiv;
    @Wire
    Div                            grayDiv;
    String                        defaultStyle        ="position:absolute;left:0px;z-index:1;background:";
    publicProgressBarHlayout() {
        Executions.createComponents("/pages/common/progressBar.zul",this,null);
        Selectors.wireVariables(this,this, Selectors.newVariableResolvers(getClass(), Hlayout.class));
        Selectors.wireComponents(this,this,false);
        this.setSpacing("0");
    }
………………
    publicvoidsetProgress(intprogress) {
        progressDiv.setWidth(progress * (widthValue - ProgressBarHlayout.VALUE_LABEL_WIDTH) /100+"px");
        this.setTooltiptext(progress +"%");
        //        progeressValueLabel.setValue(progress + "%");
        //        progeressValueLabel.setWidth(ProgressBarHlayout.VALUE_LABEL_WIDTH + "px");
        //        progeressValueLabel.setStyle("font-size:11px");
        if(progress <=30) {
            progressDiv.setStyle(defaultStyle +"#CD3D38;");
        }elseif(progress >=80) {
            progressDiv.setStyle(defaultStyle +"#69CD4B;");
        }else{
            progressDiv.setStyle(defaultStyle +"#CF9E25;");
        }
    }
对应的zul模板
<zk>
    <!-- <label id="progeressValueLabel" vflex="true" /> -->
    <divid="progressDiv"
        style="position:absolute;left:0px;z-index:1;background:;"/>
    <divid="grayDiv"style="position:relative;top:0px;left:0px;background:#D5CCBE;"/>
</zk>

15.2. 简单taglib标签式自定义组件

声明:
<?componentname="myImage"class="XX.MyImage"?>
定义组件:
public class MyImage extends Image implements AfterCompose {
    public void setMycontent(byte[] mycontent) {
        if (null!=mycontent) {
            try {
                this.setContent(new AImage("t", mycontent));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
使用组件:
<myImagemycontent="@load(item.photo)"width="300px"height="300px"/>

15.3. include宏式自定义组件

跟前面taglib风格正好相反,采用include方式,然后在被include页面里面就是正常的MVVM模式。

优点是可以使用MVVM,适合于复杂页面;示例如下:

使用方式就是include一个zul页面
<includechartInfo="@load(node)"hflex="true"vflex="true"width="@load(node.boxWidthPx)"src="@load('/pages/common/componentAbc.zul')"/>

然后被include的页面componentAbc就是一个普通的MVVM页面,

ViewModel实现如下(注意其中的init方法用来从外层页面传入参数):

import org.zkoss.bind.annotation.Init;
………………
@VariableResolver(DelegatingVariableResolver.class)
public class ComponentAbc {
    @Init
    publicvoidinit(@ExecutionArgParam("chartInfo") IndicatorChartInfo chartInfo,
            @ExecutionArgParam("maxMold") Boolean maxMold) {
        this.chartInfo = chartInfo;
        this.maxMold = maxMold;
    }

然后就没什么特别的了。

16. ZATS集成测试

16.1. ZATS概述

ZATS是用来对ZK应用进行自动化功能测试的一套测试框架。

Web应用的自动化功能测试非常重要(可以有效的保证发布质量,同时节省大量的手工回归测试成本),因此出现了很多相关技术框架和工具,

最常用的的包括QTP、Selenium;

但这些工具有两个致命的弱点:

  • 运行缓慢:传统的测试工具都是跨进程通讯的,运行测试时需要起至少三个进程——AppServer、浏览器、测试框架本身的Server,
  • 导致运行缓慢、准备工作繁琐、容易出错;
  • 测试脚本不稳定容易出错:另外由于传统测试框架是针对系统最终界面(HTML)进行测试,测试用例与页面HTML高度耦合,
  • 为复杂页面编写的测试脚本也很复杂繁琐,而且页面稍有改动脚本就会出错,开发和维护成本都很高。

这两个弱点严重制约了自动化测试的进行。

相对于Selenium等测试框架,ZATS具有很大优势(当然这只限于ZK Web应用),它解决了传统自动化测试框架的两个最大软肋:

  • 轻便快速:ZATS测试与被测页面运行在同一进程内,不需要起server和浏览器,运行起来方便快速,跟普通的JUnit单元测试基本没有区别;
  • 测试脚本稳定、可维护性好:ZATS是针对ZUL的测试,由于ZUL比最终生成的HTML要简洁的多(代码量大概只有10%),
  • 因此测试开发和维护成本很低,而且稳定;

16.2. ZATS典型测试案例

首先扩展测试框架基类,以便在每个ZATS测试用例运行前做一些统一的准备工作,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
protectedstaticZatsEnvironment    env;
    @BeforeClass
    publicstaticvoidinit() {
        env =newDefaultZatsEnvironment("./zats");//加载zats目录下的web.xml
        env.init("./web");
    }
    @AfterClass
    publicstaticvoidend() {
        Zats.end();
    }
    @After
    publicvoidafter() {
        Zats.cleanup();
    }

通常我们需要为ZATS准备一个简单的web.xml——例如这里可以去掉SpringSecurity等配置、加载测试专用的spring配置等;

然后就可以扩展这个基类来开发真正的ZATS测试了,基本套路如下:

  • 连接被测页面;
  • 定位要操作的组件;
  • 断言页面的变化或后台数据的变化;

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import java.util.List;
import junit.framework.Assert;
import org.apache.log4j.Logger;
import org.junit.Test;
import org.zkoss.zats.mimic.Client;
import org.zkoss.zats.mimic.ComponentAgent;
import org.zkoss.zats.mimic.DesktopAgent;
 
 
public class HomeTest extendsBaseZatsTestCase {
    Logger    logger    = LoggerUtil.getLogger();
    @Test
    publicvoidtestHome() {
        Client client = env.newClient();
        DesktopAgent desktop = client.connect("/pages/home.zul");//打开首页home.zul
        ComponentAgent mainTabsAgent = desktop.query("#topWindow").query("#center").query("#mainTabbox")
                .query("#mainTabs");
        logger.debug(mainTabsAgent);
        List<ComponentAgent> menuAgentList = desktop.queryAll("tree treechildren treeitem treechildren treeitem");
        logger.debug(menuAgentList.size());
        Assert.assertEquals(9, menuAgentList.size());
        menuAgentList.get(0).click();//点击打开west第一个菜单
        logger.debug("mainTabsAgent size:"+ mainTabsAgent.getChildren().size());
        Assert.assertEquals("mainTabsAgent包含tab个数",2, mainTabsAgent.getChildren().size());
    }
    @Test
    publicvoidtestProjectApply() {
        Client client = env.newClient();
        DesktopAgent desktop = client.connect("/pages/projectCodeApply.zul");
        ComponentAgent listboxAgent = desktop.query("#dataListbox");
        logger.debug(listboxAgent);
        Assert.assertNotNull(listboxAgent);
    }
}

16.3. ZATS与Mockito集成

Mockito是一个想打的Mock测试框架,Mock技术可以用来隔离外部接口、资源等依赖,使得单元测试可以不受外部依赖影响,简化测试工作并且可以方便的模拟一些异常情况;

由于ZATS测试本质上是针对运行在jetty server中的整个应用(中的zuls页面),因此属于一种端到端的功能测试,因此无法像单元测试那样通过Mock技术对接口和底层Service组件进行隔离。

为此需要对ZATS做一点Hacking:

  • 反编译EmulatorClient并且为其emulator属性增加一个getter

然后就可以想如下示例获得jetty server中的Spring上下文并获得其中的Service

EmulatorClient client = (EmulatorClient) env.newClient();
WebApplicationContext wac = WebApplicationContextUtils
                .getWebApplicationContext(client.getEmulator().getServletContext());
ArrayList<Policy> policyList = new ArrayList<Policy>();
Policy policy = new Policy();
policy.setInsurantName("李大壮");
policyList.add(policy);
PolicyCancelService policyCancelService=(PolicyCancelService) wac.getBean("policyCancelService");
Mockito.when(policyCancelService.getRetreatList(Mockito.anyMap(),Mockito.anyString())).thenReturn(policyList);
  • 当然,还有一个前提——ZATS启动加载的Spring配置的是Mockito Service,这样才能通过Mockito框架设置其行为,Spring配置如下:
mock-beans.xml
    <bean id="policyCancelService" class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="com.cpic.p17.life.service.telGps.PolicyCancelService" />
    </bean>
    <bean id="organDeptService" class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="com.cpic.p17.base.service.OrganDeptService" />
    </bean>
分享到:
评论

相关推荐

    大、小断层矿井小波SVM融合智能故障预测matlab代码.zip

    1.版本:matlab2014/2019a/2021a 2.附赠案例数据可直接运行matlab程序。 3.代码特点:参数化编程、参数可方便更改、代码编程思路清晰、注释明细。 4.适用对象:计算机,电子信息工程、数学等专业的大学生课程设计、期末大作业和毕业设计。

    垂直SeekBar(拖动条).zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    libADLMIDI1-1.5.0-bp153.1.1.x86-64.rpm

    libADLMIDI1-1.5.0-bp153.1.1.x86_64.rpm 是用于在 x86_64 架构的设备上安装的 RPM 包,具体功能如下: 名称:libADLMIDI1 版本:1.5.0 摘要:带有 OPL3 (YMF262) 模拟器的软件 MIDI 合成器库 许可证:GPL-3.0-only 和 LGPL-3.0-only 该库提供了一个基于 ADLMIDI 的软件 MIDI 合成器,它模拟了 OPL3 音源芯片(FM 合成)。它可以通过使用 ADLMIDI 库来实现多平台的 MIDI 播放和 OPL3 模拟。 该 RPM 包适用于 x86_64 架构,用于在相关设备上安装 libADLMIDI1 库文件。库文件包括: /usr/lib64/libADLMIDI.so.1 和 /usr/lib64/libADLMIDI.so.1.5.0:库文件 /usr/share/doc/packages/libADLMIDI1/AUTHORS、/usr/share/doc/packages/libADLMIDI1/README.md 等文档文件:文档文件

    基于qt+C++实现u盘插拔检测.+源码(毕业设计&课程设计&项目开发)

    基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于qt+C++实现u盘插拔检测.+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~

    Quectel_Product_Brochure_CN_V7.9.pdf

    Quectel_Product_Brochure_CN_V7.9.pdf

    更换软件主题(apk方式).zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    chepai-reg-main (2).zip

    phpstudy

    Python 入门详细教程-1天学会 Python.docx

    python入门

    二维码扫描的实现.zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    移动机器人机械臂的设计开题报告.doc

    移动机器人机械臂的设计开题报告.doc

    基于QT+C++开发的智能平台访客系统+源码

    用法链接:https://menghui666.blog.csdn.net/article/details/137977678?spm=1001.2014.3001.5502 基于QT+C++开发的智能平台访客系统+源码,包含主界面、系统设置、警情查询、调试帮助、用户退出功能。 基于QT+C++开发的智能平台访客系统+源码,包含主界面、系统设置、警情查询、调试帮助、用户退出功能。 基于QT+C++开发的智能平台访客系统+源码,包含主界面、系统设置、警情查询、调试帮助、用户退出功能。

    三菱机械臂校点说明.pptx

    三菱机械臂校点说明.pptx

    按字母索引滑动.zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    激光推送客户端demo.zip

    android 源码学习. 资料部分来源于合法的互联网渠道收集和整理,供大家学习参考与交流。本人不对所涉及的版权问题或内容负法律责任。如有侵权,请通知本人删除。感谢CSDN官方提供大家交流的平台

    c语言入门,小白进军C语言.zip

    C语言诞生于美国的贝尔实验室,由丹尼斯·里奇(Dennis MacAlistair Ritchie)以肯尼斯·蓝·汤普森(Kenneth Lane Thompson)设计的B语言为基础发展而来,在它的主体设计完成后,汤普森和里奇用它完全重写了UNIX,且随着UNIX的发展,c语言也得到了不断的完善。为了利于C语言的全面推广,许多专家学者和硬件厂商联合组成了C语言标准委员会,并在之后的1989年,诞生了第一个完备的C标准,简称“C89”,也就是“ANSI C”,截至2020年,最新的C语言标准为2018年6月发布的“C18”。 [5] C语言之所以命名为C,是因为C语言源自Ken Thompson发明的B语言,而B语言则源自BCPL语言。 1967年,剑桥大学的Martin Richards对CPL语言进行了简化,于是产生了BCPL(Basic Combined Programming Language)语言。

    Python入门到精通.zip

    python入门 单元测试和测试用例 Python标准库中的模块unittest提供了代码测试工具。 单元测试用于核实函数的某个防霾呢没有问题; 测试用例是一组单元测试,这些单元测试仪器一起核实函数在各种情形下的行为都符合要求。良好的测试用例考虑到了函数可能收到的各种收入,包含所有针对这些情形的测试。 全覆盖式测试用例包含一整套单元测试,涵盖了各种可能的函数使用方式。 对于大型项目,要实现全覆盖可能很难。通常,最初只要对针对代码的重要行为编写测试即可,等项目给广泛使用时再考虑全覆盖。 可通过的测试 创建测试用例的语法需要一段时间才能习惯,但测试用例创建后,再添加针对函数的单元测试就很简单了。要为函数编写测试用例,可先导入模块unittest以及要测试的函数,在创建一个继承unittest.TestCase的类,并编写一系列方法对函数行为的不同方面进行测试。 下面test_name_function.py一个只包含一个方法的测试用例,它检查函数get_formatted_name()在给定名和姓时能否正确的工作。

    基于matlabbenders分解算法.zip

    基于matlabbenders分解算法.zip

    dsp工程设计讲座.ppt

    dsp工程设计讲座.ppt

    Adams空间复杂机械臂动力学仿真研究.doc

    Adams空间复杂机械臂动力学仿真研究.doc

    基于Android+OpenCV的车牌识别系统源码+使用文档+全部资料(优秀项目).zip

    【资源说明】 基于Android+OpenCV的车牌识别系统源码+使用文档+全部资料(优秀项目).zip基于Android+OpenCV的车牌识别系统源码+使用文档+全部资料(优秀项目).zip基于Android+OpenCV的车牌识别系统源码+使用文档+全部资料(优秀项目).zip 【备注】 1、该项目是个人高分毕业设计项目源码,已获导师指导认可通过,答辩评审分达到95分 2、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 3、本项目适合计算机相关专业(如软件工程、计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载使用,也可作为毕业设计、课程设计、作业、项目初期立项演示等,当然也适合小白学习进阶。 4、如果基础还行,可以在此代码基础上进行修改,以实现其他功能,也可直接用于毕设、课设、作业等。 欢迎下载,沟通交流,互相学习,共同进步!

Global site tag (gtag.js) - Google Analytics