Collection and Selection
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;
}
}
- Item is a simple class that contains a property "name".
For a ViewModel's Collection 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 List or Set , ZK will wrap it as a ListModel 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.)
<grid width="400px" model="@bind(vm.itemList)">
After binding collection type property as a data source, we have to specify how to render each object of the model with <template> (ZK Developer's Reference/MVC/View/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>.
Set Iteration Variable Name
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 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:
@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 specified, it uses template that named model by default.
Template name specified.
<grid model="@bind(vm.nodes) @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.nodes) @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.nodes) @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 Developer's Reference/MVVM/Data Binding/Children Binding 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.nodes) @template(empty each.children?'menuitem':'menu')">
<template name="menu" var="node">
<menu label="@bind(node.name)">
<menupopup children="@bind(node.children) @template(empty each.children?'menuitem':'menu')"/>
</menu>
</template>
<template name="menuitem" var="node">
<menuitem label="@bind(node.name)" onClick="@command('menuClicked',node=node)" />
</template>
</menubar>
Dynamic menu bar screenshot
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 ListboxSelectionVM {
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.ListboxSelectionVM')">
<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.ListboxSelectionVM')">
<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>
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 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.
<radiogroup selectedItem="@bind(vm.pickedItem)">
<vlayout children="@load(vm.itemList)">
<template name="children">
<radio label="@load(each.name)" value="@load(each)" />
</template>
</vlayout>
</radiogroup>
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 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 ListboxSelectionVM {
private ItemService itemService = new ItemService();
private List<Item> itemList = itemService.getAllItems();
private Set pickedItemSet;
//omit getter and setter for brevity
}
Listbox with multiple selections
<window apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.ListboxSelectionVM')">
<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".
Some components don't support "selectedItems" attribute, we still can keep selection state by passing arguments.
The following ViewModel contains a set to store selected items and a command that update items in the set according to checkbox's checked status.
public class CheckboxSelectionVM {
private ItemService itemService = new ItemService();
private Set<Item> pickedItemSet = new HashSet<Item>();
@Command
@NotifyChange("pickedItemSet")
public void pick(@BindingParam("checked") boolean checked, @BindingParam("picked")Item item){
if (checked){
pickedItemSet.add(item);
}else{
pickedItemSet.remove(item);
}
}
//omit getter and setter for brevity
}
- Line 8: For details of argument annotations, please refer to ZK Developer's Reference/MVVM/Advance/Parameters.
Multiple Selections in Checkbox
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 checkbox
<window apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('org.zkoss.reference.developer.mvvm.collection.CheckboxSelectionVM')">
<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 get selected items.