Validator"
m ((via JWB)) |
|||
Line 36: | Line 36: | ||
Following is a comparison table for above two saving syntax: | Following is a comparison table for above two saving syntax: | ||
− | {| | + | {| class='wikitable' | width="100%" |
|+ | |+ | ||
! width="10%" | | ! width="10%" | | ||
Line 654: | Line 654: | ||
=Version History= | =Version History= | ||
{{LastUpdated}} | {{LastUpdated}} | ||
− | {| | + | {| class='wikitable' | width="100%" |
! Version !! Date !! Content | ! Version !! Date !! Content | ||
|- | |- |
Revision as of 02:04, 12 January 2022
This article is out of date, please refer to zk-mvvm-book/8.0/data_binding/validator for more up to date information.
User Input Validation
User input validation is an indispensable function of a web application. ZK's validator can help developers to accomplish this task. The validator is a reusable element that performs validation and stores validation messages into a validation message holder. When it's applied, it's invoked before saving data to binding target (ViewModel or middle object). When you bind a component's attribute to a validator, binder will use it to validate attribute's value automatically before saving to a ViewModel or to a middle object. If validation fails, ViewModel's (or middle object's) properties will be unchanged.
We usually provide custom validator with ViewModel's property.
public void class MyViewModel{
private Validator emailValidator = new EmailValidator();
public Validator getEmailValidator(){
return emailValidator;
}
}
Then it can be referenced on a ZUL by ViewModel's properties or a string literal with validator's full-qualified class name.
<textbox value="@save(vm.account.email) @validator(vm.emailValidator)"/>
<textbox value="@save(vm.account.email) @validator('org.foo.EmailValidator')"/>
- Binder use vm.emailValidtor to validate textbox's value before saving to vm.account.email. If validation fails, ViewModel's property: vm.account.email will keep unchanged.
<textbox value="@save(vm.account.email, before='save') @validator(vm.emailValidator)"/>
- Binder use vm.emailValidtor to validate textbox's value before executing Command 'save'.
Following is a comparison table for above two saving syntax:
@bind(vm.account.email) @validator(vm.emailValidator) | @load(vm.account.email) @save(vm.account.email, before= 'save') @validator(vm.emailValidator) | |
a component's attribute related event fires (e.g. onChange for value) | Before executing a command | |
|
| |
Not save data to ViewModel | Not save data to ViewModel & Not execute the command |
In form binding, you can apply a validator on "form" attribute or an input component. If you apply on both places, you can double validate user input. The first time is when saving data to middle object, this can give a user immediate response after input. The second time is when saving to a ViewModel upon a command, this can validate user input even he doesn't input anything and submit empty data directly.
...
<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.formValidator)">
<grid hflex="true" >
<columns>
<column width="120px"/>
<column/>
</columns>
<rows>
<row>Id
<hlayout>
<label value="@load(fx.id)"/>
</hlayout> </row>
<row>Description <textbox value="@bind(fx.description)"/></row>
<row>Quantity
<intbox value="@bind(fx.quantity) @validator(vm.quantityValidator)"/>
</row>
<row>Price
<doublebox value="@bind(fx.price) @validator(vm.priceValidator)" format="###,##0.00" />
</row>
</rows>
</grid>
</groupbox>
- Line 7: ZK will invoke vm.formValidator before executing command 'saveOrder'.
- Line 20: ZK will validate input value when onChange event fires on intbox (when a user blur the focus).
- Line 7, 20, 23: You can apply validators on form attribute and each input component respectively to perform double validation.
Validation Message Holder
Databinding provides a standard mechanism to store and display validation message. After performing validation, the validator might store a validation message in validation message holder. To use it, you have to initialize it by specifying its id in validationMessages attribute with the @id . Then you can reference it with this id.
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('foo.MyViewModel')"
validationMessages="@id('vmsgs')">
</window>
Display a Message
Validation message holder stores messages like a map with key-value pairs. The value is the validation messages that generated by validators after the validation is performed, and the default key is the binding source component (object itself, no id) that bound to validators. To retrieve and display validation message in a ZUL, you can bind a display component, i.e. label, to validation message holders with a component as the key.
The binder will reload validation messages each time after validation.
Display validation message with default key
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('foo.MyViewModel')"
validationMessages="@id('vmsgs')">
<hlayout>Value1:
<textbox id="tb1" value="@bind(vm.value1) @validator(vm.validator1)" />
<label id="m1" value="@bind(vmsgs[tb1])"/>
</hlayout>
<hlayout>Value2:
<intbox id="tb2" value="@bind(vm.value2) @validator(vm.validator2)" />
<label id="m2" value="@bind(vmsgs[self.previousSibling])"/>
</hlayout>
</window>
- You can use component's id to reference a component object. (line 5)
- You can use component's property to reference a component object with relative position that eliminates giving component an id . (line 9)
Display a Message of a Self-defined Key
You can display the first validation message which is bound to a self-defined key of a validator that extends AbstractValidator. Display validation message with self-defined key
<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>
- You can use self-defined key to display a certain message. (line 2)
Please read Self-defined Validation Message Key for the detail about the validator.
Display Multiple Messages
- Available for ZK:
Since 6.0.1
A validator can set multiple messages for a component or a self-defined key. You can get all messages from Validation Message Holder's special property texts .[1]
Display multiple messages of a form validator
<div id="formdiv" form="... @validator('fooValidator')">
...
</div>
<grid id="msggrid" model="@bind(vmsgs.texts[formdiv])" visible="@bind(not empty vmsgs.texts[formdiv])">
<template name="model" var="msg">
<row>
<label value="@bind(msg)" />
</row>
</template>
</grid>
- Using a grid to display multiple messages. (line 4)
You can also get all messages of the Validation Message Holder with syntax@bind(vmsgs.texts) and get messages of a self-defined key with syntax @bind(vmsgs.texts['a_self_defined_key']).
- ↑ In EL, vmsgs.texts is the same as vmsgs['texts'], so you should avoid using texts as your self-defined key.
Implement a Validator
You can create custom validators upon your application's requirement by implementing Validator interface or inheriting AbstractValidator for convenience.
Property Validator
Single Property Validation
We usually need to validate one property at one time, the simplest way is to inherit AbstractValidator and override validate() to implement a custom validation rule. If validation fails, use addInvalidMessage() to store validation messages to be displayed. You have to pass validator bound component object as a key to retrieve this message like we mention in section Validation Message Holder.
<intbox value="@save(vm.quantity) @validator(vm.rangeValidator)"/>
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);
}
}
};
}
- Line 4: We can get the user input data from validator2's binding source component by ctx.getProperty().getValue() .
- Line 6: addInvalidMessage() will add message into validation message holder with default key.
Dependent Property Validation
We sometimes need another property's value to validate the current property. We have to save those values that have dependency among them upon the same Command (use @save(vm.p, before='command' ) , thus binder will pass those properties that are saved upon the same command to ValidationContext and we can retrieve them with property's key to process.
Assume shipping date must be at least 3 days later than creation date.
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('eg.ValidationMessagesVM')"
validationMessages = "@id('vmsgs')">
<grid hflex="true" >
<columns>
<column width="120px"/>
<column/>
</columns>
<rows>
<!-- other input fields -->
<row>Creation Date
<hlayout>
<datebox id="cdBox"
value="@load(vm.selected.creationDate) @save(vm.selected.creationDate, before='saveOrder')
@validator(vm.creationDateValidator)"/>
<label value="@load(vmsgs[cdBox])" sclass="red" />
</hlayout>
</row>
<row>Shipping Date
<hlayout>
<datebox id="sdBox"
value="@load(vm.selected.shippingDate) @save(vm.selected.shippingDate, before='saveOrder')
@validator(vm.shippingDateValidator)"/>
<label value="@load(vmsgs[sdBox])" sclass="red" />
</hlayout>
</row>
</rows>
</grid>
</window>
- As we save vm.selected.creationDate and vm.selected.shippingDate before Command 'saveOrder', they'll be passed into validation context.
Our custom shipping date validator should get the creation date to compare.
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;
}
}
- Shipping date is the main property to be validated. The way to retrieve other properties is different the main one. (line 5)
Dependent Property Validator in Form Binding
If you want to validate a property according to another property's value in the form binding, you have to apply a validator in the form attribute instead of an input component that bound to a middle object's property. Then you can get all properties from validation context.
The following is the example to demonstrate the same shipping date validator but it's used in form binding.
Using validator in form binding
...
<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>
- Line 9: Apply a validator in form binding, not in individual input component that bound to middle object's property.
Validator for form binding
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");
}
}
};
}
- Line 4~5: The value main property is a Form when applying to a form binding. We should retrieve all properties with its key. Since 6.0.1, you could get values of the bean by ValidationContext.getProperties(Object)
public Validator getShippingDateValidator() {
return new AbstractValidator(){
public void validate(ValidationContext ctx) {
//base of main property is the bean, you can get all properties of the bean
Map<String,Property> beanProps = ctx.getProperties(ctx.getProperty().getBase());
Date shipping = (Date)beanProps.get("shippingDate").getValue();
Date creation = (Date)beanProps.get("creationDate").getValue();
//....
}
};
}
Pass and Retrieve Parameters
We can pass one or more parameters by EL expression to a validator. The parameters are written in key-value pairs format inside @validator() annotation.
Passing a constant value in a ZUL
<textbox id="keywordBox" value="@save(vm.keyword) @validator(vm.maxLengthValidator, length=3)"/>
Retrieving parameters in a validator
public class MaxLengthValidator implements Validator {
public void validate(ValidationContext ctx) {
Number maxLength = (Number)ctx.getBindContext().getValidatorArg("length");
if (ctx.getProperty().getValue() instanceof String){
String value = (String)ctx.getProperty().getValue();
if (value.length() > maxLength.longValue()){
ctx.setInvalid();
}
}else{
ctx.setInvalid();
}
}
}
- The parameter's name "length" is user defined.
Passing object in a ZUL
<combobox id="upperBoundBox" >
<comboitem label="90" value="90"/>
<comboitem label="80" value="80"/>
<comboitem label="70" value="70"/>
<comboitem label="60" value="60"/>
<comboitem label="50" value="50"/>
</combobox>
<intbox value="@save(vm.quantity) @validator(vm.upperBoundValidator, upper=upperBoundBox.selectedItem.value)"/>
- Pass a value from another component's attribute.
Self-defined Validation Message Key
You might want to set the key of a validation message on your own, especially when you use single validator to validate multiple fields. As all validation messages generated by one validator for a component have the same default key (the component object itself), in order to retrieve and display a validation message individually, you have to set different key for each message.
Assume you use only one validator in a form binding to validate all fields.
public Validator getFormValidator(){
return new AbstractValidator() {
public void validate(ValidationContext ctx) {
String val = (String)ctx.getProperties("value1")[0].getValue();
if(invalidCondition01(val)){
addInvalidMessage(ctx, "fkey1", "value1 error");
}
val = (String)ctx.getProperties("value2")[0].getValue();
if(invalidCondition02(val)){
addInvalidMessage(ctx, "fkey2", "value2 error");
}
val = (String)ctx.getProperties("value3")[0].getValue();
if(invalidCondition03(val)){
addInvalidMessage(ctx, "fkey3", "value3 error");
}
}
};
}
- Because validation messages are stored in the same context, you should use different keys for different messages.
Display validation message aside each component
<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>
Validation in Non-Conditional Property Binding
The execution of a non-conditional property binding is separated from execution of command. If an event triggers both property saving and command execution, they don't block each other.
For example:
<textbox value="@bind(vm.value) @validator(vm.myValidator)" onChange="@command('submit')"/>
- When onChange event fires, binder will perform validation before saving vm.value. No matter validation fails or not, it will execute command "submit". Validation failure only stops binder saving textbox's value into vm.value.
For another opposite example:
<textbox id="t1" value="@bind(vm.foo) " onChange="@command('submit')"/>
<textbox id="t2" value="@save(vm.bar, before='submit') @validator(vm.myValidator)"/>
- When onChange event fires, if t2's value fails in validation, it will stop the binder to execute command "submit". But the binder will still save t1's value to vm.foo.
Register Application Level Validators
- Available for ZK:
Since 6.0.1
You can register application level validators[1] by setting library-property(org.zkoss.bind.appValidators) in zk.xml.
<library-property>
<name>org.zkoss.bind.appValidators</name>
<value>foo=my.FooValidator,bar=my.BarValidator</value>
</library-property>
Then use them by validator name.
<textbox value="@bind(vm.name) @validator('foo')"/>
<textbox value="@bind(vm.value) @validator('bar')"/>
- ↑ Application level validator only has one instance and is shared between all binders.
Use Built-in Validator
ZK provides some built-in validators (see All Known Implementing Classes at Validator) that you can use it directly with syntax
@validator('validatorName')
The validatorName should be replaced with built-in validator's name which we list below:
- beanValidator
- formBeanValidator
Bean Validator
This validator integrates Java Bean Validation ([1] ) framework that defines a metadata model and API [2] to validate JavaBeans. Developers can specify the constraints of a bean's property by Java annotations and validate against the bean by API. By using ZK's bean validator, you only have to specify constraints on bean's properties then bean validator will invoke API to validate for you.
Prepare to Use JSR 303
Required Jars
A known implementation of JSR 303 is Hibernate Validator. The following is a sample dependency list in pom.xml for using Hibernate Validator:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.0.2.GA</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
Hibernate Validator assumes the use of log4j to log information. You can use any log4j implementation you prefer.
Setup
Under project classpath, you need to prepare
- log4j.properties
- META-INF/validation.xml
Here are examples of minimal settings in these files:
log4j.properties
log4j.rootLogger=info, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n
META-INF/validation.xml
<?xml version="1.0" encoding="UTF-8"?>
<validation-config
xmlns="http://jboss.org/xml/ns/javax/validation/configuration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://jboss.org/xml/ns/javax/validation/configuration">
<default-provider>org.hibernate.validator.HibernateValidator</default-provider>
<message-interpolator>org.hibernate.validator.engine.ResourceBundleMessageInterpolator</message-interpolator>
<traversable-resolver>org.hibernate.validator.engine.resolver.DefaultTraversableResolver</traversable-resolver>
<constraint-validator-factory>org.hibernate.validator.engine.ConstraintValidatorFactoryImpl</constraint-validator-factory>
</validation-config>
You can customize the setting depending on your requirement.
Usage
Add constraint in JavaBean's property with Java annotation.
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;
}
}
Use this validator with its name beanValidator with syntax:
@validator('beanValidator')
<window id="win" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init(foo.MyViewModel)"
validationMessages="@id('vmsgs')">
<textbox id="tb" value="@bind(vm.user.lastName) @validator('beanValidator')" />
<label value="@load(vmsgs[tb])"/>
</window>
Validate Form's Property Loaded from a Bean
- Available for ZK:
Since 6.0.1
It also supports to validate a form object's property loaded from a bean [3].
<grid form="@id('fx') @load(vm.user) @save(vm.user,after='save')">
<textbox id="tb" value="@bind(fx.lastName) @validator('beanValidator')"/>
<label value="@load(vmsgs[tb])"/>
</grid>
- ↑ http://jcp.org/en/jsr/detail?id=303 JSR 303
- ↑ http://jackson.codehaus.org/javadoc/bean-validation-api/1.0/index.html
- ↑ It validates the last loaded bean of the form, which means it doesn't support to validate a form that didn't loaded from a bean yet.
Form Bean Validator
- Available for ZK:
Since 6.0.1
Like Bean Validator, this validator also integrates JavaBean Validation and validates a bean's all saving properties. For the configuration and JavaBean usage, please refer to #Prepare_to_Use_JSR_303
Usage
Use this validator with the name formBeanValidator and set a unique[1] prefix key by prefix argument of validator. When any property of the bean is invalid, it puts the invalid message to validation message holder with key prefix+propertyName.
Syntax:
@validator('formBeanValidator',prefix='p_')
<window id="win" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init(foo.MyViewModel)"
validationMessages="@id('vmsgs')">
<grid width="600px" form="@id('fx') @load(vm.user) @save(vm.user,after='save')
@validator('formBeanValidator',prefix='p_')">
<textbox value="@bind(fx.firstName)"/>
<label value="@load(vmsgs['p_firstName'])"/>
</grid>
<!--more components-->
</window>
Limitation
This validator cannot validate a form object's nested property like fx.firstProp.secondProp. It can only validate a property like fx.firstProp.
- ↑ The prefix has to be unique in the same binder.
Version History
Version | Date | Content |
---|---|---|
6.0.0 | February 2012 | The MVVM was introduced. |