ZK8 Features for MVC - Shadow Elements - Part 2"

From Documentation
m (correct highlight (via JWB))
 
(35 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
{{Template:Smalltalk_Author|
 
{{Template:Smalltalk_Author|
 
|author=Robert Wenzel, Engineer, Potix Corporation
 
|author=Robert Wenzel, Engineer, Potix Corporation
|date=March 2017
+
|date=March 30, 2017
 
|version=ZK 8.0
 
|version=ZK 8.0
 
}}
 
}}
Line 7: Line 7:
 
= Introduction =
 
= Introduction =
  
In continuation of the previous article [[Small_Talks/2016/October/ZK8_Features_for_MVC_-_Shadow_Elements_-_Part_1 | ("ZK8 Features for MVC - Shadow Elements - Part 1")]] I'll focus this time on the shadow element <forEach>.
+
In continuation of the previous [[Small_Talks/2016/October/ZK8_Features_for_MVC_-_Shadow_Elements_-_Part_1 | Part 1]], this time I'll focus on the shadow element <forEach>.
  
 
'''Some background'''
 
'''Some background'''
  
The intention behind <forEach> are similar to those of the <apply> shadow element. Several historically evolved mechanisms to repeat UI components with their own specific behavior and limitations needed a unified abstraction to become more versatile and layout neutral (no additional DOM elements) at the same time.
+
The intention behind <forEach> are similar to those of the <apply> shadow element. Several historical mechanisms to render UI components repeatedly have evolved with their own specific behavior and limitations, and a unified abstraction was needed to become more versatile and layout neutral (no additional DOM elements) at the same time.
  
 
Let's look at what's been there before:
 
Let's look at what's been there before:
Line 37: Line 37:
  
 
* allows replacing the whole Model => re-render all elements
 
* allows replacing the whole Model => re-render all elements
* allows adding/removing single elements => re-render only affected elements
+
* allows adding/removing single elements => re-render only the affected elements
 
* supported only by specific components (grid/listbox/combobox/tabbox ...)
 
* supported only by specific components (grid/listbox/combobox/tabbox ...)
  
Line 54: Line 54:
 
* allows replacing the whole Model => re-render all elements
 
* allows replacing the whole Model => re-render all elements
 
* (before ZK 8) didn't update adding/removing single elements => needed to re-render all elements
 
* (before ZK 8) didn't update adding/removing single elements => needed to re-render all elements
* requires MVVM / BindComposer
+
* requires MVVM/BindComposer
  
The examples above behave quite differently, each with its own limitations. All require a component: the special forEach-attribute repeats the element itself while the model- and children-binding repeat the inner template.
+
The examples above behave quite differently, each with its own limitations. All require a component: the special forEach-attribute which repeats the element itself while the model- and children-binding repeats the inner template.
  
 
'''That's why <forEach>'''
 
'''That's why <forEach>'''
  
The new <forEach> shadow element allows static and dynamic UI composition. When used in combination with a ListModel implementation also single item updates (add/remove) result only in UI updates to the affected elements. This will improve the overall performance by saving network traffic and avoiding updates to existing DOM elements in the browser which saves render time. As with other shadow elements this works without adding additional container components. ZK will keep track of the individual positions in the component tree and DOM tree.
+
The new <forEach> shadow element allows static and dynamic UI composition. When used in combination with a ListModel implementation, single item updates (add/remove) result only in UI updates to the affected elements. This will improve the overall performance by saving network traffic and avoiding updates to existing DOM elements in the browser which saves render time. As with other shadow elements, this works without adding additional container components. ZK will keep track of the individual positions in the component tree and DOM tree.
  
 
Let's look into different usage scenarios of increasing the complexity for cases where most flexibility is needed.
 
Let's look into different usage scenarios of increasing the complexity for cases where most flexibility is needed.
  
 
= Static usage (index, array, collection) =
 
= Static usage (index, array, collection) =
 
+
Below, what the above examples (1, 3) will look like using <forEach> (<listbox> could be done in the same way, but it is still more useful when binding the model-attribute to track the selected listitems in the ListModel).
Here what the above examples (1, 3) will look like using <forEach> (<listbox> could be done in the same way but it is still more useful when binding the model-attribute to track the selected listitems in the ListModel as well)
 
  
 
<source lang="xml">
 
<source lang="xml">
Line 96: Line 95:
 
</forEach>
 
</forEach>
 
</source>
 
</source>
For "readonly" items there are 2 children while there will be 2 additional buttons otherwise.
+
For "readonly" items, there are 2 children while there will be 2 additional buttons otherwise.
  
As other shadow elements the <forEach> can be used with a static EL expression (<code>${some.expression}</code>) resolving to a java Array or Collection (without implementing ListModel). In this case the shadow element will be removed after initial rendering to save the server side memory. The resulting component tree will only contain the actually generated components.
+
As other shadow elements the <forEach> can be used with a static EL expression (<code>${some.expression}</code>) resolving to a java Array or Collection (without implementing ListModel). In this case, the shadow element will be removed after initial rendering to save the server side memory. The resulting component tree will only contain the actually generated components.
To prevent the removal of the shadow element you can add dynamicValue="true" to preserve the shadow tree in order to manipulate it later.
+
To prevent the removal of the shadow element, you can add '''dynamicValue="true"''' to preserve the shadow tree in order to manipulate it later.
  
 
= Dynamic usage (ListModel) =
 
= Dynamic usage (ListModel) =
When using a dynamic bind expression (@load / @init) or a static EL resolving into a ListModel implementation the shadow element will remain in the shadow tree. Especially when used with a ListModel it will listen to changes in the ListModel and reflect the updates automatically. This allows convenient usage of the built-in ListModel implementations (e.g. ListModelList)
+
When using a dynamic bind expression (@load / @init) or a static EL resolving into a ListModel implementation, the shadow element will remain in the shadow tree. Especially when used with a ListModel, it will listen to changes in the ListModel and reflect the updates automatically. This allows convenient usage of the built-in ListModel implementations (e.g. ListModelList).
  
Here a [http://zkfiddle.org/sample/2cjibpn/1-ForEach-with-ListModel live example on ZK Fiddle] (make sure the lates ZK 8 version is selected).
+
Here is a [http://zkfiddle.org/sample/2cjibpn/1-ForEach-with-ListModel live example on ZK Fiddle] (make sure the lates ZK 8 version is selected).
  
I suggest observing the network traffic (in the browser's developer tools) - especially the response body - verifying that only single div-elements are added removed without re-rendering the whole list of names - just by calling <javadoc class="true"  method="add(E)">org.zkoss.zul.ListModelList</javadoc>
+
I suggest observing the network traffic (in the browser's developer tools) - especially the response body - verifying that only single div-elements are added/removed without re-rendering the whole list of names - just by calling <javadoc class="true"  method="add(E)">org.zkoss.zul.ListModelList</javadoc>
 
and <javadoc class="true"  method="remove(E)">org.zkoss.zul.ListModelList</javadoc>.
 
and <javadoc class="true"  method="remove(E)">org.zkoss.zul.ListModelList</javadoc>.
  
<source lang="java" high="3,8">
+
<source lang="java" highlight="3,8">
 
   @Listen("onClick=#addBtn")
 
   @Listen("onClick=#addBtn")
 
   public void addName() {
 
   public void addName() {
Line 120: Line 119:
 
   }
 
   }
 
</source>
 
</source>
 +
 +
Example sources:
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/webapp/forEach/forEachListModel.zul forEachListModel.zul],
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/java/zk/example/foreach/NamesComposer.java NamesComposer.java]
  
 
== Adding templates ==
 
== Adding templates ==
The content of a <forEach> element will implicitly become an anonymous template, hence the following examples are equivalent and produce the same result, and shadow/component tree.
+
The content of a <forEach> element will implicitly become an anonymous template; hence the following examples are equivalent and produce the same result, and shadow/component tree.
  
<source lang="xml" high="8,16">
+
<source lang="xml" highlight="8,16">
 
<forEach items="${someCollection}">
 
<forEach items="${someCollection}">
 
     <div>
 
     <div>
Line 148: Line 151:
 
</source>
 
</source>
  
Since this is just typing overhead and doesn't add any value the <template> can be omitted (however it's not wrong to write it out explicitly if you prefer).
+
Since this is just typing overhead and doesn't add any value, the <template> can be omitted (however it's not wrong to write it out explicitly if you prefer).
  
 
== Dynamic Templates ==
 
== Dynamic Templates ==
  
Things become interesting when rendering items differently (e.g. based on a condition or property). For such cases the <apply> element can be nested inside the <forEach>.
+
Things become interesting when rendering items differently (e.g. based on a condition or property). For such cases, the <apply> element can be nested inside the <forEach>.
  
<source lang="xml" high="3,5,15,23">
+
<source lang="xml" highlight="3,5,15,23">
 
<!-- named inline templates -->
 
<!-- named inline templates -->
 
<forEach items="${someCollectionOfConfigElements}" var="configElement">
 
<forEach items="${someCollectionOfConfigElements}" var="configElement">
Line 183: Line 186:
 
* '''Line 5:''' here (as in many other cases) [[ZK_Developer's_Reference/Event_Handling/Event_Forwarding | event forwarding]] comes in handy when working on repeated elements (avoids having to deal with indices or non unique component IDs).
 
* '''Line 5:''' here (as in many other cases) [[ZK_Developer's_Reference/Event_Handling/Event_Forwarding | event forwarding]] comes in handy when working on repeated elements (avoids having to deal with indices or non unique component IDs).
  
Here a [http://zkfiddle.org/sample/3f8rpo6/2-forEach-with-dynamic-template runnable example on ZKFiddle]
+
Here is a [http://zkfiddle.org/sample/3f8rpo6/2-forEach-with-dynamic-template runnable example on ZKFiddle]
 +
 
 +
Example sources: [https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/webapp/forEach/forEachDynamicTemplate.zul forEachDynamicTemplate.zul],
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/java/zk/example/foreach/ConfigElement.java ConfigElement.java]
  
 
= Control from java code (MVC) =
 
= Control from java code (MVC) =
  
As above the simplest way to control the items rendered by <forEach> element is via ListModel (e.g. ListModelList) and add/remove elements while new templates are instantiated without extra care.
+
As mentioned above, the simplest way to control the items rendered by <forEach> element is via ListModel (e.g. ListModelList) and add/remove elements while new templates are instantiated without extra care.
However if additional control is desired e.g. replacing the whole model completely or rendering the <templates> in java code (similar to a [[ZK_Developer%27s_Reference/MVC/View/Renderer/Grid_Renderer | Grid Renderer]]) the following techniques can be used.
+
However, if additional control is desired e.g. replacing the whole model completely or rendering the <templates> in java code (similar to a [[ZK_Developer%27s_Reference/MVC/View/Renderer/Grid_Renderer | Grid Renderer]]), the following techniques can be used.
 +
 
 +
The following 4 Sections will create the same example (see picture below) in 4 different ways. I don't have a personal preference about any of the used techniques, they are equally valid and have similar performance. Simply choose which suits best into your programming model.
 +
 
 +
[[File:mvc-shadow-foreach.png]]
  
 
== Shadow Selector ==
 
== Shadow Selector ==
Line 194: Line 204:
 
To access the <forEach> element directly simply give it an ID ...
 
To access the <forEach> element directly simply give it an ID ...
  
<source lang="xml" high="9">
+
<source lang="xml" highlight="9">
 
<zk xmlns:sh="shadow">
 
<zk xmlns:sh="shadow">
 
<groupbox width="450px" apply="zk.example.ForEachComposer" mold="3d">
 
<groupbox width="450px" apply="zk.example.ForEachComposer" mold="3d">
Line 215: Line 225:
 
</source>
 
</source>
  
... and select it in your composer using a [[ZK_Developer's_Reference/MVC/Controller/Wire_Components#Shadow_Selectors | shadow selector]]. It's then possible to call <javadoc method="setItems(java.lang.Object)">org.zkoss.zuti.zul.ForEach</javadoc> directly and render the contents by calling <javadoc class="false"  method="recreate()">org.zkoss.zuti.zul.ForEach</javadoc>. With this separation you have control when to re-render the contents initially after the model was replaced. Subsequent add/removes to the model are then reflected automatically.
+
... and select it in your composer using a [[ZK_Developer's_Reference/MVC/Controller/Wire_Components#Shadow_Selectors | shadow selector]]. It's then possible to call <javadoc method="setItems(java.lang.Object)">org.zkoss.zuti.zul.ForEach</javadoc> directly and render the contents by calling <javadoc class="false"  method="recreate()">org.zkoss.zuti.zul.ForEach</javadoc>. With this separation, you are in control when to re-render the contents initially or after the model was replaced. Subsequent additions/removals of model items are reflected automatically.
  
<source lang="java" high="4,12,13">
+
<source lang="java" highlight="4,12,13">
 
public class ForEachComposer extends SelectorComposer<Component> {
 
public class ForEachComposer extends SelectorComposer<Component> {
 
private static final long serialVersionUID = 1L;
 
private static final long serialVersionUID = 1L;
Line 233: Line 243:
 
...
 
...
 
</source>
 
</source>
 +
 +
Example sources:
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/webapp/forEach/forEachExample.zul forEachExample.zul],
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/java/zk/example/foreach/ForEachComposer.java ForEachComposer.java]
  
 
== CollectionTemplate ==
 
== CollectionTemplate ==
In cases where the insert position of the items needs to be determined at runtime (i.e. without a <forEach> element in the zul file). The <javadoc type="class">org.zkoss.zuti.zul.CollectionTemplate</javadoc> is a utility class to easily do so.
+
In cases where the insert position of the items needs to be determined at runtime (i.e. without a <forEach> element in the zul file), the <javadoc type="class">org.zkoss.zuti.zul.CollectionTemplate</javadoc> is a utility class to easily do so.
  
 
<source lang="xml">
 
<source lang="xml">
Line 261: Line 275:
 
Here the template is only '''defined''' inline (not yet rendered) and a normal DIV element as the insertion host but no <forEach>.
 
Here the template is only '''defined''' inline (not yet rendered) and a normal DIV element as the insertion host but no <forEach>.
  
<source lang="java" high="14">
+
<source lang="java" highlight="14">
 
public class CollectionTemplateComposer extends SelectorComposer<Component> {
 
public class CollectionTemplateComposer extends SelectorComposer<Component> {
 
private static final long serialVersionUID = 1L;
 
private static final long serialVersionUID = 1L;
Line 281: Line 295:
 
</source>
 
</source>
  
The java code looks very similar to the above example. Instead of wiring an existing <forEach> element the CollectionTemplate (which internally adds a ForEach shadow element) is created at and inserted at runtime. A single fixed template for all elements is chosen during runtime. In this example the template(s) remain in a zul file.
+
The java code looks very similar to the above example. Instead of wiring an existing <forEach> element, the CollectionTemplate (which internally adds a ForEach shadow element) is created and inserted at runtime. A single fixed template for all elements is chosen during runtime. In this example, the template(s) remain in a zul file.
 +
 
 +
Example sources:
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/webapp/forEach/collectionTemplateExample.zul collectionTemplateExample.zul],
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/java/zk/example/foreach/CollectionTemplateComposer.java CollectionTemplateComposer.java]
  
 
== Resolve templates dynamically in java code ==
 
== Resolve templates dynamically in java code ==
Line 287: Line 305:
 
It is also possible to resolve templates dynamically (by name) in java code. For this a <javadoc type="class">org.zkoss.zuti.zul.CollectionTemplateResolver</javadoc> must be implemented.  
 
It is also possible to resolve templates dynamically (by name) in java code. For this a <javadoc type="class">org.zkoss.zuti.zul.CollectionTemplateResolver</javadoc> must be implemented.  
 
Finding a template in the zul file is simplified by the utility class <javadoc type="class">org.zkoss.zk.ui.Templates</javadoc> and its lookup(...) methods to recursively search a template by name (among parent components).
 
Finding a template in the zul file is simplified by the utility class <javadoc type="class">org.zkoss.zk.ui.Templates</javadoc> and its lookup(...) methods to recursively search a template by name (among parent components).
In the example below the template will be either '''nameTagNonDeletable''' (for members of the goodFriends collection) or '''nameTag''' (for any other name).
+
In the example below, the template will be either '''nameTagNonDeletable''' (for members of the goodFriends collection) or '''nameTag''' (for any other name).
  
<source lang="java" high="9,10">
+
<source lang="java" highlight="9,10">
 
@Override
 
@Override
 
public void doAfterCompose(Component comp) throws Exception {
 
public void doAfterCompose(Component comp) throws Exception {
Line 306: Line 324:
 
The corresponding zul file now defines the 2 templates to choose from when rendering a name tag.
 
The corresponding zul file now defines the 2 templates to choose from when rendering a name tag.
  
<source lang="xml" high="2,9">
+
<source lang="xml" highlight="2,9">
 
<div id="namesContainer">
 
<div id="namesContainer">
 
<template name="nameTag">
 
<template name="nameTag">
Line 323: Line 341:
 
</div>
 
</div>
 
</source>
 
</source>
 +
 +
Example sources:
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/webapp/forEach/collectionTemplateResolverExample.zul collectionTemplateResolverExample.zul],
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/java/zk/example/foreach/CollectionTemplateResolverComposer.java CollectionTemplateResolverComposer.java]
  
 
== Render templates in java code ==
 
== Render templates in java code ==
  
The TemplateResolver in the example above can return any <javadoc type="class">org.zkoss.zk.ui.util.Template</javadoc> implementation making it possible to provide a custom implementation of that interface.
+
The TemplateResolver in the example above can return any <javadoc type="class">org.zkoss.zk.ui.util.Template</javadoc> implementation, making it possible to provide a custom implementation of that interface.
  
 
A simple method creating the same component structure as the nameTag template from above could look like this:
 
A simple method creating the same component structure as the nameTag template from above could look like this:
Line 353: Line 375:
 
In order to call this method for each model item, the Template could be implemented as in the code below. '''RenderTemplate''' will be explained in a minute.
 
In order to call this method for each model item, the Template could be implemented as in the code below. '''RenderTemplate''' will be explained in a minute.
  
Let's first have a look at the CollectionTemplateResolver.
+
Let's have a look at the CollectionTemplateResolver first.
  
 
<source lang="java">
 
<source lang="java">
Line 375: Line 397:
 
The '''RenderTemplate''' itself is just an example of a reusable way to implement a Template as a functional interface to further reduce boiler plate code.
 
The '''RenderTemplate''' itself is just an example of a reusable way to implement a Template as a functional interface to further reduce boiler plate code.
  
<source lang="java" high="3,7,13">
+
<source lang="java" highlight="3,7,14">
 
@FunctionalInterface
 
@FunctionalInterface
 
public static interface RenderedTemplate<T> extends Template {
 
public static interface RenderedTemplate<T> extends Template {
 
Component render(T item);
 
Component render(T item);
 
@Override
 
@Override
default  Component[] create(Component parent, Component insertBefore, VariableResolver resolver, @SuppressWarnings("rawtypes") Composer composer) {
+
default  Component[] create(Component parent, Component insertBefore, VariableResolver resolver,  
 +
                          @SuppressWarnings("rawtypes") Composer composer) {
 
@SuppressWarnings("unchecked")
 
@SuppressWarnings("unchecked")
 
Component itemComp = render((T)resolver.resolveVariable("each"));
 
Component itemComp = render((T)resolver.resolveVariable("each"));
Line 394: Line 417:
 
* '''Line 3:''' the only remaining abstract method of the interface qualifying it as a functional interface
 
* '''Line 3:''' the only remaining abstract method of the interface qualifying it as a functional interface
 
* '''Line 8:''' call the render method passing in the model object to be rendered
 
* '''Line 8:''' call the render method passing in the model object to be rendered
* '''Line 13:''' in my implementation I don't care about parameters passed into the template (I only use 'each')
+
* '''Line 14:''' in this example no additional template parameters are used except the default 'each'
  
As the Template interface leaves room for your own implementation logic the examples shown here are not the only way to achieve your own application logic. Feel free, be creative - use the java language based on your own requirements.
+
As the Template interface leaves room for your own implementation logic, the examples shown here are not the only ways to achieve your own application logic. Feel free to be creative - use the java language based on your own requirements.
 +
 
 +
Example sources:
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/webapp/forEach/collectionTemplateRendererExample.zul collectionTemplateRendererExample.zul],
 +
[https://github.com/zkoss-demo/zk-mvc-shadow/blob/part-2/src/main/java/zk/example/foreach/CollectionTemplateRendererComposer.java CollectionTemplateRendererComposer.java]
  
 
= Summary =
 
= Summary =
  
ForEach and CollectionTemplate are 2 new powerful features in ZK8 also applicable to existing ZK applications following the MVC pattern.
+
ForEach and CollectionTemplate are 2 new powerful features in ZK8, they are also applicable to existing ZK applications following the MVC pattern.
  
Whenever you encounter repeated elements consider using those new features either to simplify your zul files, overcome limitations in previous versions or simply enjoy the flexibility of a ListModel implementation anywhere in your component hierarchy.
+
Whenever you encounter repeated elements, consider using those new features either to simplify your zul files, overcome limitations in previous versions or simply leverage the flexibility of a ListModel implementation anywhere in your component hierarchy.
  
 
I'll happily reading your feedback to further improve the given examples and explanations. Just let me know what you think in the comments below.
 
I'll happily reading your feedback to further improve the given examples and explanations. Just let me know what you think in the comments below.
 +
 +
== Example Sources ==
 +
 +
The code examples are available on githup in the [https://github.com/zkoss-demo/zk-mvc-shadow zk-mvc-shadow repository]
 +
:zul files https://github.com/zkoss-demo/zk-mvc-shadow/tree/part-2/src/main/webapp/forEach
 +
:java classes https://github.com/zkoss-demo/zk-mvc-shadow/tree/part-2/src/main/java/zk/example/foreach
 +
 +
== Running the Example ==
 +
Clone the repo
 +
 +
    git clone [email protected]:zkoss-demo/zk-mvc-shadow.git
 +
 +
Checkout '''part-2'''
 +
 +
    git checkout part-2
 +
 +
The example war file can be built with maven:
 +
 +
    mvn clean package
 +
 +
Execute using jetty:
 +
 +
    mvn jetty:run
 +
 +
Then access the example http://localhost:8080/mvc-shadow/forEach
  
 
{{Template:CommentedSmalltalk_Footer_new|
 
{{Template:CommentedSmalltalk_Footer_new|

Latest revision as of 04:33, 20 January 2022

DocumentationSmall Talks2017MarchZK8 Features for MVC - Shadow Elements - Part 2
ZK8 Features for MVC - Shadow Elements - Part 2

Author
Robert Wenzel, Engineer, Potix Corporation
Date
March 30, 2017
Version
ZK 8.0

Introduction

In continuation of the previous Part 1, this time I'll focus on the shadow element <forEach>.

Some background

The intention behind <forEach> are similar to those of the <apply> shadow element. Several historical mechanisms to render UI components repeatedly have evolved with their own specific behavior and limitations, and a unified abstraction was needed to become more versatile and layout neutral (no additional DOM elements) at the same time.

Let's look at what's been there before:

(1) forEach-attribute

<div forEach="${someCollection}">
    <label value="${each.label}"/>
<div>
  • repeats the component as well
  • limited to static layouts at zul parse time

(2) ListModel

<listbox model="${someListModel}">
    <template name="model">
        <listitem label="${each.label}"/>
    </template>
</listbox>
  • allows replacing the whole Model => re-render all elements
  • allows adding/removing single elements => re-render only the affected elements
  • supported only by specific components (grid/listbox/combobox/tabbox ...)

(3) children-binding restricted to MVVM

<div children="@init(someListModel)">
    <template name="children">
        <label value="${each.label}"/>
    </template>
</div>
  • repeats contained template
  • allows replacing the whole Model => re-render all elements
  • (before ZK 8) didn't update adding/removing single elements => needed to re-render all elements
  • requires MVVM/BindComposer

The examples above behave quite differently, each with its own limitations. All require a component: the special forEach-attribute which repeats the element itself while the model- and children-binding repeats the inner template.

That's why <forEach>

The new <forEach> shadow element allows static and dynamic UI composition. When used in combination with a ListModel implementation, single item updates (add/remove) result only in UI updates to the affected elements. This will improve the overall performance by saving network traffic and avoiding updates to existing DOM elements in the browser which saves render time. As with other shadow elements, this works without adding additional container components. ZK will keep track of the individual positions in the component tree and DOM tree.

Let's look into different usage scenarios of increasing the complexity for cases where most flexibility is needed.

Static usage (index, array, collection)

Below, what the above examples (1, 3) will look like using <forEach> (<listbox> could be done in the same way, but it is still more useful when binding the model-attribute to track the selected listitems in the ListModel).

<forEach items="${someCollection}">
    <div>
        <label value="${each.label}"/>
    <div>
</forEach>

<div>
    <forEach items="${someListModel}">
        <label value="${each.label}"/>
    </forEach>
</div>

By adding the shadow element you are now free to decide which component should be repeated by nesting accordingly.

<forEach> also supports multiple children (even a variable number of children/item) as in the example below.

<forEach items="${someListModel}">
    <label value="${each.label}"/>
    <button label="details" />
    <if test="${!each.readonly}">
        <button label="edit" />
        <button label="delete" />
    </if>
</forEach>

For "readonly" items, there are 2 children while there will be 2 additional buttons otherwise.

As other shadow elements the <forEach> can be used with a static EL expression (${some.expression}) resolving to a java Array or Collection (without implementing ListModel). In this case, the shadow element will be removed after initial rendering to save the server side memory. The resulting component tree will only contain the actually generated components. To prevent the removal of the shadow element, you can add dynamicValue="true" to preserve the shadow tree in order to manipulate it later.

Dynamic usage (ListModel)

When using a dynamic bind expression (@load / @init) or a static EL resolving into a ListModel implementation, the shadow element will remain in the shadow tree. Especially when used with a ListModel, it will listen to changes in the ListModel and reflect the updates automatically. This allows convenient usage of the built-in ListModel implementations (e.g. ListModelList).

Here is a live example on ZK Fiddle (make sure the lates ZK 8 version is selected).

I suggest observing the network traffic (in the browser's developer tools) - especially the response body - verifying that only single div-elements are added/removed without re-rendering the whole list of names - just by calling ListModelList.add(E) and ListModelList.remove(E).

  @Listen("onClick=#addBtn")
  public void addName() {
    namesModel.add("New Name " + counter++);
  }

  @Listen("onRemoveName=#main")
  public void removeName(ForwardEvent event) {
    namesModel.remove(event.getData());
  }

Example sources: forEachListModel.zul, NamesComposer.java

Adding templates

The content of a <forEach> element will implicitly become an anonymous template; hence the following examples are equivalent and produce the same result, and shadow/component tree.

<forEach items="${someCollection}">
    <div>
        <label value="${each.label}"/>
    <div>
</forEach>

<forEach items="${someCollection}">
    <template>
        <div>
            <label value="${each.label}"/>
        <div>
    <template>
</forEach>

<forEach items="${someCollection}">
    <template name="">
        <div>
            <label value="${each.label}"/>
        <div>
    <template>
</forEach>

Since this is just typing overhead and doesn't add any value, the <template> can be omitted (however it's not wrong to write it out explicitly if you prefer).

Dynamic Templates

Things become interesting when rendering items differently (e.g. based on a condition or property). For such cases, the <apply> element can be nested inside the <forEach>.

<!-- named inline templates -->
<forEach items="${someCollectionOfConfigElements}" var="configElement">
    <apply template="${configElement.editable ? 'editable' : 'readonly'}">
        <template name="editable">
            <textbox value="${configElement.name}" forward="onChange=onChangeConfigElementName(${configElement})"/>
        </template>
        <template name="readonly">
            <label value="${configElement.name}"/>
        </template>
    </apply>
</forEach>

<!-- alternative using named external templates -->
<forEach items="${someCollectionOfConfigElements}" var="configElement">
    <apply template="${configElement.editable ? 'editable' : 'readonly'}">
        <template name="editable" src="configElementEditor.zul"/>
        <template name="readonly" src="configElementView.zul"/>
    </apply>
</forEach>

<!-- alternative using a dynamic templateURI directly -->
<forEach items="${someCollectionOfConfigElements}" var="configElement">
    <apply templateURI="${configElement.editable ? 'configElementEditor.zul' : 'configElementView.zul'}"/>
</forEach>
  • Line 5: here (as in many other cases) event forwarding comes in handy when working on repeated elements (avoids having to deal with indices or non unique component IDs).

Here is a runnable example on ZKFiddle

Example sources: forEachDynamicTemplate.zul, ConfigElement.java

Control from java code (MVC)

As mentioned above, the simplest way to control the items rendered by <forEach> element is via ListModel (e.g. ListModelList) and add/remove elements while new templates are instantiated without extra care. However, if additional control is desired e.g. replacing the whole model completely or rendering the <templates> in java code (similar to a Grid Renderer), the following techniques can be used.

The following 4 Sections will create the same example (see picture below) in 4 different ways. I don't have a personal preference about any of the used techniques, they are equally valid and have similar performance. Simply choose which suits best into your programming model.

Mvc-shadow-foreach.png

Shadow Selector

To access the <forEach> element directly simply give it an ID ...

<zk xmlns:sh="shadow">
	<groupbox width="450px" apply="zk.example.ForEachComposer" mold="3d">
		<caption label="My Names (also draggable)">
			<button forward="onSortAsc" iconSclass="z-icon-sort-alpha-asc"/>
			<button forward="onSortDesc" iconSclass="z-icon-sort-alpha-desc"/>
			<button forward="onClearAll" iconSclass="z-icon-user-times" tooltiptext="clear all"/>
		</caption>
		<div>
			<sh:forEach id="namesList" var="name">
				<span forward="onDrop=onDropName(${name})" sclass="nameTag" draggable="true" droppable="true"> 
					<label value="${name}"/>
					<a forward="onClick=onRemoveName(${name})" iconSclass="z-icon-times"/>
				</span>
			</sh:forEach>
		</div>
		<textbox forward="onOK=onAddName" placeholder="New Name + ENTER"/>
	</groupbox>
</zk>

... and select it in your composer using a shadow selector. It's then possible to call ForEach.setItems(Object) directly and render the contents by calling recreate(). With this separation, you are in control when to re-render the contents initially or after the model was replaced. Subsequent additions/removals of model items are reflected automatically.

public class ForEachComposer extends SelectorComposer<Component> {
	private static final long serialVersionUID = 1L;

	@Wire("::shadow#namesList")
	private ForEach namesList;
	private ListModelList<String> namesModel;

	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		namesModel = new ListModelList<String>(new String[] {"Chris", "Elisabeth", "Aaron", "Berta", "Daniel"});
		namesList.setItems(namesModel);
		namesList.recreate();
...

Example sources: forEachExample.zul, ForEachComposer.java

CollectionTemplate

In cases where the insert position of the items needs to be determined at runtime (i.e. without a <forEach> element in the zul file), the CollectionTemplate is a utility class to easily do so.

<zk xmlns:sh="shadow">
	<groupbox width="450px" apply="zk.example.CollectionTemplateComposer" mold="3d">
		<caption label="My Names (also draggable)">
			<button forward="onSortAsc" iconSclass="z-icon-sort-alpha-asc"/>
			<button forward="onSortDesc" iconSclass="z-icon-sort-alpha-desc"/>
			<button forward="onClearAll" iconSclass="z-icon-user-times" tooltiptext="clear all"/>
		</caption>
		<div id="namesContainer">
			<template name="nameTag">
				<span forward="onDrop=onDropName(${each})" sclass="nameTag" draggable="true" droppable="true"> 
					<custom-attributes name="${each}"/>
					<label value="${each}"/>
					<a forward="onClick=onRemoveName(${each})" iconSclass="z-icon-times"/>
				</span>
			</template>
		</div>
		<textbox forward="onOK=onAddName" placeholder="New Name + ENTER"/>
	</groupbox>
</zk>

Here the template is only defined inline (not yet rendered) and a normal DIV element as the insertion host but no <forEach>.

public class CollectionTemplateComposer extends SelectorComposer<Component> {
	private static final long serialVersionUID = 1L;

	@Wire("#namesContainer")
	private Component namesContainer;
	private CollectionTemplate namesList;
	private ListModelList<String> namesModel;

	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		namesList = new CollectionTemplate(true);
		namesModel = new ListModelList<String>(new String[] {"Chris", "Elisabeth", "Aaron", "Berta", "Daniel"});
		namesList.setModel(namesModel);
		namesList.setTemplate("nameTag");
		namesList.apply(namesContainer);
...

The java code looks very similar to the above example. Instead of wiring an existing <forEach> element, the CollectionTemplate (which internally adds a ForEach shadow element) is created and inserted at runtime. A single fixed template for all elements is chosen during runtime. In this example, the template(s) remain in a zul file.

Example sources: collectionTemplateExample.zul, CollectionTemplateComposer.java

Resolve templates dynamically in java code

It is also possible to resolve templates dynamically (by name) in java code. For this a CollectionTemplateResolver must be implemented. Finding a template in the zul file is simplified by the utility class Templates and its lookup(...) methods to recursively search a template by name (among parent components). In the example below, the template will be either nameTagNonDeletable (for members of the goodFriends collection) or nameTag (for any other name).

	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		namesList = new CollectionTemplate(true);
		namesModel = new ListModelList<String>(new String[] {"Chris", "Elisabeth", "Aaron", "Berta", "Daniel"});
		goodFriends = new HashSet<>(Arrays.asList("Aaron", "Daniel"));
		
		namesList.setModel(namesModel);
		namesList.setTemplateResolver(nameFromModel -> 
			Templates.lookup(namesContainer, goodFriends.contains(nameFromModel) ? "nameTagNonDeletable" : "nameTag"));
		namesList.apply(namesContainer);

The corresponding zul file now defines the 2 templates to choose from when rendering a name tag.

	<div id="namesContainer">
		<template name="nameTag">
			<span forward="onDrop=onDropName(${each})" sclass="nameTag" draggable="true" droppable="true"> 
				<custom-attributes name="${each}"/>
				<label value="${each}"/>
				<a forward="onClick=onRemoveName(${each})" iconSclass="z-icon-times"/>
			</span>
		</template>
		<template name="nameTagNonDeletable">
			<span forward="onDrop=onDropName(${each})" sclass="nameTag" draggable="true" droppable="true"> 
				<custom-attributes name="${each}"/>
				<label value="${each}"/>
			</span>
		</template>
	</div>

Example sources: collectionTemplateResolverExample.zul, CollectionTemplateResolverComposer.java

Render templates in java code

The TemplateResolver in the example above can return any Template implementation, making it possible to provide a custom implementation of that interface.

A simple method creating the same component structure as the nameTag template from above could look like this:

	public Component renderNameTag(String name) {
		Span nameTag = new Span();
		nameTag.setSclass("nameTag");
		nameTag.setDraggable("true");
		nameTag.setDroppable("true");
		nameTag.setAttribute("name", name);
		nameTag.addEventListener(Events.ON_DROP, 
				(DropEvent event) -> dropName(name, (String)event.getDragged().getAttribute("name")));
		nameTag.appendChild(new Label(name));
		if(!goodFriends.contains(name)) {
			A removeLink = new A();
			removeLink.setIconSclass("z-icon-times");
			removeLink.addEventListener(Events.ON_CLICK, event -> namesModel.remove(name));
			nameTag.appendChild(removeLink);
		}
		return nameTag;
	}

Nothing special here: It creates a surrounding SPAN-element with child components and event listeners (of course it may contain a more complex structure). In order to call this method for each model item, the Template could be implemented as in the code below. RenderTemplate will be explained in a minute.

Let's have a look at the CollectionTemplateResolver first.

namesList.setTemplateResolver(new CollectionTemplateResolver<String>() {
	@Override
	public Template resolve(String nameFromModel) {
		//could decide to return a different template based on the model object 
		return new RenderedTemplate<String>() {
			@Override
			public Component render(String name) {
				return renderNameTag(name);
			}
		};
	}
});

//after some 'java-8-magic' this nicely reduces into a single line
namesList.setTemplateResolver(nameFromModel -> (RenderedTemplate<String>) this::renderNameTag);

The RenderTemplate itself is just an example of a reusable way to implement a Template as a functional interface to further reduce boiler plate code.

	@FunctionalInterface
	public static interface RenderedTemplate<T> extends Template {
		Component render(T item);
		@Override
		default  Component[] create(Component parent, Component insertBefore, VariableResolver resolver, 
                           @SuppressWarnings("rawtypes") Composer composer) {
			@SuppressWarnings("unchecked")
			Component itemComp = render((T)resolver.resolveVariable("each"));
			parent.insertBefore(itemComp, insertBefore);
			return new Component[] {itemComp};
		}
		@Override
		default Map<String, Object> getParameters() {
			return null;
		}
	}
  • Line 3: the only remaining abstract method of the interface qualifying it as a functional interface
  • Line 8: call the render method passing in the model object to be rendered
  • Line 14: in this example no additional template parameters are used except the default 'each'

As the Template interface leaves room for your own implementation logic, the examples shown here are not the only ways to achieve your own application logic. Feel free to be creative - use the java language based on your own requirements.

Example sources: collectionTemplateRendererExample.zul, CollectionTemplateRendererComposer.java

Summary

ForEach and CollectionTemplate are 2 new powerful features in ZK8, they are also applicable to existing ZK applications following the MVC pattern.

Whenever you encounter repeated elements, consider using those new features either to simplify your zul files, overcome limitations in previous versions or simply leverage the flexibility of a ListModel implementation anywhere in your component hierarchy.

I'll happily reading your feedback to further improve the given examples and explanations. Just let me know what you think in the comments below.

Example Sources

The code examples are available on githup in the zk-mvc-shadow repository

zul files https://github.com/zkoss-demo/zk-mvc-shadow/tree/part-2/src/main/webapp/forEach
java classes https://github.com/zkoss-demo/zk-mvc-shadow/tree/part-2/src/main/java/zk/example/foreach

Running the Example

Clone the repo

   git clone [email protected]:zkoss-demo/zk-mvc-shadow.git

Checkout part-2

   git checkout part-2

The example war file can be built with maven:

   mvn clean package

Execute using jetty:

   mvn jetty:run

Then access the example http://localhost:8080/mvc-shadow/forEach


Comments



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