From Documentation

Jump to: navigation, search




  • Author
    Ashish Dasnurkar, Engineer, Potix Corporation
  • Date
    August 07, 2012
  • Version
    ZK 6.0.2

Contents

Overview

In ZK there are two approaches that you can utilize to create your application UI - either use XML like ZK User Interface Markup Language, ZUML or use pure Java like you would if you were developing desktop GUI application with Java Swing. This second approach to create UI with ZK can be achieved by implementing a Richlet.

Previously, we have already covered how to implement MVVM pattern if you are using ZUML files for your application views. In this article I will demonstrate how to use MVVM pattern within a Richlet where you are creating your application UI using Java. Briefly speaking, unlike normal way of creating your views as ZUML files and specifying ZK MVVM annotations on the component tags, in Richlet one normally creates UI with various components by instantiating new instances of their respective classes, assembling the component tree that would represent the UI on the client and adding various bindings on components using Binder APIs.

For this smalltalk I will use the same Order Management example used in previous smalltalk and rewrite it as a Richlet. I wouldn't go into details of MVVM pattern itself as they are covered in detail in previous smalltalks. You can also refer to MVVM section of Developer Reference for more details.

Case Scenario

The Order Management application allows you to list orders and view details of specific order. Users are also able to create a new order and modify or delete an existing one.

Design the ViewModel

In MVVM, ViewModel contains view's state but it is loosely coupled with View. The data binding mechanism synchronizes data between View and ViewModel. Keeping this in mind and since we are only re-writing Order Management example but with Java here it is easy to imagine there is no change in the ViewModel from the previous smalltalk. The Order domain object and OrderVM3 ViewModel can be reused just as it is.

Order domain object

In brief, The Order domain object contains the following fields: "id", "description", "price", "quantity", "creationDate", "shippingDate", and their respective getters and setters.

public class Order {
        String id;
        String description;
        double price;
        int quantity;
        Date creationDate;
        Date shippingDate;
        //getter
        @DependsOn( { "price", "quantity" })
        public double getTotalPrice() {
                return price * quantity;
        }
        ...
        //setter
        ...
}

OrderVM3 ViewModel

The OrderVM3 ViewModel contains the state of view like list of all orders and currently selected order in addition to MVVM Notification annotations to specify when to reload which properties to UI components. OrderVM3 ViewModel also uses OrderService to encapsulate and decouple business logic for CRUD operations on an Order from ViewModel.


public class OrderVM3 extends OrderVM2{
        //message for confirming the deletion.
        String deleteMessage;
        
        @Override
        @Command @NotifyChange({"selected","orders","deleteMessage"})
        public void deleteOrder(){
                super.deleteOrder();
                deleteMessage = null;
        }
        
        @Command @NotifyChange("deleteMessage")
        public void confirmDelete(){
                //set the message to show to user
                deleteMessage = "Do you want to delete "+selected.getId()+" ?";
        }
        
        @Command @NotifyChange("deleteMessage")
        public void cancelDelete(){
                //clear the message
                deleteMessage = null;
        }
        //getter
}

Initialize the binder

Before creating our UI we can create a Binder instance and initialize it with our ViewModel. In our Richlet we can make use of DefaultBinder which is a Binder implementation similar to AnnotateBinder. Initializing DefaultBinder is a four step process

  1. Create root component which is to be associated with binder
  2. Instantiate a binder instance
  3. Initialize it with View model and root component
  4. Set binder as an attribute on the root component
public class OrderRichlet extends GenericRichlet {


        private Binder binder;
        
        public void service(Page page) {

                //1.Create root component which is to be associated with binder
                Window window = new Window("Order Management", "normal", false);
                window.setWidth("800px");
                window.setPage(page);

                //2.Instantiate a binder instance. Use DefaultBinder
                binder = new DefaultBinder(); 
                
                //3. Initialize it with View model and root component
                binder.init(window, new OrderVM3(), null);

                //4. Set binder as an attribute on the root component
                window.setAttribute("vm", binder.getViewModel());
                ....

                binder.loadComponent(window,true); 
...

Create UI and add bindings

Following is the preview of a View binding with a ViewModel. Here a listbox is used to display a list of all orders, three buttons to perform Create New, modify & Save and Delete an order operations. Right after the list of all orders we will have a grid with labels, textboxes, dateboxes to edit the details of a selected order.

Smalltalks-mvvm-in-zk6-design-crud-page-view.png

Orders listbox

Create UI

In Richlet environment we will create this UI by instantiating instances of UI components and assembling component tree to represent above shown UI with the help of setParent/appendChild component APIs. Below is a code snippet that constructs the order listbox.

Starting with orders listbox, let's first create Listbox and its columns, associate it with ViewModel state and also instruct binder to use a template to render it correctly.

private Component buildOrderListbox(Binder binder){
		Listbox listbox = new Listbox();
		listbox.setHeight("200px");
		listbox.setHflex("true");
		Listhead head = new Listhead();
		listbox.appendChild(head);
		head.appendChild(new Listheader("Id"));
		head.appendChild(new Listheader("Quantity"));
		head.appendChild(new Listheader("Price"));
		head.appendChild(new Listheader("Creation Date"));
		head.appendChild(new Listheader("Shipping Date"));

                // add load and save bindings using Binder APIs
                ...

		// set listbox template to instruct binder how to render it
		return listbox;
	}

Add bindings

Once the Listbox component and its corresponding Listheaders are created and assembled, next we need to instruct binder how to bind it with the ViewModel state.

Property bindings

As stated above, OrderVM3 ViewModel contains the View's state in the form of the list of all orders in the system. We can use Binder API Binder.addPropertyLoadBindings(Component, String, String, String[], String[], Map, String, Map) to add load binding for orders property of ViewModel with model attribute of listbox as shown below

    binder.addPropertyLoadBindings(listbox, "model", "vm.orders", null, null, null, null, null);

Similarly, we can also add load binding for selected property of viewmodel with selectedItem of listbox as shown below

    binder.addPropertyLoadBindings(listbox, "selectedItem", "vm.selected", null, null, null, null, null);

However, whenever user changes the selected order in the listbox we wish the ViewModel state to be updated to indicate currently selected order. To achieve this we can use Binder.addPropertySaveBindings(Component, String, String, String[], String[], Map, String, Map, String, Map) to add save binding for selected whenever selectedItem changes on a listbox component.

    binder.addPropertySaveBindings(listbox, "selectedItem", "vm.selected", null, null, null, null, null,null,null);

Template bindings

The last step is to create and associate a template with listbox which later can be utilized by the binder to render the listbox. Here we will implement listbox template for our orders listbox by implementing Template interface and override Template.create(Component, Component, VariableResolver, Composer) API to define how child components are created and add bindings to them.

class ListboxTemplate implements Template {

        @SuppressWarnings("rawtypes")
        public Component[] create(Component parent, Component insertBefore,
                        VariableResolver resolver, Composer composer){
                
                //create template components & add binding expressions
                Listitem listitem = new Listitem();
                Listcell idCell = new Listcell();
                listitem.appendChild(idCell);
                binder.addPropertyLoadBindings(idCell, "label", "item.id", null, null, null, null, null);
                Listcell quantityCell = new Listcell();
                listitem.appendChild(quantityCell);
                binder.addPropertyLoadBindings(quantityCell, "label", "item.quantity", null, null, null, null, null);
                Listcell priceCell = new Listcell();
                listitem.appendChild(priceCell);
                binder.addPropertyLoadBindings(priceCell, "label", "item.price", null, null, null, SYS_NUMBER_CONVERTER, formatedNumberArg);
                Listcell creationDateCell = new Listcell();
                listitem.appendChild(creationDateCell);
                binder.addPropertyLoadBindings(creationDateCell, "label", "item.creationDate", null, null, null, SYS_DATE_CONVERTER, formatedDateArg);
                Listcell shippingDateCell = new Listcell();
                listitem.appendChild(shippingDateCell);
                binder.addPropertyLoadBindings(shippingDateCell, "label", "item.shippingDate", null, null, null, SYS_DATE_CONVERTER, formatedDateArg);

                //append to the parent
                if (insertBefore ==null){
                        parent.appendChild(listitem);
                }else{
                        parent.insertBefore(listitem, insertBefore);
                }
                
                Component[] components = new Component[1];
                components [0] = listitem;
                
                return components;
        }
        public Map<String, Object> getParameters(){
                
                Map<String,Object> parameters = new HashMap<String, Object>();
                //set binding variable
                parameters.put("var","item");
                
                return parameters;
        }
}

As shown above, template consists of one Listitem and several Listcell components to display particular order's properties. We create instance of Listitem and append Listcell instance to it.We also add load bindings to individual Listcell instance by calling binder's addPropertyLoadBindings API to bind item variable's properties with label attribute of Listcell components. This will cause binder to load item variable property values into individual Listcell label attribute. Finally we set this template to our orders listbox as shown below

    listbox.setTemplate("model", new ListboxTemplate());

These above two code snippets are equivalent to following ZUML if we were to define UI and specify annotations in a ZUML file.

<listbox model="@load(vm.orders)" selectedItem="@bind(vm.selected)" hflex="true" height="200px">
        ...
        <template name="model" var="item">
                <listitem >
                        <listcell label="@load(item.id)"/>                              
                        <listcell label="@load(item.quantity)"/>
                        <listcell label="@load(item.price) @converter('formatedNumber', format='###,##0.00')"/>
                        <listcell label="@load(item.creationDate) @converter('formatedDate', format='yyyy/MM/dd')"/>
                        <listcell label="@load(item.shippingDate) @converter('formatedDate', format='yyyy/MM/dd')"/>
                </listitem>
        </template>
</listbox>

CRUD Toolbar

CRUD toolbar consists of three buttons for creating New order, update or delete selected one.

Create UI components

We create a Toolbar instance and add three Button instances for New, Save and Delete operations as shown below.

private Toolbar buildToolbar(Binder binder){
        Button newButton = new Button("New");
        Button saveButton = new Button("Save");
        Button deleteButton = new Button("Delete");

        Toolbar toolbar = new Toolbar();
        toolbar.appendChild(newButton);
        toolbar.appendChild(saveButton);
        toolbar.appendChild(deleteButton);
        ...

Add bindings

Next we add command bindings on these buttons by using addCommandBindings API of Binder as shown below.

        binder.addCommandBinding(newButton, Events.ON_CLICK, "'newOrder'", null);
        binder.addCommandBinding(saveButton, Events.ON_CLICK, "'saveOrder'", null);
        binder.addPropertyLoadBindings(saveButton, "disabled", "empty vm.selected", null, null, null, null, null);
        binder.addCommandBinding(deleteButton, Events.ON_CLICK, "empty vm.selected.id?'deleteOrder':'confirmDelete'", null);
        binder.addPropertyLoadBindings(deleteButton, "disabled", "empty vm.selected", null, null, null, null, null);

Here we are binding ViewModel's newOrder command with newButton component's onClick event. Whenever end user clicks on New button, binder will invoke newOrder() in ViewModel.


Order details form

Finally we will have an Order details form to input new order details or update details of a selected order. The basic idea is to bind selected ViewModel property with form and its children components. The details form should be only shown when ViewModel's selected is not empty. This can be done as shown below.

        Groupbox form = new Groupbox();
        // bind viewmodel selected property with visible attribute of our form
        binder.addPropertyLoadBindings(form, "visible", "not empty vm.selected", null, null, null, null, null);

This is how it works - Whenever user clicks New button, ViewModel will trigger newOrder() method in ViewModel which will create a new Order domain object instance, add to orders listbox and set selected state to indicate this new order. - Similarly, as explained above we have a save binding for selectedItem of listbox so whenever user selects an order in listbox ViewModel's selected is updated to reflect the current selection.

and since selected is bound to visible attribute of our form, it will be only displayed if end user clicks New button and makes a selection in orders listbox.

Creat UI and add bindings

Details form is grid of two columns. First column showing the order field name using a Label component and second column showing an input component with current value of the order property. Below is a code snippet that builds this form grid and the field bindings with selected properties.

        // create grid with two columns
        Grid grid = new Grid();
        grid.setHflex("true");
        grid.setParent(form);
        Columns columns = new Columns();
        Column labelCol = new Column();
        labelCol.setWidth("120px");
        columns.appendChild(labelCol);
        columns.appendChild(new Column());
        grid.appendChild(columns);

        Rows rows = new Rows();
        grid.appendChild(rows);
        Row idRow = new Row();
        idRow.appendChild(new Label("Id"));
        Label idLabel = new Label();
        idRow.appendChild(idLabel);
        rows.appendChild(idRow);
        
        binder.addPropertyLoadBindings(idLabel, "value", "vm.selected.id", null, null, null, null, null);

        Row descriptionRow = new Row();
        descriptionRow.appendChild(new Label("Description"));
        Textbox descriptionBox = new Textbox();
        descriptionRow.appendChild(descriptionBox);
        rows.appendChild(descriptionRow);
        //add load and save bindings for description
        String[] beforeCommand = {"saveOrder"};
        binder.addPropertyLoadBindings(descriptionBox, "value", "vm.selected.description", null, null, null, null, null);
        binder.addPropertySaveBindings(descriptionBox, "value", "vm.selected.description", beforeCommand, null, null, null, null, null, null);

        ...

Here are few notes from above code

  • Line 20 adds only load binding for viewmodel's selected order's id property. Id of an order will be autogenerated and hence doesn't need to changed or updated once created hence no save binding.
  • Line 29,30 adds load as well as save binding for viewmodel's selected order's description property. Note that the addPropertySaveBindings API fourth parameter indicates the description to be saved before binder handle saveOrder command.

Load component

Once all property load/save and command bindings are added we need to instruct binder to load component. This will cause binder to actually process the load bindings and synchronize the ViewModel state with View.

        binder.loadComponent(window,true);

Summary

In this article we demostated how to use MVVM pattern in a Richlet when you are creating UI with pure Java. The important difference with usual way of using MVVM with ZUML files as views is that you need to create binder instance, initialize it with ViewModel and add bindings on components through binder APIs. Much of MVVM benefit of decoupling View from ViewModel is still achieved with this.

Downloads

  • Please download this order richlet example code from here
  • You could also download the deployable war file from zbindexamples, it also contains example source code of this article.


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.
You got stuck here?
Let us know why!
For questions please use the forum