9 MVVM - Reference Documentation
Authors: groovyquan
Version: 0.5.1
9 MVVM
Introduction
MVVM is a variant of the Model/View/Controller(MVC) design pattern that helps achieve separation of data and logic from presentation easily. It isolates the View layer and the Model layer avoiding tight coupling between the View and Controller layer. UI designers and programmers can do their jobs in parallel and independently. Thus the productivity is enhanced and project schedule is on track. Here are some background information for users who are not familiar with the term MVVM. For those who are already familiar with what MVVM is, please skip this section.What is MVVM?
MVVM represents Model, View, and ViewModel which is identical to the Presentation Model introduced by Martin Fowler, a special variant of the famous MVC pattern. The ViewModel in MVVM design pattern acts like a special Controller for View in MVC pattern which is responsible for exposing data objects from the data Model to the View and for providing required action and logic for user requests from the View. The ViewModel is kind of like an abstraction of a View, which contains a View's states and behaviors. From another angle, it can also be said that the View layer is kind of like an UI projection of the ViewModel. Up until now, you might start to wonder: "Isn't that just what Controller is in MVC? Are you just inventing a new term?". Of course not. Another and a more important feature of ViewModel in MVVM pattern is that a ViewModel 'knows nothing about View's visual elements'. This is the key that makes MVVM design pattern different from other MVC variances.Why MVVM?
Separation of data and logic from presentationThe key feature that the ViewModel knows nothing about View's visual elements guarantees the one way dependency from View to the ViewModel thus avoiding mutual programming ripple effects between UI and the ViewModel. Consequently, it brings the following advantages:- As long as the contract is set(what data to show and what actions to proceed), the UI design and coding of ViewModel can be implemented in parallel and independently. Either side will not block the other's way.
- UI design can be easily changed from time to time without changing the ViewModel as long as the contract does not change.
- It will be easier to design different views for different devices with a common ViewModel. For a desktop browser with a bigger screen, more information can be shown on one page; while for a smart phone with limited display space, designing a wizard-based step-by-step operation UI can be done without the need to change (much of) the ViewModel.
- Since ViewModel does not "see" presentation layer, users can unit-test the ViewModel functions easily without UI elements.
What does this have to do with ZK Bind?
Implementation-wise, no matter how, someone in the system has to help synchronizing data between View and ViewModel layers. Also, this someone has to accept the user request from the View layer and bridge the request to the action and logic provided by the ViewModel layer. This someone, the kernel part of the MVVM design pattern, is either data synchronising codes written by the application developers themselves or a data binding system provided by the framework. In ZK 6, ZK Bind is this key data binding infrastructure of the MVVM design pattern. It would be easier to explain the concept with an example which has two different implementations. One in Model-View-Presenter(MVP) design pattern and another in Model-View-ViewModel(MVVM).- Model-View-Presenter(MVP) is another variance of MVC design pattern. If you are not aware of what that is, don't worry, just view the example codes.
9.1 Hello MVVM
The example - On Demand Hello World!
The use case: Press a button on the screen and show on the screen a message provided by the Model, say, "Hello World!".MVP Implementation

- A user push a button(an action) on the screen. A corresponding event is fired.
- An event listener on the back-end handles this event.
- Access data and so on.
- Then UI elements were changed accordingly to provide visual feedback to the user.
View
: helloMVP.gsp<z:window apply="HelloComposer"> <z:label id="lbl"/> <z:button id="btn" label="Show"/> </z:window>
Presenter
: HelloComposer.groovyclass HelloComposer{
def lbl def onClick_btn(Event event) {
lbl.setValue("Hello World!")
}
}
MVVM Implementation

- A user presses a button on the screen.
- A corresponding event is fired to the binder.
- The binder finds the corresponding action logic in the ViewModel and calls it.
- The action logic accesses data from Model layer and updates corresponding properties of the ViewModel.
- ViewModel notify the binder that some properties have been changed.
- Per what properties have been changed, the binder loads data from the ViewModel.
- Binder then updates the corresponding UI controls to provide visual feedback to the user.
- Which UI event is used to proceed which action logic? (so binder knows what method to call).
- Which UI attribute is used to show which data? (so binder knows what to load and what to update).
- Which UI attribute is used to input into which data? (so binder knows what property to save).
View
: helloMVVM.gsp
<z:window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('demo.HelloVM')"> <z:label value="@bind(vm.message)"/> <z:button label="Show" onClick="@command('showHello')"/> </z:window>
ViewModel
: HelloVM.groovy
class HelloVM { private String message public String getMessage() { return message } @Command @NotifyChange("message") public void showHello() { message = "Hello World!" }}
- When end user press the "Show" button, the onClick event is fired to binder.
- The binder finds that the command name in the ViewModel which is "showHello" as specified in the ZK annotation @command('showHello').
- The binder calls the showHello() method in the HelloVM and changes the message property. Note that the @NotifyChange("message") Java method annotation on showHello() method in HelloVM. It tells the binder that the property message in the HelloVM will be changed if this command is called.
- The binder then finds that the attribute value of component label is associated with the changed message property of the HelloVM(as specified in ZK annotation @bind(vm.message)). So it loads data from the property vm.message and updates the label's value attribute. The new message "Hello World!" is then shown on the screen and provides the visual feedback to the end user.
The example revised -- Pop Up the Message
Customers tend to change their minds when they see the real screen. Say, after you demonstrated the program to a customer and he/she said, "Well, I thought the Hello World! message should be in a pop up window...". No problem, let's prepare a modal window and put the message in the modal window. Easy.Revised MVP Implementation
View: helloMVP2.gsp<z:window apply="Hello2Composer"> <z:button id="btn" label="Show"/> <z:window id="popwin" title="Hello" width="300px" height="200px" visible="false"> <z:hbox align="center" pack="center" hflex="true" vflex="true"> <z:label id="lbl"/> </z:hbox> </z:window> </z:window>
class Hello2Composer extends GenericForwardComposer {
private Label popwin_lbl
private Window popwin Hello2Composer() {
super('_' as char)
} public void onClick_btn(Event event) {
popwin_lbl.setValue("Hello World!")
popwin.doModal()
}
}
Revised MVVM Implementation
View: helloMVVM2.gsp<z:window apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('demo.HelloVM')"> <z:button label="Show" onClick="@command('showHello')"/> <z:window title="Hello" width="300px" height="200px" mode="modal" visible="@bind(not empty vm.message)"> <z:hbox align="center" pack="center" hflex="true" vflex="true"> <z:label value="@bind(vm.message)"/> </z:hbox> </z:window> </z:window>
See more
Small Talks/2011/November/Hello ZK MVVM9.2 Design your first MVVM page
Case scenario
I will use a search example to show how you can archive MVVM design pattern in Grails ZK UI 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.Create Domain class
grails create-domain-class item
Item
Domain class:
class Item { static transients = ['totalPrice'] String name String description double price int quantity Item() { } public Item(String name, String description, double price, int quantity) { this.name = name this.description = description this.price = price this.quantity = quantity } public double getTotalPrice() { return price * quantity } static constraints = { } }
Create the search
service
grails create-service search
class SearchService { List<Item> search(String fitler) { if ("*".equals(fitler)) return Item.list() return Item.findAllByNameIlike("%${fitler}%") } }
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. 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.grails create-view-model demo.search
class SearchVM { //the search condition String filter = "*"; //the search result ListModelList<Item> items; //the selected item Item selected; def searchService public String getFilter() { return filter; } @NotifyChange public void setFilter(String filter) { this.filter = filter; } public ListModel<Item> getItems() { if(items==null){ doSearch(); } return items; } @NotifyChange(["items", "selected"]) @Command public void doSearch(){ items = new ListModelList<Item>(); items.addAll(searchService.search(filter)); selected = null; } public Item getSelected() { return selected; } @NotifyChange public void setSelected(Item selected) { this.selected = selected; } Converter totalPriceConverter = null; 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; } }; }}
create-view-model
,we need to notify ZK Bind that the properties of this View Model
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 set the property, it is noticed to reload components that bind to this property. Add @NotifyChange on a command method, after Binder executes the method, it is noticed to reload components that bind to these properties.
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.
With @NotifyChange, it let binder knows when to reload the view automatically.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). Here is the designed View that will be use in this article to bind with View Model:
Apply the BindComposer
Now, we have to create a ‘search.gsp’ 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.<z:window title="Search Storage Item" border="normal" width="600px" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('zkui.mvvm.SearchVM')" > ...</z:window>
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.View : search.gsp<z:textbox value="@bind(vm.filter)" instant="true"/> <z:button label="Search" disabled="@bind(empty vm.filter)"/>
Binding the listbox with search result
In ZK 6, we introduce a new feature called ‘template’, it is a perfect matching when binding a collection. We will have a listbox and a template to show the search result.View : search.gsp<z:listbox model="@bind(vm.items)" selectedItem="@bind(vm.selected)" hflex="true" height="300px"> <z:listhead> <z:listheader label="Name"/> <z:listheader label="Price" align="center" width="80px"/> <z:listheader label="Quantity" align="center" width="80px"/> </z:listhead> <z: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> </z:template> </z:listbox>
Binding the selected item
When binding the listbox, we also bind the selectedItem of the listbox to selected property of the ViewModel. You do not need to worry about selectedItem (in which its value type is a Listitem) of listbox being the incorrect data type for 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.gsp<z:groupbox visible="@load(not empty vm.selected)" hflex="true" mold="3d"> <z:caption label="@load(vm.selected.name)"/> <z:grid hflex="true"> <z:columns> <z:column width="120px"/> <z:column/> </z:columns> <z:rows> <z:row> Description <z:label value="@load(vm.selected.description)"/> </z:row> <z:row> Price <z:label value="@load(vm.selected.price) @converter('formatedNumber', format='###,##0.00')"/> </z:row> <z:row> Quantity <z:label value="@load(vm.selected.quantity)" sclass="@load(vm.selected.quantity lt 3 ?'red':'')"/> </z:row> <z:row> Total Price <z:label value="@load(vm.selected.totalPrice) @converter(vm.totalPriceConverter)"/> </z:row> </z:rows> </z:grid> </z:groupbox>
Converter totalPriceConverter = null 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 clicking on the search button. To do so, add a @command in the onClick event of the button.<z:button label="Search" onClick="@command('doSearch')" disabled="@load(empty vm.filter)"/>
- The evaluation result of the expression result has to be a 'String',
- The string must also be the name of the command
- 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.
The all:search.gsp
<%@ page contentType="text/html;charset=UTF-8" %> <html> <head> <title>MVVM Demo</title> <z:resources/> <style type="text/css"> .z-listcell.red .z-listcell-cnt, .z-label.red { color: red; } </style> </head> <body> <z:window title="Search Storage Item" border="normal" width="600px" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm')@init('zkui6demo.SearchVM')"> <z:vbox hflex="true"> <z:hbox> Filter : <z:textbox value="@bind(vm.filter)" instant="true"/> <z:button label="Search" onClick="@command('doSearch')" disabled="@load(empty vm.filter)"/> </z:hbox> <z:listbox model="@load(vm.items)" selectedItem="@bind(vm.selected)" hflex="true" height="300px"> <z:listhead> <z:listheader label="Name"/> <z:listheader label="Price" align="center" width="80px"/> <z:listheader label="Quantity" align="center" width="80px"/> </z:listhead> <z: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> </z:template> </z:listbox> <z:groupbox visible="@load(not empty vm.selected)" hflex="true" mold="3d"> <z:caption label="@load(vm.selected.name)"/> <z:grid hflex="true"> <z:columns> <z:column width="120px"/> <z:column/> </z:columns> <z:rows> <z:row>Description <z:label value="@load(vm.selected.description)"/></z:row> <z:row>Price <z:label value="@load(vm.selected.price) @converter('formatedNumber', format='###,##0.00')"/></z:row> <z:row>Quantity <z:label value="@load(vm.selected.quantity)" sclass="@load(vm.selected.quantity lt 3 ?'red':'')"/></z:row> <z:row>Total Price <z:label value="@load(vm.selected.totalPrice) @converter(vm.totalPriceConverter)"/></z:row> </z:rows> </z:grid> </z:groupbox> </z:vbox> </z:window> </body> </html>
Initialization data
Initialization data in BootStrap.groovyclass BootStrap { def init = { servletContext -> List<Item> allItems = new ArrayList<Item>() allItems.add(new Item("P001-A", "part 001, type A", nextPrice(), nextQuantity())) allItems.add(new Item("P001-B", "part 001, type B", nextPrice(), nextQuantity())) allItems.add(new Item("P001-C", "part 001, type C", nextPrice(), nextQuantity())) allItems.add(new Item("P001-D", "part 001, type D", nextPrice(), nextQuantity())) allItems.add(new Item("P001-E", "part 001, type E", nextPrice(), nextQuantity())) allItems.add(new Item("P002-A", "part 001, type A", nextPrice(), nextQuantity())) allItems.add(new Item("P002-B", "part 001, type B", nextPrice(), nextQuantity())) allItems.add(new Item("P002-C", "part 001, type C", nextPrice(), nextQuantity())) allItems.add(new Item("P002-D", "part 001, type D", nextPrice(), nextQuantity())) allItems.add(new Item("P002-E", "part 001, type E", nextPrice(), nextQuantity())) allItems.add(new Item("P003-A", "part 001, type A", nextPrice(), nextQuantity())) allItems.add(new Item("P003-B", "part 001, type B", nextPrice(), nextQuantity())) allItems.add(new Item("P003-C", "part 001, type C", nextPrice(), nextQuantity())) allItems.add(new Item("P003-D", "part 001, type D", nextPrice(), nextQuantity())) allItems.add(new Item("P003-E", "part 001, type E", nextPrice(), nextQuantity())) allItems.add(new Item("P004-A", "part 001, type A", nextPrice(), nextQuantity())) allItems.add(new Item("P004-B", "part 001, type B", nextPrice(), nextQuantity())) allItems.add(new Item("P004-C", "part 001, type C", nextPrice(), nextQuantity())) allItems.add(new Item("P004-D", "part 001, type D", nextPrice(), nextQuantity())) allItems.add(new Item("P004-E", "part 001, type E", nextPrice(), nextQuantity())) allItems*.save() } Random r = new Random(System.currentTimeMillis()) double nextPrice() { return r.nextDouble() * 300 } int nextQuantity() { return r.nextInt(10) } def destroy = { } }