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

From Documentation
(41 intermediate revisions by 4 users not shown)
Line 2: Line 2:
 
|author=Tom Yeh, Potix Corporation
 
|author=Tom Yeh, Potix Corporation
 
|date=July 20, 2011
 
|date=July 20, 2011
|version=ZK 5.1
+
|version=ZK 6.0
 
}}
 
}}
  
Line 8: Line 8:
  
 
=The Story=
 
=The Story=
Before 5.1, [[ZK Developer's Reference/MVC/View|the custom renderer]] must be implemented in Java. It is the most powerful since you could do anything with Java. However, in many cases, it is tedious since an additional Java class have to create and maintain and it is more about View than Control in MVC terminologies.
+
 +
Before ZK 6.0, [[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 ZUL as it is closer to "View"  rather than the "Control" in the Java code.
  
With 5.1, a concept called template is introduced. It allows UI designers to specify templates right in a ZUML page, and then the model can be rendered based on the template without any Java code.
+
With ZK 6.0, 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=
+
=What Is a Template=
  
 
A template is a segment of a  ZUML page enclosed with the <tt>template</tt> element as shown below.
 
A template is a segment of a  ZUML page enclosed with the <tt>template</tt> element as shown below.
Line 27: Line 28:
 
</source>
 
</source>
  
The template could contain any ZUML elements you want, even including another templates. When ZK interprets a template, ZK won't interpret its content immediately. Rather, it stores it as an instance of <tt>org.zkoss.zk.ui.util.Template</tt> into the component, such that it can be retrieved later to create components by the application or the component.
+
The template can contain any ZUML elements users desire, including other templates. When ZK interprets a template, it does not interpret its content immediately, rather, it first stores it as an instance of <tt>org.zkoss.zk.ui.util.Template</tt> into the component, such that it can be retrieved later to create components by the application or the component to create its child component(s).
  
 
=Listbox Model Rendering with Template=
 
=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.
+
With 6.0, the custom renderer can be done with a template without any Java code. For example, when wanting to render a two-dimensional array, the follow can be done to achieve the desired outcome
 
 
 
<source lang="xml" high="8,9,10,11,12,13">
 
<source lang="xml" high="8,9,10,11,12,13">
 
<?variable-resolver class="foo.FruitProvider"?>
 
<?variable-resolver class="foo.FruitProvider"?>
Line 50: Line 50:
 
</source>
 
</source>
  
where <code>fruits</code> is a two-dimensional array that can be retrieved by use of a variable resolver (<javadoc type="interface">org.zkoss.xel.VariableResolver</javadoc>)<ref>There are several ways to assign a model to a UI component. Please refer to [[ZK Developer's Reference/MVC/Model|ZK Developer's Reference]] for details.</ref> called <code>foo.FruitProvider</code> as shown below.
+
where <code>fruits</code> is a two-dimensional array that can be retrieved by use of a variable resolver (<javadoc type="interface">org.zkoss.xel.VariableResolver</javadoc>)<ref>There are several ways to assign a model to a UI component. Please refer to [[ZK Developer's Reference/MVC/Model|ZK Developer's Reference]] for detailed information.</ref> called <code>foo.FruitProvider</code> as shown below.
  
 
<source lang="java">
 
<source lang="java">
public class FruitProvide implements VariableResolver {
+
public class FruitProvider implements VariableResolver {
 
     public Object resolveVariable(String name) {
 
     public Object resolveVariable(String name) {
 
         if ("fruits".equals(name))
 
         if ("fruits".equals(name))
Line 67: Line 67:
 
</source>
 
</source>
  
As shown, the custom rendering is defined by a template called <code>model</code>. 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 <code>each</code> is assigned with the data being rendered.
+
[[File:St201107-listbox.png‎]]
 +
 
 +
As illustrated above, custom rendering is defined by a template called <code>model</code>. The template's name is important because users are allowed to associate multiple templates to one component. Furthermore, when the template is rendered, a variable called <code>each</code> is assigned with the data being rendered.
  
In additions, if the template does not set a value to the listitem (<javadoc method="setValue(java.lang.Object)">org.zkoss.zul.Listitem</javadoc>), it will be assigned with the data being rendered. It could be used to retrieve back the data being rendered.
+
Additionally, if value="${each}" is not specified to the listitem (<javadoc method="setValue(java.lang.Object)">org.zkoss.zul.Listitem</javadoc>) in the template, the renderer will automatically assign the data being rendered to the listitem in order for users to conveniently retrieve back the data when necessary.  
  
 
<blockquote>
 
<blockquote>
Line 75: Line 77:
 
<references/>
 
<references/>
 
</blockquote>
 
</blockquote>
 +
 +
==Nested Listboxes==
 +
The template can be applied recursively. Here is an example of a listbox-in-listbox:
 +
 +
<source lang="xml" high="12, 17">
 +
<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>
 +
</source>
 +
 +
[[File:St201107-listbox-in-listbox.png‎]]
 +
 +
To access the data of the outer template from the inner template, use the parent listitem's <javadoc method="getValue()">org.zkoss.zul.Listitem</javadoc> where the data is stored by default. Please look at line 8 of the following example, data can be retrieved from the outer model by travelling the component tree
 +
 +
<source lang="xml" high="8">
 +
<listbox model="${quarters}">
 +
<template name="model">
 +
<listitem>
 +
<listcell>
 +
<listbox model="${months[each]}">
 +
<template name="model">
 +
<listitem>
 +
<listcell label="${forEachStatus.index}" />
 +
<listcell>${self.parent.parent.parent.parent.parent.value}</listcell>
 +
<listcell>${each}</listcell>
 +
</listitem>
 +
</template>
 +
</listbox>
 +
</listcell>
 +
</listitem>
 +
</template>
 +
</listbox>
 +
</source>
 +
 +
Notice that, unlike [[ZUML Reference/ZUML/Attributes/forEach|the forEach attribute]], the <tt>forEachStatus</tt> is only available in the near <tt>template</tt> scope when the nested listbox is about to render.
  
 
=Grid Model Rendering with Template=
 
=Grid Model Rendering with Template=
  
Similarly, we could define a customer rendering with a template for a grid:
+
Similarly, users can define a customer rendering with a template for a grid:
  
 
<source lang="xml" high="7,8,9,10,11,12,13">
 
<source lang="xml" high="7,8,9,10,11,12,13">
Line 97: Line 156:
 
</source>
 
</source>
  
where we assume <code>books</code> is an instance of <javadoc type="interface">org.zkoss.zul.ListModel</javadoc> that contains a list of the Book instances. And, each Book instances has at least three getter methods: <code>getIsbn</code>, <code>getName</code> and <code>getDescription</code>.
+
where <code>books</code> is assumed as an instance of <javadoc type="interface">org.zkoss.zul.ListModel</javadoc> that contains a list of the Book instances while each Book instances has at least three getter methods: <code>getIsbn</code>, <code>getName</code> and <code>getDescription</code>.
 +
 
 +
Notice that the template named <tt>model</tt> must be associated with the grid, i.e., it must be a direct child element of the grid element as shown above. A common mistake is to put it under the rows element. Remember the template is a ZUML fragment telling the grid how to render a row, and the template itself is not a component.
  
 
=Tree Model Rendering with Template=
 
=Tree Model Rendering with Template=
  
Similarly, we could define a customer rendering with a template for a tree:
+
Similarly, users can also define a customer rendering with a template for a tree:
  
 
<source lang="xml" high="6,7,8,9,10,11,12,13">
 
<source lang="xml" high="6,7,8,9,10,11,12,13">
Line 120: Line 181:
 
</source>
 
</source>
  
where we assume <code>files</code> is an instance of <javadoc>org.zkoss.zul.DefaultTreeModel</javadoc>. Notice since <javadoc>org.zkoss.zul.DefaultTreeModel</javadoc> is used, <code>each</code> references an instance of <javadoc>org.zkoss.zul.DefaultTreeNode</javadoc>. Thus, we have to use <javadoc method="getData()">org.zkoss.zul.DefaultTreeNode</javadoc> to retrieve the real data.
+
assume <code>files</code> is an instance of <javadoc>org.zkoss.zul.DefaultTreeModel</javadoc>. Notice since <javadoc>org.zkoss.zul.DefaultTreeModel</javadoc> is used, <code>each</code> references an instance of <javadoc>org.zkoss.zul.DefaultTreeNode</javadoc>. Thus, to retrieve the real data, use <javadoc method="getData()">org.zkoss.zul.DefaultTreeNode</javadoc>
  
 
=Combobox Model Rendering with Template=
 
=Combobox Model Rendering with Template=
  
Here is an example of rendering a combobox with a template:
+
Here is an example of how to render a combobox with a template:
  
 
<source lang="xml" high="9,10,11,12,13">
 
<source lang="xml" high="9,10,11,12,13">
Line 145: Line 206:
 
=Using Template in Application=
 
=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.
+
"Template" is a generic feature and its use is not limited to custom model rendering. Users are able to use "template" in ZK applications too.
  
Each template is stored as part of a component. It could retrieve it by invoking the <code>getTemplate</code> method. To create the components defined in the template, just invoke the <code>create</code> method. For example,
+
Each template is stored as part of a component and can be retrieved it by invoking the <code>getTemplate</code> method. To create the component tree defined in the template, just invoke the <code>Template.create(Component parent, Component insertBefore, VariableResolver resolver)</code> method. For example,
  
 
<source lang="java">
 
<source lang="java">
comp.getTemplate("foo").create(comp, null, null);
+
comp.getTemplate("foo").create(comp, null, null, null);
 
</source>
 
</source>
  
The third argument of the <code>create</code> method is a variable resolver (<javadoc type="interface">org.zkoss.xel.VariableResolver</javadoc>). 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).
+
The third argument of the <code>create</code> method is a variable resolver (<javadoc type="interface">org.zkoss.xel.VariableResolver</javadoc>). Depending on the requirement, you could pass any implementation you like.  For example, the implementation of a listbox actually utilizes it to return the data being rendered; the code is similar to the following (for easy understanding, the code has been simplified).
 +
 
 +
For more detailed information about the variable resolver, please refer to [[ZUML Reference/ZUML/Processing Instructions/variable-resolver|ZUML Reference]].
  
 
<source lang="java">
 
<source lang="java">
public class TempalteBasedRenderer implements ListitemRenderer {
+
public class TemplateBasedRenderer implements ListitemRenderer {
 
public void render(Listitem item, final Object data) {
 
public void render(Listitem item, final Object data) {
 
final Listbox listbox = (Listbox)item.getParent();
 
final Listbox listbox = (Listbox)item.getParent();
Line 164: Line 227:
 
return "each".equals(name) ? data: null;
 
return "each".equals(name) ? data: null;
 
}
 
}
});
+
}, null);
  
 
final Listitem nli = (Listitem)items[0];
 
final Listitem nli = (Listitem)items[0];
Line 172: Line 235:
 
}
 
}
 
}
 
}
 +
</source>
 +
 +
In addition, the template allow users to specify any number of parameters with any name, and these parameters can be retrieved back by the <tt>getParameters</tt> method of the <tt>Template</tt> interface:
 +
 +
<source lang="xml">
 +
<template name="foo" var1="value1" var2="${el2}">
 +
...
 +
</template>
 +
</source>
 +
 +
If a template is located elsewhere as a separated file, to reference it, specify it in the <tt>src</tt> attribute as follows.
 +
 +
<source lang="xml">
 +
<template name="foo" src="foo.zul">
 +
...
 +
</template>
 
</source>
 
</source>
  
 
=Download=
 
=Download=
  
To test drive, you could download [https://sourceforge.net/projects/zk1/files/ZK%20Freshly/ a freshly release of 5.1.0] released after July 19.
+
To test drive, please download [https://sourceforge.net/projects/zk1/files/ZK%20Freshly/zk-6.0.0/ a freshly release of 6.0.0].
  
 
{{Template:CommentedSmalltalk_Footer|
 
{{Template:CommentedSmalltalk_Footer|
 
|name=Potix Corporation
 
|name=Potix Corporation
 
}}
 
}}

Revision as of 10:36, 22 December 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 6.0

The Story

Before ZK 6.0, 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 ZUL as it is closer to "View" rather than the "Control" in the Java code.

With ZK 6.0, 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 a 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 can contain any ZUML elements users desire, including other templates. When ZK interprets a template, it does not interpret its content immediately, rather, it first 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 to create its child component(s).

Listbox Model Rendering with Template

With 6.0, the custom renderer can be done with a template without any Java code. For example, when wanting to render a two-dimensional array, the follow can be done to achieve the desired outcome

<?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 FruitProvider 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 illustrated above, custom rendering is defined by a template called model. The template's name is important because users are allowed to associate multiple templates to one component. Furthermore, when the template is rendered, a variable called each is assigned with the data being rendered.

Additionally, if value="${each}" is not specified to the listitem (Listitem.setValue(Object)) in the template, the renderer will automatically assign the data being rendered to the listitem in order for users to conveniently retrieve back the data when necessary.


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

Nested Listboxes

The template can be applied recursively. Here is an example of a 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

To access the data of the outer template from the inner template, use the parent listitem's Listitem.getValue() where the data is stored by default. Please look at line 8 of the following example, data can be retrieved from the outer model by travelling the component tree

<listbox model="${quarters}">
	<template name="model">
		<listitem>
			<listcell>
				<listbox model="${months[each]}">
					<template name="model">
						<listitem>
							<listcell label="${forEachStatus.index}" />
							<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, the forEachStatus is only available in the near template scope when the nested listbox is about to render.

Grid Model Rendering with Template

Similarly, users can 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 books is assumed as an instance of ListModel that contains a list of the Book instances while each Book instances has at least three getter methods: getIsbn, getName and getDescription.

Notice that the template named model must be associated with the grid, i.e., it must be a direct child element of the grid element as shown above. A common mistake is to put it under the rows element. Remember the template is a ZUML fragment telling the grid how to render a row, and the template itself is not a component.

Tree Model Rendering with Template

Similarly, users can also 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>

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

Combobox Model Rendering with Template

Here is an example of how to render 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

"Template" is a generic feature and its use is not limited to custom model rendering. Users are able to use "template" in ZK applications too.

Each template is stored as part of a component and can be retrieved it by invoking the getTemplate method. To create the component tree defined in the template, just invoke the Template.create(Component parent, Component insertBefore, VariableResolver resolver) method. For example,

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

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

For more detailed information about the variable resolver, please refer to ZUML Reference.

public class TemplateBasedRenderer 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;
				}
			}, null);

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

In addition, the template allow users to specify any number of parameters with any name, and these parameters can be retrieved back by the getParameters method of the Template interface:

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

If a template is located elsewhere as a separated file, to reference it, specify it in the src attribute as follows.

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

Download

To test drive, please download a freshly release of 6.0.0.


Comments



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