Collection and Selection"

From Documentation
m (replace tt with code (via JWB))
(22 intermediate revisions by 4 users not shown)
Line 1: Line 1:
{{Template:UnderConstruction}}
+
{{ZKDevelopersReferencePageHeader}}
 +
{{Deprecated | url=[http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/collection_and_selection.html zk-mvvm-book/8.0/data_binding/collection_and_selection]|}}
  
{{ZKDevelopersReferencePageHeader}}
 
  
 
= Property Binding in Collection Type =  
 
= Property Binding in Collection Type =  
Line 21: Line 21:
  
  
For a ViewModel's <tt> Collection</tt> type property, we can bind it with those components that have '''model''' attribute like ''Listbox'', ''Grid'', or ''Tree''. If the property's type is one of Java Collection type like <tt> List </tt> or <tt> Set </tt>, ZK will wrap it as a <javadoc>org.zkoss.zul.ListModel</javadoc> object automatically. (You should not bind multiple attributes to a shared collection object (e.g. a static List) as a model because it might arise concurrent access problem.)
+
For a ViewModel's <code>Collection</code> type property, we usually bind it to those components that have '''model''' attribute like ''Listbox'', ''Grid'', or ''Tree''. (You should not bind multiple attributes to a shared collection object,e.g. a static List, as concurrent issues may arise.)  
<!--  related issue: http://tracker.zkoss.org/browse/ZK-1334 -->
 
  
 
<source lang="xml">
 
<source lang="xml">
Line 28: Line 27:
 
</source>
 
</source>
  
After binding collection type property as a data source, we have to specify how to render each object of the model with <tt> <template></tt> ([[ZK Developer's Reference/MVC/View/Template]]). and ZK will create components according to the fragment specified in <tt> <template></tt> iteratively. There are 2 implicit variables we can use in <tt> <template></tt>.
+
If the property is one of Java Collection type object like <code>List</code> or <code>Set</code>, an internal converter will convert the collection object as a <javadoc>org.zkoss.zul.ListModel</javadoc> object automatically for user's convenience. But it has limitations that we will talk them in [[ZK_Developer%27s_Reference/MVVM/Data_Binding/Collection_and_Selection#Choose_a_Component.27s_Model_Type|later section]].
 +
<!--  related issue: http://tracker.zkoss.org/browse/ZK-1334 -->
 +
 
 +
 
 +
== Implicit Iteration Variable ==
 +
 
 +
After binding collection type property as a data source, we have to specify how to render each object of the model with <code>[[ZK Developer's Reference/MVC/View/Template | <template>]]</code>. and ZK will create components according to the fragment specified in <code><template></code> iteratively. There are 2 implicit variables we can use in <code><template></code>.
  
'''each''', iteration object variable which references to each object of the model. We can use it to access an object's properties with dot notation, e.g. <tt>each.name</tt>.
+
'''each''', iteration object variable which references to each object of the model. We can use it to access an object's properties with dot notation, e.g. <code>each.name</code>.
  
'''forEachStatus''', iteration status variable. it's used to get iteration index by <tt>forEachStatus.index</tt>.
+
'''forEachStatus''', iteration status variable. it's used to get iteration index by <code>forEachStatus.index</code>.
  
<source lang="xml" high="3,8,9,10,11,12,13">
+
<source lang="xml" highlight="3,8,9,10,11,12,13">
 
<window apply="org.zkoss.bind.BindComposer"
 
<window apply="org.zkoss.bind.BindComposer"
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.ItemsVM')">
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.ItemsVM')">
Line 51: Line 56:
 
</window>
 
</window>
 
</source>
 
</source>
* When using template element, we don't need to put <tt><rows></tt> inside a <tt><grid></tt>.
+
* When using template element, we don't need to put <code><rows></code> inside a <code><grid></code>.
 
 
  
== Set Iteration Variable Name ==
 
  
 
For better readability, we can also set template's "var" and "status" attribute to change implicit variable's name.  
 
For better readability, we can also set template's "var" and "status" attribute to change implicit variable's name.  
Line 71: Line 74:
 
</source>
 
</source>
  
<source lang="xml" high="8,9,10,11,12,13,14,15">
+
<source lang="xml" highlight="8,9,10,11,12,13,14,15">
 
<window apply="org.zkoss.bind.BindComposer"
 
<window apply="org.zkoss.bind.BindComposer"
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.TreeVM')">
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.TreeVM')">
Line 91: Line 94:
 
</window>
 
</window>
 
</source>
 
</source>
* When using template element, we don't need to put <tt><treechildren></tt> inside a <tt><tree></tt>.
+
* When using template element, we don't need to put <code><treechildren></code> inside a <code><tree></code>.
  
 
== Collection Property Binding with Dynamic Template ==
 
== Collection Property Binding with Dynamic Template ==
  
Dynamic template enables developers to specify '''which template to apply upon different conditions when rendering a container component''', e.g. ''grid''. All components that support "model" attribute support dynamic template. You can use this feature in "model" attribute by syntax:
+
Dynamic template allows developers to specify '''which template to apply upon different conditions when rendering a container component''', e.g. ''grid''. All components that support "model" attribute support dynamic template. You can use this feature in "model" attribute using the following syntax:
  
<tt> @template( </tt> [EL-expression] <tt>)</tt>  
+
<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 <tt> @template </tt> specified, it uses template that named '''model''' by default.  
+
The binder will evaluate an EL expression as a template's name and look for corresponding template to render the child components. If the specified template doesn't exist in current component, it looks up the parent component's template. If no <code>@template</code> specified, it uses template that named '''model''' by default.  
  
 
<!--
 
<!--
 
'''Default case'''
 
'''Default case'''
<source lang="xml" high="1">
+
<source lang="xml" highlight="1">
  
 
<grid model="@bind(vm.itemList)">
 
<grid model="@bind(vm.itemList)">
Line 113: Line 116:
 
-->
 
-->
 
'''Template name specified.'''
 
'''Template name specified.'''
<source lang="xml" high="1">
+
<source lang="xml" highlight="1">
  
 
<grid model="@bind(vm.itemList) @template('myTemplate')">
 
<grid model="@bind(vm.itemList) @template('myTemplate')">
Line 122: Line 125:
  
 
</source>
 
</source>
* Use the template whose name is specified in <tt>@template</tt>.
+
* Use the template whose name is specified in <code>@template</code>.
  
 
We could use EL to decide which template to use.
 
We could use EL to decide which template to use.
  
 
'''Conditional'''
 
'''Conditional'''
<source lang="xml" high="1">
+
<source lang="xml" highlight="1">
  
 
<grid model="@bind(vm.itemList) @template(vm.type eq 'foo'?'template1':'template2')">
 
<grid model="@bind(vm.itemList) @template(vm.type eq 'foo'?'template1':'template2')">
Line 142: Line 145:
  
  
We also can use implicit variable, '''each''' and '''forEachStatus''', in EL expression of <tt>@template()</tt>. That means we can apply different template on different item in the binding collection dynamically.
+
We also can use implicit variable, '''each''' and '''forEachStatus''', in EL expression of <code>@template()</code>. That means we can apply different template on different item in the binding collection dynamically.
  
 
'''Condition with implicit variables'''
 
'''Condition with implicit variables'''
<source lang="xml" high="1">
+
<source lang="xml" highlight="1">
  
 
<grid model="@bind(vm.itemList) @template(each.type eq 'A'?'templateA':'templateB')">
 
<grid model="@bind(vm.itemList) @template(each.type eq 'A'?'templateA':'templateB')">
Line 167: Line 170:
 
== Collection Property in Children Binding ==
 
== Collection Property in Children Binding ==
  
For those components that do not support "model" attribute, we still can bind them to a collection type property with '''children binding''' ( please refer to [[ZK Developer's Reference/MVVM/Data Binding/Children Binding]] to know basic concept.).
+
For those components that do not support "model" attribute, we still can bind them to a collection type property with '''children binding''' ( please refer to [[ZK Developer's Reference/MVVM/Data Binding/Children Binding| ZK's Developer's Reference]] to know basic concept.).
  
 
For those components that already support "model" attribute, we also can use children binding to change original component's rendering. For example, ''Radio''s are arranged horizontally inside a ''Radiogroup'', we can use children binding on ''Vlayout'' to arrange ''Radio''s vertically.
 
For those components that already support "model" attribute, we also can use children binding to change original component's rendering. For example, ''Radio''s are arranged horizontally inside a ''Radiogroup'', we can use children binding on ''Vlayout'' to arrange ''Radio''s vertically.
  
 
'''Children binding in radiogroup'''
 
'''Children binding in radiogroup'''
<source lang="xml" high="4">
+
<source lang="xml" highlight="4">
  
 
<window apply="org.zkoss.bind.BindComposer"
 
<window apply="org.zkoss.bind.BindComposer"
Line 193: Line 196:
  
 
'''An example of dynamic menu bar'''
 
'''An example of dynamic menu bar'''
<source lang="xml" high="1,2,4,7">
+
 
 +
<source lang="xml" highlight="1,2,4,7">
 
   
 
   
 
<menubar id="mbar" children="@bind(vm.menuList) @template(empty each.children?'menuitem':'menu')">
 
<menubar id="mbar" children="@bind(vm.menuList) @template(empty each.children?'menuitem':'menu')">
 
<template name="menu" var="menu">
 
<template name="menu" var="menu">
 
<menu label="@bind(menu.name)">
 
<menu label="@bind(menu.name)">
<menupopup children="@bind(menu.children) @template(empty menu.children?'menuitem':'menu')"/>
+
<menupopup children="@bind(menu.children) @template(empty each.children?'menuitem':'menu')"/>
 
</menu>
 
</menu>
 
</template>
 
</template>
Line 207: Line 211:
  
 
</source>
 
</source>
 +
 +
The example above is to use a tree like data structure that the sub-template will render the content recursively.
  
 
'''Dynamic menu bar screenshot'''
 
'''Dynamic menu bar screenshot'''
  
[[File:Mvvm-dynamic-menu.png‎]]
+
[[File:Mvvm-dynamic-menu.png‎ | center]]
  
 
= Selection in Collection =  
 
= Selection in Collection =  
Line 221: Line 227:
  
 
'''Properties for selection'''
 
'''Properties for selection'''
<source lang="java" high="5,6">
+
<source lang="java" highlight="5,6">
  
 
public class SingleSelectionVM{
 
public class SingleSelectionVM{
Line 239: Line 245:
 
=== Binding to Selected Index ===
 
=== Binding to Selected Index ===
  
To save or restore a component's selected index, we should bind '''selectedIndex''' attribute to a ViewModel's property with <tt>@bind</tt>. If we change the property's value for selection state in ViewModel, the component's selection state will also change.
+
To save or restore a component's selected index, we should bind '''selectedIndex''' attribute to a ViewModel's property with <code>@bind</code>. If we change the property's value for selection state in ViewModel, the component's selection state will also change.
  
 
'''Listbox selectedIndex example'''
 
'''Listbox selectedIndex example'''
<source lang="xml" high="4">
+
<source lang="xml" highlight="4">
 
<window apply="org.zkoss.bind.BindComposer"  
 
<window apply="org.zkoss.bind.BindComposer"  
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.SingleSelectionVM')">
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.SingleSelectionVM')">
Line 264: Line 270:
 
=== Binding to Selected Item ===
 
=== Binding to Selected Item ===
  
To save or restore selected item we should bind '''selectedItem''' to a property whose type equals the object of the Model. In our example, we should bind "selectedItem" to an <tt>Item</tt> object.  
+
To save or restore selected item we should bind '''selectedItem''' to a property whose type equals the object of the Model. In our example, we should bind "selectedItem" to an <code>Item</code> object.  
  
 
'''Listbox selectedItem example'''
 
'''Listbox selectedItem example'''
<source lang="xml" high="4">
+
<source lang="xml" highlight="4">
 
<window apply="org.zkoss.bind.BindComposer"  
 
<window apply="org.zkoss.bind.BindComposer"  
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.SingleSelectionVM')">
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.SingleSelectionVM')">
Line 299: Line 305:
  
 
''' Tree example'''
 
''' Tree example'''
<source lang="xml" high="4">
+
<source lang="xml" highlight="4">
 
<window apply="org.zkoss.bind.BindComposer"
 
<window apply="org.zkoss.bind.BindComposer"
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.TreeSelectionVM')">
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.TreeSelectionVM')">
Line 318: Line 324:
 
</tree>
 
</tree>
 
</window>
 
</window>
 +
</source>
 +
 +
 +
 +
'''Custom layout for Radiogroup'''
 +
 +
''Radiogroup'' arranges its child ''Radio'' horizontally and users cannot change this arrangement but we could use children binding to arrange ''Radio'' vertically. In order to make "selectedItem" work correctly, we should put <code><radio></code> before <code><radiogroup></code>. 
 +
 +
since 6.5.1
 +
We can bind "selectedItem" with any type of object, not only String.
 +
 +
<source lang="xml" >
 +
<vlayout children="@load(vm.itemList)">
 +
<template name="children">
 +
<radio label="@load(each.name)" value="@load(each)" radiogroup="rg" />
 +
</template>
 +
</vlayout>
 +
<radiogroup id="rg" selectedItem="@bind(vm.pickedItem)"/>
 
</source>
 
</source>
  
Line 325: Line 349:
  
 
'''Static data model'''
 
'''Static data model'''
<source lang="xml" high="1">
+
<source lang="xml" highlight="1">
 
<radiogroup selectedItem="@bind(vm.pickedItemName)">
 
<radiogroup selectedItem="@bind(vm.pickedItemName)">
 
<radio label="Item 0" value="Item 0" />
 
<radio label="Item 0" value="Item 0" />
Line 332: Line 356:
 
</radiogroup>
 
</radiogroup>
 
</source>
 
</source>
* Line 1: <tt>vm.pickedItemName</tt> is a String property.
+
* Line 1: <code>vm.pickedItemName</code> is a String property.
  
 
In previous section, we have said that type of object "selectedItem" attribute bound should equal the object of the Model. When using static data ( no use model), the select component's value type should equal to the type of property for "selectedItem". Because ZK restores the selection state by comparing "selectedItem" attribute and selected component's "value" attribute.
 
In previous section, we have said that type of object "selectedItem" attribute bound should equal the object of the Model. When using static data ( no use model), the select component's value type should equal to the type of property for "selectedItem". Because ZK restores the selection state by comparing "selectedItem" attribute and selected component's "value" attribute.
Line 340: Line 364:
 
http://www.zkoss.org/forum/listComment/20542
 
http://www.zkoss.org/forum/listComment/20542
 
-->
 
-->
 
 
'''Custom layout for radiogroup'''
 
 
''Radiogroup'' arranges its child ''Radio'' horizontally and users cannot change this arrangement. We could use children binding to arrange ''Radio'' vertically and also keep "selectedItem" work correctly.
 
 
since 6.0.3 or 6.5.1
 
We can bind "selectedItem" with any type of object, not only String.
 
 
<source lang="xml" high="2,3">
 
<radiogroup selectedItem="@bind(vm.pickedItem)">
 
<vlayout children="@load(vm.itemList)">
 
<template name="children">
 
<radio label="@load(each.name)" value="@load(each)" />
 
</template>
 
</vlayout>
 
</radiogroup>
 
</source>
 
  
  
Line 364: Line 370:
 
Because ''Tabbox'' doesn't support "model" attribute, we should do a little extra work to keep selection state. We can create a ViewModel with a variable for selected item and create a command method to save selected item passed by command binding.
 
Because ''Tabbox'' doesn't support "model" attribute, we should do a little extra work to keep selection state. We can create a ViewModel with a variable for selected item and create a command method to save selected item passed by command binding.
  
<source lang="java" high="8,9,10,11,12">
+
<source lang="java" highlight="8,9,10,11,12">
 
public class CustomSingleSelectionVM {
 
public class CustomSingleSelectionVM {
  
Line 382: Line 388:
 
First we use children binding to create ''Tab'' and ''Tabpanel'' dynamically then write a command binding on "onSelect" to pass selected item. Then we can set "selected" attribute by comparing selected item with the object bound with each ''Tab''.
 
First we use children binding to create ''Tab'' and ''Tabpanel'' dynamically then write a command binding on "onSelect" to pass selected item. Then we can set "selected" attribute by comparing selected item with the object bound with each ''Tab''.
  
<source lang="xml" high="6,7">
+
<source lang="xml" highlight="6,7">
  
 
<div apply="org.zkoss.bind.BindComposer" width="600px"
 
<div apply="org.zkoss.bind.BindComposer" width="600px"
Line 406: Line 412:
 
== Multiple Selections ==
 
== Multiple Selections ==
  
Some components support multiple selections, but it needs to be enabled before using it. The way to enable multiple selections depends on property's type you bind with "model" attribute. If property type is a Java Collection type like <tt>List, Set, or Map </tt>, we should specify <tt>multiple="true"</tt> on the component to enable multiple selections.  If a property's type is ListModel and it implements <javadoc>org.zkoss.zul.ext.Selectable</javadoc>, we should call <tt>setMultiple(true)</tt> to enable it.
+
Some components support multiple selections, but it needs to be enabled before using it. The way to enable multiple selections depends on property's type you bind with "model" attribute. If the property type is Java Collection type such as <code>List, Set, or Map</code>, we should specify <code>multiple="true"</code> on the component to enable multiple selections.  If a property's type is <code>ListModel</code> and it implements <javadoc>org.zkoss.zul.ext.Selectable</javadoc>, we should call <code>setMultiple(true)</code> to enable it.
  
To keep multiple selections state, we should bind '''selectedItems''' to a property whose type is <tt>Set</tt>.
+
To keep multiple selections state, we should bind '''selectedItems''' to a property whose type is <code>Set</code>.
  
 
'''Selected Set'''
 
'''Selected Set'''
<source lang="java" high="5">
+
<source lang="java" highlight="5">
  
 
public class MultipleSelectionsVM{
 
public class MultipleSelectionsVM{
Line 425: Line 431:
  
 
'''Multiple selections enabled Listbox'''
 
'''Multiple selections enabled Listbox'''
<source lang="xml" high="3,4">
+
<source lang="xml" highlight="3,4">
 
<window apply="org.zkoss.bind.BindComposer"  
 
<window apply="org.zkoss.bind.BindComposer"  
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.MultipleSelectionsVM')">
 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.MultipleSelectionsVM')">
Line 443: Line 449:
 
</window>
 
</window>
 
</source>
 
</source>
* Line 3: Because <tt>vm.itemList</tt> is Java list object, we can enable multiple selection by set "multiple" to "true".
+
* Line 3: Because <code>vm.itemList</code> is Java list object, we can enable multiple selection by set "multiple" to "true".
  
  
=== Multiple Selections in Checkboxes ===
+
=== Custom Multiple Selections ===
  
 
Some components don't support "selectedItems" attribute like ''Checkbox'', we still can keep selection state by passing arguments. Create the following ViewModel contains a set to store selected items and a command that update items in the set according to passed arguments.
 
Some components don't support "selectedItems" attribute like ''Checkbox'', we still can keep selection state by passing arguments. Create the following ViewModel contains a set to store selected items and a command that update items in the set according to passed arguments.
  
<source lang="java" high="4,8">
+
<source lang="java" highlight="4,8">
public class MultipleSelectionsVM {
+
public class CustomMultipleSelectionsVM {
  
 
private ItemService itemService = new ItemService();
 
private ItemService itemService = new ItemService();
Line 472: Line 478:
  
  
''Checkbox'' doesn't support "model" attribute, so we use children binding to render them upon item list. We also bind "onCheck" attribute to a command and pass selected <tt>Item</tt> object to command method for updating selected set.
+
''Checkbox'' doesn't support "model" attribute, so we use children binding to render them upon item list. We also bind "onCheck" attribute to a command and pass selected <code>Item</code> object to command method for updating selected set.
  
 
'''Selected items for checkboxes'''
 
'''Selected items for checkboxes'''
<source lang="xml" high="3,6">
+
<source lang="xml" highlight="3,6">
  
 
<window apply="org.zkoss.bind.BindComposer"
 
<window apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.MultipleSelectionsVM')">
+
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.CustomMultipleSelectionsVM ')">
 
<vlayout id="vlayout" children="@load(vm.itemList)">
 
<vlayout id="vlayout" children="@load(vm.itemList)">
 
<template name="children">
 
<template name="children">
Line 490: Line 496:
 
</source>
 
</source>
 
* Line 6: Pass arguments in command binding to update selected items.
 
* Line 6: Pass arguments in command binding to update selected items.
 +
 +
= Choose a Component's Model Type=
 +
 +
As we mentioned in previous section, we could bind a Java Collection type property to be a component's model . An internal converter will convert it to <javadoc>org.zkoss.zul.LIstModel</javadoc> automatically for us. It is convenient but one of its cost is "scroll position lost when collection size changes". If you scroll to an item of index 50 and delete (or add) an item. The component will force to scroll back to first item because it has to re-convert and re-render all items. One simple way to handle this is to set ''Listbox'' as "paging" mold. Another way is to wrap the collection object with <javadoc>org.zkoss.zul.ListModelList</javadoc>.
 +
 +
One more issue about performance is ''Listbox'' re-renders all items
 +
<ref> If ROD enabled, it only renders visible items . </ref>
 +
when using Java collection object as the model in default mold. When you change the collection property by removing or adding an item, you have to notify its change. This will trigger ''Listbox'' to re-render from the beginning. But if you use <code>ListModelList</code> as a model and modify it by calling its method <code>add()</code> and <code>remove()</code>. <code>ListModelList</code> will update to client automatically without explicitly specifying property notification. And this update only re-renders the affected (added or removed) item instead of all items which is more efficient.
 +
<ref> If you still specify property notification for <code>ListModelList</code> property, ''Listbox'' renders all items (If ROD is disabled). Then you won't gain performance improvement. </ref>
 +
 +
But if you have huge amount of data, and getting them takes unbearable long time. You should implement your own model object which we will talk in [[ZK_Developer%27s_Reference/MVVM/Advance/Displaying_Huge_Amount_of_Data| Advance]] section.
 +
 +
'''Use Java List as a model'''
 +
<source lang="java" highlight="2, 8,12,18">
 +
 +
public class ModelTypeVM extends SingleSelectionVM{
 +
protected List<Item> itemList;
 +
 +
@Init
 +
public void init(){
 +
pickedItem = new Item();
 +
itemService = new ItemService(100);
 +
itemList = itemService.getAllItems();  //return a java.util.List
 +
...
 +
}
 +
 +
@Command @NotifyChange({"itemList","pickedItem"})
 +
public void add(){
 +
itemList.add(pickedItem);
 +
pickedItem = new Item();
 +
}
 +
 +
@Command  @NotifyChange({"itemList","pickedItem"})
 +
public void delete(){
 +
int index = itemList.indexOf(pickedItem);
 +
if (index != -1){
 +
itemList.remove(index);
 +
pickedItem = new Item();
 +
}
 +
 +
}
 +
 +
//omit setter and getter
 +
}
 +
 +
</source>
 +
* Line 8: <code>itemList</code> is java.util.List object.
 +
* Line 12, 18: Because we change the property "itemList", we should notify binder to reload it.
 +
 +
 +
 +
'''Use ListModelList as a model'''
 +
<source lang="java" highlight="3,10, 13, 20">
 +
 +
public class ModelTypeVM extends SingleSelectionVM{
 +
 +
private ListModelList<Item> itemListModel;
 +
 +
@Init
 +
public void init(){
 +
pickedItem = new Item();
 +
itemService = new ItemService(100);
 +
...
 +
itemListModel = new ListModelList<Item>(itemService.getAllItems());
 +
}
 +
 +
@Command @NotifyChange("pickedItem")
 +
public void modelAdd(){
 +
itemListModel.add(pickedItem);
 +
pickedItem = new Item();
 +
}
 +
 +
@Command  @NotifyChange("pickedItem")
 +
public void modelDelete(){
 +
int index = itemListModel.indexOf(pickedItem);
 +
if (index != -1){
 +
itemListModel.remove(index);
 +
pickedItem = new Item();
 +
}
 +
 +
}
 +
 +
//omit setter and getter
 +
}
 +
</source>
 +
* Line 10: We can simply wrap a java.util.List by passing it into <code>ListModelList</code>'s constructor.
 +
* Line 13, 20: Because <code>ListModelList</code> will update change to clients automatically, we can ignore to notify the property "itemListModel".
 +
 +
 +
<references/>
  
 
=Version History=
 
=Version History=
 
{{LastUpdated}}
 
{{LastUpdated}}
{| border='1px' | width="100%"
+
{| class='wikitable' | width="100%"
 
! Version !! Date !! Content
 
! Version !! Date !! Content
 
|-
 
|-

Revision as of 15:04, 12 January 2022


Collection and Selection


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


Property Binding in Collection Type

Assume that we have following ViewModel which has a collection type property:

public class ItemsVM {

	private ItemService itemService = new ItemService();
	private List<Item> itemList = itemService.getAllItems();
	
	public List<Item> getItemList(){
		return itemList;
	}
}


For a ViewModel's Collection type property, we usually bind it to those components that have model attribute like Listbox, Grid, or Tree. (You should not bind multiple attributes to a shared collection object,e.g. a static List, as concurrent issues may arise.)

	<grid width="400px" model="@bind(vm.itemList)">

If the property is one of Java Collection type object like List or Set, an internal converter will convert the collection object as a ListModel object automatically for user's convenience. But it has limitations that we will talk them in later section.


Implicit Iteration Variable

After binding collection type property as a data source, we have to specify how to render each object of the model with <template>. and ZK will create components according to the fragment specified in <template> iteratively. There are 2 implicit variables we can use in <template>.

each, iteration object variable which references to each object of the model. We can use it to access an object's properties with dot notation, e.g. each.name.

forEachStatus, iteration status variable. it's used to get iteration index by forEachStatus.index.

<window apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.ItemsVM')">
	<grid width="400px" model="@bind(vm.itemList)">
		<columns>
			<column label="index" />
			<column label="name" />
		</columns>
		<template name="model" >
			<row>
				<label value="@bind(forEachStatus.index)" />
				<label value="@bind(each.name)" />
			</row>
		</template>
	</grid>
</window>
  • When using template element, we don't need to put <rows> inside a <grid>.


For better readability, we can also set template's "var" and "status" attribute to change implicit variable's name.

var="myvar", set the iteration object variable's name. If you don't set "status" attribute at the same time, ZK will set iteration status variable's name to myvarStatus, appending "Status" as the postfix.

status="mystatus", set iteration status variable's name.

The ViewModel for tree example

public class TreeVM {

	TreeModel<TreeNode<String>> itemTree;
	//omit getter and setter for brevity
}
<window apply="org.zkoss.bind.BindComposer"
	viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.TreeVM')">
	<tree model="@bind(vm.itemTree)" width="400px" >
		<treecols>
			<treecol label="name" />
			<treecol label="index" />
		</treecols>
		<template name="model" var="node" status="s">
			<treeitem>
				<treerow>
					<treecell label="@bind(node.data)" />
					<treecell label="@bind(s.index)" />
				</treerow>
			</treeitem>
		</template>
	</tree>
...
</window>
  • When using template element, we don't need to put <treechildren> inside a <tree>.

Collection Property Binding with Dynamic Template

Dynamic template allows developers to specify which template to apply upon different conditions when rendering a container component, e.g. grid. All components that support "model" attribute support dynamic template. You can use this feature in "model" attribute using the following syntax:

@template( [EL-expression] )

The binder will evaluate an EL expression as a template's name and look for corresponding template to render the child components. If the specified template doesn't exist in current component, it looks up the parent component's template. If no @template specified, it uses template that named model by default.

Template name specified.

<grid model="@bind(vm.itemList) @template('myTemplate')">
	<template name="myTemplate">
	<!-- child components -->
	</template>
</grid>
  • Use the template whose name is specified in @template.

We could use EL to decide which template to use.

Conditional

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

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


We also can use implicit variable, each and forEachStatus, in EL expression of @template(). That means we can apply different template on different item in the binding collection dynamically.

Condition with implicit variables

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

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

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


Collection Property in Children Binding

For those components that do not support "model" attribute, we still can bind them to a collection type property with children binding ( please refer to ZK's Developer's Reference to know basic concept.).

For those components that already support "model" attribute, we also can use children binding to change original component's rendering. For example, Radios are arranged horizontally inside a Radiogroup, we can use children binding on Vlayout to arrange Radios vertically.

Children binding in radiogroup

<window apply="org.zkoss.bind.BindComposer"
	viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.ItemsVM')">
	<radiogroup>
		<vlayout children="@bind(vm.itemList)">
			<template name="children">
				<radio label="@load(each.name)" value="@load(each.name)"></radio>
			</template>
		</vlayout>
	</radiogroup>
</window>

Combine with Dynamic Template

We can combine children binding with dynamic template in order to render different child components upon different conditions.

Here is an example to create a menu bar dynamically. If a menu item has no sub-menu, it creates Menuitem otherwise it creates Menu.


An example of dynamic menu bar

 
	<menubar id="mbar" children="@bind(vm.menuList) @template(empty each.children?'menuitem':'menu')">
		<template name="menu" var="menu">
			<menu label="@bind(menu.name)">
				<menupopup children="@bind(menu.children) @template(empty each.children?'menuitem':'menu')"/>
			</menu>
		</template>
		<template name="menuitem" var="item">
			<menuitem label="@bind(item.name)" onClick="@command('menuClicked',node=item)" />
		</template>
	</menubar>

The example above is to use a tree like data structure that the sub-template will render the content recursively.

Dynamic menu bar screenshot

Mvvm-dynamic-menu.png

Selection in Collection

Listbox and Tree store user selection state including single or multiple selection. Single selection is enabled by default. The way to enable multiple selection depends on object's type you bind with "model" attribute. ZK allows us to bind selection state with ViewModel's property.

Single Selection

For single selection state, ZK provides 2 kind of state. One is selected index and another is selected item of a model. We can create 2 properties in ViewModel for them respectively.

Properties for selection

public class SingleSelectionVM{

	private ItemService itemService = new ItemService();
	private List<Item> itemList = itemService.getAllItems();
	private int pickedIndex;
	private Item pickedItem;

	//omit getter and setter for brevity
}
  • Line 5: pickedIndex is an integer for selected index.
  • Line 6: pickedItem's type should equal to the object's type in collection.


Binding to Selected Index

To save or restore a component's selected index, we should bind selectedIndex attribute to a ViewModel's property with @bind. If we change the property's value for selection state in ViewModel, the component's selection state will also change.

Listbox selectedIndex example

<window apply="org.zkoss.bind.BindComposer" 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.SingleSelectionVM')">
	<listbox  width="400px" model="@bind(vm.itemList)" 
	selectedIndex="@bind(vm.pickedIndex)">
		<listhead>
			<listheader label="index"/>
			<listheader label="name"/>
		</listhead>
		<template name="model" var="item" status="s">
			<listitem>
				<listcell label="@bind(s.index)"/>
				<listcell label="@bind(item.name)"/>
			</listitem>
		</template>
	</listbox>	
</window>


Binding to Selected Item

To save or restore selected item we should bind selectedItem to a property whose type equals the object of the Model. In our example, we should bind "selectedItem" to an Item object.

Listbox selectedItem example

<window apply="org.zkoss.bind.BindComposer" 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.SingleSelectionVM')">
	<listbox  width="400px" model="@bind(vm.itemList)" 
    selectedItem="@bind(vm.pickedItem)" >
		<listhead>
			<listheader label="index"/>
			<listheader label="name"/>
		</listhead>
		<template name="model" var="item" status="s">
			<listitem>
				<listcell label="@bind(s.index)"/>
				<listcell label="@bind(item.name)"/>
			</listitem>
		</template>
	</listbox>


The usage is similar for Tree. Most components that support "model" attribute support above 2 properties, but some do not, e.g. Tree doesn't support selectedIndex attribute. (Please refer to each component's javadoc for supported attributes).

ViewModel for tree example

public class TreeSelectionVM {

	private TreeModel<TreeNode<String>> itemTree;
	private String pickedItem;
	//omit getter and setter for brevity
}

Tree example

<window apply="org.zkoss.bind.BindComposer"
	viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.TreeSelectionVM')">
	<tree id="tree" model="@bind(vm.itemTree) " width="600px" 
	selectedItem="@bind(vm.pickedItem)">
		<treecols>
			<treecol label="name" />
			<treecol label="index" />
		</treecols>
		<template name="model" var="node" status="s">
			<treeitem open="@bind(node.open)">
				<treerow>
					<treecell label="@bind(node.data)" />
					<treecell label="@bind(s.index)" />
				</treerow>
			</treeitem>
		</template>
	</tree>
</window>


Custom layout for Radiogroup

Radiogroup arranges its child Radio horizontally and users cannot change this arrangement but we could use children binding to arrange Radio vertically. In order to make "selectedItem" work correctly, we should put <radio> before <radiogroup>.

since 6.5.1

We can bind "selectedItem" with any type of object, not only String.

		 <vlayout children="@load(vm.itemList)">
			<template name="children">
				<radio label="@load(each.name)" value="@load(each)" radiogroup="rg" />
			</template>
		</vlayout>
		<radiogroup id="rg" selectedItem="@bind(vm.pickedItem)"/>

Binding to Selected Item with Static UI Data

When using static data, we still can get and save "selectedItem" with similar rule. In the following example, we should bind "selectedItem" with a String property. Because we set String to Radio's value. Their types have to match.

Static data model

		<radiogroup selectedItem="@bind(vm.pickedItemName)">
			<radio label="Item 0" value="Item 0" />
			<radio label="Item 1" value="Item 1" />
			<radio label="Item 2" value="Item 2" />
		</radiogroup>
  • Line 1: vm.pickedItemName is a String property.

In previous section, we have said that type of object "selectedItem" attribute bound should equal the object of the Model. When using static data ( no use model), the select component's value type should equal to the type of property for "selectedItem". Because ZK restores the selection state by comparing "selectedItem" attribute and selected component's "value" attribute.


Custom Single Selection

Because Tabbox doesn't support "model" attribute, we should do a little extra work to keep selection state. We can create a ViewModel with a variable for selected item and create a command method to save selected item passed by command binding.

public class CustomSingleSelectionVM {

	private ItemService itemService = new ItemService();
	private List<Item> itemList = itemService.getAllItems();
	private Item pickedItem = itemList.get(0);
	
	//omit setter and getter for brevity
	@NotifyChange("pickedItem")
	@Command
	public void select(@BindingParam("item") Item item){
		pickedItem = item;
	}
}

First we use children binding to create Tab and Tabpanel dynamically then write a command binding on "onSelect" to pass selected item. Then we can set "selected" attribute by comparing selected item with the object bound with each Tab.

<div apply="org.zkoss.bind.BindComposer" width="600px"
	viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.CustomSingleSelectionVM')">
	<tabbox>
		<tabs children="@load(vm.itemList)">
			<template name="children">
				<tab label="@load(each.name)" selected="@load(vm.pickedItem eq each)"
					onSelect="@command('select',item=each)" />
			</template>
		</tabs>
		<tabpanels children="@load(vm.itemList)">
			<template name="children">
				<tabpanel height="200px">
					<label value="@load(each.name)" />
				</tabpanel>
			</template>
		</tabpanels>
	</tabbox>
</div>

Multiple Selections

Some components support multiple selections, but it needs to be enabled before using it. The way to enable multiple selections depends on property's type you bind with "model" attribute. If the property type is Java Collection type such as List, Set, or Map, we should specify multiple="true" on the component to enable multiple selections. If a property's type is ListModel and it implements Selectable, we should call setMultiple(true) to enable it.

To keep multiple selections state, we should bind selectedItems to a property whose type is Set.

Selected Set

public class MultipleSelectionsVM{

	private ItemService itemService = new ItemService();
	private List<Item> itemList = itemService.getAllItems();
	private Set pickedItemSet;

	//omit getter and setter for brevity
}


Multiple selections enabled Listbox

<window apply="org.zkoss.bind.BindComposer" 
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.MultipleSelectionsVM')">
	<listbox  width="400px" model="@bind(vm.itemList)" checkmark="true" multiple="true"
	selectedItems="@bind(vm.pickedItemSet)" >
		<listhead>
			<listheader label="index"/>
			<listheader label="name"/>
		</listhead>
		<template name="model" var="item" status="s">
			<listitem>
				<listcell label="@bind(s.index)"/>
				<listcell label="@bind(item.name)"/>
			</listitem>
		</template>
	</listbox>
</window>
  • Line 3: Because vm.itemList is Java list object, we can enable multiple selection by set "multiple" to "true".


Custom Multiple Selections

Some components don't support "selectedItems" attribute like Checkbox, we still can keep selection state by passing arguments. Create the following ViewModel contains a set to store selected items and a command that update items in the set according to passed arguments.

public class CustomMultipleSelectionsVM {

	private ItemService itemService = new ItemService();
	private Set<Item> pickedItemSet = new HashSet<Item>();

	@Command
	@NotifyChange("pickedItemSet")
	public void pick(@BindingParam("checked") boolean isPicked, @BindingParam("picked")Item item){
		if (isPicked){
			pickedItemSet.add(item);
		}else{
			pickedItemSet.remove(item);
		}
	}
	
	//omit getter and setter for brevity
}


Checkbox doesn't support "model" attribute, so we use children binding to render them upon item list. We also bind "onCheck" attribute to a command and pass selected Item object to command method for updating selected set.

Selected items for checkboxes

<window apply="org.zkoss.bind.BindComposer"
	viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.CustomMultipleSelectionsVM ')">
	<vlayout id="vlayout" children="@load(vm.itemList)">
		<template name="children">
			<checkbox label="@load(each.name)"
				onCheck="@command('pick', checked=self.checked, picked=each)">
			</checkbox>
		</template>
	</vlayout>
	<label value="@bind(vm.pickedItemSet)"/>
</window>
  • Line 6: Pass arguments in command binding to update selected items.

Choose a Component's Model Type

As we mentioned in previous section, we could bind a Java Collection type property to be a component's model . An internal converter will convert it to LIstModel automatically for us. It is convenient but one of its cost is "scroll position lost when collection size changes". If you scroll to an item of index 50 and delete (or add) an item. The component will force to scroll back to first item because it has to re-convert and re-render all items. One simple way to handle this is to set Listbox as "paging" mold. Another way is to wrap the collection object with ListModelList.

One more issue about performance is Listbox re-renders all items [1] when using Java collection object as the model in default mold. When you change the collection property by removing or adding an item, you have to notify its change. This will trigger Listbox to re-render from the beginning. But if you use ListModelList as a model and modify it by calling its method add() and remove(). ListModelList will update to client automatically without explicitly specifying property notification. And this update only re-renders the affected (added or removed) item instead of all items which is more efficient. [2]

But if you have huge amount of data, and getting them takes unbearable long time. You should implement your own model object which we will talk in Advance section.

Use Java List as a model

public class ModelTypeVM extends SingleSelectionVM{
	protected List<Item> itemList;

	@Init
	public void init(){
		pickedItem = new Item();
		itemService = new ItemService(100);
		itemList = itemService.getAllItems();  //return a java.util.List
		...
	}
	
	@Command @NotifyChange({"itemList","pickedItem"})
	public void add(){
		itemList.add(pickedItem);
		pickedItem = new Item();
	}
	
	@Command  @NotifyChange({"itemList","pickedItem"})
	public void delete(){
		int index = itemList.indexOf(pickedItem);
		if (index != -1){
			itemList.remove(index);
			pickedItem = new Item();
		}
		
	}

	//omit setter and getter
}
  • Line 8: itemList is java.util.List object.
  • Line 12, 18: Because we change the property "itemList", we should notify binder to reload it.


Use ListModelList as a model

public class ModelTypeVM extends SingleSelectionVM{

	private ListModelList<Item> itemListModel;
	
	@Init
	public void init(){
		pickedItem = new Item();
		itemService = new ItemService(100);
		...
		itemListModel = new ListModelList<Item>(itemService.getAllItems());
	}

	@Command @NotifyChange("pickedItem")
	public void modelAdd(){
		itemListModel.add(pickedItem);
		pickedItem = new Item();
	}
	
	@Command  @NotifyChange("pickedItem")
	public void modelDelete(){
		int index = itemListModel.indexOf(pickedItem);
		if (index != -1){
			itemListModel.remove(index);
			pickedItem = new Item();
		}
		
	}	

	//omit setter and getter
}
  • Line 10: We can simply wrap a java.util.List by passing it into ListModelList's constructor.
  • Line 13, 20: Because ListModelList will update change to clients automatically, we can ignore to notify the property "itemListModel".


  1. If ROD enabled, it only renders visible items .
  2. If you still specify property notification for ListModelList property, Listbox renders all items (If ROD is disabled). Then you won't gain performance improvement.

Version History

Last Update : 2022/01/12


Version Date Content
6.5.0 September 2012 Further explanation about collection and selection.




Last Update : 2022/01/12

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