Create Data Binding Programmatically"

From Documentation
Line 9: Line 9:
 
= Binder API Usage=
 
= Binder API Usage=
  
There are 3 basic steps to create data bindings:
+
There are 3 basic steps involved to create data binding:
  
 
# Initialize the binder
 
# Initialize the binder
 
# Store data objects as attributes
 
# Store data objects as attributes
#: To make data objects be available for EL expressions
+
#: To make data objects available for EL expressions
 
# Add data bindings
 
# Add data bindings
  
Line 87: Line 87:
 
* Step 1:
 
* Step 1:
 
** Line 3: Create a <javadoc>org.zkoss.bind.DefaultBinder</javadoc> to use as its Javadoc suggests.
 
** Line 3: Create a <javadoc>org.zkoss.bind.DefaultBinder</javadoc> to use as its Javadoc suggests.
** Line 17: We should initialize <tt>DefaultBinder</tt> before using it. The first parameter is root component. The second parameter is ViewModel object. In this example, the composer plays the role as a ViewModel.
+
** Line 17: We should initialize <tt>DefaultBinder</tt> before using it. The first parameter is a root component. The second parameter is ViewModel object. In this example, the composer plays the role as a ViewModel.
 
* Step 2:
 
* Step 2:
 
** Line 19: Set the data bean as an attribute of the ''Grid'', this can make the bean be accessible for EL expression by its key: <tt>person</tt>.
 
** Line 19: Set the data bean as an attribute of the ''Grid'', this can make the bean be accessible for EL expression by its key: <tt>person</tt>.
 
* Step 3:
 
* Step 3:
 
** Line 21,23: Add save- or load-binding between the person bean's property <tt>person.firstName</tt> and the <tt>firstNameBox</tt>'s <tt>value</tt> attribute. There are many parameters that we don't use in this example, so we pass <tt>null</tt>.  please refer to Javadoc: <javadoc>org.zkoss.bind.Binder</javadoc> for more details.
 
** Line 21,23: Add save- or load-binding between the person bean's property <tt>person.firstName</tt> and the <tt>firstNameBox</tt>'s <tt>value</tt> attribute. There are many parameters that we don't use in this example, so we pass <tt>null</tt>.  please refer to Javadoc: <javadoc>org.zkoss.bind.Binder</javadoc> for more details.
* Line 27: When we use load-binding or init-binding, we need to call <tt>loadComponent()</tt> to load data for all bindings inside a component which is specified at first parameter. In our example, all binding inside ''Grid'' and its children components will be loaded. The second parameter indicates loading init-binding or not, as we don't use init-binding, we set it to "false".
+
* Line 27: When we use load-binding or init-binding, we need to call <tt>loadComponent()</tt> to load data for all bindings inside a component which is specified at the first parameter. In our example, all binding inside ''Grid'' and its children components will be loaded. The second parameter indicates loading init-binding or not, as we don't use init-binding, we set it to "false".
  
  
Line 130: Line 130:
 
== Re-load Data After Change ==
 
== Re-load Data After Change ==
  
If we change the data bean, we could trigger the binder to reload the data to components with load-binding created for us. Assume that there is a "Reset" button, click the button can clear all user input. We can implement this function as follows:
+
If we change the data bean, we could trigger the binder to reload the data to components with load-binding created for us. Assume that there is a "Reset" button, by clicking the button you can clear all user input. We can implement this function like the following:
  
 
<source lang="java" high="12,13,14, 17">
 
<source lang="java" high="12,13,14, 17">
Line 161: Line 161:
 
</source>
 
</source>
  
* Line 12: We could replace <tt>person</tt> with new instance to reset it.  
+
* Line 12: We could replace <tt>person</tt> with a new instance to reset it.  
 
* Line 13: In this case, we should also replace the attribute in ''Grid''.
 
* Line 13: In this case, we should also replace the attribute in ''Grid''.
 
* Line 14: Let the binder reload the load-bindings inside the <tt>grid</tt> which is specified at first parameter.
 
* Line 14: Let the binder reload the load-bindings inside the <tt>grid</tt> which is specified at first parameter.
* Line 17: This is another way to trigger reloading data bindings, change properties and notify which properties you changed by <tt>binder.notifyChange(myBean, "propertyName")</tt>. In our example, we pass <tt>"*"</tt> as a property name which means '''all properties'''.
+
* Line 17: This is another way to trigger reload of data bindings, change properties and notify which properties you changed by <tt>binder.notifyChange(myBean, "propertyName")</tt>. In our example, we pass <tt>"*"</tt> as a property name which means '''all properties'''.
  
 
= Add Data Binding for Collections=
 
= Add Data Binding for Collections=

Revision as of 10:35, 3 January 2013

candidate page location:

  1. ZK_Developer's_Reference/MVC/Advance/Save and Load with Data Binding
  2. ZK Developer's Reference/MVVM/Advance/Create_Data_Binding_Programmatically


Overview

Under the MVC approach, getting component's attribute value by getter method requires developers to write a lot of routine codes in a composer. However, under MVVM approach, all attributes' values are saved to ViewModel's properties automatically without calling any methods because of data binding. Through using a binder to add data binding for components, we also can enjoy this benefit in a composer. This section introduces how to obtain and utilise this benefit with basic usages of a binder.

Binder API Usage

There are 3 basic steps involved to create data binding:

  1. Initialize the binder
  2. Store data objects as attributes
    To make data objects available for EL expressions
  3. Add data bindings


Assume we have a form to fill in personal information.

	<window apply="org.zkoss.reference.developer.mvvm.advance.DynamicBindingComposer" width="600px">
		<grid >
			<rows>
				<row>
					First Name:
					<textbox id="fn"/>
				</row>
				<row>
					Last Name:
					<textbox id="ln"/>
				</row>
				<row>
					Age:
					<intbox/>
				</row>
				<row spans="2">
					<div>
						<button label="Submit" />
						<button label="Reset" />
					</div>
				</row>
				<row spans="2">
					<div>
					Preview: I am <label id="fnLabel" /> <label id="lnLabel" />, <label id="ageLabel"/> years old.
					</div>
				</row>
			</rows>
		</grid>
		...
	</window>

We hope that user input can be automatically saved to a bean instead of calling getter manually. We can bind each input component to a bean's property in a composer.

3 basic steps example

public class DynamicBindingComposer extends SelectorComposer {

	private Binder binder = new DefaultBinder();

	@Wire("grid")
	private Grid grid;
	
	@Wire("#fn") 
	private Textbox firstNameBox;
	...
	private Person person;

	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		
		binder.init(comp,this, null);
		
		grid.setAttribute("person", person);
		
		binder.addPropertySaveBindings(firstNameBox, "value", "person.firstName"
				, null, null, null, null, null,null,null);
		binder.addPropertyLoadBindings(firstNameBox, "value", "person.firstName"
				, null, null, null, null, null);
		// add more data bindings...
		
		binder.loadComponent(grid, false); //load beans' data to initialize components
	}
  • Step 1:
    • Line 3: Create a DefaultBinder to use as its Javadoc suggests.
    • Line 17: We should initialize DefaultBinder before using it. The first parameter is a root component. The second parameter is ViewModel object. In this example, the composer plays the role as a ViewModel.
  • Step 2:
    • Line 19: Set the data bean as an attribute of the Grid, this can make the bean be accessible for EL expression by its key: person.
  • Step 3:
    • Line 21,23: Add save- or load-binding between the person bean's property person.firstName and the firstNameBox's value attribute. There are many parameters that we don't use in this example, so we pass null. please refer to Javadoc: Binder for more details.
  • Line 27: When we use load-binding or init-binding, we need to call loadComponent() to load data for all bindings inside a component which is specified at the first parameter. In our example, all binding inside Grid and its children components will be loaded. The second parameter indicates loading init-binding or not, as we don't use init-binding, we set it to "false".


After these data bindings are created, each time when we need to get user input, we can use the data bean directly instead of getting from a specific components as follows:

public class DynamicBindingComposer extends SelectorComposer {
	private Person person;
	...

	@Listen("onClick = button[label='Submit']")
	public void submit(){
		Messagebox.show("I am "+person.getFirstName()+" "
			+person.getLastName()+", "+person.getAge()+" years old.");
	}

	...
}


Re-load Data After Change

If we change the data bean, we could trigger the binder to reload the data to components with load-binding created for us. Assume that there is a "Reset" button, by clicking the button you can clear all user input. We can implement this function like the following:

public class DynamicBindingComposer extends SelectorComposer {

	private Binder binder = new DefaultBinder();
	
	@Wire("grid")
	private Grid grid;
	...
	private Person person;

	@Listen("onClick = button[label='Reset']")
	public void reset(){
		person = new Person();
		grid.setAttribute("person", person);
		binder.loadComponent(grid, false);
	}

	public void resetAlternative(){
		person.setFirstName("");
		person.setLastName("");
		person.setAge(0);
		binder.notifyChange(person, "*");
	}

	...
}
  • Line 12: We could replace person with a new instance to reset it.
  • Line 13: In this case, we should also replace the attribute in Grid.
  • Line 14: Let the binder reload the load-bindings inside the grid which is specified at first parameter.
  • Line 17: This is another way to trigger reload of data bindings, change properties and notify which properties you changed by binder.notifyChange(myBean, "propertyName"). In our example, we pass "*" as a property name which means all properties.

Add Data Binding for Collections

If components are dynamically created, we can't just write data binding annotation on a ZUL. In this case, we have to add data binding with a binder.

Assume that we hope end users can edit an item directly in a Listbox. Hence we could put a Textbox in each Listcell and make each Textbox bind to each object of Listbox's model. This data binding cannot be made by writing annotation in a zul because those Textboxs are dynamically created.


Dev-ref-mvvm-adv-data-binding-programmatically.png
Edit in a Listcell


Data Binding in ItemRenderer

The renderer class renders the items of a component's data model. We create those components that render data beans for the Listbox in a renderer, so we can also create data binding in it.

Create data bindings in a renderer

public class DynamicCollectionBindingComposer extends SelectorComposer {

	private Binder binder = new DefaultBinder();

	@Wire("listbox")
	private Listbox listbox;
	private ListModelList<Person> personList;
	...
	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		
		//initialize binder
		...
		//set data bean as an attribute
		...
		//add data binding
		...
		listbox.setModel(personList);
		listbox.setItemRenderer(new MyListboxRenderer());
		
		//load beans' data to initialize components
	}
	...

	class MyListboxRenderer implements ListitemRenderer{

		public void render(Listitem listitem, Object data, int index)
				throws Exception {

			//store the data bean as an attribute which can be accessed by EL expression
			listitem.setAttribute("bean", data);
			
			//first name
			Listcell fnCell = new Listcell();
			listitem.appendChild(fnCell);
			Textbox fnBox = new Textbox();
			fnBox.setInplace(true);
			fnCell.appendChild(fnBox);
			binder.addPropertyLoadBindings(fnBox, "value", "bean.firstName"
					, null, null, null, null, null);
			binder.addPropertySaveBindings(fnBox, "value", "bean.firstName"
					, null, null, null, null, null, null, null);

			//last name
			...

			//age
			...

			//delete button
			...
		}
		
	}
}
  • Line 20: Use our custom renderer in the Listbox.
  • Line 33: Set data bean as an attribute of a Listitem to make it available for EL expression, bean.propertyname.
  • Line 40,42: Add a data binding between a Textbox's value attribute and a property bean.firstName.
  • Other data bindings for "last name" and "age" are created similarly as "first name".

After completing these data bindings, when users edit in a Textbox inside the Listbox, the data is automatically saved to the binding bean of the model.