ZK是一套以 AJAX/XUL/Java 爲基礎的網頁應用程序開發框架,用於豐富網頁應用程序的使用界面。最大的好處是,在設計AJAX網絡應用程序時,輕鬆簡便的操做就像設計桌面程序同樣。 ZK包含了一個以AJAX爲基礎、事件驅動(event-driven)、高互動性的引擎,同時還提供了豐富多樣、可重複使用的XUL與HTML組件,以及以 XML 爲基礎的使用界面設計語言 ZK User-interfaces Markup Language (ZUML)。這是百度給的介紹。javascript
然而做爲一個國外開源框架,它給個人感受就是不用寫前端javascript,瀏覽器與服務器的交互次數變多,服務器壓力變大。因此,使用該框架者應該慎重綜合考慮。不要爲了一時的偷懶毀掉整個項目。下面我就將羅列出我開發過程當中積累的一些開發技巧、要點、注意事項,以及ZK開發的方法。css
Ctrl頁面定義頁籤頭和頁籤容器變量:
private Tabs tabs;
private Tabpanels tabpanels;前端
而後在初始化afterCompose方法中初始化上面2個變量java
public void afterCompose(@ContextParam(ContextType.VIEW) Component view) { tabs = (Tabs) view.getParent().getParent().getParent().query("tabs"); tabpanels = (Tabpanels) view.getParent().getParent(); ... }
Ctrl類中其餘方法觸發打開新頁籤web
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爲顯示,構造通用方法,供系通通一使用緩存
// 在對應的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> ......
必填字段添加紅色*號標記,因在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的默認選中(默認選擇某個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);的使用!
介紹樹形結構的實現:
...... <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支持兩種機制的Validation:
constraint前端驗證:在輸入組件的constraint屬性指定驗證規則,進行簡單的驗證;
集成JSR30三、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使用方式
直接屬性驗證(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(fkey一、fkey二、fkey3)是能夠本身在java代碼本身定義(詳見下面java代碼註釋掉的部分),默認key爲驗證組件的id(t4一、t4二、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
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>
一、事件傳值,事件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()); }
二、擴展參數方式傳值,注意標籤中的"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()); }
一、在指定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); }
二、後臺得到頁面選中的checkbox
在容器id爲subSendTypesGroupBox中得到選中的checkbox,返回一個checkbox的集合。
subSendTypesGroupBox.queryAll("checkbox[checked=true]"))
當ZK組件默認樣式不知足需求時,咱們能夠擴展其css定製展示形式,主要有三種方式:
須要全局配置以下屬性,不然總記錄數大了會有性能問題
<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>
要爲grid或listbox列表的每行記錄增長全選checkbox,需如下三部:
1、zul中設置屬性
<listbox multiple="true" checkmark="true">
multiple爲false時每行前面出現單選按鈕radio
2、ctrl中對準備set給列表的ListModelList設置model.setMultiple(true);
3、在listbox或grid中增長
<custom-attributes org.zkoss.zul.listbox.rod="false" org.zkoss.zul.listbox.rightSelect="false" />
<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>