ZK MVVM Form Binding CRUD with Spring and Hibernate - Part 10

jQuery Masked Input Plugin

        


In the Previous part 9, we have fixed some bugs in changing the theme after user login. In this article, we will focus on the field validation. For the fields SSN, DOB and zipcode, we will apply masking stuff, so then, end user cannot add junk values into these fields.

As s standard, the watermark and masking features (also called input prompt) guide users in filling out an input with the specified format.
ZK's strong integration of jQuery allows developers to leverage the vast jQuery libraries to achieve the desired effects.

In this article, the masked input and watermark input plugins are used. I have already published a step by step article how to create Masked Input component using jquery Plugin. Please refer here before continue the rest of the article.

Project Structure.
image

image

image

Step 1:
As per this article, let us add the jquery plugins in our project. Create a folder called “js” under the webapp and add the jquery files as shown.
image
We need to inform ZK that, we are going use this jquery plugin. That can be done by adding reference in the using Language add-on file.
We have already having our own ZKAddon.xml file. So in that , let us add the reference of this jquery plugins. Here is the modified full code for ZKAddon.xml file.

<?xml version="1.0" encoding="UTF-8"?>
<language-addon>
<addon-name>myaddon</addon-name>
<language-name>xul/html</language-name>
<stylesheet href="/css/login.css" type="text/css" />
<stylesheet href="/css/style.css" type="text/css" />
<javascript src="/js/jquery.maskedinput-1.3.js" />
<javascript src="/js/watermarkinput.js" />
</language-addon>


Step 2:
Next we will create our own component called as “MaskedBox”. First let us create the java class to support our new component. In the utilities package, create a class “MaskedBox” as shown.
image


package zkexample.utilities;

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>
<stylesheet href="/css/login.css" type="text/css" />
<stylesheet href="/css/style.css" type="text/css" />
<javascript src="/js/jquery.maskedinput-1.3.js" />
<javascript src="/js/watermarkinput.js" />

<component>
<component-name>MaskedBox</component-name>
<component-class>zkexample.utilities.MaskedBox
</component-class>
<extends>textbox</extends>
</component>

</language-addon>

Step 3:
Next we need to write converter to overcome the problem of masking characters stored when there is no input. For more details about this problem, Please refer here.
Let us create class called “MaskConverter” in the utilities package which implements org.zkoss.bind.Converter. Basically, this converter removes the masking character before saving to domain object.
image


package zkexample.utilities;

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("x", "")
.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;
}
}


The above converter will take care of all string data types. Similarly, we need to write converter to take care of Date type fields. So let us create another converter called MyDateFormatConverter in the package utilities
image


package zkexample.utilities;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

import org.zkoss.bind.BindContext;
import org.zkoss.bind.Converter;
import org.zkoss.zk.ui.Component;

public class MyDateFormatConverter implements Converter {
/**
* Convert Date to String.
*
* @param val
* date to be converted
* @param comp
* associated component
* @param ctx
* bind context for associate Binding and extra parameter (e.g.
* format)
* @return the converted String
*/

/**
* 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.
*/

private static SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");

public Object coerceToUi(Object val, Component comp, BindContext ctx) {
final Date date = (Date) val;
return date == null ? null : sdf.format(date);
}

/**
* Convert String to Date.
*
* @param val
* date in string form
* @param comp
* associated component
* @param ctx
* bind context for associate Binding and extra parameter (e.g.
* format)
* @return the converted Date
*/
public Object coerceToBean(Object val, Component comp, BindContext ctx) {
final String date = (String) val;
sdf.setLenient(false);
try {
return date == null ? null : sdf.parse(date);
} catch (ParseException e) {
comp.invalidate();
return null;

}
}

}


Step 4:
Now we are ready to use our new component. In the UserCRUD.zul, we will remove the textbox component for SSN, DOB and Zipcode and add our new component MaskedBox as shown here.


<?page title="" contentType="text/html;charset=UTF-8"?>
<zk>

<window id="userCRUD" border="none"
apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('zkexample.zkoss.UserCRUDVM')">
<div
form="@id('fx') @load(vm.selectedRecord) @save(vm.selectedRecord, before='saveThis')">

<div width="96%">
<span width="100%">
<div sclass="sectionTitle">
<separator />
<label id="lcaption" sclass="sectionTitleLabel"
value="Add/Edit Users" />
<label id="readonly" sclass="sectionTitleLabel"
value="(ReadOnly)" visible="@load(vm.makeAsReadOnly)" />
<image
src="@load(fxStatus.dirty?'images/unsaved32x32.png':'')" />

</div>
</span>
<div id="buttonsDiv"
style="float:right;margin-top:6px;">
<button label="Save and Close" mold="trendy"
visible="@load(not vm.makeAsReadOnly)"
onClick="@command('saveThis', action=0)"
sclass="mybutton button theme small">
</button>
<button label="Save and Add" mold="trendy"
visible="@load(not vm.makeAsReadOnly)"
onClick="@command('saveThis', action=1)"
sclass="mybutton button theme small">
</button>
<button
label="@load(vm.makeAsReadOnly ?'Close':'Cancel')" mold="trendy"
onClick="@command('cancel')"
sclass="mybutton button theme small">
</button>
</div>
<div style="clear: both;"></div>
<div sclass="sectionSeperator"></div>
</div>

<span width="100%">
<div sclass="sectionTitle">
<separator />
<label value="Personel Information"
sclass="sectionTitleLabel" />
<separator />
</div>
</span>

<panel width="95%" sclass="sectionPanel">
<panelchildren>
<separator />
<grid sclass="vgrid">
<columns>
<column hflex="10%"></column>
<column hflex="10%"></column>
<column hflex="10%"></column>
<column hflex="10%"></column>
</columns>
<rows>
<row>
<vlayout>
<label value="User Account No"
sclass="flabel" />
<textbox id="accountNo"
readonly="@load(vm.makeAsReadOnly)" mold="rounded" hflex="1"
value="@bind(fx.userAccountNumber)" />
</vlayout>
<vlayout>
<label value="Last Name"
sclass="flabel" />
<textbox id="lastname"
readonly="@load(vm.makeAsReadOnly)" mold="rounded" hflex="1"
value="@bind(fx.lastName)" />
</vlayout>
<vlayout>
<label value="First Name"
sclass="flabel" />
<textbox id="firstname"
readonly="@load(vm.makeAsReadOnly)" mold="rounded" hflex="1"
value="@bind(fx.firstName)" />
</vlayout>
<vlayout>
<label value="Middle Name"
sclass="flabel" />
<textbox id="middleName"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.middleName)" />
</vlayout>

</row>
<row>

<vlayout>
<label value="Email"
sclass="flabel" />
<textbox id="email" hflex="1"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.email)" />
</vlayout>
<cell colspan="2">
<hbox>
<vlayout>
<label value="SSN"
sclass="flabel" />
<MaskedBox
format="999-99-9999" width="115%"
readonly="@load(vm.makeAsReadOnly)"
value="@bind(fx.SSN) @converter('zkexample.utilities.MaskConverter')">
</MaskedBox>
</vlayout>
<vlayout>
<label value="DOB"
sclass="flabel" />
<MaskedBox
waterMark="mm/dd/yyyy" format="99/99/9999"
readonly="@load(vm.makeAsReadOnly)" width="100%"
value="@bind(fx.DOB) @converter('zkexample.utilities.MyDateFormatConverter')">
</MaskedBox>
</vlayout>
</hbox>
</cell>
</row>
</rows>
</grid>
</panelchildren>
</panel>
<separator />

<separator />
<span width="100%">
<div sclass="sectionTitle">
<separator />
<label value="Address Information"
sclass="sectionTitleLabel" />
<separator />
</div>
</span>
<panel width="95%" sclass="sectionPanel">
<panelchildren>
<grid sclass="vgrid">
<columns>
<column hflex="1"></column>
<column hflex="1"></column>
<column hflex="1"></column>
</columns>
<rows>
<row>
<cell colspan="2">
<vlayout>
<label value="Address"
sclass="flabel" />
<textbox id="address1" hflex="2"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.address1)" />
</vlayout>
</cell>
</row>
<row>
<cell colspan="2">
<vlayout>
<textbox id="address2" hflex="2"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.address2)" />
</vlayout>
</cell>
</row>
<row>
<vlayout>
<label value="City" sclass="flabel" />
<textbox id="City" hflex="1"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.city)" />
</vlayout>
<vlayout>
<grid>
<columns>
<column width="60%"></column>
<column></column>
</columns>
<rows>
<row>
<vlayout>
<label value="State"
sclass="flabel" />
<textbox id="State"
readonly="@load(vm.makeAsReadOnly)" hflex="1"
mold="rounded" value="@bind(fx.state)" />
</vlayout>
<vlayout>
<label
value="ZipCode" sclass="flabel" />

<MaskedBox
format="99999-9999" hflex="1"
readonly="@load(vm.makeAsReadOnly)"
value="@bind(fx.zipCode) @converter('zkexample.utilities.MaskConverter')">
</MaskedBox>

</vlayout>
</row>
</rows>
</grid>
</vlayout>
</row>
</rows>
</grid>
</panelchildren>
</panel>
<separator />
<span width="100%">
<div sclass="sectionTitle">
<separator />
<label value="Login information"
sclass="sectionTitleLabel" />
</div>
</span>
<panel width="95%" sclass="sectionPanel">
<panelchildren>
<separator />
<grid sclass="vgrid">
<columns>
<column width="20%"></column>
<column width="26%"></column>
</columns>
<rows>
<row>
<vlayout>
<label value="Login ID"
sclass="flabel" />
<textbox id="loginid" hflex="1"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.userLoginID)" />
</vlayout>
<vlayout>
<label value="Password"
sclass="flabel" />
<textbox id="password" width="80%"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.password)" />
</vlayout>
<vlayout>
<label value="Theme"
sclass="flabel" />
<combobox model="@load(vm.themes)"
width="30%" mold="rounded" readonly="@load(vm.makeAsReadOnly)"
selectedItem="@bind(fx.theme)" value="@bind(fx.theme)" />
</vlayout>
</row>
</rows>
</grid>
</panelchildren>
</panel>
</div>
</window>
</zk>

Now you can run the project and add new user and check how our new component works.
Next Part 11, we will see how to upload a image file and store in the database.

You can download the source here.

Don’t forget to check online demo here. Use username as wing and password as wing