Envisage ZK 6.0: Rendering List and Tree Model with Templates"

From Documentation
Line 8: Line 8:
  
 
=The Story=
 
=The Story=
  Before ZK 5.1, [[ZK Developer's Reference/MVC/View|the custom renderer]] must be implemented through Java. Although Java is very powerful and anything can be achieved with it, it is still tedious and not always the most feasible solution having to create and maintain an additional Java class every single time. Speaking in terms of the MVC concept, in many cases, it makes much more sense to place the renderer to the client side as client side is closer to "View"  rather than the "Control" on the server side.
+
   
 +
Before ZK 5.1, [[ZK Developer's Reference/MVC/View|the custom renderer]] must be implemented through Java. Although Java is very powerful and anything can be achieved with it, it is still tedious and not always the most feasible solution having to create and maintain an additional Java class every single time. Speaking in terms of the MVC concept, in many cases, it makes much more sense to place the renderer to the client side as client side is closer to "View"  rather than the "Control" on the server side.
  
 
With 5.1, a new concept called "template" is introduced. It allow UI designers to specify templates right in a ZUML page, the model can then be rendered based on the template without any Java code.
 
With 5.1, a new concept called "template" is introduced. It allow UI designers to specify templates right in a ZUML page, the model can then be rendered based on the template without any Java code.

Revision as of 03:21, 27 July 2011

DocumentationSmall Talks2011JulyEnvisage ZK 6.0: Rendering List and Tree Model with Templates
Envisage ZK 6.0: Rendering List and Tree Model with Templates

Author
Tom Yeh, Potix Corporation
Date
July 20, 2011
Version
ZK 5.1

The Story

Before ZK 5.1, the custom renderer must be implemented through Java. Although Java is very powerful and anything can be achieved with it, it is still tedious and not always the most feasible solution having to create and maintain an additional Java class every single time. Speaking in terms of the MVC concept, in many cases, it makes much more sense to place the renderer to the client side as client side is closer to "View" rather than the "Control" on the server side.

With 5.1, a new concept called "template" is introduced. It allow UI designers to specify templates right in a ZUML page, the model can then be rendered based on the template without any Java code.

What Is Template

A template is a segment of a ZUML page enclosed with the template element as shown below.

<window>
    <template name="foo">
      <textbox/>
      <grid>
         <columns/>
      </grid>
   </template>
...

The template could contain any ZUML elements you want, even including other templates. When ZK interprets a template, ZK won't interpret its content immediately. Rather, it stores it as an instance of org.zkoss.zk.ui.util.Template into the component, such that it can be retrieved later to create components by the application or the component.

Listbox Model Rendering with Template

With 5.1, the custom renderer can be done with a template without any Java code. For example, assume we want to render a two-dimensional array, then we could do as follows.

<?variable-resolver class="foo.FruitProvider"?>

<listbox model="${fruits}">
	<listhead>
		<listheader label="Name" sort="auto"/>
		<listheader label="Weight" sort="auto"/>
	</listhead>
	<template name="model">
		<listitem>
			<listcell label="${each[0]}"/>
			<listcell label="${each[1]}"/>
		</listitem>
	</template>
</listbox>

where fruits is a two-dimensional array that can be retrieved by use of a variable resolver (VariableResolver)[1] called foo.FruitProvider as shown below.

public class FruitProvide implements VariableResolver {
    public Object resolveVariable(String name) {
        if ("fruits".equals(name))
            return new ListModelArray(
                new String[][] {
                    {"Apple", "10kg"},
                    {"Orange", "20kg"},
                    {"Mango", "12kg"}
                });
        return null;
    }
}

St201107-listbox.png

As shown, the custom rendering is defined by a template called model. The template's name is important since you could associate any number of templates to one component. Furthermore, when the template is rendered, a variable called each is assigned with the data being rendered.

In additions, if the template does not set a value to the listitem (Listitem.setValue(Object)), it will be assigned with the data being rendered. It could be used to retrieve back the data being rendered.


  1. There are several ways to assign a model to a UI component. Please refer to ZK Developer's Reference for details.

Nested Listboxes

The template can be applied recursively. For example, here is an example of listbox-in-listbox:

<zk>
	<zscript><![CDATA[
	ListModel quarters = new ListModelArray(new String[] {"Q1", "Q2", "Q3", "Q4"});
	Map months = new HashMap();
	months.put("Q1", new ListModelArray(new String[] {"Jan", "Feb", "Mar"}));
	months.put("Q2", new ListModelArray(new String[] {"Apr", "May", "Jun"}));
	months.put("Q3", new ListModelArray(new String[] {"Jul", "Aug", "Sep"})); 
	months.put("Q4", new ListModelArray(new String[] {"Oct", "Nov", "Dec"}));
	ListModel qs = (quarters);
	]]></zscript>
	<listbox model="${quarters}">
		<template name="model">
			<listitem>
				<listcell>${each}</listcell>
				<listcell>
					<listbox model="${months[each]}">
						<template name="model">
							<listitem label="${each}"/>
						</template>
					</listbox>
				</listcell>
			</listitem>
		</template>
	</listbox>
</zk>

St201107-listbox-in-listbox.png

If you'd like to access the data of the outer template in the inner template, you could use the parent listitem's Listbox.getValue() where the data is stored by default. For example, at line 8 of the following example, we retrieve the data of outer model by traveling the component tree:

<listbox model="${quarters}">
	<template name="model">
		<listitem>
			<listcell>
				<listbox model="${months[each]}">
					<template name="model">
						<listitem>
							<listcell>${self.parent.parent.parent.parent.parent.value}</listcell>
							<listcell>${each}</listcell>
						</listitem>
					</template>
				</listbox>
			</listcell>
		</listitem>
	</template>
</listbox>

Notice that, unlike the forEach attribute, there is no forEachStatus available, because, when the nested listbox is about to render, the each variable is no longer available (ROD and other lifecycle issue).

Grid Model Rendering with Template

Similarly, we could define a customer rendering with a template for a grid:

<grid model="${books}">
	<columns>
		<column label="ISBN" sort="auto"/>
		<column label="Name" sort="auto"/>
		<column label="Description"/>
	</columns>
	<template name="model">
		<row>
			<label value="${each.isbn}"/>
			<label value="${each.name}"/>
			<label value="${each.description}"/>
		</row>
	</template>
</grid>

where we assume books is an instance of ListModel that contains a list of the Book instances. And, each Book instances has at least three getter methods: getIsbn, getName and getDescription.

Tree Model Rendering with Template

Similarly, we could define a customer rendering with a template for a tree:

<tree model="${files}">
	<treecols>
		<treecol label="Path"/>
		<treecol label="Description"/>
	</treecols>
	<template name="model">
		<treeitem context="menupopup">
			<treerow>
				<treecell label="${each.data.path}"/>
				<treecell label="${each.data.description}"/>
			</treerow>
		</treeitem>
	</template>
</tree>

where we assume files is an instance of DefaultTreeModel. Notice since DefaultTreeModel is used, each references an instance of DefaultTreeNode. Thus, we have to use DefaultTreeNode.getData() to retrieve the real data.

Combobox Model Rendering with Template

Here is an example of rendering a combobox with a template:

<window>
	ListModel infos = new ListModelArray(
		new String[][] {
			{"Apple", "10kg"},
			{"Orange", "20kg"},
			{"Mango", "12kg"}
		});
	</zscript>			
	<combobox model="${infos}">
		<template name="model">
			<comboitem label="${each[0]}: ${each[1]}"/>
		</template>
	</combobox>
</window>

Using Template in Application

Templates are a generic feature. The use is not limited to the custom model rendering. You could use templates in your applications too.

Each template is stored as part of a component. It could retrieve it by invoking the getTemplate method. To create the components defined in the template, just invoke the create method. For example,

comp.getTemplate("foo").create(comp, null, null);

The third argument of the create method is a variable resolver (VariableResolver). Depending on your requirement, you could pass any implementation you like. For example, the implementation of listbox actually utilizes it to return the data being rendered, and the code is similar to the following (for easy understanding, the code has been simplified).

public class TempalteBasedRenderer implements ListitemRenderer {
	public void render(Listitem item, final Object data) {
		final Listbox listbox = (Listbox)item.getParent();
		final Component[] items = listbox.getTemplate("model").create(listbox, item,
			new VariableResolver() {
				public Object resolveVariable(String name) {
					return "each".equals(name) ? data: null;
				}
			});

		final Listitem nli = (Listitem)items[0];
		if (nli.getValue() == null) //template might set it
			nli.setValue(data);
		item.detach();
	}
}

In additions, the template allows the user to specify any number of parameters with any name, and these parameters can be retrieved back by the getParameters method of the Tempalte interface:

<tempalte name="foo" var1="value1" var2="${el2}">
...
</template>

If a template is located as a separated file, you could specify it in the src attribute as follows.

<template name="foo" src="foo.zul">
...
</template>

Download

To test drive, you could download a freshly release of 5.1.0 released after July 19.


Comments



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