Saturday, 29 June 2013

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

Validation using Hibernate Validator

        

In the previous article 11, we have completed the part of uploading image file into the database.  In this article, let us see how we can validate user input. There are different ways in ZK Framework to validate the user input, but here i am going to show a different way using JSR 303 Validation.

Before starting, i  would highly recommend to read this post in my blog for better understanding of the validation framework.

Project Structure

image

Step 1:
First we will see which are the mandatory fields in the user creation page and then we will show indication (*) near the fields. The following fields are mandatory as our sql script.

First Name, Last Name, userAccountNumber, address1, city , state, zipcode , email, userloginid, password  and theme.

First in style.css , we will add one more css class for label as shown here

.flblreq.z-label {
font-size: 12px;
font-weight: bolder;
color: red;
}


Open UserCRUD.zul and add the asterisk symbol near by the above fields as show 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>
<div>
<image content="@bind(vm.myImage)" width="150px"
style="overflow:auto;z-index:999;position:absolute;right:30px;top:56px;width:150px;height:140px;background-color:#ff99cc;" />
<button Label="Add" sclass="mybutton button theme small"
width="40px" upload="true,maxsize=300"
visible="@load(not vm.makeAsReadOnly)"
style="position:absolute;right:115px;top:200px;"
onUpload="@command('upload', upEvent=event)" mold="trendy" />
<button Label="Del" sclass="mybutton button theme small"
visible="@load(not vm.makeAsReadOnly)" width="40px"
style="position:absolute;right:30px;top:200px;"
onClick="@command('removeImage')" mold="trendy" />
</div>
<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>
<hlayout>
<label value="User Account No"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="accountNo"
readonly="@load(vm.makeAsReadOnly)" mold="rounded" hflex="1"
value="@bind(fx.userAccountNumber)" />
</vlayout>
<vlayout>
<hlayout>
<label value="Last Name"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="lastname"
readonly="@load(vm.makeAsReadOnly)" mold="rounded" hflex="1"
value="@bind(fx.lastName)" />
</vlayout>
<vlayout>
<hlayout>
<label value="First Name"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<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>
<hlayout>
<label value="Email"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<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>
<hlayout>
<label value="Address"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<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>
<hlayout>
<label value="City"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<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>
<hlayout>
<label
value="State" sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="State"
readonly="@load(vm.makeAsReadOnly)" hflex="1"
mold="rounded" value="@bind(fx.state)" />
</vlayout>
<vlayout>
<hlayout>
<label
value="ZipCode" sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<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>
<hlayout>
<label value="Login ID"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="loginid" hflex="1"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.userLoginID)" />
</vlayout>
<vlayout>
<hlayout>
<label value="Password"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<textbox id="password" width="80%"
readonly="@load(vm.makeAsReadOnly)" mold="rounded"
value="@bind(fx.password)" />
</vlayout>
<vlayout>
<hlayout>
<label value="Theme"
sclass="flabel" />
<label value="*"
sclass="flblreq" />
</hlayout>
<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 see the output as follows
image

Step 2:
Next we will add the Hibernate validator dependency in our POM File.

	<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.1.Final</version>
</dependency>

Step 3:
Next we will use hibernate built in constraint @NotBlank to add message to the Not null fields in the UserProfile.java POJO as shown here
 


package zkexample.domain;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

import org.hibernate.annotations.NamedQuery;
import org.hibernate.validator.constraints.NotBlank;

@Entity
@Table(name = "userprofile")
@NamedQuery(name = "UserProfile.findUserByUserID", query = "SELECT usr FROM UserProfile as usr WHERE usr.userLoginID = ?")
public class UserProfile implements Serializable {

/**
*
*/
private static final long serialVersionUID = 1L;

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;

@NotBlank(message = "First Name cannot be empty")
private String firstName;

@NotBlank(message = "Last Name cannot be empty")
private String lastName;

private String middleName;

@NotBlank(message = "User Account Number cannot be empty")
private String userAccountNumber;

private String SSN;
@NotBlank(message = "Address1 cannot be empty")
private String address1;

private String address2;

@NotBlank(message = "City cannot be empty")
private String city;

@NotBlank(message = "State cannot be empty")
private String State;

@NotBlank(message = "ZipCode cannot be empty")
private String zipCode;

@NotBlank(message = "Email cannot be empty")
private String email;

@NotBlank(message = "Login Name cannot be empty")
private String userLoginID;

@NotBlank(message = "Password cannot be empty")
private String password;

private Integer system;

@NotBlank(message = "Theme cannot be empty")
private String theme;

@Column(name = "userPhoto")
@Lob
private byte[] userPhoto;

@Temporal(TemporalType.DATE)
private Date DOB;

public long getId() {
return id;
}

public void setId(long id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getMiddleName() {
return middleName;
}

public void setMiddleName(String middleName) {
this.middleName = middleName;
}

public String getUserAccountNumber() {
return userAccountNumber;
}

public void setUserAccountNumber(String userAccountNumber) {
this.userAccountNumber = userAccountNumber;
}

public Date getDOB() {
return DOB;
}

public void setDOB(Date dOB) {
DOB = dOB;
}

public String getSSN() {
return SSN;
}

public void setSSN(String sSN) {
SSN = sSN;
}

public String getAddress1() {
return address1;
}

public void setAddress1(String address1) {
this.address1 = address1;
}

public String getAddress2() {
return address2;
}

public void setAddress2(String address2) {
this.address2 = address2;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getState() {
return State;
}

public void setState(String state) {
State = state;
}

public String getZipCode() {
return zipCode;
}

public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getUserLoginID() {
return userLoginID;
}

public void setUserLoginID(String userLoginID) {
this.userLoginID = userLoginID;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public Integer getSystem() {
return system;
}

public void setSystem(Integer system) {
this.system = system;
}

public String getTheme() {
return theme;
}

public void setTheme(String theme) {
this.theme = theme;
}

public byte[] getUserPhoto() {
return userPhoto;
}

public void setUserPhoto(byte[] userPhoto) {
this.userPhoto = userPhoto;
}

}

Step 4:
Next step to show all these validation message in the list box window on click of save in the USERCRUD.ZUL File. For Simple example, please look at this post.

First we will create java class to hold all the validation message. In the package utilities, create the following validationMessage.java class as shown.
package zkexample.utilities;

public class ValidationMessage {
private String message;

public ValidationMessage(String message) {
this.message = message;
}

public ValidationMessage() {
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}

}

Next we will add our validation message modal window. In the webapp folder, add new zul file called as ValidateWindow.zul
image


<?xml version="1.0" encoding="UTF-8"?>
<zk>
<window id="ValidateWindow" title="Validation" height="auto" width="500px"
border="normal" minimizable="false" mode="modal" maximizable="false"
closable="true" action="hide: slideUp" sclass="mymodal"
apply="org.zkoss.bind.BindComposer" onCancel="@command('closeThis')"
onOK="@command('closeThis')"
viewModel="@id('vm') @init('zkexample.utilities.formValidationVM')">

<listbox vflex="1" sclass="mylist" width="100%" id="test" model="@load(vm.allMessages)">
<listhead sizable="true">
<listheader label="" />
</listhead>
<template name="model" var="p1">
<listitem>
<listcell>
<label value="@load(p1.message)" sclass="flabel" />
</listcell>
</listitem>
</template>
</listbox>
<separator />
<div align="center">
<button label="Close" sclass="mybutton button theme small" mold="trendy" onClick="@command('closeThis')" />
</div>
</window>
</zk>

We will add the VM For the above zul in the package utilities in the name of formValidationVM.java

package zkexample.utilities;
import java.util.List;

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.bind.annotation.ExecutionArgParam;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.select.Selectors;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zul.Window;

public class formValidationVM {

@Wire("#ValidateWindow")
private Window win;

private List<ValidationMessage> allMessages = null;


public List<ValidationMessage> getAllMessages() {
return allMessages;
}

public void setAllMessages(List<ValidationMessage> allMessages) {
this.allMessages = allMessages;
}

@AfterCompose
public void init(@ContextParam(ContextType.VIEW) Component view,@ExecutionArgParam("vList") List<ValidationMessage> vList) {
Selectors.wireComponents(view, this, false);
setAllMessages(vList);
}

@Command
public void closeThis() {
win.detach();
}
}

Step 5:
Next we will add our validation routine to validate the any hibernate POJO Class. In the utilities package, add the following java class

image

ValidatorUtil.java


package zkexample.utilities;

import java.util.HashSet;
import java.util.Set;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;

public class ValidatorUtil {

private final ValidatorFactory factory;

public ValidatorUtil() {
factory = Validation.buildDefaultValidatorFactory();
}

public <T> HashSet<ConstraintViolation<?>> validate(final T instance) {
final Validator validator = factory.getValidator();

final Set<ConstraintViolation<T>> violations = validator.validate(
instance, Default.class);

if (!violations.isEmpty()) {
final Set<ConstraintViolation<?>> constraints = new HashSet<ConstraintViolation<?>>(
violations.size());

for (final ConstraintViolation<?> violation : violations) {
constraints.add(violation);
}

return (HashSet<ConstraintViolation<?>>) constraints;
}
return null;
}

}


Add the following method in MyLib.java  to validate any hibernate POJO Class

public static <T> boolean IsValidBean(final T instance) {

List<ValidationMessage> vList = new ArrayList<ValidationMessage>();

final Set<ConstraintViolation<?>> constraints = new ValidatorUtil()
.validate(instance);
if (constraints == null) {
return true;
}
for (final ConstraintViolation<?> violation : constraints) {
vList.add(new ValidationMessage(violation.getMessage()));
}
if (vList.size() > 0) {
final HashMap<String, Object> map = new HashMap<String, Object>();
map.put("vList", vList);
Executions.createComponents("ValidateWindow.zul",
null, map);
return false;
} else
return true;
}

Step 6:
Finally, we will call the validator routine in the saveThis Method of UserCRUDVM.java


	@Command
public void saveThis(@BindingParam("action") Integer action) {

if (MyLib.IsValidBean(this.selectedRecord) == false) {
return;
}

if (myImage != null) {
byte[] bFile = myImage.getByteData();
this.selectedRecord.setUserPhoto(bFile);
} else
this.selectedRecord.setUserPhoto(null);
CRUDService.Save(this.selectedRecord);
MyLib.showSuccessmessage();
if (action == 0) {
final HashMap<String, Object> map = new HashMap<String, Object>();
map.put("centerArea", centerArea);
centerArea.getChildren().clear();
Executions.createComponents("userList.zul", centerArea, map);
}
if (action == 1) {
this.selectedRecord = new UserProfile();
BindUtils.postNotifyChange(null, null, UserCRUDVM.this,
"selectedRecord");

}
}


Now run the application and click Add New Button and just Click either “Save and Close” or “Save and Add”. You can see all the validation message as shown

image
In the Next part 13, we will see how we can add more constraints and also how we can create our constraints.

You can download the source here.
Don’t forget to check online demo here. Use username as wing and password as wing

        

6 comments:

  1. Estupendo trabajo, quizas puedas incluir la seccion de permisos de usuarios. Gracias
    (he usado google traslater disculpas si algo no esta claro)

    ReplyDelete
  2. Muchas gracias por este tutorial.

    Me sirve para utilizarlo en el proyecto que estoy
    trabajando actualmente.

    Gracias,

    ReplyDelete
  3. Is it possible to make a generic validator is used in each view models in question without having to make a generic zul?

    ReplyDelete
  4. simplesmente perfeito

    ReplyDelete