Two-way Data Binding with ZUML Annotations

From Documentation
DocumentationSmall Talks2006NovemberTwo-way Data Binding with ZUML Annotations
Two-way Data Binding with ZUML Annotations

Author
Henri Chen, Principal Engineer, Potix Corporation
Date
November 16, 2006
Version
Applicable to ZK 2.2 RC and later.



The Issue

Since the day one ZK has provided data binding mechanism via EL expressions. The UI component can be initialized its value via simple EL expressions when the ZUL page is loaded.

<textbox id="firstName" value="${person.firstName}"/>
<textbox id="lastName value="${person.lastName}"/>
<label id="fullName" value="${person.fullName}"/>


This data binding mechanism via EL expression is elegant on its simplicity. However, it got some limitations, too. The EL expressions are evaluated immediately and only once when the ZUML page is loaded. After that, the EL expressions are then gone and application developers have to write plumbing codes to transfer values among UI components and data beans properties. It can be tedious and error prone.

An ideal data binding mechanism is of course more then just initializing the UI components. An idea data binding mechanism should be declarative(less coding), dynamic, intuitive, and two-way (support read and write). That is, the developer should not write lots tedious plumbing codes to move values among UI components and backend data beans.

This article would show you a simple data binding example and walk through our data binding codes based on the new ZUML annotations mechanism.

The Solution

As we have said, ZK is for presentation layer only. We will not force developers to use any specific data binding mechanism. Rather, we would like to provide a mechanism to enable different implementations for the data bindings. In a previous smalltalk, Data-Binding Implementation for ZK, Chanwit Kaewkasi has demonstrated how to do data binding by subclassing XUL component to provide two addtional attributes bind and dataSource and overriding getValue() and setValue() methods. It works well but you have to subclass each components that related. To decouple the implementation and the ZK UI components, the ZK team provides the ZUML annotations mechanism since version 2.2RC to allow for data binding metadata declaration. Of course, the ZUML annotation is not solely for the data binding implementation. Yet it is quite a proper place to declare data binding metadata.

ZUML Annotations

ZUML Annotations provide data about a component that is not part of the component itself. They have no direct effect on the operation of the component they annotate. Rather, they are mainly used by a tool or a manager to examine at runtime. The content and meanings of annotations are only understood by the tool or the manager. For details on how to use the new ZUML annotations, please refer to the ZK Developer's Reference


Data Binding

What a data binding mechanism trying to resolve is to automate the data copy plumbing codes among UI components and the backend data beans. Its target is simple, to minimize and simplify coding. Application developers only have to declare some kind of mappings to tell the binding manager which UI component is mapped to which property of the data beans, etc. And by calling some simple binding manager methods, all trivial and tedious plumbing codes are done automatically.

A Simple Example

We use a simple example to illustrate how to use our data binding manager and how that is worked with ZUML annotations. Notice that annotations is a neutral technology to data binding implementation. You are freely to define any annotations you like as long as your data binding manager understands the designated annotations. So the data binding annotation used here is simply for our data binding implementation.

In our example, we defined a Person class as the backend data bean that will hold the profile of a person, say, first name, last name, age, gener, etc. To simplify the description, we use only three fields here.


Person.java

public class Person {
    private String _firstName="";
    private String _lastName="";
    
    //getter and setters
    public void setFirstName(String firstName) {
        _firstName = firstName;
    }
    public String getFirstName() {
        return _firstName;
    }
    public void setLastName(String lastName) {
        _lastName = lastName;
    }
    public String getLastName() {
        return _lastName;
    }
    public void setFullName(String f) {
        //do nothing.
    }
    public String getFullName() {
        return _firstName + " " + _lastName;
    }
}


The traditional zuml page that utilize the EL expressions to initialize the UI components is listed below:

person1.zul

<window id="mainwin">
  <zscript>
    //prepare the example person object
    Person person = new Person();
    person.setFirstName("Hello");
    person.setLastName("ZK");
  </zscript>
  
  <grid width="400px">
  <rows>
  <row>
    First Name:
    <textbox id="firstName" value="${person.firstName}" onChange="update();"/>
  </row>

  <row>
    Last Name:
    <textbox id="lastName" value="${person.lastName}" onChange="update();"/>
  </row>
  
  <row>
    Full Name:
    <label id="fullName" value="${person.fullName}"/>
  </row>
  </rows>
  </grid>
  
  <zscript>
    void update() {
     //plumbing code to copy from UI components to data bean object
        person.setFirstName(firstName.getValue());
        person.setLastName(lastName.getValue());
        
          //more plumbing code if more fields ...
      
        //plumbing code to copy from data bean object to UI component "fullName"
        fullName.setValue(person.getFullName());

          //more plumbing code if more fields ...
          
    }
  </zscript>
</window>

Person.gif

As you can see, the person1.zul example code above is quite typical. It is a personal data editing page. A Person Java object is used as the backend data bean. The UI components of the "person1.zul" file is initialized via ZK's simple EL expressions as usual. An end user can type in personal information and the fields of the backend data beans is updated immediately. The updated fields is then read back to show on the other fields to reflect the changes. We simplify the number of fields to firstName and lastName only. The fullName is a read only label that will reflect the changed full name immediately via the onChange event handler of the two Textboxes. Inside the update() event hander, we write codes to copy the Textbox component values to the person data bean properties one by one and then read from the data bean to the value attribute of the fullName Label component to reflect the changes.


The Annotated Example

Now let us refactor the above person1.zul example to person2.zul with annotations and our implementation of data binding manager:


person2.zul

<window id="mainwin" xmlns:a="http://www.zkoss.org/2005/zk/annotation">
  <zscript>
    //prepare the example person object
    Person person = new Person();
    person.setFirstName("Hello");
    person.setLastName("ZK");
  </zscript>
  
  <grid width="400px">
  <rows>
  <row>
    First Name:
    <a:bind value="person.firstName"/>
    <textbox id="firstName" onChange="update();"/>
  </row>

  <row>
    Last Name:
    <a:bind value="person.lastName"/>
    <textbox id="lastName" onChange="update();"/>
  </row>
  
  <row>
    Full Name:
    <a:bind value="person.fullName"/>
    <label id="fullName"/>
  </row>
  </rows>
  </grid>
  
  <zscript>
    //prepare the AnnotateDataBinder
    AnnotateDataBinder binder = new AnnotateDataBinder(mainwin);
    binder.bindBean("person", person);

    //initialize UI components
    binder.loadAll();
    
    void update() {
      //copy UI components values to data bean properties in one method call.
      binder.saveAll();
      
      //load an UI component value from data bean property in one method call.
      binder.loadComponent(fullName);
    }
  </zscript>
</window>


Each annotation associate each UI component attribute to a data bean property in a fairly straightforward way. First, declare an xml namespace for ZUML annotation tags. In our data binding implementation, the only annotation tag name understood by our binding manager is a:bind. Each ZUML annotation is declared in front of the annotated UI component. As the example shows, the value attribute of Textbox component firstName is associated to firstName property of the data bean person.

<window id="mainwin" xmlns:a="http://www.zkoss.org/2005/zk/annotation">
  ...
    <a:bind value="person.firstName"/>
    <textbox id="firstName" onChange="update();"/>


We then new an AnnotateDataBinder object, binder, our data binding manager. The constructor argument tells the binder that the concerned components is Window mainwin and all its descendants. The binder will walk through the components structure and read the associated ZUML annotations to book keeping the data binding information. Then we call binder.bindBean("person", person) to associate the id "person" to the real data bean person object. Now the binder gets all information and we can make it work for us.

//prepare the AnnotateDataBinder
AnnotateDataBinder binder = new AnnotateDataBinder(mainwin);
binder.bindBean("person", person);

//initialize UI components
binder.loadAll();


By calling only one data binding manager method, loadAll(), all UI components is loaded with the properties from the backend data bean person. This is used to initialize the ZUML page from the backend data bean.

Now let us discuss the update() method, the onChange event handler. Again, by calling only one data binding manager method, saveAll(), the backend data bean is updated with related UI components' values, say, Textbox firstName and lastName. To force load the value of Label fullName from the backend data bean person, we can simply call this data binding manager method, loadComponent(fullName).

void update() {
  //copy UI components values to data bean properties in one method call.
  binder.saveAll();
  
  //load an UI component value from data bean property in one method call.
  binder.loadComponent(fullName);
}


For a more useful use case, application developer might want to submit a form and store data to the database on a button click. Just prepare a submit button and write an onClick event handler, you can persist the backend data bean in two simple lines of codes.

void doSubmit() {
  //copy UI components values to data bean properties in one method call.
  binder.saveAll();

  //persist backend data bean "person" into database 
  PersonDAO.saveOrUpdate(person);
}

Summary

Apparently, there are still spaces for improvement. However, this article shows the potential of the annotated data binding. As you can see, in the person2.zul example, we have taken out all EL expressions. The initialization, update, and load operations are now integrated into this data binding mechanism in a whole. You can load all or load a specific component. Also you can save all or save only a specific component. The method calls are simple. The annotation syntax is intuitive. We hope you enjoy this new ZK feature and develop implementations based on this new annotation mechanism.


Ruler.gif


Download the example code here.




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