ZK8 Features for MVC - Shadow Elements - Part 2

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

Author
Robert Wenzel, Engineer, Potix Corporation
Date
December 2016
Version
ZK 8.0

Introduction

In continuation of the previous article LINK ME I'll focus this time on the shadow element <forEach>.

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.

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 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 repeats the element itself while the model- and children-binding repeat the inner template.

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.

<forEach> usages

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)

<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. For "readonly" items there are 2 children while there will be 2 additional buttons otherwise.

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

static usage (index, array, collection)

As other shadow elements the <forEach> can be used with a static EL expression (${some.expression}) resolving to a java Array or Collection (not 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)

http://zkfiddle.org/sample/2cjibpn/1-ForEach-with-ListModel

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 LINK ME becomes handy when working on repeated elements (avoids having to deal with indices or non unique component IDs. see zkfiddle)

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. However if additional control is desired e.g. replacing the whole model completely or rendering the <templates> in java code (similar to a ItemRenderer in a grid) the following techniques can be used.

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 LINK ME. It's then possible to call setItems() directly and render the contents by calling recreate(), 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 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();
...

CollectionTemplate

Define templates from java code

Render templates in java code

Summary

Comments



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