Small Custom ZK Component based on jquery
Thanks to benbai and gekkio for helping me in the ZK Forum to complete this example.
In this example, we will see how to handle masked input values such as Phone No, zipcode, SSN, TIN, etc.
For your further reference, you can see the following http://www.zkoss.org/zkdemo/effects/form_effect
You can download the jquery plugin from http://digitalbush.com/projects/masked-input-plugin/
Step 1: If you are new to ZK, then setup the Development environment by following this document.
Step 2:
Create a ZK Project and name as MaskedInput
Create a folder called js under the webcontent folder and add the two jquery files as shown.
We need inform ZK that, we are going use this jquery plugin. That can be done using Language add-on file. Under the webcontent folder, create a xml file and name as ZKAddon.xml.
Paste the below code.
<?xml version="1.0" encoding="UTF-8"?>
<language-addon>
<addon-name>myaddon</addon-name>
<language-name>xul/html</language-name>
<javascript src="/js/jquery.maskedinput-1.3.js" />
<javascript src="/js/watermarkinput.js" />
</language-addon>
Next we need to refer this addon file in zk.xml file under the web-inf folder. Update the zk.xml file as follows
<?xml version="1.0" encoding="UTF-8"?>
<!--
Created by ZK Studio
-->
<zk>
<device-config>
<device-type>ajax</device-type>
<timeout-uri>/timeout.zul</timeout-uri><!-- An empty URL can cause the browser to reload the same URL -->
</device-config>
<language-config>
<addon-uri>/ZKAddon.xml</addon-uri>
</language-config>
</zk>
Step 4:
Next we will create component java class file to support our new component. Create a package called “zkexample” and create a class called “MaskedBox” as shown
package zkexample;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Textbox;
public class MaskedBox extends Textbox {
/**
*
*/
private static final long serialVersionUID = 1L;
private String format;
private String waterMark = "";
public String getFormat() {
return format;
}
public void setFormat(String format) {
this.format = format;
}
public String getWaterMark() {
return waterMark;
}
public void setWaterMark(String waterMark) {
this.waterMark = waterMark;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public MaskedBox() {
setMold("rounded");
this.addEventListener(Events.ON_CREATE, new EventListener() {
@Override
public void onEvent(Event event) throws Exception {
String mask;
if (waterMark.equals(""))
mask = "jq(this.getInputNode()).mask('" + format + "');";
else
mask = "jq(this.getInputNode()).mask('" + format
+ "');jq(this.getInputNode()).Watermark('"
+ waterMark + "'," + "'" + "gray" + "'" + ");";
setWidgetListener("onBind", mask);
}
});
}
}
Next we need to update our ZKAddon.xml file about this new component. Here is the updated ZKAddon.xml file
<?xml version="1.0" encoding="UTF-8"?>
<language-addon>
<addon-name>myaddon</addon-name>
<language-name>xul/html</language-name>
<javascript src="/js/jquery.maskedinput-1.3.js" />
<javascript src="/js/watermarkinput.js" />
<component>
<component-name>MaskedBox</component-name>
<component-class>zkexample.MaskedBox
</component-class>
<extends>textbox</extends>
</component>
</language-addon>
That’s all. We are ready use our new component. Update the index.zul as follows and run on tomcat server
<?page title="Auto Generated index.zul"?>
<window title="Masked Input!!" border="normal" width="450px">
<label value="You are using: ${desktop.webApp.version}" />
<separator></separator>
<grid>
<columns>
<column width="20%"></column>
<column></column>
<column></column>
</columns>
<rows>
<row>
<label value="SSN" />
<MaskedBox format="999-99-9999"></MaskedBox>
<label value="Format : 999-99-9999" />
</row>
<row>
<label value="NPI" />
<MaskedBox format="9999999999"></MaskedBox>
<label value="Format : 9999999999" />
</row>
<row>
<label value="DEA" />
<MaskedBox format="aa9999999"></MaskedBox>
<label value="Format : aa9999999" />
</row>
<row>
<label value="EIN" />
<MaskedBox format="99-9999999"></MaskedBox>
<label value="Format : 99-9999999" />
</row>
<row>
<label value="Phone" />
<MaskedBox waterMark="(###) ###-####" format="(999) 999-9999"></MaskedBox>
<label value="Format : (999) 999-9999')" />
</row>
<row>
<label value="ZipCode" />
<MaskedBox format="99999-9999"></MaskedBox>
<label value="format : 99999-9999" />
</row>
<row>
<label value="DOB" />
<MaskedBox waterMark="mm/dd/yyyy" format="99/99/9999"></MaskedBox>
<label value="format : 99/99/9999" />
</row>
<row>
<label value="Phone + Ext" />
<MaskedBox
format="(999) 999-9999? x99999">
</MaskedBox>
<label value="format : (999) 999-9999? x99999" />
</row>
</rows>
</grid>
</window>
Well, if you are start using this in the production, you will have some problems. The problem if you just navigate in the input area without entering any values, then the masking characters will be stored. Let us see how to handle this problem now.
Step 5:
Let us convert this example into MVVM. Under the zkexample package, create a class called person as follows
package zkexample;
public class Person {
private String SSN;
private String NPI;
private String DEA;
private String EIN;
private String Phone;
private String zipCode;
private String DOB;
private String phoneExt;
public String getSSN() {
return SSN;
}
public void setSSN(String sSN) {
SSN = sSN;
}
public String getNPI() {
return NPI;
}
public void setNPI(String nPI) {
NPI = nPI;
}
public String getDEA() {
return DEA;
}
public void setDEA(String dEA) {
DEA = dEA;
}
public String getEIN() {
return EIN;
}
public void setEIN(String eIN) {
EIN = eIN;
}
public String getPhone() {
return Phone;
}
public void setPhone(String phone) {
Phone = phone;
}
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getDOB() {
return DOB;
}
public void setDOB(String dOB) {
DOB = dOB;
}
public String getPhoneExt() {
return phoneExt;
}
public void setPhoneExt(String phoneExt) {
this.phoneExt = phoneExt;
}
}
Now let us modify our index.zul and we will attach View model to this zul file as follows
<?page title="Auto Generated index.zul"?>
<window title="Masked Input!!" border="normal" width="450px"
apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('zkexample.PersonVM')">
<label value="You are using: ${desktop.webApp.version}" />
<separator></separator>
<div
form="@id('fx') @load(vm.selectedRecord) @save(vm.selectedRecord, before='saveThis')">
<grid>
<columns>
<column width="20%"></column>
<column></column>
<column></column>
</columns>
<rows>
<row>
<label value="SSN" />
<MaskedBox format="999-99-9999"
value="@bind(fx.SSN)">
</MaskedBox>
<label value="Format : 999-99-9999" />
</row>
<row>
<label value="NPI" />
<MaskedBox format="9999999999"
value="@bind(fx.NPI)">
</MaskedBox>
<label value="Format : 9999999999" />
</row>
<row>
<label value="DEA" />
<MaskedBox format="aa9999999"
value="@bind(fx.DEA)">
</MaskedBox>
<label value="Format : aa9999999" />
</row>
<row>
<label value="EIN" />
<MaskedBox format="99-9999999"
value="@bind(fx.EIN)">
</MaskedBox>
<label value="Format : 99-9999999" />
</row>
<row>
<label value="Phone" />
<MaskedBox waterMark="(###) ###-####"
value="@bind(fx.phone)" format="(999) 999-9999">
</MaskedBox>
<label value="Format : (999) 999-9999')" />
</row>
<row>
<label value="ZipCode" />
<MaskedBox format="99999-9999"
value="@bind(fx.zipCode)">
</MaskedBox>
<label value="format : 99999-9999" />
</row>
<row>
<label value="DOB" />
<MaskedBox waterMark="mm/dd/yyyy"
format="99/99/9999" value="@bind(fx.DOB)">
</MaskedBox>
<label value="format : 99/99/9999" />
</row>
<row>
<label value="Phone + Ext" />
<MaskedBox format="(999) 999-9999? x99999"
value="@bind(fx.phoneExt)">
</MaskedBox>
<label value="format : (999) 999-9999? x99999" />
</row>
</rows>
</grid>
<button onClick="@command('saveThis')" label="Submit"></button>
</div>
</window>
Under the zkexample package, create PersonVM as follows
package zkexample;
import org.zkoss.bind.annotation.AfterCompose;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.select.Selectors;
public class PersonVM {
private Person selectedRecord;
public Person getSelectedRecord() {
return selectedRecord;
}
public void setSelectedRecord(Person selectedRecord) {
this.selectedRecord = selectedRecord;
}
@AfterCompose
public void initSetup(@ContextParam(ContextType.VIEW) Component view) {
Selectors.wireComponents(view, this, false);
selectedRecord= new Person();
}
@Command
public void saveThis() {
System.out.println("SSN " + selectedRecord.getSSN() );
System.out.println("NPI " + selectedRecord.getNPI() );
System.out.println("DEA " + selectedRecord.getDEA() );
System.out.println("EIN" + selectedRecord.getEIN() );
System.out.println("Phone " + selectedRecord.getPhone() );
System.out.println("Zipcode " + selectedRecord.getZipCode());
System.out.println("DOB" + selectedRecord.getDOB());
System.out.println("Phone Ext" + selectedRecord.getPhoneExt() );
}
}
Now run the application , just using tab, navigate the input area without entering any values and click the submit button.
You can see the following output in the console
SSN ___-__-____
NPI __________
DEA _________
EIN__-_______
Phone (___) ___-____
Zipcode _____-____
DOB__/__/____
Phone Ext(___) ___-____ x_____
Now you understand the Problem ? This can be overcome using ZK Converter concept.
Let us create our own converter as follows
Under zksample package, create class called MaskConverter which implements org.zkoss.bind.Converter
package zkexample;
import org.zkoss.bind.BindContext;
import org.zkoss.bind.Converter;
import org.zkoss.zk.ui.Component;
public class MaskConverter implements Converter {
/**
* The method coerceToUi() is invoked when loading ViewModel's property to
* component and its return type should correspond to bound component
* attribute's value[1]. The coerceToBean() is invoked when saving. If you
* only need to one way conversion, you can leave unused method empty.
*/
public Object coerceToUi(Object val, Component comp, BindContext ctx) {
// do nothing
return val;
}
public Object coerceToBean(Object val, Component comp, BindContext ctx) {
/*
* Here we will check only masking characters are present, if so, then
* return null
*/
final String propValue = (String) val;
if (IsEmptyByMask(propValue))
return null;
else
return val;
}
public boolean IsEmptyByMask(String s1) {
if (isEmpty(s1) == false) {
s1 = s1.replaceAll("_", "").replace("(", "").replace(")", "")
.replace("-", "").replace(" ", "").replace("/", "").trim();
if (isEmpty(s1))
return true;
else
return false;
}
return true;
}
public static boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}
}
Basically, this converter removes the masking character before saving to domain object. Now let us apply this convert to our MaskedBox Component. Here is the updated index.zul where we applied our converter.
<?page title="Auto Generated index.zul"?>
<window title="Masked Input!!" border="normal" width="450px"
apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('zkexample.PersonVM')">
<label value="You are using: ${desktop.webApp.version}" />
<separator></separator>
<div
form="@id('fx') @load(vm.selectedRecord) @save(vm.selectedRecord, before='saveThis')">
<grid>
<columns>
<column width="20%"></column>
<column></column>
<column></column>
</columns>
<rows>
<row>
<label value="SSN" />
<MaskedBox format="999-99-9999"
value="@bind(fx.SSN) @converter('zkexample.MaskConverter')">
</MaskedBox>
<label value="Format : 999-99-9999" />
</row>
<row>
<label value="NPI" />
<MaskedBox format="9999999999"
value="@bind(fx.NPI) @converter('zkexample.MaskConverter')">
</MaskedBox>
<label value="Format : 9999999999" />
</row>
<row>
<label value="DEA" />
<MaskedBox format="aa9999999"
value="@bind(fx.DEA) @converter('zkexample.MaskConverter')">
</MaskedBox>
<label value="Format : aa9999999" />
</row>
<row>
<label value="EIN" />
<MaskedBox format="99-9999999"
value="@bind(fx.EIN) @converter('zkexample.MaskConverter')">
</MaskedBox>
<label value="Format : 99-9999999" />
</row>
<row>
<label value="Phone" />
<MaskedBox waterMark="(###) ###-####"
value="@bind(fx.phone) @converter('zkexample.MaskConverter')"
format="(999) 999-9999">
</MaskedBox>
<label value="Format : (999) 999-9999')" />
</row>
<row>
<label value="ZipCode" />
<MaskedBox format="99999-9999"
value="@bind(fx.zipCode) @converter('zkexample.MaskConverter')">
</MaskedBox>
<label value="format : 99999-9999" />
</row>
<row>
<label value="DOB" />
<MaskedBox waterMark="mm/dd/yyyy"
format="99/99/9999"
value="@bind(fx.DOB) @converter('zkexample.MaskConverter')">
</MaskedBox>
<label value="format : 99/99/9999" />
</row>
<row>
<label value="Phone + Ext" />
<MaskedBox format="(999) 999-9999? x99999"
value="@bind(fx.phoneExt) @converter('zkexample.MaskConverter')">
</MaskedBox>
<label value="format : (999) 999-9999? x99999" />
</row>
</rows>
</grid>
<button onClick="@command('saveThis')" label="Submit"></button>
</div>
</window>
Now run the project and the following is output.
SSN null
NPI null
DEA null
EINnull
Phone null
Zipcode null
DOBnull
Phone Extnull
You can download the source here.