小谈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刷新问题

小谈ZK框架技巧_第1张图片

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

必填字段添加红色*号标记,因在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



你可能感兴趣的:(zk,技巧,页面)