MVVM in ZK 6 - Design your first MVVM page"

From Documentation
(new zk bind syntax)
Line 7: Line 7:
 
=ZK 6 & MVVM=
 
=ZK 6 & MVVM=
  
In ZK 6, we introduce a whole new data binding system called ZK Bind which is easy to use, flexible and supports MVVM development model. MVVM is an UI design pattern, similar to MVC, it represents Mode, View and ViewModel. The main concept of MVVM design pattern is to separate the data and logic from the presentation.
+
In ZK 6, we introduce a whole new data binding system called ZK Bind which is easy to use, flexible and supports MVVM development model. MVVM is an UI design pattern, similar to MVC, it represents Model, View and ViewModel. The main concept of MVVM design pattern is to separate the data and logic from the presentation.
  
 
You can read ZK Bind's [[Small_Talks/2011/October/Envisage_ZK_6:_The_Next_Generation_Data_Binding_System | introduction here]]. For a short to MVVM in ZK 6, please refer to [[Small_Talks/2011/October/Hello_ZK_MVVM | this article]]
 
You can read ZK Bind's [[Small_Talks/2011/October/Envisage_ZK_6:_The_Next_Generation_Data_Binding_System | introduction here]]. For a short to MVVM in ZK 6, please refer to [[Small_Talks/2011/October/Hello_ZK_MVVM | this article]]
Line 19: Line 19:
 
==Design the View Model==
 
==Design the View Model==
  
Follow the design concept of MVVM, we should design the view model first, we should not consider the visual effect. A view model should not depend on a View, but consider the data and actions as it contract for interacting with the View. In this scenario, we need a String as a filter and a ListModelList<Item> for the search result and a doSearch() method to perform the search command. Further, we also need a selected filed to keep the item which is currently selected.
+
Following the design concept of MVVM, we should design the View Model first without considering the visual effect. A View Model should not depend on a View, but consider the data and actions as its contract for interacting with the View. In this scenario, we need a String as a filter and a ListModelList<Item> for the search result and a <tt>doSearch()</tt> method to perform the search command. Furthermore, we also need a selected field to keep the item which is currently selected.
  
 
As you can see, I am defending a View Model and it is isolated from the View, which means that it is possible to be reused by another View and even tested by pure Java code. Following is the code of the View Model.
 
As you can see, I am defending a View Model and it is isolated from the View, which means that it is possible to be reused by another View and even tested by pure Java code. Following is the code of the View Model.
Line 34: Line 34:
 
Item selected;
 
Item selected;
  
@NotifyChange({"items","selected"})
+
@Command @NotifyChange({"items","selected"})
 
public void doSearch(){
 
public void doSearch(){
 
items = new ListModelList<Item>();
 
items = new ListModelList<Item>();
Line 59: Line 59:
 
</source>
 
</source>
  
SearchVM is definitely a POJO, and because it is, we need to notify ZK Bind that the properties of this POJO were changed. In ZK Bind, it has the binder to help the binding between View and View Model. By adding <tt>@NotifyChange</tt> on a setter method of a property, after binder set the property, it is noticed to reload components that bind to this property. Add <tt>@NotifyChange</tt> on a command method, after Binder executes the method, it is noticed to reload components that bind to these properties.
+
SearchVM is definitely a POJO, and because it is, we need to notify ZK Bind that the properties of this POJO were changed. In ZK Bind, it has the binder to help the binding between View and View Model. By adding <tt>@NotifyChange</tt> on a setter method of a property, after binder sets the property, it is notified to reload components that are binded to this property. When adding <tt>@NotifyChange</tt> to a command method, after binder executes the method, it is notified to reload components that are binded to these properties. To declare a command, we have to add <tt>@Command</tt> to a command method and the method name becomes the command name by default.
  
For example, I add @NotifyChange on setFilter, when binder sets the value by setFilter(), it is noticed to reload any binding that is related to filter property of the view model. I also added @NotifyChange({“items”,”selected”}) on doSearch(), that is because when doing the search, I will search by the filter and has a new item list and also reset selected to null, so I need to notify Binder this 2 properties was changed.  
+
For example, add <tt>@NotifyChange</tt> to setFilter, when binder sets the value by <tt>setFilter()</tt>, it is notified to reload any binding that is related to filter property of the View Model. I also added <tt>@NotifyChange</tt>({"items","selected"}) on <tt>doSearch()</tt>, that is because when doing the search, I will search by the filter and has a new item list and also reset selected to null, so I need to notify binder that these 2 properties were changed. It is also required to add @Command to doSearch() and its command name would be "doSearch".
  
With @NotifyChange, it let binder knows when to reload the view automatically.
+
With <tt>@NotifyChange</tt>, it lets binder know when to reload View automatically.
  
 
==Design a View with ZK Bind==
 
==Design a View with ZK Bind==
  
After the View Mode is ready, we are now able to bind View to View Model, not only the data, but also the action, and reloading the changed data automatically (thanks the @NotifyChange in SearchVM).  
+
Now that the View Model is ready, we are able to bind View to View Model, not only the data, but also the action, and the automatic reloading of changed data (thanks to <tt>@NotifyChange</tt> in SearchVM).  
  
Here is the designed View that will be use in this article to bind with View Model:
+
Below is the designed View that will be used in this article to bind with View Model:
  
 
[[File:smalltalks-mvvm-in-zk6-view-example.png]]
 
[[File:smalltalks-mvvm-in-zk6-view-example.png]]
Line 75: Line 75:
 
==Apply the BindComposer==
 
==Apply the BindComposer==
  
Now, we have to create a ‘search.zul’ and bind it to the View Model. To do this, set the <tt>‘apply’</tt> attribute to <tt>‘org.zkoss.bind.BindComposer’</tt> as the composer, and bind the <tt>‘viewModel’</tt> to ‘SearchVM’ that was just created. BindComposer will read all the ZK Bind syntax and create a binder to bind View and View Model.
+
Now, we have to create a "search.zul" and bind it to the View Model. To do this, set the <tt>apply</tt> attribute to <tt>org.zkoss.bind.BindComposer</tt> as the composer, and bind the <tt>viewModel</tt> to "SearchVM" that was just created. BindComposer will read all the ZK Bind syntax and create a binder to bind View and View Model.
  
 
===View : search.zul===
 
===View : search.zul===
Line 81: Line 81:
 
<source lang="xml" high="2">
 
<source lang="xml" high="2">
 
<window title="Search Storage Item" border="normal" width="600px"
 
<window title="Search Storage Item" border="normal" width="600px"
     apply="org.zkoss.bind.BindComposer" viewModel="@bind(vm='org.zkoss.mvvm.examples.search.SearchVM')" >
+
     apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('org.zkoss.bind.examples.search.SearchVM')" >
 
...
 
...
  
Line 87: Line 87:
 
</source>
 
</source>
  
The <tt>@bind(name=expression)</tt> here, is the syntax to assign a View Model to the binder, <tt>‘name’</tt> is the name of the View Model which will be referenced in nested binding. <tt>‘expression’</tt> is an EL 2.2 expression and there are some rules about this expression; if the evaluated result is a ‘String’, it uses ‘String’ as a class name to create the view model whereas if the result is a ‘Class’, it creates the View Model by ‘Class.newInstance()’. Moreover, if the result is a non-primitive Object then it will use it directly, other-wise, it complains an exception.
+
The <tt>@id(name) @init(expression)</tt> here, is the syntax to assign a View Model to the binder, <tt>name</tt> is a string for the name of the View Model which will be referenced in nested binding. <tt>expression</tt> is an EL 2.2 expression and there are some rules about this expression;  
 +
*If the evaluated result is a "String", it uses "String" as a class name to create the View Model
 +
*If the result is a "Class", it creates the View Model by <tt>Class.newInstance()</tt>
 +
*If the result is a non-primitive Object, then it will use it directly, other-wise, it complains an exception.
  
 
==Binding the textbox with filter data==
 
==Binding the textbox with filter data==
  
We need a textbox to represent the filter data, when user types in the textbox, the data will automatically update to ‘vm.filter’, and we also want the search button disabled if the value of ‘vm.filter’ is empty.
+
We need a textbox to represent the filter data, when user types in the textbox, data will be automatically updated to "vm.filter". We also want the search button disabled if the value of "vm.filter" is empty.
  
 
===View : search.zul===
 
===View : search.zul===
Line 97: Line 100:
 
<source lang="xml">
 
<source lang="xml">
 
<textbox value="@bind(vm.filter)" instant="true"/>  
 
<textbox value="@bind(vm.filter)" instant="true"/>  
<button label="Search" disabled="@bind(empty vm.filter)"/>
+
<button label="Search" disabled="@load(empty vm.filter)"/>
 
</source>
 
</source>
  
The <tt>@bind(expression)</tt> here, is the <tt>two-way binding</tt> syntax to bind component’s attribute and the VM’s property together, it constructs a prompt binding, which means that the change of a component’s attribute caused by a user’s action will also be saved to the VM’s property. And any changed notification of the property will cause a reload of the component.
+
The <tt>@bind(expression)</tt> here, is the <tt>two-way binding</tt> syntax to bind component's attribute and the VM's property together, it constructs a non-conditional binding, which means that the change of a component's attribute caused by a user's action will also be saved to the VM's property. And any changed notification of the property will cause a reload of the component. It's also a shortcut syntax of "@load(expression) @save(expression)" without  condition. <tt>@load(expression)</tt> is the one-way binding for loading a value and <tt>@save(expression)</tt> is the one-way binding for saving a value.
  
In the above example, I have binded the ‘vm.filter’ to the ‘value’ attribute of the textbox, and the ‘disabled’ attribute of the button. When editing the textbox, ‘vm.filter’ will be changed and the button will immediately be enabled or disabled depending on the value of ‘vm.filter’.
+
In the above example, I have binded the "vm.filter" to the "value" attribute of the textbox, and the "disabled" attribute of the button. And I use <tt>@load(expression)</tt> on "disabled" attribute, because it needs to load value from VM's property. When editing the textbox, "vm.filter" will be changed and the button will immediately be enabled or disabled depending on the value of "vm.filter".
  
 
==Binding the listbox with search result==
 
==Binding the listbox with search result==
  
In ZK 6, we introduce a new feature called <tt>‘template’</tt>, it is a perfect matching when binding a collection. We will have a listbox and a template to show the search result.
+
In ZK 6, we introduce a new feature called <tt>template</tt>, it is a perfect match when binding a collection. We will have a listbox and a template to show the search result.
  
 
===View : search.zul===
 
===View : search.zul===
  
 
<source lang="xml" high="1,10,11">
 
<source lang="xml" high="1,10,11">
<listbox model="@bind(vm.items)" selectedItem="@bind(vm.selected)" hflex="true" height="300px">
+
<listbox model="@load(vm.items)" selectedItem="@bind(vm.selected)" hflex="true" height="300px">
 
<listhead>
 
<listhead>
 
<listheader label="Name"/>
 
<listheader label="Name"/>
Line 119: Line 122:
 
<template name="model" var="item">
 
<template name="model" var="item">
 
<listitem >
 
<listitem >
<listcell label="@bind(item.name)"/>
+
<listcell label="@load(item.name)"/>
<listcell label="@bind(item.price) @converter('formatedNumber', format='###,##0.00')"/>
+
<listcell label="@load(item.price) @converter('formatedNumber', format='###,##0.00')"/>
<listcell label="@bind(item.quantity)" sclass="@bind(item.quantity lt 3 ?'red':'')"/>
+
<listcell label="@load(item.quantity)" sclass="@bind(item.quantity lt 3 ?'red':'')"/>
 
</listitem>
 
</listitem>
 
</template>
 
</template>
Line 127: Line 130:
 
</source>
 
</source>
  
The <tt>@bind(expression)</tt> can also apply to <tt>‘model’</tt> attribute of a component. When binding to a model, we have to provide a template, which name is ‘model’, to render each item in the model. In the template, we have to set the name of ‘var’, so we could use it as an item in the template. We also introduce the <tt>@converter(expression)</tt> syntax, so you can write a ‘Converter’ to convert data to attribute when loading to component, and convert back when saving to bean.
+
The <tt>@load(expression)</tt> can also be applied to the <tt>model</tt> attribute of a component. When binding to a model, we have to provide a template, which is named "model", to render each item in the model. In the template, we have to set the name "var", so we could use it as an item in the template. We also introduce the <tt>@converter(expression)</tt> syntax, so you can write a "Converter" to convert data to attributes when loading to components, and convert back when saving to beans.
  
In the above example, the ‘model’ of listbox binds to ‘vm.items’, and the ‘selectedItem’ binds to ‘vm.selected’ with a template that has responsibility to render each item of the ‘vm.items’. Of course, we could also use @bind in the template, even bind to a ‘sclass’ attribute of a listcell with a flexible expression ‘item.quantity lt 3?’red’:’’’. Look into ‘item.price’, it is a double, I want it to be shown with an expected format, therefore a built-in converter ‘formatedNumber’ and a format argument ###,##0.00’ are used.
+
In the above example, the "model" of listbox binds to "vm.items", while the "selectedItem" binds to "vm.selected" with a template that has responsibility to render each of the items of "vm.items". Of course, we can also use <tt>@load()</tt> in the template, or even bind it to a "sclass" attribute of a listcell with a flexible expression "<nowiki>item.quantity lt 3?'red':''</nowiki>". Look into "item.price", it is a double, I want it to be shown with an expected format, therefore a built-in converter 'formatedNumber' and a format argument of '###,##0.00' are used.
  
 
==Binding the selected item==
 
==Binding the selected item==
  
When binding the listbox, we also bind the ‘selectedItem’ of listbox to ‘selected’ property of View Model. You do not need to worry about selectedItem(in which its value type is a Listitem) of listbox being the incorrect data type of the model because binder will convert it to item automatically. By the binding of ‘selectedItem’, when selecting an item in the listbox, the ‘selected’ property will be updated to the selected item and displayed in detail.
+
When binding the listbox, we also bind the "selectedItem" of the listbox to "selected" property of the View Model. You do not need to worry about selectedItem (in which its value type is a Listitem) of listbox being the incorrect data type of the model because binder will convert it to item automatically. By the binding of "selectedItem", when selecting an item in the listbox, the "selected" property will be updated to the selected item and displayed in detail.
  
 
===View : search.zul===
 
===View : search.zul===
  
 
<source lang="xml" high="1,2,10,11,12">
 
<source lang="xml" high="1,2,10,11,12">
<groupbox visible="@bind(not empty vm.selected)" hflex="true" mold="3d">
+
<groupbox visible="@load(not empty vm.selected)" hflex="true" mold="3d">
<caption label="@bind(vm.selected.name)"/>
+
<caption label="@load(vm.selected.name)"/>
 
<grid hflex="true" >
 
<grid hflex="true" >
 
<columns>
 
<columns>
Line 146: Line 149:
 
</columns>
 
</columns>
 
<rows>
 
<rows>
<row>Description <label value="@bind(vm.selected.description)"/></row>
+
<row>Description <label value="@load(vm.selected.description)"/></row>
<row>Price <label value="@bind(vm.selected.price) @converter('formatedNumber', format='###,##0.00')"/></row>
+
<row>Price <label value="@load(vm.selected.price) @converter('formatedNumber', format='###,##0.00')"/></row>
<row>Quantity <label value="@bind(vm.selected.quantity)"  sclass="@bind(vm.selected.quantity lt 3 ?'red':'')"/></row>
+
<row>Quantity <label value="@load(vm.selected.quantity)"  sclass="@load(vm.selected.quantity lt 3 ?'red':'')"/></row>
<row>Total Price <label value="@bind(vm.selected.totalPrice) @converter(vm.totalPriceConverter)"/></row>
+
<row>Total Price <label value="@load(vm.selected.totalPrice) @converter(vm.totalPriceConverter)"/></row>
 
</rows>
 
</rows>
 
</grid>
 
</grid>
Line 155: Line 158:
 
</source>
 
</source>
  
In the example above, we bind ‘visible’ attribute of groupbox with the expression ‘not empty vm.selected’, so that the groupbox will only appear visible when user selects an item. Oppositely, if no item is selected, this groupbox will not show up.
+
In the example above, we bind <tt>visible</tt> attribute of groupbox with the expression <tt>not empty vm.selected</tt>, so that the groupbox will only be visible when user selects an item. Oppositely, if no item is selected, this groupbox will not show up.
The <tt>@converter()</tt> is used again here but with some differences. The converter now comes from the View Model . Of course, a ‘getTotalPriceConverter’ method needs to be prepared in View Model and return a Converter.
+
The <tt>@converter()</tt> is used again here but with some differences. The converter now comes from the View Model . Of course, a <tt>getTotalPriceConverter</tt> method needs to be prepared in View Model and return a Converter.
  
 
===ViewModel : SearchVM.java===
 
===ViewModel : SearchVM.java===
Line 182: Line 185:
 
==Binding the button action to a command==
 
==Binding the button action to a command==
  
Now, we have to perform a command of the View Model when clicking on the search button. To do so, add a @bind in the onClick event of the button.
+
Now, we have to perform a command of the View Model when user clicks the search button. To do so, add a <tt>@command</tt> in the onClick event of the button.
  
 
<source lang="xml">
 
<source lang="xml">
<button label="Search" onClick="@bind('doSearch')" disabled="@bind(empty vm.filter)"/>
+
<button label="Search" onClick="@command('doSearch')" disabled="@load(empty vm.filter)"/>
 
</source>
 
</source>
  
The <tt>@bind(expression)</tt> syntax in the event of a component represents the binding of an event to a command of the View Model. It has some rules.  
+
The <tt>@command(expression)</tt> syntax in the event of a component represents the binding of an event to a command of the View Model. It has some rules.  
  
#The evaluation result of the expression result has to be a ‘String’,  
+
#The evaluation result of the expression result has to be a 'String',  
 
#The string must also be the name of the command
 
#The string must also be the name of the command
#View model must have an executable method that has the name of the command.
+
#View model must have an executable method that has the annotation <tt>@Command('commandName')</tt>, if value of <tt>@Command</tt> is empty, binder use the method name as command name by default.
  
In the case above, onClick is binded with a ‘doSearch’ command. When clicking on the button, binder will go through the command lifecycle <ref>I talk about command lifecycle in [http://books.zkoss.org/wiki/Small_Talks/2011/November/MVVM_in_ZK_6_-_Design_CRUD_page_by_MVVM_pattern#What_is_a_Command_Execution MVVM in ZK 6 - Design CRUD page by MVVM pattern]</ref> and execute the doSearch method of view model.
+
In the case above, <tt>onClick</tt> is binded with a <tt>doSearch</tt> command. When clicking on the button, binder will go through the command lifecycle <ref>I talked about command lifecycle in [http://books.zkoss.org/wiki/Small_Talks/2011/November/MVVM_in_ZK_6_-_Design_CRUD_page_by_MVVM_pattern#What_is_a_Command_Execution MVVM in ZK 6 - Design CRUD page by MVVM pattern]</ref> and then execute the <tt>doSearch</tt> method of the View Model.
 
<blockquote>
 
<blockquote>
 
----
 
----
Line 206: Line 209:
 
==Various View==
 
==Various View==
  
One of the advantages of MVVM is that the View Model is a contact class. It holds data, action and logic. This means, users are allowed to use various Views as long as the View can be applied to the View Model. Here is an example with various views, we can bind combox with ‘filter’, so users not only can type the text but also select by a pre-defended filter. Even on the action, it is also possible to redefine it to be performed when the filter is selected or changed.
+
One of the advantages of MVVM is that the View Model is a contract class. It holds data, action and logic. This means, users are allowed to use various Views as long as the View can be applied to the View Model. Here is an example of various views, we can bind combox with "filter", so users not only can type the text but also select by a pre-defended filter. Even on the action, it is also possible to redefine it to be performed when the filter is selected or changed.
  
 
===ViewModel : SearchVM.java ===
 
===ViewModel : SearchVM.java ===
  
 
<source lang="xml" high="1">
 
<source lang="xml" high="1">
<combobox value="@bind(vm.filter)" onSelect="@bind('doSearch')" onChange="@bind('doSearch')">
+
<combobox value="@bind(vm.filter)" onSelect="@command('doSearch')" onChange="@command('doSearch')">
 
<comboitem label="*" value="*"/>
 
<comboitem label="*" value="*"/>
 
<comboitem label="A" value="A"/>
 
<comboitem label="A" value="A"/>
Line 221: Line 224:
 
==Test a view model==
 
==Test a view model==
  
Since the View Model is definitely a POJO, it is also possible to do unit test on the View Model. Here is a very straight forward test case to test SearchVM.
+
Since the View Model is definitely a POJO, it is also possible to do a unit test on the View Model. Here is a very straight forward test case to test SearchVM.
  
 
===TestCase : SearchVMTestCase.java===
 
===TestCase : SearchVMTestCase.java===
Line 255: Line 258:
 
</source>
 
</source>
  
Of course, there will be problems in preparing the testing environment, for example, how to get the ‘SearchService’ that we are using in the example? However, this really depends on what container framework you use (for example, Spring, Seam or CDI) but this is not the topic of this article.
+
Of course, problems will occur in preparing the testing environment, including, how to get the 'SearchService' that we are using in the example? This really depends on what container framework you use (for example, Spring, Seam or CDI), however, this is not the topic of this article.
  
 
=Syntax review=
 
=Syntax review=
Line 266: Line 269:
 
! '''Explanation'''
 
! '''Explanation'''
 
|-
 
|-
| '''viewModel=@bind(name=expression)'''
+
| '''viewModel="@id(name) @init(expression)"'''
 
| Sets the View Model
 
| Sets the View Model
*Have to use with the component that has apply=org.zkoss.bind.BindComposer”, if there is no such syntax, the View Model will be set to a composer
+
*Has to be used with a component that has <tt>apply="org.zkoss.bind.BindComposer"</tt>, if there is no such syntax, the View Model will be set to a composer
*The ‘name’ is the name of the View Model
+
*The 'name' is the name of the View Model
*The ‘expression’ - if the evaluated value is a String, it uses this String as the class name to create a View Model instance.
+
*The 'expression' - if the evaluated value is a String, it uses this String as the class name to create a View Model instance.
*The ‘expression’ - if the evaluated value is a Class, it uses this Class to create a View Model instance.
+
*The 'expression' - if the evaluated value is a Class, it uses this Class to create a View Model instance.
*The ‘expression’ - if the evaluated value is not a primitive type, it uses it as a View Model instance directly.
+
*The 'expression' - if the evaluated value is not a primitive type, it uses it as a View Model instance directly.
 
|-
 
|-
| '''comp-attribute=@bind(expression)'''
+
| '''comp-attribute="@load(expression)"'''
| Two-way binding between component’s attribute and the property of an expression.
+
| One-way binding to load property of an expression to a component's attribute.
*If the attribute allows modifications by users (ex, the value of textbox), then the expression has to be a savable expression(ex, ‘vm.filter’)
+
* Users can specify binding condition in expression with arugment <tt>before</tt> and <tt>after</tt>, i.e. <tt>@load(vm.filter, after='myCommand')</tt>. The binder will load the property after executing the command.
*If the attribute is not modifiable by users (ex, the sclass of label), then the expression can be any expression that returns the correct data type of the attribute.
+
|-
*Notify change example: for the expression ‘vm.filter’, if any notification say vm. or vm.’filter’ (here vm means an instance) was changed, the attribute will be reloaded.  
+
| '''comp-attribute="@save(expression)"'''
*More complex example: for the expression ‘e.f.g.h’, if any notification say e.f.g.’h’,e.f.’g’, e.’f’ or e. was changed , the attribute of the component will be reloaded.
+
| One-way binding to save component's attribute to the property of an expression.
 +
* Users can specify binding condition in expression with argument <tt>before</tt> and <tt>after</tt>, i.e. <tt>@save(vm.filter, before='myCommand')</tt>. The binder will save the property before executing the command.
 +
|-
 +
| '''comp-attribute="@bind(expression)"'''
 +
| Two-way binding between component's attribute and the property of an expression without any condition.
 +
*It equals <tt>@load(expression) @save(expression)</tt>
 +
*If the component's attribute does not support <tt>@save</tt>, binder will ignore it automatically.
 +
*Notify change example: for the expression 'vm.filter', if any notification say vm. or vm.'filter' (here vm means an instance) was changed, the attribute will be reloaded.  
 +
*More complex example: for the expression 'e.f.g.h', if any notification say e.f.g.'h',e.f.'g', e.'f' or e. was changed , the attribute of the component will be reloaded.
 
|-
 
|-
 
|'''@converter(expression, arg = arg-expression)'''
 
|'''@converter(expression, arg = arg-expression)'''
 
| Provide a converter for a binding
 
| Provide a converter for a binding
*The ‘expression’ is used directly if the evaluated result is a Converter
+
*The 'expression' is used directly if the evaluated result is a Converter
*The ‘expression’ - if the evaluated result is a literal, then get a Converter from view model if it has a ‘getConverter(name:Stirng):Converter’ method.
+
*The 'expression' - if the evaluated result is a literal, then get a Converter from the View Model if it has a <tt>'getConverter(name:Stirng):Converter' method</tt>.
*The ‘expression’ - if the evaluated result is a string and cannot find converter from view model, then get converter from ZK Bind built-in converters.
+
*The 'expression' - if the evaluated result is a string and cannot find converter from the View Model, then get converter from ZK Bind built-in converters.
*You can pass many arguemnts to Converter when doing convert. The ‘arg-expression’ will also be evaluated before calling to the converter method.
+
*You can pass many arguments to Converter when doing convert. The 'arg-expression' will also be evaluated before calling to the converter method.
 
|-
 
|-
| '''comp-event=@bind(expression, arg =another-expression)'''
+
| '''comp-event="@command(expression, arg =another-expression)"'''
| Event-command binding of component’s event
+
| Event-command binding of component's event
 
*The evaluated result of the expression has to be a string, while the string is also the name of the command.
 
*The evaluated result of the expression has to be a string, while the string is also the name of the command.
 
*When event is fired, it will follow the ZK Bind Lifecycle to execute the command
 
*When event is fired, it will follow the ZK Bind Lifecycle to execute the command
*View Model has to provide a method which has the name of the command.
+
*View Model has to provide a method which are annotated <tt>@Command</tt> with the command name.
*If the command method has only one Map argument, it will assign a Map which contains evaluated value of arguments
 
 
|}
 
|}
  
Line 304: Line 314:
 
|-
 
|-
 
| '''@NotifyChange on setter'''
 
| '''@NotifyChange on setter'''
|Notify properties were changed after setting the property by binder
+
|Notify the binder of a bean's property(ies) changes after it calls the setter method.
*If no value exists in the annotation, notify the bean that property was changed
+
*If no value exists in the annotation, notify bean that the property was changed
*If value exists in the annotation, use the value as the property or properties (if the value is a string array), notify the bean that property or properties has been changed.
+
*If value exists in the annotation, use the value as the property or properties (if the value is a string array), notify bean that the property or properties have been changed.
 
|-
 
|-
 
| '''@NotifyChange on command method'''
 
| '''@NotifyChange on command method'''
|Notify properties were changed after doing the command by binder
+
|Notify the binder of a bean's property(ies) change after it calls the command method
*If value exists in the annotation, use the value as the property or properties (if the value is a string array), notify the bean that property or properties has been changed.
+
*If value exists in the annotation, use the value as the property or properties (if the value is a string array), notify bean that property or properties have been changed.
 +
|-
 +
| '''@Command('commanName') '''
 +
|Declare a method to correspond to a command.
 +
*Annotation's value which is optional is a String for the command name. If it's not provided, method name is used as the command name by default.
 
|}
 
|}
  

Revision as of 07:43, 6 December 2011

DocumentationSmall Talks2011NovemberMVVM in ZK 6 - Design your first MVVM page
MVVM in ZK 6 - Design your first MVVM page

Author
Dennis Chen, Senior Engineer, Potix Corporation
Date
November 9, 2011
Version
ZK 6

ZK 6 & MVVM

In ZK 6, we introduce a whole new data binding system called ZK Bind which is easy to use, flexible and supports MVVM development model. MVVM is an UI design pattern, similar to MVC, it represents Model, View and ViewModel. The main concept of MVVM design pattern is to separate the data and logic from the presentation.

You can read ZK Bind's introduction here. For a short to MVVM in ZK 6, please refer to this article

In this article, I will use a real case to show how you can write your first MVVM page with some basic ZK Bind syntax.

Case scenario

I will use a search example to show how you can archive MVVM design pattern in ZK 6 by using ZK Bind. Please imagine yourself creating a search page in which the searching of an item is done by a filter string; the search result of items is displayed in a list and when selecting on an item, it will also show the details of the selected item.

Design the View Model

Following the design concept of MVVM, we should design the View Model first without considering the visual effect. A View Model should not depend on a View, but consider the data and actions as its contract for interacting with the View. In this scenario, we need a String as a filter and a ListModelList<Item> for the search result and a doSearch() method to perform the search command. Furthermore, we also need a selected field to keep the item which is currently selected.

As you can see, I am defending a View Model and it is isolated from the View, which means that it is possible to be reused by another View and even tested by pure Java code. Following is the code of the View Model.

ViewModel : SearchVM.java

public class SearchVM {
	//the search condition
	String filter;
	//the search result
	ListModelList<Item> items;
	//the selected item
	Item selected;

	@Command @NotifyChange({"items","selected"})
	public void doSearch(){
		items = new ListModelList<Item>();
		items.addAll(getSearchService().search(filter));
		selected = null;
	}

	@NotifyChange
	public void setFilter(String filter) {
		this.filter = filter;
	}

	@NotifyChange
	public void setSelected(Item selected) {
		this.selected = selected;
	}

	//other getter…

	protected SearchService getSearchService(){
		//search service implementation..
	}
}

SearchVM is definitely a POJO, and because it is, we need to notify ZK Bind that the properties of this POJO were changed. In ZK Bind, it has the binder to help the binding between View and View Model. By adding @NotifyChange on a setter method of a property, after binder sets the property, it is notified to reload components that are binded to this property. When adding @NotifyChange to a command method, after binder executes the method, it is notified to reload components that are binded to these properties. To declare a command, we have to add @Command to a command method and the method name becomes the command name by default.

For example, add @NotifyChange to setFilter, when binder sets the value by setFilter(), it is notified to reload any binding that is related to filter property of the View Model. I also added @NotifyChange({"items","selected"}) on doSearch(), that is because when doing the search, I will search by the filter and has a new item list and also reset selected to null, so I need to notify binder that these 2 properties were changed. It is also required to add @Command to doSearch() and its command name would be "doSearch".

With @NotifyChange, it lets binder know when to reload View automatically.

Design a View with ZK Bind

Now that the View Model is ready, we are able to bind View to View Model, not only the data, but also the action, and the automatic reloading of changed data (thanks to @NotifyChange in SearchVM).

Below is the designed View that will be used in this article to bind with View Model:

Smalltalks-mvvm-in-zk6-view-example.png

Apply the BindComposer

Now, we have to create a "search.zul" and bind it to the View Model. To do this, set the apply attribute to org.zkoss.bind.BindComposer as the composer, and bind the viewModel to "SearchVM" that was just created. BindComposer will read all the ZK Bind syntax and create a binder to bind View and View Model.

View : search.zul

<window title="Search Storage Item" border="normal" width="600px"
    apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('org.zkoss.bind.examples.search.SearchVM')" >
...

</window>

The @id(name) @init(expression) here, is the syntax to assign a View Model to the binder, name is a string for the name of the View Model which will be referenced in nested binding. expression is an EL 2.2 expression and there are some rules about this expression;

  • If the evaluated result is a "String", it uses "String" as a class name to create the View Model
  • If the result is a "Class", it creates the View Model by Class.newInstance()
  • If the result is a non-primitive Object, then it will use it directly, other-wise, it complains an exception.

Binding the textbox with filter data

We need a textbox to represent the filter data, when user types in the textbox, data will be automatically updated to "vm.filter". We also want the search button disabled if the value of "vm.filter" is empty.

View : search.zul

<textbox value="@bind(vm.filter)" instant="true"/> 
<button label="Search" disabled="@load(empty vm.filter)"/>

The @bind(expression) here, is the two-way binding syntax to bind component's attribute and the VM's property together, it constructs a non-conditional binding, which means that the change of a component's attribute caused by a user's action will also be saved to the VM's property. And any changed notification of the property will cause a reload of the component. It's also a shortcut syntax of "@load(expression) @save(expression)" without condition. @load(expression) is the one-way binding for loading a value and @save(expression) is the one-way binding for saving a value.

In the above example, I have binded the "vm.filter" to the "value" attribute of the textbox, and the "disabled" attribute of the button. And I use @load(expression) on "disabled" attribute, because it needs to load value from VM's property. When editing the textbox, "vm.filter" will be changed and the button will immediately be enabled or disabled depending on the value of "vm.filter".

Binding the listbox with search result

In ZK 6, we introduce a new feature called template, it is a perfect match when binding a collection. We will have a listbox and a template to show the search result.

View : search.zul

<listbox model="@load(vm.items)" selectedItem="@bind(vm.selected)" hflex="true" height="300px">
	<listhead>
		<listheader label="Name"/>
		<listheader label="Price" align="center" width="80px" />
		<listheader label="Quantity" align="center" width="80px" />
	</listhead>
	<template name="model" var="item">
		<listitem >
			<listcell label="@load(item.name)"/>				
			<listcell label="@load(item.price) @converter('formatedNumber', format='###,##0.00')"/>
			<listcell label="@load(item.quantity)" sclass="@bind(item.quantity lt 3 ?'red':'')"/>	
		</listitem>
	</template>
</listbox>

The @load(expression) can also be applied to the model attribute of a component. When binding to a model, we have to provide a template, which is named "model", to render each item in the model. In the template, we have to set the name "var", so we could use it as an item in the template. We also introduce the @converter(expression) syntax, so you can write a "Converter" to convert data to attributes when loading to components, and convert back when saving to beans.

In the above example, the "model" of listbox binds to "vm.items", while the "selectedItem" binds to "vm.selected" with a template that has responsibility to render each of the items of "vm.items". Of course, we can also use @load() in the template, or even bind it to a "sclass" attribute of a listcell with a flexible expression "item.quantity lt 3?'red':''". Look into "item.price", it is a double, I want it to be shown with an expected format, therefore a built-in converter 'formatedNumber' and a format argument of '###,##0.00' are used.

Binding the selected item

When binding the listbox, we also bind the "selectedItem" of the listbox to "selected" property of the View Model. You do not need to worry about selectedItem (in which its value type is a Listitem) of listbox being the incorrect data type of the model because binder will convert it to item automatically. By the binding of "selectedItem", when selecting an item in the listbox, the "selected" property will be updated to the selected item and displayed in detail.

View : search.zul

<groupbox visible="@load(not empty vm.selected)" hflex="true" mold="3d">
	<caption label="@load(vm.selected.name)"/>
	<grid hflex="true" >
		<columns>
			<column width="120px"/>
			<column/>
		</columns>
		<rows>
			<row>Description <label value="@load(vm.selected.description)"/></row>
			<row>Price <label value="@load(vm.selected.price) @converter('formatedNumber', format='###,##0.00')"/></row>
			<row>Quantity <label value="@load(vm.selected.quantity)"  sclass="@load(vm.selected.quantity lt 3 ?'red':'')"/></row>
			<row>Total Price <label value="@load(vm.selected.totalPrice) @converter(vm.totalPriceConverter)"/></row>
		</rows>
	</grid>
</groupbox>

In the example above, we bind visible attribute of groupbox with the expression not empty vm.selected, so that the groupbox will only be visible when user selects an item. Oppositely, if no item is selected, this groupbox will not show up. The @converter() is used again here but with some differences. The converter now comes from the View Model . Of course, a getTotalPriceConverter method needs to be prepared in View Model and return a Converter.

ViewModel : SearchVM.java

public Converter getTotalPriceConverter(){
	if(totalPriceConverter!=null){
		return totalPriceConverter;
	}
	return totalPriceConverter = new Converter(){
		public Object coerceToBean(Object val, Component component,
				BindContext ctx) {
			return null;//never called in this example
		}
		public Object coerceToUi(Object val, Component component,
				BindContext ctx) {
			if(val==null) return null;
			String str = new DecimalFormat("$ ###,###,###,##0.00").format((Double)val);
			return str;
		}		
	};
}

Binding the button action to a command

Now, we have to perform a command of the View Model when user clicks the search button. To do so, add a @command in the onClick event of the button.

<button label="Search" onClick="@command('doSearch')" disabled="@load(empty vm.filter)"/>

The @command(expression) syntax in the event of a component represents the binding of an event to a command of the View Model. It has some rules.

  1. The evaluation result of the expression result has to be a 'String',
  2. The string must also be the name of the command
  3. View model must have an executable method that has the annotation @Command('commandName'), if value of @Command is empty, binder use the method name as command name by default.

In the case above, onClick is binded with a doSearch command. When clicking on the button, binder will go through the command lifecycle [1] and then execute the doSearch method of the View Model.


  1. I talked about command lifecycle in MVVM in ZK 6 - Design CRUD page by MVVM pattern

Show case

Various View

One of the advantages of MVVM is that the View Model is a contract class. It holds data, action and logic. This means, users are allowed to use various Views as long as the View can be applied to the View Model. Here is an example of various views, we can bind combox with "filter", so users not only can type the text but also select by a pre-defended filter. Even on the action, it is also possible to redefine it to be performed when the filter is selected or changed.

ViewModel : SearchVM.java

<combobox value="@bind(vm.filter)" onSelect="@command('doSearch')" onChange="@command('doSearch')">
	<comboitem label="*" value="*"/>
	<comboitem label="A" value="A"/>
	<comboitem label="B" value="B"/>
	<comboitem label="C" value="C"/>
</combobox>

Test a view model

Since the View Model is definitely a POJO, it is also possible to do a unit test on the View Model. Here is a very straight forward test case to test SearchVM.

TestCase : SearchVMTestCase.java

public class SearchVMTestCase {
	@Test
	public void test01(){
		SearchVM vm = new SearchVM();
		
		Assert.assertNull(vm.getItems());
		vm.doSearch();
		Assert.assertNotNull(vm.getItems());
		Assert.assertEquals(20, vm.getItems().getSize());
		
		vm.setFilter("A");
		vm.doSearch();
		Assert.assertNotNull(vm.getItems());
		Assert.assertEquals(4, vm.getItems().getSize());
		
		vm.setFilter("B");
		vm.doSearch();
		Assert.assertNotNull(vm.getItems());
		Assert.assertEquals(4, vm.getItems().getSize());
		
		vm.setFilter("X");
		vm.doSearch();
		Assert.assertNotNull(vm.getItems());
		Assert.assertEquals(0, vm.getItems().getSize());
	}
}

Of course, problems will occur in preparing the testing environment, including, how to get the 'SearchService' that we are using in the example? This really depends on what container framework you use (for example, Spring, Seam or CDI), however, this is not the topic of this article.

Syntax review

ZUL annotation syntax

Syntax Explanation
viewModel="@id(name) @init(expression)" Sets the View Model
  • Has to be used with a component that has apply="org.zkoss.bind.BindComposer", if there is no such syntax, the View Model will be set to a composer
  • The 'name' is the name of the View Model
  • The 'expression' - if the evaluated value is a String, it uses this String as the class name to create a View Model instance.
  • The 'expression' - if the evaluated value is a Class, it uses this Class to create a View Model instance.
  • The 'expression' - if the evaluated value is not a primitive type, it uses it as a View Model instance directly.
comp-attribute="@load(expression)" One-way binding to load property of an expression to a component's attribute.
  • Users can specify binding condition in expression with arugment before and after, i.e. @load(vm.filter, after='myCommand'). The binder will load the property after executing the command.
comp-attribute="@save(expression)" One-way binding to save component's attribute to the property of an expression.
  • Users can specify binding condition in expression with argument before and after, i.e. @save(vm.filter, before='myCommand'). The binder will save the property before executing the command.
comp-attribute="@bind(expression)" Two-way binding between component's attribute and the property of an expression without any condition.
  • It equals @load(expression) @save(expression)
  • If the component's attribute does not support @save, binder will ignore it automatically.
  • Notify change example: for the expression 'vm.filter', if any notification say vm. or vm.'filter' (here vm means an instance) was changed, the attribute will be reloaded.
  • More complex example: for the expression 'e.f.g.h', if any notification say e.f.g.'h',e.f.'g', e.'f' or e. was changed , the attribute of the component will be reloaded.
@converter(expression, arg = arg-expression) Provide a converter for a binding
  • The 'expression' is used directly if the evaluated result is a Converter
  • The 'expression' - if the evaluated result is a literal, then get a Converter from the View Model if it has a 'getConverter(name:Stirng):Converter' method.
  • The 'expression' - if the evaluated result is a string and cannot find converter from the View Model, then get converter from ZK Bind built-in converters.
  • You can pass many arguments to Converter when doing convert. The 'arg-expression' will also be evaluated before calling to the converter method.
comp-event="@command(expression, arg =another-expression)" Event-command binding of component's event
  • The evaluated result of the expression has to be a string, while the string is also the name of the command.
  • When event is fired, it will follow the ZK Bind Lifecycle to execute the command
  • View Model has to provide a method which are annotated @Command with the command name.

Java syntax

Syntax Explanation
@NotifyChange on setter Notify the binder of a bean's property(ies) changes after it calls the setter method.
  • If no value exists in the annotation, notify bean that the property was changed
  • If value exists in the annotation, use the value as the property or properties (if the value is a string array), notify bean that the property or properties have been changed.
@NotifyChange on command method Notify the binder of a bean's property(ies) change after it calls the command method
  • If value exists in the annotation, use the value as the property or properties (if the value is a string array), notify bean that property or properties have been changed.
@Command('commanName') Declare a method to correspond to a command.
  • Annotation's value which is optional is a String for the command name. If it's not provided, method name is used as the command name by default.

Summary

In this article, the concept of designing a MVVM page was demonstrated, it is a good design pattern since users are allowed to design a View Model that is isolated from a View, and you only concert the data and the behavior making it possible to reuse this View Model with another view and can even perform a unit test of this View Model.

I also showed how ZK Bind helps you to accomplish MVVM with ZUL. With the power of ZK Bind you can easily bind ZUL to a View Model with well-defined annotation. There will be more upcoming articles discussing more about how MVVM works in ZK 6, before then, any feedback is always welcomed.

Downloads

[zbindexamples ] : You could download the deployable war file here, it also contains example source code of this article


Comments



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