From Documentation

Jump to: navigation, search




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

Contents

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 git@github.com: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.
You got stuck here?
Let us know how we can improve this page
For specific questions please use the forum