(Quick Reference)

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 presentation

The 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

  1. A user push a button(an action) on the screen. A corresponding event is fired.
  2. An event listener on the back-end handles this event.
  3. Access data and so on.
  4. 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.groovy

class HelloComposer{
    def lbl

def onClick_btn(Event event) { lbl.setValue("Hello World!") } }

The Presenter was injected with the View components Label lbl and the event listener onClick_btn(). As shown above, the characteristics of a MVP design pattern is that the Presenter(the Controller) would need to hold references to the View layer UI components so that it can update the states of the View and generate visual feedback. As in this example, the value of the Label component lbl is updated with "Hello World" and provides visual feedback to the user.

MVVM Implementation

As stated in the paragraph earlier, data binder plays the role of syncing data between UI and ViewModel.

  1. A user presses a button on the screen.
  2. A corresponding event is fired to the binder.
  3. The binder finds the corresponding action logic in the ViewModel and calls it.
  4. The action logic accesses data from Model layer and updates corresponding properties of the ViewModel.
  5. ViewModel notify the binder that some properties have been changed.
  6. Per what properties have been changed, the binder loads data from the ViewModel.
  7. Binder then updates the corresponding UI controls to provide visual feedback to the user.

Apparently, UI designer has to tell binder at least the following things:

  • 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).

In ZK Bind, ZK Annotation is utilized to do these jobs: 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!" }

}

Per this example, let's run through the program flow:

  1. When end user press the "Show" button, the onClick event is fired to binder.
  2. The binder finds that the command name in the ViewModel which is "showHello" as specified in the ZK annotation @command('showHello').
  3. 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.
  4. 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.

In this MVVM implementation with data binding system, the HelloVM is a simple POJO and refers none of the UI components. It only exposes the message contents via getMessage() and provides the necessary action logic showHello(). It does not know how these information or action logic will be used. It is the UI designer's job to decide which UI components are to be used in which layout as seen in HelloMVVM.gsp.

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>

Presenter: Hello2Composer.groovy

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() } }

The View gsp file has to be revised and the Presenter java file has to be totally rewritten. This time, it injects the pop up window popwin, the label popwin_lbl inside the pop up window and so on because the View has changed.

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>

The advantages of using MVVM design pattern comes in when customers changes their requirements on the View. UI Designers can proceed the changes independently and the HelloVM java file does not have to be changed because what the customer required was to change the way how the message is presented not the message itself. Notice that here the show/hide of the modal window is controlled whether the message is empty or not(visible="@bind(not empty vm.message)").

See more

Small Talks/2011/November/Hello ZK MVVM

9.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

Modify the 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

Modify the SearchService:

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

The above command will generate the ViewModel in grails-app/vms/SearchVM.groovy

Modify the ViewModel :

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; }

}; }

}

SearchVM is created by grails command 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>

The @bind(name=expression) here, is the syntax to assign a View Model to the binder, ‘name’ is 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 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.

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)"/>

The @bind(expression) here, is the two-way binding 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. 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’.

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>

The @load(expression) can also be applied to the model attribute of the listbox 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 the listbox binds to vm.items while the selectedItem binds to vm.selected with a template that has responsibility to render each entry of the items of vm.items. Of course, we can also use @load() in the template, or even bind it to the sclass attribute of a listcell with a flexible expression item.quantity lt 3 ? 'red' : ''. Look into item.price. It is a double number. 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 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>
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 ViewModel . Of course, a getTotalPriceConverter method needs to be prepared in ViewModel and return a Converter.

ViewModel : SearchVM.groovy

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 @command(expression) syntax in the event of a component represents the binding of an event to a command of the ViewModel. 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 and then execute the doSearch method of the ViewModel.

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.groovy

class 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 = { } }

Downloads ZK UI MVVM Demo

https://github.com/xiaochong/zkui/downloads

More...

Small Talks/2011/November/MVVM in ZK 6 - Design your first MVVM page