Create Data Binding Programmatically"
(28 intermediate revisions by 2 users not shown) | |||
Line 1: | Line 1: | ||
+ | |||
+ | {{Deprecated Content}} | ||
+ | |||
= Overview = | = Overview = | ||
− | Under MVC approach, getting component's attribute value by | + | 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= | = Binder API Usage= | ||
− | There are 3 basic steps to create data | + | 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 | + | #: To make data objects available for EL expressions |
# Add data bindings | # Add data bindings | ||
Line 14: | Line 17: | ||
Assume we have a form to fill in personal information. | Assume we have a form to fill in personal information. | ||
− | <source lang="xml"> | + | <source lang="xml" high='6,10, 14'> |
<window apply="org.zkoss.reference.developer.mvvm.advance.DynamicBindingComposer" width="600px"> | <window apply="org.zkoss.reference.developer.mvvm.advance.DynamicBindingComposer" width="600px"> | ||
<grid > | <grid > | ||
Line 47: | Line 50: | ||
</source> | </source> | ||
− | We hope that user input can be automatically saved to a bean. | + | 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''' | '''3 basic steps example''' | ||
Line 80: | Line 83: | ||
} | } | ||
</source> | </source> | ||
− | * Line 3: Create a <javadoc>org.zkoss.bind.DefaultBinder</javadoc> to use as its Javadoc suggests. | + | * Step 1: |
− | * 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 3: Create a <javadoc>org.zkoss.bind.DefaultBinder</javadoc> to use as its Javadoc suggests. |
− | * 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 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. |
− | * 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. | + | * Step 2: |
− | * Line 27: | + | ** 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: | ||
+ | ** 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 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". | ||
+ | <!-- | ||
The codes to add data binding: | The codes to add data binding: | ||
<source lang="java"> | <source lang="java"> | ||
Line 95: | Line 102: | ||
</source> | </source> | ||
− | They | + | They equal to the below data binding annotation in a ZUL: |
<source lang="xml"> | <source lang="xml"> | ||
<textbox value="@bind(vm.person.firstName)"/> | <textbox value="@bind(vm.person.firstName)"/> | ||
</source> | </source> | ||
+ | --> | ||
− | + | 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: | |
− | After data | ||
<source lang="java" high='7'> | <source lang="java" high='7'> | ||
public class DynamicBindingComposer extends SelectorComposer { | public class DynamicBindingComposer extends SelectorComposer { | ||
− | + | private Person person; | |
... | ... | ||
Line 119: | Line 126: | ||
− | == Re-load | + | == Re-load Data After Change == |
− | If we change the data bean, we could trigger the binder to reload | + | 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=" | + | <source lang="java" high="12,13,14, 17"> |
public class DynamicBindingComposer extends SelectorComposer { | public class DynamicBindingComposer extends SelectorComposer { | ||
+ | private Binder binder = new DefaultBinder(); | ||
+ | |||
+ | @Wire("grid") | ||
+ | private Grid grid; | ||
... | ... | ||
+ | private Person person; | ||
+ | |||
@Listen("onClick = button[label='Reset']") | @Listen("onClick = button[label='Reset']") | ||
public void reset(){ | public void reset(){ | ||
Line 132: | Line 145: | ||
grid.setAttribute("person", person); | grid.setAttribute("person", person); | ||
binder.loadComponent(grid, false); | binder.loadComponent(grid, false); | ||
− | + | } | |
− | + | ||
− | + | public void resetAlternative(){ | |
− | + | person.setFirstName(""); | |
+ | person.setLastName(""); | ||
+ | person.setAge(0); | ||
+ | binder.notifyChange(person, "*"); | ||
} | } | ||
Line 143: | Line 159: | ||
</source> | </source> | ||
− | * Line | + | * Line 12: We could replace <tt>person</tt> with a new instance to reset it. |
− | * Line | + | * Line 13: In this case, we should also replace the attribute in ''Grid''. |
− | * Line | + | * Line 14: Let the binder reload the load-bindings inside the <tt>grid</tt> which is specified at first parameter. |
− | * Line | + | * 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= | ||
Line 152: | Line 168: | ||
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. | 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 | + | Assume that we want end users to be able to edit an item directly in a ''Listbox'', 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 ''Textbox'' are dynamically created. |
Line 159: | Line 175: | ||
− | == | + | == Data Binding in ItemRenderer == |
+ | |||
+ | The [[ZK_Developer%27s_Reference/MVC/View/Renderer| 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''' | |
+ | <source lang="java" high='20, 32,40,42'> | ||
+ | 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{ | class MyListboxRenderer implements ListitemRenderer{ | ||
Line 171: | Line 211: | ||
throws Exception { | throws Exception { | ||
− | //store as an attribute which can be accessed by EL expression | + | //store the data bean as an attribute which can be accessed by EL expression |
listitem.setAttribute("bean", data); | listitem.setAttribute("bean", data); | ||
Line 196: | Line 236: | ||
} | } | ||
+ | } | ||
</source> | </source> | ||
+ | * 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, <tt>bean.propertyname</tt>. | ||
+ | * Line 40,42: Add a data binding between a ''Textbox'''s <tt>value</tt> attribute and a property <tt>bean.firstName</tt>. | ||
+ | * Other data bindings for "last name" and "age" are created in a similar way to "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. | |
− | |||
− |
Latest revision as of 04:58, 1 April 2022
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:
- Initialize the binder
- Store data objects as attributes
- To make data objects available for EL expressions
- 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 want end users to be able to edit an item directly in a Listbox, 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 Textbox are dynamically created.
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 in a similar way to "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.