ListModel and Databinding Enhanced Combobox

From Documentation
DocumentationSmall Talks2008JanuaryListModel and Databinding Enhanced Combobox
ListModel and Databinding Enhanced Combobox

Author
Jumper Chen, Engineer, Potix Corporation
Date
January 4, 2008
Version
Applicable to ZK 3.0.2 Freshly (zk-3.0.2-FL-2008-01-04 and later)


Introduction

In the previous article(this), we have described how to realize the feature of auto-complete with Combobox. The solution is not very good because you need to manipulate the data set inside the view component directly. So this implementation breaks the traditional MVC pattern, which separates the data (model) from user interface (view) via controller to communicate between. However, from now on, ListModel now supports combobox so you no longer have to mix the model with view. Besides, ListSubModel also supports to implement auto-complete in an easier way.

In the article, we will demonstrate you how to use ListModel and ListSubModel to achieve auto-complete with combobox without additional implementation. org.zkoss.zul.ListSubModel interface is designed for Combobox, and the renderer of combobox will use ListSubModel to get the subset of data from list model to render the data in combobox according to users' input.


ListModel Supports Combobox

Here is a ListModel example. We use the SimpleListModel, which implements the ListSubModel interface, to filter the data model of combobox to achieve the feature of auto-complete.

The demo zul file is shown below.

<zk>
  <zscript>
    String[] data = new String[30];
    for(int j=0; j &lt; data.length; ++j) {
      data[j] = "option "+j;
    }
    ListModel strset = new SimpleListModel(data);
  </zscript>
  <combobox id="list" width="200px" model="&#36;{strset}"/> 
</zk>

As you can see, this example is similar to the live data demo from our demo site. The SimpleListModel will fetch a maximum of 10 rows from data model of combobox, but if you'd like to display more than 10 rows, you have to implement the ListModel by yourself.

Note: The concept of Combobox's list model is different from Listbox. In Listbox, the data of UI is load-on-demand from ListModel, but in Combobox, it loads all data from list model to UI at the first time, but if ListSubModel interface is also implemented, only those data which qualifies users input criteria will be loaded.

In the SimpleListModel class, we need to implement a method getSubModel() that returns a subset model from combobox's model as follows.


 public ListModel getSubModel(Object value, int nRows) {
    final String idx = value == null ? "" : objectToString(value);
    if (nRows < 0) nRows = 10;
    final LinkedList data = new LinkedList();
    for (int i = 0; i < _data.length; i++) {
      if (idx.equals("") || _data[i].toString().startsWith(idx)) {
        data.add(_data[i]);
        if (--nRows <= 0) break; //done
      }
    }
    return new SimpleListModel(data);
  }


As you can see, the getSubModel() will return the subset of the list model. It finds all data whose prefix is the same with the value. So, you could implement the method as your want.

Note: the nRows is that the number of rows are suggested to return (as the returned ListModel instance). It's a suggestion for developer to follow.

Version Note: Since 5.0.4, there are simpler way to override the retrieval of sub model: SimpleListModel.getMaxNumberInSubModel(int) and SimpleListModel.inSubModel(Object, Object).

Databinding Supports Combobox

In this demo, we use the feature of Databinding to bind the data set to combobox. This is a simple example that we need to prepare a data set, e.g. a collection of VO (value object) or POJO, and to specify annotations as values within components’ declarations or their properties directly. If you have been exposed to databinding, you could skip this section detail.

This demo zul file is separated two parts as follows.


  • First: DataSet
<zscript>
public class Order {
private String _orderName;
private String _orderNumber;
public void setOrderName(String o) {
  _orderName = o;
}
public String getOrderName() {
  return _orderName;
}
public void setOrderNumber(String n) {
  _orderNumber = n;
}
public String getOrderNumber() {
  return _orderNumber;
}
}   
  int count = 30;
  List orders = new LinkedList();
  for(int j= 0; j &lt; count; ++j) {
	Order o = new Order();
	o.setOrderName("OrderName - " + j );
	o.setOrderNumber("OrderNumber - " + j);
	orders.add(o);
  }   
  selected = orders.get(0);
</zscript>

As you can see, we declare an Order object and create it into the orders collection, which associates with the data binder via a target attribute "model", and we need to pre-declare the selected variable for data bound to the "selectedItem" attribute of the combobox.


  • Second: UI Component
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit" ?> 
<window width="500px">

... // DataSet above

<combobox model="@{orders}" selectedItem="@{selected}" value="@{selected.orderName}">
	 <comboitem self="@{each=order}" label="@{order.orderName}" value="@{order.orderNumber}"/>
 </combobox>
<grid>
  <auxhead>
	<auxheader align="center" colspan="2">Order Information</auxheader>
  </auxhead>
  <columns>
	<column align="center" width="200px" label="Item"/>
	<column align="center" width="200px" label="Value"/>
</columns>  
  <rows>
	<row>OrderNumber: <label value="@{selected.orderNumber}"/></row>
	<row>OrderName: <label  value="@{selected.orderName}"/></row>
  </rows>
</grid>
</window>


In the example above, the Combobox "model" attribute is data bound to a Java List "orders". When loading, the data binder would convert the Java collection list into ListModelList, a "live collection" ListModel, and "loads" it into "model" attribute of the Combobox. The special "each" attribute that bound to the Comboitem is the entry variable used in constructing the real Comboitem. That is, when the BindingComboitemRenderer construct the real Comboitem, the pulled in data would be associated with this variable name and used in the Comboitem. In the example, the each "order" variable would be used for each entry in the collection variable "orders" and used in constructing its label and value attribute, "order.orderName" and "order.orderNumber". The special "each" attribute is very important here since Data Binder use it to distinguish a "template" Comboitem from "real" Comboitem.


The "selectedItem" attribute of the Combobox is data bound to a variable "selected". The Combobox "selecteItem" attribute is supposed to accept a Comboitem. However, again, ZK apply a SelectedComboitemConverter that would convert the "selected Comboitem" to the associated entry in the ListModel only. (Because Combobox don't support the concept of setting selected item) In the example, it would be the selected "order". Then, you can advance databound the "selected" variable to show the details of the selected "order" and a List-Detail type of application is easily implemented.

Conclusion

These two features of ListModel and Databinding enhance the capability of Combobox, and it becomes easier to realize the MVC pattern. If you have any question about how to use two features with Combobox, please feel free to leave comment here or post to ZK forum




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