MVVM in ZK6: Form Binding"
m (correct highlight (via JWB)) |
|||
(2 intermediate revisions by the same user not shown) | |||
Line 53: | Line 53: | ||
|- | |- | ||
! <center>Syntax</center> | ! <center>Syntax</center> | ||
− | | < | + | | <code> @bind(vm.selected.price) </code> || <code>@load(vm.selected.price) @save(vm.selected.price, before= 'saveOrder')</code> |
|- | |- | ||
! <center>Save When</center> | ! <center>Save When</center> | ||
Line 75: | Line 75: | ||
'''orderForm.zul''' | '''orderForm.zul''' | ||
− | <source lang="xml" | + | <source lang="xml" highlight="3,5,18,21,27,31,34,40"> |
<window title="Order Management" border="normal" width="600px" | <window title="Order Management" border="normal" width="600px" | ||
apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('org.zkoss.bind.examples.order.OrderFormVM')" | apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('org.zkoss.bind.examples.order.OrderFormVM')" | ||
Line 126: | Line 126: | ||
</source> | </source> | ||
* Line 3: Set validation message holder ID, please refer to [[ZK_Developer%27s_Reference/MVVM/Data_Binding/Validator]] | * Line 3: Set validation message holder ID, please refer to [[ZK_Developer%27s_Reference/MVVM/Data_Binding/Validator]] | ||
+ | * Line 5: To use form binding, we annotate “form” attribute of the groupbox with <code> @id(‘fx’) </code> . | ||
+ | * Line 18,21,27,31,34,40: <code> fx </code> represents the loaded object which is <code> vm.selected </code> in this case. Developers can treat <code> fx </code> as a middle object which contains duplicated properties of the original object <code> vm.selected </code> and plays a role like a cache. Before clicking button to execute the ‘saveOrder’ command, ZK saves the input data into the middle object <code> fx </code> instead of real target object <code> vm.selected </code>. | ||
− | + | To gain batch saving feature, simply specify the command in <code>@save()</code> to <code>before=‘saveOrder’</code>. With this feature, a ‘’command’’ must be specified to <code>@save()</code>, either <code>before='saveOrder'</code> or <code>after='saveOrder'</code> | |
− | |||
− | To gain batch saving feature, simply specify the command in < | ||
Note that when you are not using “form binding”, you can choose whether or not to rely on a ‘command’ to execute the saving process. | Note that when you are not using “form binding”, you can choose whether or not to rely on a ‘command’ to execute the saving process. | ||
− | But the issue is, expressions like < | + | But the issue is, expressions like <code>@save(vm.selected.description,before='saveOrder')</code> lacks the ability to validate single fields right after users’ input. Validation is performed only when the binder tries to save the input value into a bean’s property. On the other hand, specifying expressions like <code>@save(vm.selected.description,before='saveOrder')</code> on each input component doesn’t save value immediately after users change focus (onChange event fires), thus no validation occurs. Hence, after we remove the <code>before = 'a-command' </code> expression from each input component, the input data are now saved to properties of <code>fx</code>, the middle object, as an onChange event fires. So every time after users inputs data in a field and changes the focus, ZK saves the value immediately into the middle object thus invoking the validation of that field and as a result achieves immediate validation of a single field. |
Line 146: | Line 146: | ||
− | "Form binding" is a very convenient form of databinding if a developer wishes to keep the domain object from dirty (unconfirmed) data and to store temporary user input before user confirmation (i.e. by clicking a button). It saves users’ modification in a middle object i.e. '< | + | "Form binding" is a very convenient form of databinding if a developer wishes to keep the domain object from dirty (unconfirmed) data and to store temporary user input before user confirmation (i.e. by clicking a button). It saves users’ modification in a middle object i.e. '<code>fx</code>' without affecting the real target object. When users confirm by clicking the save button (or executing a command abstractly), it saves those data into the real target object. If users cancels their modification, there is also no need for developers to clear the dirty data from target object thus reducing maintenance burden. |
= Form Validator – Validate upon Multiple Fields = | = Form Validator – Validate upon Multiple Fields = | ||
− | After the expression < | + | After the expression <code>before='saveOrder'</code> has been removed from each input component, the original shipping date validator will no longer work as the following code will also be modified: |
'''OrderVM2.java''' | '''OrderVM2.java''' | ||
Line 162: | Line 162: | ||
'''orderForm.zul''' | '''orderForm.zul''' | ||
− | <source lang="xml" | + | <source lang="xml" highlight="1"> |
<groupbox form="@id('fx') @load(vm.selected) @save(vm.selected, before='saveOrder') @validator(vm.shippingDateValidator)" | <groupbox form="@id('fx') @load(vm.selected) @save(vm.selected, before='saveOrder') @validator(vm.shippingDateValidator)" | ||
visible="@bind(not empty vm.selected)" | visible="@bind(not empty vm.selected)" | ||
Line 172: | Line 172: | ||
'''OrderFormVM.java''' | '''OrderFormVM.java''' | ||
− | <source lang="java" | + | <source lang="java" highlight="4,5"> |
public Validator getShippingDateValidator() { | public Validator getShippingDateValidator() { | ||
Line 198: | Line 198: | ||
There are some differences when implementing a form validator. | There are some differences when implementing a form validator. | ||
# VlidationContext contains all properties which are saved to the form. | # VlidationContext contains all properties which are saved to the form. | ||
− | # The method of getting main property, < | + | # The method of getting main property, <code> ctx.getProperty().getValue() </code>, return a <code>Form</code> object instead of a specific property. |
= Form Dirty Status: Indicate Modification Status = | = Form Dirty Status: Indicate Modification Status = | ||
Line 205: | Line 205: | ||
'''orderForm.zul''' | '''orderForm.zul''' | ||
− | <source lang="xml" | + | <source lang="xml" highlight="5"> |
<row> | <row> | ||
Line 222: | Line 222: | ||
Dirty status is stored in an auto-created variable with a naming convention of: | Dirty status is stored in an auto-created variable with a naming convention of: | ||
− | < | + | <code> |
[formId]Status | [formId]Status | ||
− | </ | + | </code> |
− | In this case, it’s < | + | In this case, it’s <code>fxStatus </code> for the form’s id is <code> fx </code>. Its dirty property indicates that whether the form has been modified by users or not. |
[[File:smalltalks-mvvm-in-zk6-formbinding-form-dirty.png | center | 500px]] | [[File:smalltalks-mvvm-in-zk6-formbinding-form-dirty.png | center | 500px]] | ||
+ | <div style="text-align:center"> '''Showing form data dirty''' </div> | ||
+ | |||
After users modify a field, an exclamation icon shows up next to “Id” field. If users click “Save” button or modify field to original value, the exclamation icon disappears. | After users modify a field, an exclamation icon shows up next to “Id” field. If users click “Save” button or modify field to original value, the exclamation icon disappears. | ||
Line 239: | Line 241: | ||
! Syntax !! Explanation | ! Syntax !! Explanation | ||
|- | |- | ||
− | | < | + | | <code> form="@id(’string’) @save(expression) @load(expression)" </code> |
| | | | ||
Form binding must be saved before or after a command, | Form binding must be saved before or after a command, | ||
− | expression in < | + | expression in <code> @save </code> must contain: <code> expression, [before | after]= ‘a-command’ </code> |
|- | |- | ||
|} | |} |
Latest revision as of 04:19, 20 January 2022
Hawk Chen, Engineer, Potix Corporation
February 01, 2012
ZK 6
Foreword
This small talk will introduce a feature of ZK MVVM called form binding. This feature will be demonstrated by rewriting the example application in the article: MVVM in ZK 6 - Design CRUD page by MVVM pattern. We suggest you to read the previous article first for better understanding.
Pros and Cons of the two Saving Resorts
In previous small talk, MVVM in ZK 6 - Design CRUD page by MVVM pattern, we build a "Order Management System" that can create, list, delete, modify orders. The image below is its UI.
The previous small talk mentioned about two saving resorts of ZK MVVM
- “property binding”
- “save before command”
Each of them has its own advantages.
- The “Property binding” resort saves user input to a property right after a component fires an onChange event (Users change cursor’s focus to another component). So the user input is validated immediately for single field but not validated when clicking the “Save” button
- The “save before command” resort solves some issues which are caused by property binding, for example, saving a new item with invalid value. Since saving before a command invokes validators before executing a command, if validation fails, it does not save the invalid value into a property. However, at the same time, we also lose immediate validation on single field feature which means that if users enter an invalid quantity value e.g. zero, they won't get an error message immediately when they continue to fill in the next field.
Validation before a command in property binding
So now what we need is a mechanism that is able to validate input twice at different time frames. One is when users finish typing their input and the other is when users click the ‘save’ button. By choosing one of the two resorts above cannot solve our problem, because each of them only validates once at a particular moment i.e. the moment of saving input into properties.
Comparison Table
Following is a comparison table comparing the two saving resorts:
Property Binding Save Before Command Syntax @bind(vm.selected.price)
@load(vm.selected.price) @save(vm.selected.price, before= 'saveOrder')
Save When a component fires an onChange event Before executing a command. Pros Immediately validate for single field Batch save & validate all fields. Cons - No validation when executing a command
- Save value directly to the bean - might mislead users that an item is persisted
- No immediate validation of single fields after users' input
- Lengthy syntax to write for each component
Using Form Binding – Introduction to a Middle Object
It seems that we can’t have our cake and eat it too, really? Like Barack Obama said: “Yes we can”. With ZK MVVM form binding, you can have the cake and eat it too! Using form binding, we can validate single fields immediately after users enter the value and then validate once again when users click the “Save” button to batch save.
orderForm.zul
<window title="Order Management" border="normal" width="600px" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('org.zkoss.bind.examples.order.OrderFormVM')" validationMessages="@id('vmsgs')"> ... <groupbox form="@id('fx') @load(vm.selected) @save(vm.selected, before='saveOrder') @validator(vm.shippingDateValidator)" id="formGroup" visible="@bind(not empty vm.selected)" hflex="true" mold="3d"> <grid hflex="true" > <columns> <column width="120px"/> <column/> </columns> <rows> <row>Id <hlayout> <label value="@load(fx.id)"/> <image src="@load(fxStatus.dirty?'exclamation.png':'')"/> </hlayout> </row> <row>Description <textbox value="@bind(fx.description)"/></row> <row>Quantity <hlayout> <intbox id="qbox" value="@bind(fx.quantity) @validator(vm.quantityValidator)"/> <label value="@load(vmsgs[qbox])" sclass="red" /> </hlayout> </row> <row>Price <hlayout> <doublebox id="pbox" value="@bind(fx.price) @validator(vm.priceValidator)" format="###,##0.00" /> <label value="@load(vmsgs[pbox])" sclass="red" /> </hlayout> </row> <row>Total Price <label value="@load(fx.totalPrice) @converter('formatedNumber', format='###,##0.00')" /></row> <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 value="@bind(fx.shippingDate)"/> <label value="@load(vmsgs[formGroup])" sclass="red" /> </hlayout> </row> </rows> </grid> </groupbox> ... </window>
- Line 3: Set validation message holder ID, please refer to ZK_Developer's_Reference/MVVM/Data_Binding/Validator
- Line 5: To use form binding, we annotate “form” attribute of the groupbox with
@id(‘fx’)
. - Line 18,21,27,31,34,40:
fx
represents the loaded object which isvm.selected
in this case. Developers can treatfx
as a middle object which contains duplicated properties of the original objectvm.selected
and plays a role like a cache. Before clicking button to execute the ‘saveOrder’ command, ZK saves the input data into the middle objectfx
instead of real target objectvm.selected
.
To gain batch saving feature, simply specify the command in
@save()
tobefore=‘saveOrder’
. With this feature, a ‘’command’’ must be specified to@save()
, eitherbefore='saveOrder'
orafter='saveOrder'
Note that when you are not using “form binding”, you can choose whether or not to rely on a ‘command’ to execute the saving process.
But the issue is, expressions like@save(vm.selected.description,before='saveOrder')
lacks the ability to validate single fields right after users’ input. Validation is performed only when the binder tries to save the input value into a bean’s property. On the other hand, specifying expressions like@save(vm.selected.description,before='saveOrder')
on each input component doesn’t save value immediately after users change focus (onChange event fires), thus no validation occurs. Hence, after we remove thebefore = 'a-command'
expression from each input component, the input data are now saved to properties offx
, the middle object, as an onChange event fires. So every time after users inputs data in a field and changes the focus, ZK saves the value immediately into the middle object thus invoking the validation of that field and as a result achieves immediate validation of a single field.
The interaction among ZUL, middle object, and the target object is illustrated below:
- The arrow indicates the data flow, it flows to its target when a particular event fires.
"Form binding" is a very convenient form of databinding if a developer wishes to keep the domain object from dirty (unconfirmed) data and to store temporary user input before user confirmation (i.e. by clicking a button). It saves users’ modification in a middle object i.e. '
fx
' without affecting the real target object. When users confirm by clicking the save button (or executing a command abstractly), it saves those data into the real target object. If users cancels their modification, there is also no need for developers to clear the dirty data from target object thus reducing maintenance burden.Form Validator – Validate upon Multiple Fields
After the expression
before='saveOrder'
has been removed from each input component, the original shipping date validator will no longer work as the following code will also be modified:OrderVM2.java
Date creation = (Date)ctx.getProperties("creationDate")[0].getValue()
ValidationContext no longer contains “creationDate” property as it is now not saved by the command.
In order to get multiple properties’ value, we have to specify shipping date validator as the form’s validator and change the implementation.orderForm.zul
<groupbox form="@id('fx') @load(vm.selected) @save(vm.selected, before='saveOrder') @validator(vm.shippingDateValidator)" visible="@bind(not empty vm.selected)" hflex="true" mold="3d">
We change the original shipping date validator's implementation as follows:
OrderFormVM.java
public Validator getShippingDateValidator() { return new Validator(){ public void validate(ValidationContext ctx) { //Date shipping = (Date)ctx.getProperty().getValue();//the main property Date shipping = (Date)ctx.getProperties("shippingDate")[0].getValue(); Date creation = (Date)ctx.getProperties("creationDate")[0].getValue(); //do dependent validation, shipping date have to large than creation more than 3 days. if(!CaldnearUtil.isDayAfter(creation,shipping,3)){ ctx.setInvalid(); validationMessages.put("shippingDate", "must large than creation date at least 3 days"); }else{ validationMessages.remove("shippingDate"); } //notify the binder of validation message changed ctx.getBindContext().getBinder().notifyChange(validationMessages, "shippingDate"); } }; }
There are some differences when implementing a form validator.
- VlidationContext contains all properties which are saved to the form.
- The method of getting main property,
ctx.getProperty().getValue()
, return aForm
object instead of a specific property.
Form Dirty Status: Indicate Modification Status
It’s a common requirement for users to know that whether they have modified a form’s data (dirty status) or not, developers therefore adds a feature that would remind users of this with an UI effect. For example, some text editors appends a star symbol ‘*’ on the title bar to remind users of modified text file. "Form binding" preserves the dirty status in an internal variable which we will talk about in the next section.
orderForm.zul
<row> Id <hlayout> <label value="@load(fx.id)" /> <image src="@load(fxStatus.dirty?'exclamation.png':'')" /> </hlayout> </row>
Add an exclamation icon right next to Id value.
Form Status Variable
Dirty status is stored in an auto-created variable with a naming convention of:
[formId]Status
In this case, it’s
fxStatus
for the form’s id isfx
. Its dirty property indicates that whether the form has been modified by users or not.Showing form data dirty
After users modify a field, an exclamation icon shows up next to “Id” field. If users click “Save” button or modify field to original value, the exclamation icon disappears.Syntax Review
ZK Bind Annotation Syntax in ZUL
Syntax Explanation form="@id(’string’) @save(expression) @load(expression)"
Form binding must be saved before or after a command, expression in
@save
must contain:expression, [before | after]= ‘a-command’
Summary
"Form binding" automatically creates a middle object for developers to keep out dirty data before user confirmation thus reducing developer’s burden of cleaning dirty data when users cancels their input. Developers can also perform extra actions like validation before saving data to real target object and retrieving form's dirty status.
See Also
- Envisage ZK 6: The Next Generation Data Binding System
- Hello ZK MVVM
- MVVM in ZK 6 - Design your first MVVM page
- MVVM in ZK 6 - Design CRUD page by MVVM pattern
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.