ZK/How-Tos/Data-Binding

How do I apply annotated data-binding on a Regular Macro Component?

edit

macrobind.zul: The "view" definition of the macro component, a first name and last name fill-up form. Notice how the onChange event of the textbox is propagated to the space owner using the ZK3 'forward' feature.

<?xml version="1.0" encoding="UTF-8"?>
<grid>
<rows>
  <row>First Name: 
    <textbox id="firstName" forward="onChange=onFirstNameChange"/>
  </row>
  <row>Last Name: 
    <textbox id="lastName" forward="onChange=onLastNameChange"/>
  </row>
  <row>Full Name: <label id="fullName"/></row>
</rows>
</grid>

macrobind.zs: The class definition of the macro component, Username. It delegates API calls down to the real UI components.

import org.zkoss.zk.ui.*;
import org.zkoss.zul.*;

public class Username extends HtmlMacroComponent {
  
  public void setFirstName(String str) {
    getFellow("firstName").setValue(str);
    refreshFullName();
  }
  
  public String getFirstName() {
    return getFellow("firstName").getValue();
  }
  
  public void setLastName(String str) {
    getFellow("lastName").setValue(str);
    refreshFullName();
  }
  
  public String getLastName() {
    return getFellow("lastName").getValue();
  }

  private void refreshFullName() {
    getFellow("fullName").setValue(getFirstName() + " " + getLastName());
  }

}

usemacrobind.zul: The example application that use the Username macro component and apply annotation data-binding. Notice that the save-when definition in the @{...} binding. It tells DataBinder to save Username's value to the Person object when the event is fired. The access:both tells DataBinder that the Username object is both for loading and saving. The button at the bottom is for checking whether the properties of the Person object are really changed.

<?xml version="1.0" encoding="UTF-8"?>
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?> 
<?init zscript="macrobind.zs" ?>
<?component name="username" macro-uri="macrobind.zul" class="Username"?>
<window xmlns:a="http://www.zkoss.org/2005/zk/annotation">
<zscript>
//define a Person data object for data-binding test
public class Person {
  private String _firstName;
  private String _lastName;
  
  public Person(String first, String last) {
    _firstName = first;
    _lastName = last;
  }

  public void setFirstName(String str) {
    _firstName = str;
  }
  
  public String getFirstName() {
    return _firstName;
  }
  
  public String setLastName(String str) {
    _lastName = str;
  }
  
  public String getLastName() {
    return _lastName;
  }
}

Person person = new Person("Hello", "World");

</zscript>
<!--
   see http://www.zkoss.org/javadoc/3.0.0/zkplus/org/zkoss/zkplus/databind/AnnotateDataBinder.html
-->
<username 
	firstName="@{person.firstName,save-when='self.onFirstNameChange',access=both}" 
	lastName="@{person.lastName,save-when='self.onLastNameChange',access=both}"/>

<button label="check Person" onClick='alert(person.firstName+" "+person.lastName)'/>
</window>

Note that you could have done the same thing in ZK2.4 without use of the 'forward' feature by using this version of macrobind.zul that explicitly fires an event on the macro component.

<?xml version="1.0" encoding="UTF-8"?>
<grid>
<rows>
  <row>First Name: 
    <textbox id="firstName">
      <attribute name="onChange"><!-- delegate event to the Macro Component -->
        Events.sendEvent(spaceOwner, new Event("onFirstNameChange", spaceOwner));
      </attribute>
    </textbox>
  </row>
  <row>Last Name: 
    <textbox id="lastName">
      <attribute name="onChange"><!-- delegate event to the Macro Component -->
        Events.sendEvent(spaceOwner, new Event("onLastNameChange", spaceOwner));
      </attribute>
    </textbox>
  </row>
  <row>Full Name: <label id="fullName"/></row>
</rows>
</grid>

How do I dynamically render a grid from a collection of Java Objects?

edit

people.zs: We need some POJOs to data bind to. Here is a quick file to create the pojo class that we will bind our xul grid to.

/* 
   create a person class. 
   chould be compiled Java on your classpath we do it in beanshell 
   as it is quick and easy
*/
public class Person{
	private String firstName;
	private String lastName;
	public String getFirstName(){
		return firstName;
	}
	public void setFirstName(String firstName){
		this.firstName = firstName;
	}
	public String getLastName(){
		return lastName;
	}
	public void setLastName(String lastName){
		this.lastName = lastName;
	}
	public Person(String firstName, String lastName){
		this.setFirstName(firstName);
		this.setLastName(lastName);
	}
}

example-people.zs: We need some sample data POJOs. Here is the file to create them

// as sample list of persons. would be loaded from a database see 
// the sample app

personA = new Person("john","wayne");
personB = new Person("jane","doe");
persons = new java.util.ArrayList();
persons.add(personA);
persons.add(personB);

index.zul: Bring it all together in a data binding example:

<?init zscript="people.zs" ?>
<window>
  <zscript src="example-people.zs"/>
  <zscript>
  
// down to business. define how to assign components to each 
// row for each object in the list that we are going to render
// as rows. note the setParent(row) that hooks the new component
// into the Desktop

    public class MyRowRenderer implements RowRenderer {
      public void render(Row row, Object data) {
        new Label("First Name:").setParent(row);
        new Textbox(((Person)data).getFirstName()).setParent(row);
        new Label("Last Name:").setParent(row);
        new Textbox(((Person)data).getLastName()).setParent(row);
      }
    }

// create a simple data mode wapping our list of persons
    ListModel model = new SimpleListModel(persons.toArray());

// instantiate our custom row renderer
    RowRenderer rowRenderer = new MyRowRenderer();
  </zscript>
  <!-- create a grid and assign the model and the renderer  --> 
  <grid model="${model}" rowRenderer="${rowRenderer}"/>
</window>