MVVM in ZK6: Form Binding

From Documentation
DocumentationSmall Talks2012FebruaryMVVM in ZK6: Form Binding
MVVM in ZK6: Form Binding

Author
Hawk Chen, Engineer, Potix Corporation
Date
February 01, 2012
Version
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.

Smalltalks-mvvm-in-zk6-design-crud-page-view.png


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
  • Smalltalks-mvvm-in-zk6-formbinding-field-validation.png
    Validation after focus change in property binding


  • 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.

    Smalltalks-mvvm-in-zk6-formbinding-batch-save-validation.png
    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 is vm.selected in this case. Developers can treat fx as a middle object which contains duplicated properties of the original object vm.selected and plays a role like a cache. Before clicking button to execute the ‘saveOrder’ command, ZK saves the input data into the middle object fx instead of real target object vm.selected .

    To gain batch saving feature, simply specify the command in @save() to before=‘saveOrder’. With this feature, a ‘’command’’ must be specified to @save(), either before='saveOrder' or after='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 the before = 'a-command' expression from each input component, the input data are now saved to properties of fx, 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:

    Mvvm-form-binding.png
    • 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.

    1. VlidationContext contains all properties which are saved to the form.
    2. The method of getting main property, ctx.getProperty().getValue() , return a Form 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 is fx . Its dirty property indicates that whether the form has been modified by users or not.

    Smalltalks-mvvm-in-zk6-formbinding-form-dirty.png
    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

    1. Envisage ZK 6: The Next Generation Data Binding System
    2. Hello ZK MVVM
    3. MVVM in ZK 6 - Design your first MVVM page
    4. MVVM in ZK 6 - Design CRUD page by MVVM pattern


    Comments



    Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.