Simon has posted 6 posts at DZone. View Full User Profile

Test Driving the MVVM Pattern with ZK Ajax

10.20.2011
| 5924 views |
  • submit to reddit

The Bindings


Within the ZUL file property values in the format the "@{binding}" define how the value data, display state (e.g. visible/disabled), and command methods exposed by the ViewModel are bound to the View. The View is the combined desktop of screen Components which are described by the ZUL file. The listbox which defines the list of items has a binding for its properties "model" and "selectedItem":

<listbox
    model="@{toDoViewModel.reminders, load-after='add.onClick,update.onClick,delete.onClick'}"
    selectedItem="@{toDoViewModel.selectedReminder}">

The "model" property of the listbox the list of items to be displayed. This is bound to the "reminders" property of the "toDoViewModel" bean. Within the ViewModel class this property is a getter which queries the ReminderService:

protected ReminderService reminderService;
 
public List<Reminder> getReminders() {
    return this.reminderService.findAll();
}

The "model" binding attribute above has 'load-after' attributes. These specify that the model be reloaded (and hence re-rendered) whenever the onClick events of the add, update or delete buttons have been run. This has the effect of automatically re-rendering the list when the ReminderService has been updated in response to user actions.

The rendering of the list of reminders is defined by the "@{each=loopVar}" binding:

<listitem self="@{each=reminder}">
    <listcell label="@{reminder.name}"/>
    <listcell label="@{reminder.priority}"/>
    <listcell label="@{reminder.date, converter='org.zkforge.zktodo2.DateFormatConverter'}"/>
</listitem>

The "each" loop iterates over the collection bound as the "model" and defines a loop variable "reminder". At each iteration a new ListItem instance is created containing three Listcells. The labels of the listcell components are bound to the properties of the current loop variable Reminder object. In this manner the list of business objects returned by the ReminderService from the database is rendered into the screen without having written any custom code. The 'load-after' attributes of the model binding cause the list to be re-fetched and re-rendered any time the add, update or delete buttons are clicked.

The "selectedItem" property of the listbox is updated whenever the user click on an item in the list. This property is bound to the "selectedReminder" property of the "toDoViewModel" bean. In the ViewModel class this property is a regular getter and setter of a member variable:

protected Reminder selectedReminder;
 
public Reminder getSelectedReminder() {
    return selectedReminder;
}
 
public void setSelectedReminder(Reminder reminder) {
    this.selectedReminder = reminder;
    if( this.selectedReminder == null ){
        this.selectedReminder = new Reminder();
    }
}

Whenever the user clicks on a different item in the list the setter of the ViewModel is invoked passing in the Reminder business object which was used to render the particular listitem. In this manner the "selectedReminder" property of the ViewModel is always the user's current selection.

The editing panel allows the user to edit the selected Reminder entity. This is easily achieved by binding the input components of the edit panel onto the "selectedItem" property of the ViewModel:

<hbox>
 Item:<textbox cols="40" constraint="no empty"
    value="@{toDoViewModel.selectedReminder.name, load-after='add.onClick,delete.onClick'}"/>
 Priority:<intbox id="priority" cols="1" constraint="no empty"
    value="@{toDoViewModel.selectedReminder.priority, load-after='add.onClick,delete.onClick'}"
    />
 Date:<datebox id="date" cols="14" constraint="no empty"
    value="@{toDoViewModel.selectedReminder.date, load-after='add.onClick,delete.onClick'}"
    onChange="@{toDoViewModel, converter='org.zkforge.zktodo2.binding.InputEventCommandConverter'}"/>
</hbox>

A Textbox is bound to the name property of the selected Reminder business object. Similarly an Intbox is bound to the priority attribute and a Datebox is bound to the date property. When a user clicks the add or delete buttons the "selectedReminder" attribute of the ViewModel will change. As the Binder knows it has changed the attribute of the model it will reload the edit panel. The bindings also include 'load-after' hints to reload the panel in response to buttons being pressed which can change the "selectedReminder" of the ViewModel.

The editing panel bindings both read and write. When the user enters data into the textbox the onChange event handler will cause the Binder to invoke the "name" setter on the currently selected Reminder bean. In this manner no code is required to cause the edit panel to update the business objects. All that work is done automatically by the Binder as dictated by the bindings in the ZUL file.

The "add", "update" and "delete" buttons are as follows:

<button id="add" label="Add" width="36px" height="24px" onClick="@{toDoViewModel}"/>
<button id="update" label="Update" width="46px" height="24px" onClick="@{toDoViewModel}"/>
<button id="delete" label="Delete" width="46px" height="24px" onClick="@{toDoViewModel}"/>

The onClick events are bound to methods of the ViewModel. The target method name is set by convention; the method name of the command to invoke on the ViewModel is defined by the ID property of the component. The buttons have IDs of "add", "update" and "delete" so these are the names of the methods which are invoked on the "toDoViewModel" bean:

public void delete() {
    if( this.selectedReminder.getId() != null ){
        try {
            this.reminderService.delete(selectedReminder);
            this.selectedReminder = new Reminder();
        } catch (EntityNotFoundException e) {
            e.printStackTrace();
        }
    }
}
 
public void update() {
    if( this.selectedReminder.getId() != null ){
        try {
            this.reminderService.merge(selectedReminder);
        } catch (EntityNotFoundException e) {
            e.printStackTrace();
        }
    }
}
 
public void add() {
    if( this.selectedReminder.getId() == null ){
        this.reminderService.persist(this.selectedReminder);
        this.selectedReminder = new Reminder();
    }
}

These three business methods of the ViewModel work directly with the selected Reminder entity. The selected Reminder is the one held by the "selectedItem" binding which writes to the "selectedReminder" property of the ViewModel. There is no code required to pull the correct business object out of a list; the ZK Binder technology does the work. Recall that the edit panel has 'load-after' hints to refresh the panel after both the "add" and "delete" buttons are clicked. We can see from the code above that this is required because the corresponding methods in the ViewModel change the "selectedReminder" property.

The binder is tracking the dependencies of the components which are bound to the same property; when it writes to a ViewModel property it automatically reloads all screen variables bound to the same property. When you use the edit panel the components write directly into the Reminder entity; and this is reflected instantly within the list. We only need to provide 'load-after' hints when business logic changes a bound property in a way which cannot be anticipated by the Binder.

Although not required for this screen the Binder is able to bind the "visible" and "disabled" attributes of Components onto boolean ViewModel properties. The example application has a screen weather-station-mvvm.zul which makes dynamically disables the edit panel fields based on the state of a ViewModel property. Using a "visible" binding we could also make the entire edit panel or individual fields show and hide themselves based on a boolean property of the ViewModel.

The ViewModel In Focus


The ViewModel logic presented within this article is materially different from a Presenter within the MVP pattern. It does not push business state into the screen components. Instead the business state is pulled into the View from the ViewModel by the AnnotateDataBinder. The ViewModel is also different from a Controller within the ZK MVC pattern. A ZK Controller implemented as a GenericForwardComposer subclass requires specifically named event handler methods which take the ZK Event object as a method argument. In contrast the ViewModel class is not a UI framework type; it implements no UI framework methods. Instead the CommandBinder class mediates between the event handling logic and the business methods.

Although not shown above a converter can be specified with "onClick=${bean, converter}". The converter can extract business objects from the onClick event or its target component. These resolved business objects will then be passed as arguments to the bound business method. Fully decoupling the ViewModel from the presentation framework makes it a pure Model of a logic UI independent of the presentation framework. Such a model can be exposed via a soap interface and invoked by remote programs. It can also be bound to multiple screens. An industrial administrator user might access one ZUL page optimized for a mouse and keyboard. Technicians out in the field might access the same ViewModel via a different ZUL page optimized for a rugged touch screen device with large high contrast buttons. We might have a special set of screen presenting the same ViewModel in manner which is suitable for the visually impaired.

Concluding Remarks


The screen discussed within this articles was created with remarkably few lines of code. The eclipse metrics plugin calculates the "method lines of code" count for the class ZkToDoViewModel as 28 lines. The ZkToDoViewModel class has no compile time dependencies on the ZK framework classes. We rely totally on the reflection capabilities of the ZK Binder classes to create the dynamic screen which drives the model to update the database.

The ViewModel is the naked screen behaviour and state. It is a true stateful ScreenModel or ApplicationModel that mediates between the User and the services of the system. This makes it reusable between different screens. It is also easier to JUnit test the ViewModel as a standalone class. Its the Binder technology of ZK which is the key to this succinct OO approach. Calling the approach Model-View-Binder (or more precisely View-Binder-Model) may guide more people into organising their UI logic around business orientated ViewModels. Long live the Binder!

References


  1. Presentation Patterns In ZK slides presented to the UK ZK Users Group
  2. Josh Smith's inspirational Microsoft .Net MVVM Article
  3. Martin Fowler GUI Patterns pages UI Architectures
  4. Book on how to build a layered domain model with Spring POJOs In Action
  5. ZK MVC Screen for POJOs In Action book code Test Driving ZK FoodToGo
  6. Book on designing "domain objects first" for supple code Evans Domain Driven Design
  7. MVC article Desktop MVC Patterns ZK, Spring & JPA
  8. Original MVP article SmallTalks 2008 Best MVC Patterns
Published at DZone with permission of its author, Simon Massey.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Simon Massey replied on Mon, 2011/12/05 - 9:10am

 

ZK MVVM has now been giving a major uplift with the new ZK6 "ZK Bind" features!

Envisage ZK 6: The Next Generation Data Binding System

Hello ZK MVVM

 

Long live the binder! 

Simon

Simon Massey replied on Mon, 2011/12/05 - 9:11am

 

The sample code for this project has now moved to GitHub: 

https://github.com/simbo1905/ZkToDo2

 

 

Simon Massey replied on Tue, 2013/05/14 - 3:19pm

And here is the latest version of the code (using new ZK6 features) running on the Heroku cloud: 

http://glowing-light-1070.herokuapp.com/presentationmodel.zul 

The code will get shutdown if it has no activity for a while. So you have to wait away for the process to startup automatically if you are accessing after it has been shutdown.The warm up takes a while but after that it seem quite lively.

Simon

Rick Reumann replied on Sat, 2012/02/11 - 4:33pm

Just looking for a place to THANK YOU for the great work with https://github.com/simbo1905/ZkToDo2  - It hits the perfect sweet spot for someone wanting to getting into ZK using spring, maven, jpa AND with the new ZK6 binding stuff which is really impressive.

Many sample apps out there either show too much and typically with outdated examples (which ends up just confusing the newbie) or they show too little (making the app not very practical for when you really want to get started building a 'real' app.

Awesome job!

Simon Massey replied on Fri, 2012/07/13 - 12:41am in response to: Rick Reumann

Rick, 

Thanks for your kind words. It is great to hear when someone has found some inspiration in the code. 

Simon

Simon Massey replied on Thu, 2012/12/27 - 1:38pm

The source code from this article is now the subject of a design patterns article covering mvvm, mvc, and mvp patterns called Implementing event-driven GUI patterns using the ZK Java AJAX framework 

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.