Data binding
From Documentation
Basic Concept
Data binding is a mechanism that automates the data-copy plumbing codes between UI components and the data source. Application developers only have to tell data binding manager about the associations between UI components and the data source. Then, data -binding manager will do the loading (load data from the data source to UI components)and saving (save data from UI component into the data source) job automatically.
Adding a Data Source
First of all, we have to define a data source as a data bean. In this example, we use Person class as an example that holds the information of a person, say, first name, and last name.
Person.java
public class Person { private String _firstName = ""; private String _lastName = ""; // getter and setters public void setFirstName(String firstName) { _firstName = firstName; } public String getFirstName() { return _firstName; } public void setLastName(String lastName) { _lastName = lastName; } public String getLastName() { return _lastName; } public void setFullName(String f) { // do nothing. } public String getFullName() { return _firstName + " " + _lastName; } }
Then, declare a data source in the page as follows,
<zscript><![CDATA[ //prepare the example person object Person person = new Person(); person.setFirstName("Tom"); person.setLastName("Hanks"); ]]> </zscript>
Activates Data Binding Manager
Activates Data Binding Manager by defining the page Initializer at the top of the page.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>This initiator class do following things:
- Creates an AnnotateDataBinder instance.
- Sets the AnnotateDataBinder instance as a variable with the name "binder" stored in the component as specified in arg0 "component-path".(if arg0 is not specified, use Page instead.)
- Calls DataBinder.loadAll() to initiate all UI components from the associated data source.
Associate UI Components with Data Source
After adding source data, activating data-binding manager, you have to define required UI objects, and associate them with the data source. Use ZUML annotation expression to tell data-binding manager the relationship between the data source and UI components. Its usage is very straightforward, simply declare the annotation into the component's attribute directly.
<component-name attribute-name="@{bean-name.attribute-name}"/>
- component-name represents a UI component
- attribute-name represents an attribute of UI component or the data source
- bean-name represents a data source
We use Grid as an example, and associate it with the data source, a Person instance. In this example, data-binding manager will automates the synchronization between UI components and the data source automatically.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?> <window> <zscript><![CDATA[ //prepare the example person object Person person = new Person(); person.setFirstName("George"); person.setLastName("Bush"); ]]> </zscript> <grid width="400px"> <rows> <row> First Name: <textbox value="@{person.firstName}"/></row> <row> Last Name: <textbox value="@{person.lastName}"/></row> <row> Full Name: <label value="@{person.fullName}"/></row> </rows> </grid> </window>
When to Load Data from Data Source to UI
Data Binding Manager is triggered by events, or users' activities. Thus, you must specify events in the ZUML annotation expression with load-when tag expression to tell Data Binding Manager when to load data from data source into the component's attribute.
<component-name attribute-name="@{bean-name.attribute-name,
load-when='component-id.event-name'}"/>
- component-id represents the ID of a UI component
- event-name represents the event name
Multiple definition is allowed and would be called one by one.
In the following example, we demonstrate an example that the fullname of a Person will be updated automatically once his/her first name or last name has been modified.
Data Binding Manager will re-load value of Label whose id is fullName, from person.fullName when the either the value of Textbox whose id is firstName or lastName has been changed, in other words, onChange event is triggered.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?>
<window> <zscript><![CDATA[ //prepare the example person object Person person = new Person(); person.setFirstName("George"); person.setLastName("Bush"); ]]> </zscript> <grid width="400px"> <rows> <row> First Name: <textbox id="firstName" value="@{person.firstName}"/> </row> <row> Last Name: <textbox id="lastName" value="@{person.lastName}"/> </row> <row> Full Name: <label id="fullName" value="@{person.fullName, load-when='firstName.onChange,lastName.onChange'}"/> </row> </rows> </grid> </window>
When to Save Data from UI Components to the Data Source
Data Binding Manager is triggered by events, or users' activities. Thus, you must specify events in the ZUML annotation expression with save-when tag expression to tell Data Binding Manager when to save the attribute of the component into the data source.
<component-name attribute-name="@{bean-name.attribute-name,save-when='component-id.event-name''}"/>
- component-id represents the ID of a UI component
- event-name represents the event name
Multiple definition is allowed and would be called one by one.
In the following example, Data Binding Manager will save the attribute "value" of Textbox "firstName" into "person.firstName" when the Textbox itself fires "onChange" event.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?> <window width="500px"> <zscript><![CDATA[ Person person = new Person(); person.setFirstName("Bill"); person.setLastName("Gates"); ]]> </zscript> <listbox> <listhead> <listheader label="First Name" width="100px"/> <listheader label="Last Name" width="100px"/> <listheader label="Full Name" width="100px"/> </listhead> <listitem> <listcell> <textbox id="firstName" value="@{person.firstName, save-when='self.onChange'}"/> </listcell> <listcell> <textbox id="lastName" value="@{person.lastName, save-when='self.onChange'}"/> </listcell> <listcell label="@{person.fullName}"/> </listitem> </listbox> </window>
Associate the Same Data Source with Multiple UI Components
One data source could be associated with multiple UI components. Once the data source had been modified, those associated UI components will be updated automatically by Data Binding Manager.
In the following example. we use ZUML annotation expression to associate a data source, a Person instance, "selected" with multiple UI components, including Listbox, and Grid. Once the user selects an item in the Listbox, the Grid will display information of the selected person accordingly.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?> <window width="500px"> <zscript><![CDATA[ //prepare the example person Person selected = new Person(); ]]> </zscript> <listbox rows="4" selectedItem="@{selected}"> <listhead> <listheader label="First Name" width="100px" /> <listheader label="Last Name" width="100px" /> <listheader label="Full Name" width="100px" /> </listhead> <listitem> <listcell label="George" /> <listcell label="Bush" /> </listitem> <listitem> <listcell label="Bill" /> <listcell label="Gates" /> </listitem> </listbox> <!-- show the detail of the selected person --> <grid> <rows> <row> First Name: <textbox value="@{selected.firstName}" /> </row> <row> Last Name: <textbox value="@{selected.lastName}" /> </row> </rows> </grid> </window>
Associate UI Components with a Collection
It can be very useful to associate a collection with a UI components, and Data Binding Manager will convert the collection into UI components accordingly.
- Prepare the data source of Collection
- Associate the collection with model attribute of those supported UI components, ex. Listbox, Grid, and Tree.
- Define a template of UI component
- Define a variable, whatever you want, to represent each instance in the Collection with self attribute.<component-name self="@{each='variable-name'}"/> The variable-name could only be seen by component-name and its child components.
- Associate UI components with the variable
<component-name attribute-name="@{variable-name.attribute-name}"/>
In the following example, we demonstrate how to associate a collection with Listbox to display a list of persons.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?> <window width="500px"> <zscript><![CDATA[ //prepare the example persons List int count = 30; List persons = new ArrayList(); for(int j= 0; j < count; ++j) { Person personx = new Person(); personx.setFirstName("Tom"+j); personx.setLastName("Hanks"+j); persons.add(personx); } ]]> </zscript> <listbox rows="4" model="@{persons}"> <listhead> <listheader label="First Name" width="100px" /> <listheader label="Last Name" width="100px" /> <listheader label="Full Name" width="100px" /> </listhead> <!-- define variable person here--> <listitem self="@{each='person'}"> <listcell> <textbox value="@{person.firstName}" /> </listcell> <listcell> <textbox value="@{person.lastName}" /> </listcell> <listcell label="@{person.fullName}" /> </listitem> </listbox> </window>
Customization of Conversion between the Data Source and UI Components
If you want to do the conversion between the Data Source and UI components by yourself, you could specify the class name of the converter in the converter tag expression to tell Data Binding Manager to use your own way to do the conversion between the data source and UI components.
<component-name attribute-name="@{bean-name.attribute- name,converter='class-name'}"/>
Multiple definition is NOT allowed and the later defined would override the previous defined one.
- Define a class that implements TypeConverter with the following methods
- coerceToUI, converts an value object into UI component attribute type.
- coerceToBean, converts an value object to bean property type.
- Specify the class name of converter into the converter tag expression
In the following example, we demonstrate you how to convert a boolean value into different images instead of pure text.
First of all, you have to define a class that implements TypeConverter. myTypeConverter converts the boolean into different images accordingly.
import org.zkoss.zkplus.databind.TypeConverter; import org.zkoss.zul.Listcell; public class myTypeConverter implements TypeConverter { public Object coerceToBean(java.lang.Object val, org.zkoss.zk.ui.Component comp) { return null; } public Object coerceToUi(java.lang.Object val, org.zkoss.zk.ui.Component comp) { boolean married = (Boolean) val; if (married) ((Listcell) comp).setImage("/img/true.png"); else ((Listcell) comp).setImage("/img/false.png"); return null; } }
Specify myTypeConverter with the convert tag expression to be associated with the married attribute of Person instance.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?> <window width="500px"> <zscript><![CDATA[ //prepare the example persons List List persons = new ArrayList(); persons.add(new Person("Tom", "Yeh", true)); persons.add(new Person("Henri", "Chen", true)); persons.add(new Person("Jumper", "Chen", false)); persons.add(new Person("Robbie", "Cheng", false)); ]]> </zscript> <listbox rows="4" model="@{persons}"> <listhead> <listheader label="First Name" width="100px" /> <listheader label="Last Name" width="100px" /> <listheader label="Married" width="100px" /> </listhead> <listitem self="@{each=person}"> <listcell label="@{person.firstName}" /> <listcell label="@{person.lastName}" /> <listcell label="@{person.married, converter='myTypeConverter'}" /> </listitem> </listbox> </window>
Define the Access Privilege of Data Binding Manager
For better control of data-binding manager, you can set the access mode of the attribute-name of the component-name to be both(load/save), load(load Only), save(save Only), or none(neither) .
<component-name attribute-name="@{bean-name.attribute-name,access='type-name'}"/>
- type-name represents a certain kind of access mode
Multiple definition is NOT allowed and the later defined would override the previous defined one.
In the following example, if the value of Textbox, "firstName", and "lastName" has been modified, the value of Listcell, "fullname", will remain unchanged because Data Binding manager is informed not to update its value.
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?> <window width="500px"> <zscript><![CDATA[ Person person = new Person(); person.setFirstName("Bill"); person.setLastName("Gates"); ]]> </zscript> <listbox> <listhead> <listheader label="First Name" width="100px" /> <listheader label="Last Name" width="100px" /> <listheader label="Full Name" width="100px" /> </listhead> <listitem> <listcell> <textbox id="firstName" value="@{person.firstName}" /> </listcell> <listcell> <textbox id="lastName" value="@{person.lastName}" /> </listcell> <listcell id="fullName" label="@{person.fullName, access='none'}" /> </listitem> </listbox> </window>
See Also
From Java Doc:
- AnnotateDataBinder very clear explanation of databinding.
From ZK Forum:
- Listbox databinding for ZKmobile cannot find associated CollectionItem
- How do I bind data to a radiogroup?
- Which Components support Databinding?
- Databind and variable in forward
- Databinding with GenericAutowireComposer
- Databinding with radiogroups
- How to trigger binder save without save-when
- Databinding/dynamic listbox dynamic columns
- Classes that extends from AnnotateDataBinderInit are not working to mine
- Tree drag'n drop and binding
- Combobox not binding when cleared
- Constraints and databinding
- Binding Date/Timestamp to Datebox AND Timebox
- Problem with Autobinding and WrongValueException exception
- To do data validation while Data Binding
- MVC and databinding
- If model changed after binding, you can use binder.loadAll()
- How to bind a ManyToMany Relation (Jointable) with a multiselect Listbox
From ZK Smalltalk:
- ListModel and Databinding Enhanced Combobox
- Y-Grid Support Drag-Drop and DataBinding
- ZK CRUD Demo Application
- Data Binding Collection Data with ZUML Annotations
- Zero Code Data Binding with ZUML Annotations
- Two-way Data Binding with ZUML Annotations
- Data-Binding Implementation for ZK
- Classes that extends from AnnotateDataBinderInit are not working to mi