Property Binding"

From Documentation
m ((via JWB))
 
(20 intermediate revisions by 5 users not shown)
Line 1: Line 1:
 
{{ZKDevelopersReferencePageHeader}}
 
{{ZKDevelopersReferencePageHeader}}
 +
{{Deprecated | url=[http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/property_binding.html zk-mvvm-book/8.0/data_binding/property_binding]|}}
 +
  
 
= Two Way Data Binding =
 
= Two Way Data Binding =
  
Property binding makes developers bind any component's attribute to ViewModel's property and specify its access privilege. There are 3 kinds of access privilege: '''save-load (<tt> @bind </tt>)''', '''save only (<tt> @save </tt>)''' and '''load only(<tt> @load </tt>)'''.
+
Property binding makes developers bind any component's attribute to ViewModel's property and specify its access privilege. There are 3 kinds of access privilege: '''save-load (<code>@bind</code>)''', '''save only (<code>@save</code>)''' and '''load only(<code>@load</code>)'''.
  
We usually use save only binding on input components, e.g., textbox, to collect user input into a JavaBean and load only binding when displaying data. If you have both needs, you can use save-load binding. We also call it '''Non-conditional Property Binding''', because you cannot specify conditional in this binding.
+
We usually use save only binding on input components, e.g., textbox, to collect user input into a JavaBean and load only binding when displaying data. If you have both needs, you can use save-load binding. We also call it '''Non-conditional Property Binding''', because you cannot specify condition in this binding.
  
 
'''Save-load binding'''
 
'''Save-load binding'''
Line 37: Line 39:
  
  
The timing of saving a component attributes' value to ViewModel is '''when the attribute related event fires'''. For example, textbox's value is saved on the onChange event firing. After you finish your input in a textbox and move the cursor to next input field, the input data is saved to ViewModel at that time. The timing of loading is specified by <tt> @NotifyChange </tt>.
+
The timing of saving a component attributes' value to ViewModel is '''when the attribute related event fires'''. For example, textbox's value is saved on the onChange event firing. After you finish your input in a textbox and move the cursor to next input field, the input data is saved to ViewModel at that time. The timing of loading is specified by <code>@NotifyChange</code>.
  
 
If ViewModel's property is a map, the syntax is as follows:
 
If ViewModel's property is a map, the syntax is as follows:
Line 70: Line 72:
 
= Conditional Binding =
 
= Conditional Binding =
  
Sometimes we need to control the timing of saving or loading instead of depending upon the attribute related event firing. We can specify property <tt> before </tt> or <tt> after </tt> to control the timing upon ViewModel's Command. Therefore, only before or after executing specified Command, the attribute's value is saved (or loading)  to a ViewModel.
+
Sometimes we need to control the timing of saving or loading instead of depending upon the attribute related event firing. We can specify property <code>before</code> or <code>after</code> to control the timing upon ViewModel's Command. Therefore, only before or after executing specified Command, the attribute's value is saved (or loading)  to a ViewModel.
  
 
Assume that there is an order management application with a database. The following zul example is one of its page, listbox displays order list and doublebox is for editing an order detail.
 
Assume that there is an order management application with a database. The following zul example is one of its page, listbox displays order list and doublebox is for editing an order detail.
  
 
'''Save before a command'''
 
'''Save before a command'''
<source lang="xml" high="6,12,26">
+
<source lang="xml" highlight="6,12,27">
 
<listbox model="@load(vm.orders)" selectedItem="@bind(vm.selected)" hflex="true" height="200px">
 
<listbox model="@load(vm.orders)" selectedItem="@bind(vm.selected)" hflex="true" height="200px">
 
<template name="model" var="item">
 
<template name="model" var="item">
Line 109: Line 111:
  
 
</source>
 
</source>
* The listcell and doublebox are both bound to <tt> vm.selected.price </tt>. ( line 6,26)
+
* The listcell and doublebox are both bound to <code>vm.selected.price</code>. ( line 6,27)
 
* If doublebox's value is saved immediately after user input, listcell's label which is also bound to the same ViewModel's property will also change. This effect might mislead the user that the value has been saved to database.  
 
* If doublebox's value is saved immediately after user input, listcell's label which is also bound to the same ViewModel's property will also change. This effect might mislead the user that the value has been saved to database.  
* To eliminate this misleading effect, developer might hope to batch save all editing result after the user click a "Save" button. We can achieve this by specifying Command's name for property <tt> before </tt> in <tt> @save </tt>. The value of intbox and doublebox are batch-saved when the use clicks "Save" button. (line 23)
+
* To eliminate this misleading effect, developer might hope to batch save all editing result after the user click a "Save" button. We can achieve this by specifying Command's name for property <code>before</code> in <code>@save</code>. The value of intbox and doublebox are batch-saved when the use clicks "Save" button. (line 23)
  
 
= Execution Order =
 
= Execution Order =
Line 131: Line 133:
  
 
== Multiple Conditions ==
 
== Multiple Conditions ==
We also can specify multiple Command's name in an array of string literal for property <tt> before </tt> or <tt> after </tt>like following:
+
We also can specify multiple Command's name in an array of string literal for property <code>before</code> or <code>after</code>like following:
  
 
'''Load after multiple commands'''
 
'''Load after multiple commands'''
Line 142: Line 144:
  
 
= Collection Binding=
 
= Collection Binding=
We need collection binding when we bind a container component's "model" attribute of , e.g. listbox or grid, to a ViewModel. That target property of ViewModel must be a <tt> Collection </tt> object, e.g. <tt> List </tt> or <tt> Set </tt>. When the getter method returns <tt> List </tt>, binder wraps it as ListModel automatically. If getter already returns a ListModel object, binder won't wrap it.
+
We need collection binding when we bind a container component's "model" attribute of , e.g. listbox or grid, to a ViewModel. That target property of ViewModel must be a <code>Collection</code> object, e.g. <code>List</code> or <code>Set</code>. When the getter method returns <code>List</code>, binder wraps it as ListModel automatically. If getter already returns a ListModel object, binder won't wrap it.
 
 
We usually use this binding with <template> and specify its "var" attribute to name the '''iteration variable''' which represents each object of the collection. ZK automatically set the name of the implicit '''iteration status variable''' upon the name of iteration variable and this variable stores the index of iteration. E.g. if you set <tt> var="item" </tt>, current iteration index is <tt> itemStatus.index </tt>. If you don't specify iteration variable name in var, their default variable name are <tt> '''each''' </tt> and <tt> '''eachStatus''' </tt>.
 
  
We'll use the following ViewModel to demonstrate usage of collection binding.
+
We are using the following ViewModel to demonstrate usage of collection binding.
  
 
'''ViewModel for collection binding'''
 
'''ViewModel for collection binding'''
Line 172: Line 172:
 
}
 
}
 
</source>
 
</source>
 +
== Iteration Variable ==
 +
 +
=== "var" Attribute ===
 +
We usually use Collection Binding with <template> and specify its "var" attribute to name the '''iteration variable''' which represents each entry of the collection:
  
 +
<source lang="xml" highlight="1">
 +
<template name="model" var="item">
 +
...
 +
</template>
 +
</source>
 +
If you don't specify iteration variable name in var, the default name of var is: <code>'''each'''</code>.
 +
 +
=== Implicit Iteration Status Variable ===
 +
ZK automatically set the variable name of iteration status upon the name of iteration variable and this variable stores the index of iteration. E.g. if you set <code>var="item"</code>, then iteration index would be <code>itemStatus.index</code>:
 +
<source lang="xml" highlight="1,3">
 +
<template name="model" var="item">
 +
<listitem >
 +
<listcell label="@load(itemStatus.index)"/>
 +
</listitem>
 +
</template>
 +
</listbox>
 +
</source>
 +
If you don't specify iteration variable name in var, the default variable name of Implicit Iteration Status is: <code>'''forEachStatus'''</code>.
  
 
== Binding on Listbox ==
 
== Binding on Listbox ==
  
 
'''Collection binding with listbox'''
 
'''Collection binding with listbox'''
<source lang="xml" high="1,12,14">
+
<source lang="xml" highlight="1,12,14">
  
 
<listbox model="@load(vm.orders)" selectedItem="@bind(vm.selected)" hflex="true" height="200px">
 
<listbox model="@load(vm.orders)" selectedItem="@bind(vm.selected)" hflex="true" height="200px">
Line 201: Line 223:
  
 
</source>
 
</source>
* We bind listbox's model to a Collection type property: <tt> List<Order> </tt>. (line 1)
+
* We bind listbox's model to a Collection type property: <code>List<Order></code>. (line 1)
* We bind "seletedItem" to a ViewModel's property of same object type (<tt> Order </tt>) in a collection. When each time a user select an item of the listbox, binder will save the selected object to ViewModel. Therefore, we can get user's selection by this way. (line 1)
+
* We bind "seletedItem" to a ViewModel's property of same object type (<code>Order</code>) in a collection. When each time a user select an item of the listbox, binder will save the selected object to ViewModel. Therefore, we can get user's selection by this way. (line 1)
* The value of "var" attribute: item represents an object of the model, so use dot notation to reference its property like <tt> item.quantity </tt>. Its index in the collection can be referenced by <tt> itemStatus.index </tt>. (line 12)
+
* The value of "var" attribute: item represents an object of the model, so use dot notation to reference its property like <code>item.quantity</code>. Its index in the collection can be referenced by <code>itemStatus.index</code>. (line 12)
 
* With power of EL, we can implement simple presentation logic on the ZUL. Here we display quantity number in red color when it's less than 3. (line 14)
 
* With power of EL, we can implement simple presentation logic on the ZUL. Here we display quantity number in red color when it's less than 3. (line 14)
  
Line 256: Line 278:
 
== Dynamic Template ==
 
== Dynamic Template ==
  
Dynamic template enables developers to specify '''which template to apply upon different conditions when rendering a container component''', e.g. grid. It is supported in grid, listbox, combobox, selectbox, and tree. You can use this feature with <tt> @template( </tt> [EL-expression] <tt>)</tt>. The binder will treat evaluation result of EL expression as a template's name and look for corresponding template to render child components. If the specified template doesn't exist in current component, it looks up parent component's template. If no <tt> @template </tt> assigned, it uses template that named '''model''' by default.  
+
Dynamic template enables developers to specify '''which template to apply upon different conditions ''', e.g. grid. It is supported in grid, listbox, combobox, selectbox, and tree. You can use this feature with <code>@template(</code> [EL-expression] <code>)</code>. The binder will evaluate EL expression as a template's name and look for corresponding template to render child components. If the specified template doesn't exist in current component, it looks up parent component's template. If no <code>@template</code> assigned, it uses template that named '''model''' by default.  
  
 
'''Default case'''
 
'''Default case'''
 
<source lang="xml">
 
<source lang="xml">
  
<grid model="@bind(vm.nodes)">
+
<grid model="@bind(vm.orders)">
 
<template name="model">
 
<template name="model">
 
<!-- child components -->
 
<!-- child components -->
Line 272: Line 294:
 
<source lang="xml">
 
<source lang="xml">
  
<grid model="@bind(vm.nodes) @template('myTemplate')">
+
<grid model="@bind(vm.orders) @template('myTemplate')">
 
<template name="myTemplate">
 
<template name="myTemplate">
 
<!-- child components -->
 
<!-- child components -->
Line 285: Line 307:
 
<source lang="xml">
 
<source lang="xml">
  
<grid model="@bind(vm.nodes) @template(vm.type='foo'?'template1':'template2')">
+
<grid model="@bind(vm.orders) @template(vm.type='foo'?'template1':'template2')">
 
<template name="template1">
 
<template name="template1">
 
<!-- child components -->
 
<!-- child components -->
Line 298: Line 320:
  
  
It also provides implicit variable: '''each''' (the instance of item in binding collection) and '''eachStatus''' (the iteration status) when evaluating the template for each row. That is, you can request to use different <template> per each item in the binding collection.
+
It also provides implicit variable: '''each''' (the instance of item in binding collection) and '''forEachStatus''' (the iteration status) when evaluating the template for each row. That is, you can request to use different <template> per each item in the binding collection.
  
 
<source lang="xml">
 
<source lang="xml">
  
<grid model="@bind(vm.nodes) @template(each.type='A'?'templateA':'templateB')">
+
<grid model="@bind(vm.orders) @template(each.type='A'?'templateA':'templateB')">
 
<template name="templateA">
 
<template name="templateA">
 
<!-- child components -->
 
<!-- child components -->
Line 317: Line 339:
  
 
=Version History=
 
=Version History=
{{LastUpdated}}
+
 
{| border='1px' | width="100%"
+
{| class='wikitable' | width="100%"
 
! Version !! Date !! Content
 
! Version !! Date !! Content
 
|-
 
|-

Latest revision as of 07:35, 8 July 2022

Stop.png This article is out of date, please refer to zk-mvvm-book/8.0/data_binding/property_binding for more up to date information.


Two Way Data Binding

Property binding makes developers bind any component's attribute to ViewModel's property and specify its access privilege. There are 3 kinds of access privilege: save-load (@bind), save only (@save) and load only(@load).

We usually use save only binding on input components, e.g., textbox, to collect user input into a JavaBean and load only binding when displaying data. If you have both needs, you can use save-load binding. We also call it Non-conditional Property Binding, because you cannot specify condition in this binding.

Save-load binding

<window id="window" title="maximize test" border="normal"
			maximizable="true" maximized="@bind(vm.maximized)">

</window>
  • In 2 way (save-load) binding, when window's maximized attribute is changed, it will save value back to ViewModel's "maximized" property, and vice versa.

Save only binding

<textbox value="@save(vm.person.firstname)"/>
<textbox value="@save(vm.person.lastname)"/>
<intbox value="@save(vm.person.age)"/>
  • In save-only binding, only textbox's value change will be saved to ViewModel. ViewModel's change won't be loaded to textbox.

Load only binding

<label value="@load(vm.welcomeMessage)" />
  • In load-only binding, only ViewModel's property change will be load to label.


The timing of saving a component attributes' value to ViewModel is when the attribute related event fires. For example, textbox's value is saved on the onChange event firing. After you finish your input in a textbox and move the cursor to next input field, the input data is saved to ViewModel at that time. The timing of loading is specified by @NotifyChange.

If ViewModel's property is a map, the syntax is as follows:

<label value="@load(vm.myMapping['myKey'])" />

Limitation

Can't save to variable

In the following example, the value of person.name is loaded to textbox and after editing, the value is then saved to person.name.

<textbox value="@bind(person.name)"/>

However, if the expression only contains first-term[1], 'load from' would still work, but 'save to' would not be allowed to perform. In the following example, the value myname is loaded to textbox, but after editing the value, EL exception occurs when saving value back.

<textbox value="@bind(myname)"/>

  1. first-term is a variable that comes from a variable resolver in which its value cannot be saved by EL directly

Conditional Binding

Sometimes we need to control the timing of saving or loading instead of depending upon the attribute related event firing. We can specify property before or after to control the timing upon ViewModel's Command. Therefore, only before or after executing specified Command, the attribute's value is saved (or loading) to a ViewModel.

Assume that there is an order management application with a database. The following zul example is one of its page, listbox displays order list and doublebox is for editing an order detail.

Save before a command

		<listbox model="@load(vm.orders)" selectedItem="@bind(vm.selected)" hflex="true" height="200px">
			<template name="model" var="item">
				<listitem >
					<listcell label="@load(item.id)"/>				
					<listcell label="@load(item.quantity)"/>
					<listcell label="@load(item.price)" />
				</listitem>
			</template>
		</listbox>
		<toolbar>
			<button label="New" onClick="@command('newOrder')" />
			<button label="Save" onClick="@command('saveOrder')" disabled="@bind(empty vm.selected)" />
			<!-- show confirm dialog when selected is persisted -->
			<button label="Delete" onClick="@command(empty vm.selected.id?'deleteOrder':'confirmDelete')" 
			disabled="@load(empty vm.selected)" />
		</toolbar>
			<grid hflex="true" >
				<columns>
					<column width="120px"/>
					<column/>
				</columns>
				<rows>
					<row>Quantity
						<intbox value="@load(vm.selected.quantity) @save(vm.selected.quantity,before='saveOrder') "/>
					</row>
					<row>Price 
						<doublebox value="@load(vm.selected.price) @save(vm.selected.price,before='saveOrder') " 
						format="###,##0.00"/>
					</row>
				</rows>
			</grid>
  • The listcell and doublebox are both bound to vm.selected.price. ( line 6,27)
  • If doublebox's value is saved immediately after user input, listcell's label which is also bound to the same ViewModel's property will also change. This effect might mislead the user that the value has been saved to database.
  • To eliminate this misleading effect, developer might hope to batch save all editing result after the user click a "Save" button. We can achieve this by specifying Command's name for property before in @save. The value of intbox and doublebox are batch-saved when the use clicks "Save" button. (line 23)

Execution Order

If a component has non-conditional property and command binding at the same time. The execution order is :

  1. Save binding
  2. Execute the command
  3. Load binding

A component with multiple binding

<textbox value="@bind(vm.filter)" onChange="@command('doSearch')"/>
  • When onChange event fires, the binder saves vm.filter first, executes command "doSearch", then loads vm.filter.

Multiple Conditions

We also can specify multiple Command's name in an array of string literal for property before or afterlike following:

Load after multiple commands

<label value="@load(vm.person.firstname, after={'update','delete'})"/>


When we use property binding to collect user input, we might need to validate it before saving to a ViewModel. ZK provides a standard validation mechanism through a reusable element called validator. We'll describe its detail here.

Collection Binding

We need collection binding when we bind a container component's "model" attribute of , e.g. listbox or grid, to a ViewModel. That target property of ViewModel must be a Collection object, e.g. List or Set. When the getter method returns List, binder wraps it as ListModel automatically. If getter already returns a ListModel object, binder won't wrap it.

We are using the following ViewModel to demonstrate usage of collection binding.

ViewModel for collection binding

public class OrderVM {

	//the order list
	private List<Order> orders;

	//the selected order
	private Order selected;

	public List<Order> getOrders() {
		return orders;
	}

	public Order getSelected() {
		return selected;
	}

	public void setSelected(Order selected) {
		this.selected = selected;
	}
}

Iteration Variable

"var" Attribute

We usually use Collection Binding with <template> and specify its "var" attribute to name the iteration variable which represents each entry of the collection:

<template name="model" var="item">
...
</template>

If you don't specify iteration variable name in var, the default name of var is: each.

Implicit Iteration Status Variable

ZK automatically set the variable name of iteration status upon the name of iteration variable and this variable stores the index of iteration. E.g. if you set var="item", then iteration index would be itemStatus.index:

	<template name="model" var="item">
		<listitem >
			<listcell label="@load(itemStatus.index)"/>	
		</listitem>
	</template>
</listbox>

If you don't specify iteration variable name in var, the default variable name of Implicit Iteration Status is: forEachStatus.

Binding on Listbox

Collection binding with listbox

<listbox model="@load(vm.orders)" selectedItem="@bind(vm.selected)" hflex="true" height="200px">
	<listhead>
		<listheader label="Row Index"/>
		<listheader label="Id"/>
		<listheader label="Quantity" align="center" width="80px" />
		<listheader label="Price" align="center" width="80px" />				
		<listheader label="Creation Date" align="center" width="100px" />
		<listheader label="Shipping Date" align="center" width="100px" />
	</listhead>
	<template name="model" var="item">
		<listitem >
			<listcell label="@load(itemStatus.index)"/>	
			<listcell label="@load(item.id)"/>				
			<listcell label="@load(item.quantity)" style="@load(item.quantity lt 3?'color:red':'')"/>
			<listcell label="@load(item.price)"/>
			<listcell label="@load(item.creationDate)"/>
			<listcell label="@load(item.shippingDate)"/>
		</listitem>
	</template>
</listbox>
  • We bind listbox's model to a Collection type property: List<Order>. (line 1)
  • We bind "seletedItem" to a ViewModel's property of same object type (Order) in a collection. When each time a user select an item of the listbox, binder will save the selected object to ViewModel. Therefore, we can get user's selection by this way. (line 1)
  • The value of "var" attribute: item represents an object of the model, so use dot notation to reference its property like item.quantity. Its index in the collection can be referenced by itemStatus.index. (line 12)
  • With power of EL, we can implement simple presentation logic on the ZUL. Here we display quantity number in red color when it's less than 3. (line 14)


Binding multiple selections

	<listbox selectedItems="@bind(vm.selected)" model="@load(vm.model)">
	<!-- other components -->
	</listbox>

ViewModel for multiple selections

public class ListModelSelection {
	private List<String> model;
	private Set<String> selected;

	//getter and setter
}
  • We can retrieve multiple selections by binding the attribute "selectedItems" to a Set.

Binding on Grid

The usage is similar for grid.

Collection binding usage for grid

	<grid model="@load(vm.orders)">
		<columns>
			<column label="Id"/>
			<column label="Quantity"/>
			<column label="Price"/>
		</columns>
		<template name="model" var="item" >
			<row>
				<label value="@bind(item.id)"/>
				<label value="@bind(item.quantity)"/>
				<label value="@bind(item.price)"/>
			</row>
		</template>
	</grid>

Dynamic Template

Dynamic template enables developers to specify which template to apply upon different conditions , e.g. grid. It is supported in grid, listbox, combobox, selectbox, and tree. You can use this feature with @template( [EL-expression] ). The binder will evaluate EL expression as a template's name and look for corresponding template to render child components. If the specified template doesn't exist in current component, it looks up parent component's template. If no @template assigned, it uses template that named model by default.

Default case

<grid model="@bind(vm.orders)">
	<template name="model">
	<!-- child components -->
	</template>
</grid>

Template name specified.

<grid model="@bind(vm.orders) @template('myTemplate')">
	<template name="myTemplate">
	<!-- child components -->
	</template>
</grid>

You could use EL to decide which template to use.

Conditional

<grid model="@bind(vm.orders) @template(vm.type='foo'?'template1':'template2')">
	<template name="template1">
	<!-- child components -->
	</template>

	<template name="template2">
	<!-- child components -->
	</template>
</grid>


It also provides implicit variable: each (the instance of item in binding collection) and forEachStatus (the iteration status) when evaluating the template for each row. That is, you can request to use different <template> per each item in the binding collection.

<grid model="@bind(vm.orders) @template(each.type='A'?'templateA':'templateB')">
	<template name="templateA">
	<!-- child components -->
	</template>

	<template name="templateB">
	<!-- child components -->
	</template>

</grid>
  • Assume that the object in binding collection has a property "type". Its value could be A or B. (line 1)

Version History

Version Date Content
6.0.0 February 2012 The MVVM was introduced.




Last Update : 2022/07/08

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