小谈ZK框架技巧

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

        然而作为一个国外开源框架,它给我的感觉就是不用写前端javascript,浏览器与服务器的交互次数变多,服务器压力变大。所以,使用该框架者应该慎重综合考虑。不要为了一时的偷懒毁掉整个项目。下面我就将罗列出我开发过程中积累的一些开发技巧、要点、注意事项,以及ZK开发的方法。

打开新页签的方法
  • Ctrl页面定义页签头和页签容器变量:
    private Tabs tabs;
    private Tabpanels tabpanels;

  • 然后在初始化afterCompose方法中初始化上面2个变量

    public void afterCompose(@ContextParam(ContextType.VIEW) Component view) {
              tabs = (Tabs) view.getParent().getParent().getParent().query("tabs");
              tabpanels = (Tabpanels) view.getParent().getParent();
        ...
    }
  • Ctrl类中其他方法触发打开新页签

    public void onAdd() {
               Tab tabNew = new Tab();
               tabNew.setId("tab_id");//Tab的ID,必须唯一           
               tabNew.setClosable(true);
               tabNew.setLabel("TabName");//Tab名称           
               try {
                      Tabpanel tabpanel = ZkUtils.newTab(tabNew, "/web/**/**.zul", tabs,
    					tabpanels, ZkUtils.OverFlowType.AUTO, null);
               } catch (TabDuplicateException ex) {
                      logger.error(ex.getMessage());
               }
    
               logger.debug("正在新页签中打开页面【***.zul】");
    }
  • 注意事项:
    新打开的页签页面zul中的window组件的id不要命名,否则会因window重名而无法重复打开多个。

关闭页签方法

打开新页签时的参数map中传入tab,然后就可以在tab页面中tab.close()关闭了......
TODO

金额统一按照小数点后2为显示,通用方法

金额统一按照小数点后2为显示,构造通用方法,供系统统一使用

// 在对应的zul页面引入如下代码<?xel-method prefix="c" name="formatStock" class="com.misumi.newcim.ctrl.component.FormatElNumber"
   signature="java.lang.String formatStock(double)"?>
......//然后按照如下方式应用<label value="${c:formatStock(each.igcStandardCredit)}"></label>
......
combobox刷新问题

必填字段,添加红色*号标记

必填字段添加红色*号标记,因在textbox后方添加时,会导致textbox框缩小,与其他非必填项的textbox长度不一致
设计改为将*号显示在字段名称后方括号内    name(红色*)。  具体更改方案:
原lable字段更改为hbox

<hbox pack="end" align="end" hflex="true">
    <label value="客户编码(" />
    <label value="*" sclass="red" />
    <label value="):" />
</hbox>
页面提示信息统一管理

信息一览实现配置文件读取显示方式,配置在webinfo目录文件下,app.properties、sys.porperties文件分别载入基本功能区域的信息提示和系统管理的信息提示。具体案例如下:
启动时zk.xml文件加载配置:

<system-config>
    <label-location>/WEB-INF/i18n/sys.properties</label-location>
    <label-location>/WEB-INF/i18n/app.properties</label-location>
</system-config>

字符串:
sys.porperties中配置 

common.sys.queryError=检索失败!

Ctrl引用              

Labels.getLabel("common.sys.queryError");

带参数字符串:
sys.porperties中配置

sys.NotifyRuleCtrl.ruleCodeRepeat=规则代码{0}和已有的重复,请修改后再保存。

Ctrl引用         

Labels.getLabel("sys.NotifyRuleCtrl.ruleCodeRepeat", new java.lang.Object[] { notifyRule.getCode() });

页面constraint引用参考:

constraint="/^\w{1,6}$/:${labels.customer.cust.detail.groupCode.error}"
Combobox下拉列表组件的使用

Combobox的默认选中(默认选择某个comboitem项)是常见需求,但是这里有些说道:
简单概括就是:zk通过==比较(即地址比较)来决定选中哪个comboitem

zul页面:

<combobox  selectedItem="@bind(fx.custCatg)" model="@load(vm.custCatgList)"
 itemRenderer="com.misumi.newcim.ctrl.renderer.ComboitemRenderer4CustCatg"></combobox>

例如上面的示例,fx.custCatg必须和vm.custCatgList中的某个元素是相同的实例,才能实现该元素的选中。
因此,即便你为fx.custCatg设置的po与vm.custCatgList中的某个元素id相同(在数据库中是同一条记录),但是如果它们在内存中不指向同一实例,也无法默认选中!

在hibernate作为持久层的时候,怎么才能确保上面的条件呢? 有两种方式:

  • 开启OpenSessionInView,hibernate可以确保session一级缓存中任一id的PO实例只有一个,于是只要为fx.custCatg设置了PO就ok;

  • 循环vm.custCatgList找到匹配的元素,然后赋值给fx.custCatg

注意 combobox.setValue(null);的使用!

Tree树形组件概述

介绍树形结构的实现:

......
<parameTreeBandbox selectProcessor="@load(vm.selectProcessorComplaintType)" 
bandboxdisabled="@load(vm.allowUpdateComplaint)" 
bandboxData="@load(vm.bandboxComplaintTypeData)" 
width="250px"
paramTreeType="@load(vm.treeComplaintType)" 
bandboxValue="@save(fx2.complaintType) @load(vm.complaintType)"
bandboxConstraint="@load(vm.const4NoEmpty)" />
......

自定义的树形组件:

ParameTreeBandbox.java

setSelectProcessor():设置树形的选中事件
setBandboxdisabled():设置树形可用不可用
setBandboxData():设置树形的数据源
setParamTreeType():设置树形的数据类型
setBandboxValue():设置树形的value
setBandboxConstraint():设置树形的校验

实现一颗树形结构:

//在页面引入 
..........<?component name="parameTreeBandbox" extends="vlayout" 
    class="com.misumi.newcim.ctrl.component.ParameTreeBandbox"?>..........
<parameTreeBandbox 
selectProcessor="@load(vm.selectProcessorComplaintType)"	
bandboxdisabled="@load(vm.allowUpdateComplaint)" 
bandboxData="@load(vm.bandboxComplaintTypeData)" 
width="250px"paramTreeType="@load(vm.treeComplaintType)" 
bandboxValue="@save(fx2.complaintType) @load(vm.complaintType)"
bandboxConstraint="@load(vm.const4NoEmpty)" />
..........

ComplaintDetailCtrl.java

//声明数据类型
public String getTreeComplaintType() {		
    return "com.misumi.newcim.model.system.ParamTreeComplaintType";
}
//构造数据源
public ParameTreeBandboxData getBandboxComplaintTypeData() {		
    return new ParameTreeBandboxData() {
			@Override	
			public List<ParamTree> queryData(Class className) {
				........
			}
		};
}
//重写选中事件
public ParameTreeBandboxSelectProcessor getSelectProcessorComplaintType() {	
    return new ParameTreeBandboxSelectProcessor() {
			@Override			
			public void onSelect(ParamTree pt) {
				........
			}
		};
}
ZK表单验证终极方案——constraint扩展

 

表单校验是重要且常见的需求,ZK支持两种机制的Validation:

  • constraint前端验证:在输入组件的constraint属性指定验证规则,进行简单的验证;

  • 集成JSR303、Hibernate Validation的服务端验证:在model字段上声明annotation,可以进行服务器端复杂校验,例如需要访问数据库的;

前者的优势是简单、用户体验好(焦点离开后立刻验证);后者优势是支持复杂的校验;

经过一番尝试后,建议优先采用constraint前端校验,
但是当表单过大出滚动条 且验证的字段很多时,漂浮的标签显示错误信息使得页面效果太凌乱、显示也不正常,
因此我们扩展了constraint,以label文本在出错字段下面红字显示错误提示。

zul代码

<label value="客户编码:" />
<vlayout>
	<textbox width="250px" value="@bind(fx.chaUniqueKey)" constraint="@load(vm.noEmptyConst)" disabled="@load(vm.allowUpdate)" />
	<label sclass="red" />
</vlayout>
<label value="客户名称:" />
<cell colspan="4">
	<vlayout>
		<textbox width="93%" value="@bind(fx.customerNtvName)" constraint="@load(vm.noEmptyConst)" disabled="@load(vm.allowUpdate)" />
		<label sclass="red" />
	</vlayout>
</cell>

VM代码

public Constraint getNoEmptyConst() {		
    return new BaseConstraint("no empty, start_before :不能为空哦!");
}	
public Constraint getNatureNumConst() {		
    return new BaseConstraint("no empty,no zero, no negative:rec数不能为空,且只能为自然数哦!");
}	
public Constraint getSoNumConst() {		
    return new BaseConstraint("no empty,/^\\d{9}|\\d{12}|\\d{14}|\\d{15}$/:SO号码不能为空,且必须为9位、12位、14位或15位!");
}	
public Constraint getClassifyCodeConst() {		
    return new BaseConstraint("no empty,/^(\\d){0,8}$/:classifycode不能超过8位且必须输入数字!");
}        
public Constraint getEmailConstraint() {		
    return new BaseConstraint("/^(.+@.+\\.[a-z]+)|^$/ :非法的email!");
}

多个日期组件datebox可以互相影响对方的选中范围!!!

validator使用方式
  • validator使用方式

    • 直接属性验证(Property Validator)
      对组件的属性相关的验证事件触发在onchange等情况下。适合单个属性的验证。
      例子:

      zul页面

      <intbox value="@save(vm.quantity) @validator(vm.rangeValidator)"/>

Model.java

public Validator getRangeValidator(){	
    return new AbstractValidator() {		
        public void validate(ValidationContext ctx) {			
            Integer val = (Integer)ctx.getProperty().getValue();			
            if(val<10 || val>100){
		addInvalidMessage(ctx, "value must not < 10 or > 100, but is "+val);
	    }
	}
    };
}
  •  

    • 依赖性的验证(Dependent Property Validation )
      适合对form表单进行提交集体验证。我们通常需要验证一个属性在同一时间,最简单的方法是继承和重写abstractvalidator validate()实现自定义验证规则。
      例子:

      zul页面

      ...<toolbar>
      	...	<button label="Save" onClick="@command('saveOrder')" disabled="@bind(empty vm.selected)" />
      	...</toolbar><groupbox 
      form="@id('fx') @load(vm.selected)@save(vm.selected, before='saveOrder') 
      	@validator(vm.shippingDateValidator)">	<grid hflex="true" >
      		<columns>
      			<column width="120px"/>
      			<column/>
      		</columns>
      		<!-- other components -->
      		<rows>
      			<row>Creation Date 
      				<hlayout> 
      					<datebox id="cdBox" value="@bind(fx.creationDate) @validator(vm.creationDateValidator)"/>
      					<label value="@load(vmsgs[cdBox])" sclass="red" />
      				</hlayout>	
      			</row>
      			<row>Shipping Date 
      				<hlayout> 
      					<datebox id="sdBox" value="@bind(fx.shippingDate)"/>
      					<label value="@load(vmsgs[sdBox])" sclass="red" />
      				</hlayout>
      			</row>	
      		</rows>
      	</grid></groupbox>

      Validator.jva继承abstractvalidator

      public class ShippingDateValidator extends AbstractValidator{	
          public void validate(ValidationContext ctx) {
      		Date shipping = (Date)ctx.getProperty().getValue();//the main property		Date creation = (Date)ctx.getProperties("creationDate")[0].getValue();//the collected		//multiple fields dependent validation, shipping date have to large than creation more than 3 days.		if(!isDayAfter(creation,shipping,3)){
      			addInvalidMessage(ctx, "must large than creation date at least 3 days");
      		}
      	}	
      	static public boolean isDayAfter(Date date, Date laterDay , int day) {		
              	if(date==null) return false;		
              	if(laterDay==null) return false;
      		Calendar cal = Calendar.getInstance();
      		Calendar lc = Calendar.getInstance();
      		
      		cal.setTime(date);
      		lc.setTime(laterDay);		
      		int cy = cal.get(Calendar.YEAR);		
      		int ly = lc.get(Calendar.YEAR);		
      		int cd = cal.get(Calendar.DAY_OF_YEAR);		
      		int ld = lc.get(Calendar.DAY_OF_YEAR);		
      		return (ly*365+ld)-(cy*365+cd) >= day; 
      	}
      }
  • 页面提示信息展示(Validation Message Holder)
    继承和重写abstractvalidator validate()实现自定义验证规则后,如果验证失败,使用addinvalidmessage()存储验证显示的消息。此时消息被加载到 Validation Message Holder中,页面定义validationMessages即可获取。其中下面例子中,@bind(vmsgs['fkey1'])、@bind(vmsgs['fkey2'])、@bind(vmsgs['fkey3'])这里面的key(fkey1、fkey2、fkey3)是可以自己在java代码自己定义(详见下面java代码注释掉的部分),默认key为验证组件的id(t41、t42、t43)
    例子:

    zul页面

    <window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('foo.MyViewModel')" validationMessages="@id('vmsgs')"><vbox form="@id('fx') @load(vm) @save(vm,before='submit') @validator(vm.formValidator)">
    		<hbox><textbox id="t41" value="@bind(fx.value1)"/><label id="l41" value="@bind(vmsgs['fkey1'])"/></hbox>
    		<hbox><textbox id="t42" value="@bind(fx.value2)"/><label id="l42" value="@bind(vmsgs['fkey2'])"/></hbox>
    		<hbox><textbox id="t43" value="@bind(fx.value3)"/><label id="l43" value="@bind(vmsgs['fkey3'])"/></hbox>
    		<button id="submit" label="submit" onClick="@command('submit')" />
    	</vbox></window>

    Validator中addInvalidMessage()添加验证消息

    public Validator getShippingDateValidator() {		
        return new AbstractValidator(){			
            public void validate(ValidationContext ctx) {
            	Date shipping = (Date)ctx.getProperties("shippingDate")[0].getValue();
    		Date creation = (Date)ctx.getProperties("creationDate")[0].getValue();		
                    //dependent validation, shipping date have to later than creation date for more than 3 days.				
                    if(!CalendarUtil.isDayAfter(creation,shipping,3)){
                    	addInvalidMessage(ctx,"must large than creation date at least 3 days");					
                    	//addInvalidMessage(ctx, "fkey1", "value1 error");					
                    	//addInvalidMessage(ctx, "fkey2", "value2 error");					
                    	//addInvalidMessage(ctx, "fkey3", "value3 error");
    		}
    	}
        };
    }
  • 采用Java annotation实现Validator注入

    • 直接使用现有框架的annotation,如org.hibernate.validator。例子:

      Java annotation实现Validator注入

      public static class User{    
          private String _lastName = "Chen";
           
          @NotEmpty(message = "Last name can not be null")   
           public String getLastName() {       
            return _lastName;
          }     
          public void setLastName(String name) {
              _lastName = name;
          }
      }
    • 自定义Java annotation并实现Validator

      Model.java

      @DistinctSysRole(targetProperty = "code")
      public class SysRole {        
          private String  code;	
          public String getCode() {	
      	    return code;
          }	
          public void setCode(String code) {		
              this.code = code;
          }
      }

      自定义Java annotation

      import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
      import static java.lang.annotation.ElementType.TYPE;
      import static java.lang.annotation.RetentionPolicy.RUNTIME;
      import java.lang.annotation.Documented;
      import java.lang.annotation.Retention;
      import java.lang.annotation.Target;
      import javax.validation.Constraint;
      import javax.validation.Payload;
      
      @Target({ TYPE, ANNOTATION_TYPE })
      @Retention(RUNTIME)
      @Constraint(validatedBy = SysRoleDistinctValidator.class)
      @Documentedpublic @interface DistinctSysRole {	
          message() default "角色代码不能重复";	
          String targetProperty();	
          Class<?>[] groups() default {};	
          Class<? extends Payload>[] payload() default {};
      
      }

自定义Validator

public class SysRoleDistinctValidator implements ConstraintValidator<DistinctSysRole, SysRole> {

	@Resource
	RoleService	roleService;

	@Override	
	public void initialize(DistinctSysRole constraintAnnotation) {		
	    // TODO Auto-generated method stub
	}

	@Override	
	public boolean isValid(SysRole role, ConstraintValidatorContext context) {		
	    return roleService.checkDistinct(role);
	}
}

Validator的更多详细的介绍,参考官方解说:
http://books.zkoss.org/wiki/ZK_Developer%27s_Reference/MVVM/Data_Binding/Validator

foreach注意事项

listbox中常用foreach对model进行循环展示,但请注意:
Access each and forEachStatus in Event Listeners

However, you cannot access the values of each and forEachStatus in an event listener because their values are reset after the XML element which forEach is associated has been evaluated.

For example, the following code will not work:

<button label="${each}" forEach="${countries}"
    onClick="alert(each)"/> <!-- incorrect!! -->

When the onClick event is received, the each object no longer exists.

There is a simple solution: store the value in the component's attribute, so you can retrieve it when the event listener is called. For example,

<button label="${each}" forEach="${countries}"
    onClick='alert(self.getAttribute("country"))'>
        <custom-attributes country="${each}"/>
</button>
zk前后台传值技巧

1、事件传值,事件t:

<listitem onClick="@command('onCheckAttachment',t=event)" id="${each.id}">
	<listcell />
	<listcell label="${each.attachName}" />
	<listcell label="${each.attachSize}" />
	<listcell label="${c:formatDate(each.crtDttm, 'yyyy/MM/dd hh:mm')}" />
</listitem>

ctrl中用e.getTarget()接收:

@Command
public void onCheckAttachment(@BindingParam("attachment") Attachment a, @BindingParam("t") Event e) {
	Listitem item = (Listitem) e.getTarget();
	logger.debug(((Attachment) item.getValue()).getAttachName());
}

2、扩展参数方式传值,注意标签中的"abc",该名字可以自由定义:

<listitem onClick="@command('onCheckAttachment',t=event)" id="${each.id}">
	<custom-attributes abc="${each.id}" />
	<listcell />
	<listcell label="${each.attachName}" />
	<listcell label="${each.attachSize}" />
	<listcell label="${c:formatDate(each.crtDttm, 'yyyy/MM/dd hh:mm')}" />
</listitem>

ctrl中用item.getAttribute("abc")方式获取:

@Command
public void onCheckAttachment(@BindingParam("attachment") Attachment a, @BindingParam("t") Event e) {
	Listitem item = (Listitem) e.getTarget();
	a = (Attachment) item.getAttribute("abc");
	logger.debug(((Attachment) item.getValue()).getAttachName());
}
动态创建checkbox组、显示已选项

1、在指定div中设置动态创建checkbox
页面:<div id="sendTypeCheckboxGroup" width="100%" hflex="true" style="overflow:auto;"></div>

后台:

for (ParamTree sub : pt.getChildList()) {
	Checkbox checkbox = new Checkbox(sub.getText()); //这是设置checkbox的label
	checkbox.setAttribute("name", "sendType"); //设置提交时绑定的实体属性
	checkbox.setValue(sub.getCode());
        //如果已有一个保存已选项值的集合checkedCodeList
        if(checkedCodeList.contains(sub.getCode())){
         checkbox.setChecked(true);
        }
	sendTypeCheckboxGroup.getChildren().add(checkbox);
	Space separator = new Space();
	separator.setWidth("10px");
	sendTypeCheckboxGroup.getChildren().add(separator);
}

2、后台获得页面选中的checkbox
在容器id为subSendTypesGroupBox中获得选中的checkbox,返回一个checkbox的集合。
subSendTypesGroupBox.queryAll("checkbox[checked=true]"))

ZK样式扩展技巧

当ZK组件默认样式不满足需求时,我们可以扩展其css定制展现形式,主要有三种方式:

  1.     覆盖zclass:会影响全局
  2.     定义sclass,组件中引用:只影响引用的组件
  3.     直接在组件style属性中定义样式:只影响该组件

分页要点

需要全局配置如下属性,否则总记录数大了会有性能问题

    <library-property>
		<name>org.zkoss.zul.grid.rod</name>
		<value>true</value>
	</library-property>
	<library-property>
		<name>org.zkoss.zul.listbox.rod</name>
		<value>true</value>
	</library-property>

listbox/grid列表多选全选

要为grid或listbox列表的每行记录增加全选checkbox,需以下三部:

一、zul中设置属性

<listbox multiple="true" checkmark="true">

multiple为false时每行前面出现单选按钮radio

二、ctrl中对准备set给列表的ListModelList设置model.setMultiple(true);

三、在listbox或grid中增加

<custom-attributes org.zkoss.zul.listbox.rod="false" org.zkoss.zul.listbox.rightSelect="false" />

zul名称过长省略显示表达式

<listcell if="${c:length(each.projectDescription) gt 8}" tooltiptext="${each.projectDescription}">
    ${c:substring(each.projectDescription,0,8)}...
</listcell>
<listcell if="${c:length(each.projectDescription) le 8}" tooltiptext="${each.projectDescription}">
    ${each.projectDescription}
</listcell>

通过css简单的实现{*}列表单元格内容过长的省略号效果{*}、强制文本不换行

<listcell label="${each.ntvAddress}" tooltiptext="${each.ntvAddress}" style="text-overflow:ellipsis;word-break: keep-all;" />

当内容超出listheader规定的宽度时,自动附加省略号,
同时通过tooltiptext实现鼠标滑过显示全文。

注意:当超长文本中包含中文,会导致换行,如果希望只显示一行,可添加style word-break: keep-all;

必填字段,添加红色*号标记

必填字段添加红色*号标记,因在textbox后方添加时,会导致textbox框缩小,与其他非必填项的textbox长度不一致
设计改为将*号显示在字段名称后方括号内 name(红色*): 具体更改方案:
原lable字段更改为hbox

<hbox pack="end" align="end" hflex="true">
  <label value="客户编码(" />
  <label value="*" sclass="red" />
  <label value="):" />
</hbox>

 

转载于:https://my.oschina.net/u/163737/blog/668272

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值