Validator"
Dennischen (talk | contribs) (→Usage) |
m ((via JWB)) |
||
(48 intermediate revisions by 8 users not shown) | |||
Line 1: | Line 1: | ||
{{ZKDevelopersReferencePageHeader}} | {{ZKDevelopersReferencePageHeader}} | ||
− | + | {{Deprecated | url=[http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/validator.html zk-mvvm-book/8.0/data_binding/validator]|}} | |
= User Input Validation = | = 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. | + | 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. | We usually provide custom validator with ViewModel's property. | ||
Line 12: | Line 12: | ||
public void class MyViewModel{ | public void class MyViewModel{ | ||
private Validator emailValidator = new EmailValidator(); | private Validator emailValidator = new EmailValidator(); | ||
+ | |||
public Validator getEmailValidator(){ | public Validator getEmailValidator(){ | ||
return emailValidator; | return emailValidator; | ||
Line 19: | Line 20: | ||
</source> | </source> | ||
− | Then it can be referenced on a ZUL. | + | Then it can be referenced on a ZUL by '''ViewModel's properties''' or a '''string literal with validator's full-qualified class name'''. |
<source lang="xml"> | <source lang="xml"> | ||
<textbox value="@save(vm.account.email) @validator(vm.emailValidator)"/> | <textbox value="@save(vm.account.email) @validator(vm.emailValidator)"/> | ||
− | <textbox value="@save(vm.account.email) @validator('foo.EmailValidator')"/> | + | <textbox value="@save(vm.account.email) @validator('org.foo.EmailValidator')"/> |
</source> | </source> | ||
* 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. | * 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. | ||
Line 33: | Line 34: | ||
* Binder use vm.emailValidtor to validate textbox's value before executing Command 'save'. | * Binder use vm.emailValidtor to validate textbox's value before executing Command 'save'. | ||
− | Following is a comparison table | + | Following is a comparison table for above two saving syntax: |
− | {| | + | {| class='wikitable' | width="100%" |
|+ | |+ | ||
− | ! | + | ! width="10%" | |
+ | ! width="45%"| <center>Property Binding</center> | ||
+ | ! width="45%"|<center>Save Before Command</center> | ||
|- | |- | ||
! <center>Syntax Example</center> | ! <center>Syntax Example</center> | ||
− | | < | + | | <code>@bind(vm.account.email) @validator(vm.emailValidator)</code> |
+ | || <code>@load(vm.account.email) @save(vm.account.email, before= 'save') @validator(vm.emailValidator)</code> | ||
|- | |- | ||
! <center>Save When</center> | ! <center>Save When</center> | ||
− | | a component's attribute related event fires (e.g. onChange for value) || Before executing a command | + | | a component's attribute related event fires (e.g. onChange for value) || Before executing a command |
− | |||
− | |||
− | |||
|- | |- | ||
− | ! <center> | + | ! <center>Validation Timing</center> |
− | |||
− | |||
− | |||
| | | | ||
+ | * Immediately validate for single field | ||
* No validation when executing a command | * No validation when executing a command | ||
− | * | + | || |
− | + | * Batch validate all fields when triggering specified command | |
* No immediate validation of single fields after a user input | * No immediate validation of single fields after a user input | ||
− | |||
|- | |- | ||
+ | ! <center>Validation Fails</center> | ||
+ | | '''Not''' save data to ViewModel | ||
+ | || '''Not''' save data to ViewModel & '''Not''' execute the command | ||
|} | |} | ||
Line 64: | Line 65: | ||
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. | 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. | ||
− | <source lang="xml" | + | <source lang="xml" highlight="7,20,23"> |
− | + | ... | |
+ | <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)"> | <groupbox form="@id('fx') @load(vm.selected) @save(vm.selected, before='saveOrder') @validator(vm.formValidator)"> | ||
<grid hflex="true" > | <grid hflex="true" > | ||
Line 89: | Line 95: | ||
</source> | </source> | ||
− | * | + | * Line 7: ZK will invoke <code>vm.formValidator</code> before executing command <code>'saveOrder'</code>. |
− | + | * Line 20: ZK will validate input value when onChange event fires on intbox (when a user blur the focus). | |
− | * ZK will validate | + | * Line 7, 20, 23: You can apply validators on form attribute and each input component respectively to perform double validation. |
= Validation Message Holder= | = 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 <code>@id</code>. Then you can reference it with this id. | |
− | <source lang="xml" | + | <source lang="xml" highlight="2"> |
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('foo.MyViewModel')" | <window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('foo.MyViewModel')" | ||
validationMessages="@id('vmsgs')"> | validationMessages="@id('vmsgs')"> | ||
Line 103: | Line 109: | ||
</source> | </source> | ||
+ | == 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. | 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. | ||
Line 108: | Line 115: | ||
'''Display validation message with default key''' | '''Display validation message with default key''' | ||
− | <source lang="xml" | + | <source lang="xml" highlight="2,5,9"> |
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('foo.MyViewModel')" | <window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('foo.MyViewModel')" | ||
validationMessages="@id('vmsgs')"> | validationMessages="@id('vmsgs')"> | ||
Line 123: | Line 130: | ||
* You can use component's id to reference a component object. (line 5) | * 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) | * 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 <javadoc> org.zkoss.bind.validator.AbstractValidator</javadoc>. | ||
+ | '''Display validation message with self-defined key''' | ||
+ | <source lang="xml" highlight="2"> | ||
+ | <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> | ||
+ | </source> | ||
+ | * You can use self-defined key to display a certain message. (line 2) | ||
+ | |||
+ | Please read [[#Self-defined Validation Message Key| Self-defined Validation Message Key]] for the detail about the validator. | ||
+ | |||
+ | == Display Multiple Messages == | ||
+ | |||
+ | {{ZK EE}} | ||
+ | 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 '''<code>texts</code>''' .<ref>In EL, <code>vmsgs.texts</code> is the same as <code>vmsgs['texts']</code>, so you should avoid using <code>texts</code> as your self-defined key.</ref> | ||
+ | |||
+ | '''Display multiple messages of a form validator''' | ||
+ | <source lang="xml" highlight="4"> | ||
+ | <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> | ||
+ | </source> | ||
+ | * Using a grid to display multiple messages. (line 4) | ||
+ | |||
+ | You can also get all messages of the Validation Message Holder with syntax<code>@bind(vmsgs.texts)</code> and get messages of a self-defined key with syntax <code>@bind(vmsgs.texts['a_self_defined_key'])</code>. | ||
+ | |||
+ | <blockquote> | ||
+ | ---- | ||
+ | <references/> | ||
+ | </blockquote> | ||
= Implement a Validator = | = Implement a Validator = | ||
Line 129: | Line 181: | ||
== Property Validator == | == Property Validator == | ||
===Single Property Validation === | ===Single Property Validation === | ||
− | We usually need to validate one property at one time, the simplest way is to inherit < | + | We usually need to validate one property at one time, the simplest way is to inherit <code>AbstractValidator</code> and override <code>validate()</code> to implement a custom validation rule. If validation fails, use <code>addInvalidMessage()</code> 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'''. |
<source lang="xml"> | <source lang="xml"> | ||
Line 135: | Line 187: | ||
</source> | </source> | ||
− | <source lang="java" | + | <source lang="java" highlight="4,6"> |
public Validator getRangeValidator(){ | public Validator getRangeValidator(){ | ||
Line 148: | Line 200: | ||
} | } | ||
</source> | </source> | ||
− | * We can get the user input data from validator2's binding source component by < | + | * Line 4: We can get the user input data from validator2's binding source component by <code>ctx.getProperty().getValue()</code>. |
− | * addInvalidMessage() will add message into validation message holder. | + | * Line 6: addInvalidMessage() will add message into validation message holder with default key. |
===Dependent Property Validation === | ===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 < | + | 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 <code>@save(vm.p, before='command'</code>) , thus binder will pass those properties that are saved upon the same command to <code>ValidationContext</code> and we can retrieve them with property's key to process. |
Assume shipping date must be at least 3 days later than creation date. | Assume shipping date must be at least 3 days later than creation date. | ||
− | <source lang="xml" | + | <source lang="xml" highlight="13,21"> |
<window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('eg.ValidationMessagesVM')" | <window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('eg.ValidationMessagesVM')" | ||
Line 192: | Line 244: | ||
Our custom shipping date validator should get the creation date to compare. | Our custom shipping date validator should get the creation date to compare. | ||
− | <source lang="java" | + | <source lang="java" highlight="5"> |
public class ShippingDateValidator extends AbstractValidator{ | public class ShippingDateValidator extends AbstractValidator{ | ||
Line 229: | Line 281: | ||
== Dependent Property Validator in Form Binding == | == 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 form attribute instead of an input component that bound to a middle object's property. Then you can get all properties from validation context. | + | 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 <code>form</code> 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. | The following is the example to demonstrate the same shipping date validator but it's used in form binding. | ||
'''Using validator in form binding''' | '''Using validator in form binding''' | ||
− | <source lang="xml" | + | <source lang="xml" highlight="8,9"> |
− | + | ... | |
− | <groupbox form="@id('fx') @load(vm.selected) | + | <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" > | <grid hflex="true" > | ||
<columns> | <columns> | ||
Line 262: | Line 320: | ||
</source> | </source> | ||
− | * Apply a validator in form binding, not in individual input component that bound to middle object's property. | + | * Line 9: Apply a validator in form binding, not in individual input component that bound to middle object's property. |
'''Validator for form binding''' | '''Validator for form binding''' | ||
− | <source lang="java" | + | <source lang="java" highlight="4,5"> |
public Validator getShippingDateValidator() { | public Validator getShippingDateValidator() { | ||
Line 274: | Line 332: | ||
Date creation = (Date)ctx.getProperties("creationDate")[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. | //dependent validation, shipping date have to later than creation date for more than 3 days. | ||
− | if(! | + | if(!CalendarUtil.isDayAfter(creation,shipping,3)){ |
addInvalidMessage(ctx,"must large than creation date at least 3 days"); | addInvalidMessage(ctx,"must large than creation date at least 3 days"); | ||
} | } | ||
Line 282: | Line 340: | ||
</source> | </source> | ||
− | * The value main property is a Form when applying to a form binding. We should retrieve all properties with its key. | + | * Line 4~5: The value main property is a <code>Form</code> 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 <javadoc method="getProperties(java.lang.Object)">org.zkoss.bind.ValidationContext</javadoc> |
− | + | ||
− | <source lang="java" | + | |
+ | <source lang="java" highlight="5,6,7"> | ||
public Validator getShippingDateValidator() { | public Validator getShippingDateValidator() { | ||
Line 297: | Line 356: | ||
}; | }; | ||
} | } | ||
+ | |||
+ | </source> | ||
== Pass and Retrieve Parameters == | == 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 < | + | We can pass one or more parameters by EL expression to a validator. The parameters are written in key-value pairs format inside <code>@validator()</code> annotation. |
'''Passing a constant value in a ZUL''' | '''Passing a constant value in a ZUL''' | ||
Line 315: | Line 376: | ||
'''Retrieving parameters in a validator''' | '''Retrieving parameters in a validator''' | ||
− | <source lang="java" | + | <source lang="java" highlight="4"> |
public class MaxLengthValidator implements Validator { | public class MaxLengthValidator implements Validator { | ||
Line 336: | Line 397: | ||
'''Passing object in a ZUL''' | '''Passing object in a ZUL''' | ||
− | <source lang="xml" | + | <source lang="xml" highlight="8"> |
<combobox id="upperBoundBox" > | <combobox id="upperBoundBox" > | ||
Line 355: | Line 416: | ||
Assume you use only one validator in a form binding to validate all fields. | Assume you use only one validator in a form binding to validate all fields. | ||
− | <source lang="java" | + | <source lang="java" highlight="7"> |
public Validator getFormValidator(){ | public Validator getFormValidator(){ | ||
Line 382: | Line 443: | ||
'''Display validation message aside each component''' | '''Display validation message aside each component''' | ||
− | <source lang="xml" | + | <source lang="xml" highlight="2"> |
<vbox form="@id('fx') @load(vm) @save(vm,before='submit') @validator(vm.formValidator)"> | <vbox form="@id('fx') @load(vm) @save(vm,before='submit') @validator(vm.formValidator)"> | ||
Line 404: | Line 465: | ||
</source> | </source> | ||
− | * 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 | + | * 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. |
Line 416: | Line 477: | ||
</source> | </source> | ||
* 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. | * 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 = | ||
+ | |||
+ | {{ZK EE}} | ||
+ | Since 6.0.1 | ||
+ | |||
+ | You can register application level validators<ref>Application level validator only has one instance and is shared between all binders.</ref> by setting library-property(''org.zkoss.bind.appValidators'') in zk.xml. | ||
+ | <source lang="xml"> | ||
+ | <library-property> | ||
+ | <name>org.zkoss.bind.appValidators</name> | ||
+ | <value>foo=my.FooValidator,bar=my.BarValidator</value> | ||
+ | </library-property> | ||
+ | </source> | ||
+ | Then use them by validator name. | ||
+ | <source lang="xml"> | ||
+ | <textbox value="@bind(vm.name) @validator('foo')"/> | ||
+ | <textbox value="@bind(vm.value) @validator('bar')"/> | ||
+ | </source> | ||
+ | |||
+ | <blockquote> | ||
+ | ---- | ||
+ | <references/> | ||
+ | </blockquote> | ||
= Use Built-in Validator = | = Use Built-in Validator = | ||
− | ZK provides some built-in validators | + | ZK provides some built-in validators (see All Known Implementing Classes at <javadoc> org.zkoss.bind.Validator </javadoc>) that you can use it directly with syntax |
+ | |||
+ | '''<code>@validator('validatorName')</code>''' | ||
+ | |||
+ | The '''<code>validatorName</code>''' should be replaced with built-in validator's name which we list below: | ||
+ | * <code>beanValidator</code> | ||
+ | * <code>formBeanValidator</code> | ||
== Bean Validator == | == Bean Validator == | ||
Line 449: | Line 539: | ||
====Setup==== | ====Setup==== | ||
Under project classpath, you need to prepare | Under project classpath, you need to prepare | ||
− | #< | + | #<code>log4j.properties</code> |
− | #< | + | #<code>META-INF/validation.xml</code> |
Here are examples of minimal settings in these files: | Here are examples of minimal settings in these files: | ||
− | < | + | <code>log4j.properties</code> |
<source lang="php"> | <source lang="php"> | ||
log4j.rootLogger=info, stdout | log4j.rootLogger=info, stdout | ||
Line 462: | Line 552: | ||
</source> | </source> | ||
− | < | + | <code>META-INF/validation.xml</code> |
<source lang="xml"> | <source lang="xml"> | ||
<?xml version="1.0" encoding="UTF-8"?> | <?xml version="1.0" encoding="UTF-8"?> | ||
Line 483: | Line 573: | ||
Add constraint in JavaBean's property with Java annotation. | Add constraint in JavaBean's property with Java annotation. | ||
− | <source lang="java" | + | <source lang="java" highlight="4"> |
public static class User{ | public static class User{ | ||
private String _lastName = "Chen"; | private String _lastName = "Chen"; | ||
Line 498: | Line 588: | ||
</source> | </source> | ||
− | Use this validator with its name ''' | + | Use this validator with its name ''beanValidator'' with syntax: |
− | <source lang="xml" | + | |
+ | <code>@validator('beanValidator')</code> | ||
+ | |||
+ | <source lang="xml" highlight="3"> | ||
<window id="win" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init(foo.MyViewModel)" | <window id="win" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init(foo.MyViewModel)" | ||
validationMessages="@id('vmsgs')"> | validationMessages="@id('vmsgs')"> | ||
Line 507: | Line 600: | ||
</source> | </source> | ||
− | Since 6.0.1 | + | ==== Validate Form's Property Loaded from a Bean ==== |
− | <source lang="xml" | + | |
+ | {{ZK EE}} | ||
+ | Since 6.0.1 | ||
+ | |||
+ | It also supports to '''validate a form object's property''' loaded from a bean <ref>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.</ref>. | ||
+ | <source lang="xml" highlight="2"> | ||
<grid form="@id('fx') @load(vm.user) @save(vm.user,after='save')"> | <grid form="@id('fx') @load(vm.user) @save(vm.user,after='save')"> | ||
<textbox id="tb" value="@bind(fx.lastName) @validator('beanValidator')"/> | <textbox id="tb" value="@bind(fx.lastName) @validator('beanValidator')"/> | ||
Line 521: | Line 619: | ||
== Form Bean Validator == | == Form Bean Validator == | ||
− | |||
− | Like Bean Validator, this validator integrates | + | {{ZK EE}} |
+ | 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 === | === Usage === | ||
− | Use this validator with | + | Use this validator with the name '''formBeanValidator''' and set a unique<ref>The prefix has to be unique in the same binder. </ref> 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. |
− | <source lang="xml" | + | |
+ | Syntax: | ||
+ | |||
+ | <code>@validator('formBeanValidator',prefix='p_')</code> | ||
+ | |||
+ | <source lang="xml" highlight="4,6"> | ||
<window id="win" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init(foo.MyViewModel)" | <window id="win" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init(foo.MyViewModel)" | ||
validationMessages="@id('vmsgs')"> | validationMessages="@id('vmsgs')"> | ||
Line 538: | Line 643: | ||
</window> | </window> | ||
</source> | </source> | ||
+ | |||
+ | === Limitation === | ||
+ | This validator cannot validate a form object's nested property like <code>fx.firstProp.secondProp</code>. It can only validate a property like <code>fx.firstProp</code>. | ||
<blockquote> | <blockquote> | ||
Line 544: | Line 652: | ||
</blockquote> | </blockquote> | ||
− | + | =Version History= | |
− | + | {| class='wikitable' | width="100%" | |
− | |||
− | |||
− | |||
− | {| | ||
! Version !! Date !! Content | ! Version !! Date !! Content | ||
|- | |- |
Latest revision as of 07:35, 8 July 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 asvmsgs['texts']
, so you should avoid usingtexts
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. |