Shadow for MVC"

From Documentation
m
 
(42 intermediate revisions by 3 users not shown)
Line 3: Line 3:
 
__TOC__
 
__TOC__
  
 +
{{versionSince| 8.0.0}}
 
=Introduction=
 
=Introduction=
  
Since ZK 8.0.0, we introduce shadow elements like a boilerplate code to help application developers to compose the html layout with some dynamic data. It is inspired from Shadow DOM to enable better composition of ZK components. For more detail, please check out our [http://books.zkoss.org/zk-mvvm-book/8.0/shadow_elements/shadow_elements.html Official ZK MVVM Book]. Shadow elements can not only be used with MVVM binding but also MVC pattern, but there are some differences. We will give more discussion in the following sections.
+
In ZK 8.0.0, we have introduced shadow elements, such as a boilerplate code, to help application developers compose HTML layouts with dynamic data. It is inspired by Shadow DOM to enable better composition of ZK components. For more details, please check out our [https://books.zkoss.org/zk-mvvm-book/10.0/shadow_elements/index.html ZK MVVM Reference]. You can also use shadow elements with the MVC pattern; however, there are some differences. We will discuss this more in the following sections.
  
In MVC pattern, developers can declare shadow tags in zul files while the behavior is very different without MVVM annotation.  
+
In the MVC pattern, developers can declare shadow tags in zul files, but the behavior is very different without MVVM annotation.  
For Example,
+
For example,
 
<source lang="xml">
 
<source lang="xml">
 
<apply template="any" />
 
<apply template="any" />
Line 15: Line 16:
 
</template>
 
</template>
 
</source>
 
</source>
The shadow element apply will not exist once the output is rendered to client, so developers can't dynamically change the template value. For this purpose, we provide two kinds of Java class for those who have favor of MVC, that is, <javadoc>org.zkoss.zuti.zul.ShadowTemplate</javadoc> and <javadoc>org.zkoss.zuti.zul.CollectionTemplate</javadoc>.
+
The shadow element "apply" will not exist after the output is rendered to the client, so developers can't dynamically change the template content. For this purpose, we provide two kinds of Java classes for those who favor MVC:
 +
* <javadoc>org.zkoss.zuti.zul.ShadowTemplate</javadoc>
 +
* <javadoc>org.zkoss.zuti.zul.CollectionTemplate</javadoc>
  
They are NOT like normal shadow elements defined in zul but you can only create instances in Java code.
+
They are NOT like the typical shadow elements defined in zul but components you can only create in Java code.
 +
 
 +
= Setup =
 +
Before using shadow elements, make sure you include the required jar - <code>zuti.jar</code>. With maven, you should add the dependency below:
 +
<source lang='xml'>
 +
    <dependency>
 +
        <groupId>org.zkoss.zk</groupId>
 +
        <artifactId>zuti</artifactId>
 +
        <version>${zk.version}</version>
 +
    </dependency>
 +
</source>
 +
 
 +
= Wire Shadow Components =
 +
Like wiring a UI component, you can [[ZK%20Developer's%20Reference/MVC/Controller/Wire%20Components#Shadow_Selectors | wire a shadow component]].
  
 
=Use ShadowTemplate=
 
=Use ShadowTemplate=
  
<javadoc>org.zkoss.zuti.zul.ShadowTemplate</javadoc> is a utility class to let developers to apply shadow elements in Java class. It has the similar behavior with <javadoc>org.zkoss.zuti.Apply</javadoc>, for example, developers can specify the template or pass parameters. The difference is that developers must designate a boolean value, called '''autodrop''', to indicate whether to drop those rendered children or not. If true, every time user changed template or detach from the original host, ShadowTemplate will <javadoc method="recreate()">org.zkoss.zuti.Apply</javadoc> or removed the children, otherwise, rendered children will be remained. After instantiating ShadowTemplate instance, developers can trigger <javadoc method="apply(org.zkoss.zk.ui.Component)">org.zkoss.zuti.zul.ShadowTemplate</javadoc> to compose the specified template with shadow host passed as parameter. Note that, the passed host should be the same one if '''autodrop''' is true, or pass null to detach the original host first.
+
<javadoc>org.zkoss.zuti.zul.ShadowTemplate</javadoc> is a utility class that allows developers to apply shadow elements in Java class. It has a similar behavior to <javadoc>org.zkoss.zuti.zul.Apply</javadoc>; for example, developers can specify the template or pass parameters. The difference is that developers must designate a boolean value, called '''autodrop''', to indicate whether to drop those rendered children or not. If true, every time the user changes the template or detaches from the original host, ShadowTemplate will <javadoc method="recreate()">org.zkoss.zk.ui.HtmlShadowElement</javadoc> or remove the children; otherwise, rendered children will remain. After instantiating ShadowTemplate instance, developers can trigger <javadoc method="apply(org.zkoss.zk.ui.Component)">org.zkoss.zuti.zul.ShadowTemplate</javadoc> to compose the specified template, with shadow host passed as a parameter. Note: the passed host should be the same one if '''autodrop''' is true, or pass null to detach the original host first.
 +
 
 +
 
 +
Note: <code>ShadowTemplate</code> doesn't support setting both template and template URI at the same time; one of them should be a null or empty string before setting another.  
  
 
==Example==
 
==Example==
Line 27: Line 46:
 
<source lang="xml">
 
<source lang="xml">
 
<zk>
 
<zk>
    <div apply="DemoComposer">
+
<div apply="DemoComposer">
        <div id="host1"></div>
+
<div id="host1"></div>
    </div>
+
</div>
 +
<template name="labels">
 +
<label value="zul label"/>
 +
<x:label>xhtml label</x:label>
 +
<n:span>native span</n:span>
 +
</template>
 
</zk>
 
</zk>
 
</source>
 
</source>
 
and in DemoComposer.java
 
and in DemoComposer.java
<source lang="java">
+
<syntaxhighlight lang="java" highlight="6,7,8" line>
 +
@Wire
 +
Div host1;
 +
 
 
public void doAfterCompose(Component comp) throws Exception {
 
public void doAfterCompose(Component comp) throws Exception {
 
super.doAfterCompose(comp);
 
super.doAfterCompose(comp);
 
ShadowTemplate st = new ShadowTemplate(true); //autodrop = true
 
ShadowTemplate st = new ShadowTemplate(true); //autodrop = true
 +
st.setTemplate("labels");
 +
st.apply(host1);
 
}
 
}
</source>
+
</syntaxhighlight>
=Use CollectionTemplate=
+
* Line 6: we instantiate a new ShadowTemplate with '''autodrop''' which is equal to true.
First, you have to decide which component to extend from. <javadoc>org.zkoss.zul.Div</javadoc> is a common choice as it is a simple component. However, here our example extends from <javadoc>org.zkoss.zul.Row</javadoc>, so it can be used under <javadoc>org.zkoss.zul.Rows</javadoc>, which the regular macros cannot.
+
* Line 7: assign the template name to <code>st</code>.
 +
* Line 8: call apply method and shadow host is <code>Div host1</code>.
  
Second, you have to implement a template (in a ZUML document) to define what child components the composite component has. Then, you have to implement a Java class to put them together.
+
Then, we can see template "labels" are rendered and the created components are attached to <code>host1</code>.
  
==Implement a Template==
+
If we have a button to change the template:
The implementation of a template is straightforward. There is nothing special to handle. Since it is rendered by <javadoc method="createComponents(java.lang.String, org.zkoss.zk.ui.Component, java.util.Map)">org.zkoss.zk.ui.Execution</javadoc>, you could pass whatever data you prefer to it (through the <code>arg</code> argument).
+
<source lang="java">
 
+
@Listen("onClick = #btn")
Suppose we have a template as follows, and it is placed at <code>/WEB-INF/composite/username.zul</code>.
+
public void clickBtn() {
 
+
st.setTemplate("othertemplate");
<source lang="xml">
+
st.apply(st.getShadowHost());
<zk>
+
}
  Usename: <textbox id="mc_who"/>
 
</zk>
 
 
</source>
 
</source>
 +
Those components rendered before will be detached first before attaching the new ones. Note: developers have to call <code>apply(host)</code> method again.
  
==Implement a Java Class==
+
If developers want to apply other shadow hosts, please apply null first and then reapply like this:
 
+
<source lang="java">
To implement a Java class we shall:
+
st.apply(null);
 
+
st.apply(otherHost);
# Extend from the component class you want.
 
# (Optional) Implement <javadoc>org.zkoss.zk.ui.IdSpace</javadoc> to make it an [[ZK Developer's Reference/UI Composing/Component-based UI#ID_Space|ID space owner]].
 
# Render the template in the constructor by the use of <javadoc method="createComponents(java.lang.String, org.zkoss.zk.ui.Component, java.util.Map)">org.zkoss.zk.ui.Executions</javadoc> or others.
 
# (Optional) Wire variables, components and event listeners after rendering with the use of <javadoc method="wireVariables(org.zkoss.zk.ui.Component, java.lang.Object, java.util.List)">org.zkoss.zk.ui.select.Selectors</javadoc> (wiring variables), <javadoc method="wireComponents(org.zkoss.zk.ui.Component, java.lang.Object, boolean)">org.zkoss.zk.ui.select.Selectors</javadoc> (wiring components) and <javadoc method="wireEventListeners(org.zkoss.zk.ui.Component, java.lang.Object)">org.zkoss.zk.ui.select.Selectors</javadoc> (wiring event listener).
 
 
 
For example,
 
 
 
<source lang="Java" high="9,14,17,18,19">
 
package foo;
 
 
 
import org.zkoss.zk.ui.IdSpace;
 
import org.zkoss.zk.ui.select.Selectors;
 
import org.zkoss.zul.Row;
 
import org.zkoss.zul.Textbox;
 
 
 
public class Username extends Row implements IdSpace {
 
    @Wire
 
    private Textbox mc_who; //will be wired when Components.wireVariables is called
 
 
 
    public Username() {
 
        //1. Render the template
 
        Executions.createComponents("/WEB-INF/composite/username.zul", this, null);
 
 
 
        //2. Wire variables, components and event listeners (optional)
 
        Selectors.wireVariables(this, this, null);
 
        Selectors.wireComponents(this, this, false);
 
        Selectors.wireEventListeners(this, this);
 
    }
 
    public String getWho() {
 
        return mc_who.getValue();
 
    }
 
    public void setWho(String who) {
 
        mc_who.setValue(who);
 
    }
 
    //public void onOK() {..} //Add event listeners if required, and wired by Components.addForwards
 
}
 
 
</source>
 
</source>
 +
And the rendered components will also be detached.
  
After <javadoc method="createComponents(java.lang.String, org.zkoss.zk.ui.Component, java.util.Map)">org.zkoss.zk.ui.Executions</javadoc> is called, all components specified in the template will be instantiated and become the child component of the composite component (Row). Notice that the URI must match the location of the template correctly.
+
Another case is when '''autodrop''' is equal to false. Here, neither changing the template nor applying other hosts (yes, you can apply whichever hosts you want) will cause rendered components to be detached.
  
Depending on the implementation you want, you could wire the data members (<code>mc_who</code>) by calling <javadoc method="wireComponents(org.zkoss.zk.ui.Component, java.lang.Object, boolean)">org.zkoss.zk.ui.select.Selectors</javadoc>. This method will search all data members and setter methods and ''wire'' the component with the same ID. Similarly, <javadoc method="wireEventListeners(org.zkoss.zk.ui.Component, java.lang.Object)">org.zkoss.zk.ui.select.Selectors</javadoc> is used to wire event listeners.
+
=Use CollectionTemplate=
  
For more information, please refer to the [[ZK Developer's Reference/MVC/Controller/Wire Variables|the Wire Components section]] and
+
<javadoc>org.zkoss.zuti.zul.CollectionTemplate</javadoc> is similar to <javadoc>org.zkoss.zuti.zul.ShadowTemplate</javadoc>. The difference is that developers can assign <javadoc>org.zkoss.zul.ListModel</javadoc> and <javadoc>org.zkoss.zuti.zul.CollectionTemplateResolver</javadoc> for iterative rendering.
[[ZK Developer's Reference/MVC/Controller/Wire Event Listeners|Wire Event the Listeners section]] sections.
 
  
<blockquote>
+
==Example==
----
+
The basic usage is simple. Here we demonstrate by using the previous sample code:
Notice that there is a utility called [http://github.com/zanyking/ZK-Composite| ZK Composite]. With the help of [http://github.com/zanyking/ZK-Composite| ZK Composite], components are created and wired automatically based on the Java annoataions you provide. In other words, Step 3 and 4 are done automatically. For more information, please refer to the [[#Define Components with Java Annotations|Define Components with Java Annotations]] section.
+
<source lang="xml" highlight="6,7,8">
</blockquote>
+
<zk>
 
+
<div apply="DemoComposer">
===Wire Spring-managed Beans===
+
<div id="host1"></div>
<javadoc method="wireVariables(org.zkoss.zk.ui.Component, java.lang.Object, java.util.List)">org.zkoss.zk.ui.select.Selectors</javadoc> will wire variables that can be resolved by the registered variable resolver. In additions to [[ZUML Reference/ZUML/Processing Instructions/variable-resolver|the variable-resolver directive]], you can create any variable resolver manually and pass it as the third argument. <javadoc method="newVariableResolvers(java.lang.Class, java.lang.Class)">org.zkoss.zk.ui.select.Selectors</javadoc> provides a convenient way to instantiate variable resolvers. For example, let us say we'd like to wire Spring-manage beans, then we can do as follows.
+
</div>
 
+
<template name="labels">
<source lang="java" high="1,3,10">
+
<label value="zul one ${each} "></label>
@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver)
+
<x:label>xhtml one ${each} </x:label>
public class Username extends Row implements IdSpace {
+
<n:span>native one ${each} </n:span>
    @WireVariable
+
</template>
    private User user;
+
</zk>
 
 
    public Username() {
 
        Executions.createComponents("/WEB-INF/composite/username.zul", this, null);
 
 
 
        Selectors.wireVariables(this, this,
 
            Selectors.newVariableResolvers(getClass(), Row.class));
 
        Selectors.wireComponents(this, this, false);
 
        Selectors.wireEventListeners(this, this);
 
    }
 
...
 
 
</source>
 
</source>
 +
The <code>each</code> in line 6, 7, 8 represents each item in ListModel, and in DemoComposer.java
 +
<source lang="java" highlight="3,8">
 +
@Wire
 +
Div host1;
 +
ListModel model = new ListModelList(Arrays.asList(new String[]{"1", "2", "3"}));
  
<javadoc method="newVariableResolvers(java.lang.Class, java.lang.Class)">org.zkoss.zk.ui.select.Selectors</javadoc> will look for the <code>@VariableResolver</code> annotation and instantiate it automatically. As shown, we annotate <javadoc>org.zkoss.zkplus.spring.DelegatingVariableResolver</javadoc> to resolve Spring managed bean.
+
public void doAfterCompose(Component comp) throws Exception {
 
+
super.doAfterCompose(comp);
For more information, please refer to [[ZK Developer's Reference/MVC/Controller/Wire Variables|the Wire Variables section]].
+
CollectionTemplate ct = new CollectionTemplate(true); //autodrop = true
 
+
ct.setModel(model);
===ID Space===
+
ct.setTemplate("labels");
 
+
ct.apply(host1);
Unless you extend a component that is an [[ZK Developer's Reference/UI Composing/Component-based UI#ID_Space|ID space owner]] (such as <javadoc>org.zkoss.zul.Window</javadoc>), all child components specified in the template will be in the same ID space as its parent. It might be convenient at the first glance. However, it will cause the ID conflict if we have multiple instances of the same composite component. Thus, it is generally suggested to make the composite component as a space owner
+
}
 
 
It can be done easily by implementing an extra interface <javadoc>org.zkoss.zk.ui.IdSpace</javadoc>. No other method needs to be implemented.
 
 
 
<source lang="java">
 
public class Username extends Row implements IdSpace {
 
...
 
 
</source>
 
</source>
 +
Developers have to prepare a <javadoc>org.zkoss.zul.ListModel</javadoc> and assign it to the <code>CollectionTemplate</code> instance; they will then see that the template is created multiple times. Similarly, in cases where either the template or model is changed, <code>apply(host)</code> must be triggered for the effect to take place. The benefit of using <code>CollectionTemplate</code> is that every time the model's content changes, the layout will change as well, no matter if '''autodrop''' is true or false.
  
Of course, if you prefer not to have an additional ID space, you don't need to implement an <javadoc>org.zkoss.zk.ui.IdSpace</javadoc>.
+
==CollectionTemplateResolver==
 
+
More advanced usage is to assign <javadoc>org.zkoss.zuti.zul.CollectionTemplateResolver</javadoc> to resolve template by evaluating the variable reference from model in runtime.
=Use Composite Component=
+
<source lang="xml" highlight="7,12">
 
+
<zk>
Like macros and any other primitive components, you have to declare a composite component before using it. This can be done by using [[ZUML Reference/ZUML/Processing Instructions/component|component directives]]. Then, we could use it the same way (they are actually primitive components). For example,
+
<div id="root" apply="DemoComposer">
 
+
<div id="host1"></div>
<source lang="xml">
+
<?component name="username" extends="row" class="foo.Username"?>
+
<template name="male">
 
+
<div>
<grid>
+
<label>I'm male, my name is ${each.name}</label>
    <rows>
+
</div>
      <username who="Joe"/>
+
</template>
      <username who="Hellen"/>
+
<template name="female">
    </rows>
+
<div>
</grid>
+
<label>I'm female, my name is ${each.name}</label>
 +
</div>
 +
</template>
 +
</div>
 +
</zk>
 
</source>
 
</source>
 +
The <code>each</code> in line 7, 12 represents each item in ListModel, and in DemoComposer.java
 +
<source lang="java" highlight="3,14">
 +
@Wire
 +
Div host1;
 +
ListModelList<Person> model = new ListModelList<Person>(new ArrayList<Person>() {{
 +
add(new Person(true));
 +
add(new Person(false));
 +
add(new Person(false));
 +
add(new Person(true));
 +
}});
  
=Define Composite Components as Standard Components=
+
public void doAfterCompose(Component comp) throws Exception {
 +
super.doAfterCompose(comp);
 +
CollectionTemplate ct = new CollectionTemplate(true); //autodrop = true
 +
ct.setModel(model);
 +
ct.setTemplateResolver(new MyCollectionTemplateResolver<Person>());
 +
ct.apply(host1);
 +
}
  
If a composite component is used in multiple pages, it is better to define it in the application level, such that it can be accessed in any page without any [[ZUML Reference/ZUML/Processing Instructions/component|component directives]].
+
public class MyCollectionTemplateResolver<E extends Person> implements CollectionTemplateResolver<E> {
 +
public Template resolve(E o) {
 +
if (o.getGender())
 +
return root.getTemplate("male");
 +
else
 +
return root.getTemplate("female");
 +
}
 +
}
  
There are basic two approaches to define a component in the application level:
+
public class Person {
 
+
String name = "old name";
#Define it in a XML file which is called [[ZK Client-side Reference/Language Definition|language addon]].
+
boolean isMale = true;
#Define it with Java annoataions.
+
.... getter and setter
 
+
}
==Define Components in a Language Addon==
 
 
 
A language addon is a XML file providing additional component definitions or customizing the standard components. For example, you can define the username component described in the previous section as follows.
 
 
 
<source lang="xml">
 
<language-addon>
 
    <addon-name>myapp</addon-name>
 
    <component>
 
        <component-name>username</component-name>
 
        <extends>rows</extends>
 
        <component-class>foo.Username</component-class>
 
    </component>
 
</language-addon>
 
 
</source>
 
</source>
 +
In this example, we assign a <code>CollectionTemplateResolver</code> instead of template name or URI, and you will see template "male" is rendered when the gender of <code>Person</code> variable is male. That means, <code>CollectionTemplate</code> provides not only <code>setTemplate</code> and <code>setTemplateURI</code> but also supports determining template dynamically by giving <javadoc>org.zkoss.zuti.zul.CollectionTemplateResolver</javadoc> like line 14, so the template will be resolved by evaluating the variable reference from model in runtime.
  
For more information, please refer to [[ZK Developer's Reference/Customization/Component Properties#Application-wide_Initialization|Customization: Component Properties]].
 
  
==Define Components with Java Annotations==
 
  
Instead of maintaining the definitions in the language addon as described above, you can define the component with Java annotation with a utility called [https://github.com/zanyking/ZK-Composite ZK Composite]. For example,
+
Because these 3 methods: <code>setTemplate()</code>, <code>setTemplateURI()</code> and <code>setTemplateResolver()</code>, serve the same purpose, please just call one of them. If you call them all, the later one will override the previous one.
  
<source lang="java">
+
=Comparison=
@Composite(name="username", macroURI="/WEB-INF/partial/username.zul")
 
public class Username extends Rows implements IdSpace {
 
    @Wire
 
    private Textbox mc_who; //will be wired when Components.wireVariables is called
 
 
    //Note: no need to create components and wire variables/components
 
  
    public String getWho() {
+
Although the behavior of ShadowTemplate and [[ZK Developer's Reference/UI Composing/Macro Component|Macro component]] looks similar, there are some differences.
        return mc_who.getValue();
 
    }
 
    public void setWho(String who) {
 
        mc_who.setValue(who);
 
    }
 
}
 
</source>
 
  
This approach is suggested if you have to develop several composite components. As shown, it is more convenient since you don't have to maintain a separated XML file (the language addon). Furthermore, it will create the components and wire them automatically based on the annotations.
+
{| class='wikitable' | width="100%"
 +
!
 +
! ShadowTemplate
 +
! Macro Component
 +
|-
 +
| change host/parent
 +
| if '''autodrop''' is true, the rendered components will change parent; otherwise,
 +
they will stick with the same parent(or host).
 +
| doesn't matter if it is in-line or not; the rendered components will change parent.
 +
|-
 +
| change template/uri
 +
| if '''autodrop''' is true, the rendered components will be detached; otherwise,
 +
they will stick with the same parent(or host).
 +
| doesn't matter if it is in-line or not; the rendered components will be detached.
 +
|}
 +
In short, while using Macro components, we would have to instantiate more than one to achieve this goal. ShadowTemplate has more flexibility for templating; with only one ShadowTemplate instance, developers can render anywhere without losing those rendered components. CollectionTemplate, too, can render template iteratively with ListModel, a task impossible for Macro component.
  
Notice that it requires [http://github.com/zanyking/ZK-Composite/downloads additional JAR files], please refer to [[Small Talks/2011/December/Define Composite Component using Java Annotation in ZK6|Small Talks: Define Composite Component using Java Annotation in ZK6]] for the details.
 
  
=Version History=
 
{{LastUpdated}}
 
{| border='1px' | width="100%"
 
! Version !! Date !! Content
 
|-
 
| &nbsp;
 
| &nbsp;
 
| &nbsp;
 
|}
 
  
 
{{ZKDevelopersReferencePageFooter}}
 
{{ZKDevelopersReferencePageFooter}}

Latest revision as of 08:45, 26 January 2024

Since 8.0.0

Introduction

In ZK 8.0.0, we have introduced shadow elements, such as a boilerplate code, to help application developers compose HTML layouts with dynamic data. It is inspired by Shadow DOM to enable better composition of ZK components. For more details, please check out our ZK MVVM Reference. You can also use shadow elements with the MVC pattern; however, there are some differences. We will discuss this more in the following sections.

In the MVC pattern, developers can declare shadow tags in zul files, but the behavior is very different without MVVM annotation. For example,

<apply template="any" />
<template name="any">
    ...
</template>

The shadow element "apply" will not exist after the output is rendered to the client, so developers can't dynamically change the template content. For this purpose, we provide two kinds of Java classes for those who favor MVC:

They are NOT like the typical shadow elements defined in zul but components you can only create in Java code.

Setup

Before using shadow elements, make sure you include the required jar - zuti.jar. With maven, you should add the dependency below:

    <dependency>
        <groupId>org.zkoss.zk</groupId>
        <artifactId>zuti</artifactId>
        <version>${zk.version}</version>
    </dependency>

Wire Shadow Components

Like wiring a UI component, you can wire a shadow component.

Use ShadowTemplate

ShadowTemplate is a utility class that allows developers to apply shadow elements in Java class. It has a similar behavior to Apply; for example, developers can specify the template or pass parameters. The difference is that developers must designate a boolean value, called autodrop, to indicate whether to drop those rendered children or not. If true, every time the user changes the template or detaches from the original host, ShadowTemplate will HtmlShadowElement.recreate() or remove the children; otherwise, rendered children will remain. After instantiating ShadowTemplate instance, developers can trigger ShadowTemplate.apply(Component) to compose the specified template, with shadow host passed as a parameter. Note: the passed host should be the same one if autodrop is true, or pass null to detach the original host first.


Note: ShadowTemplate doesn't support setting both template and template URI at the same time; one of them should be a null or empty string before setting another.

Example

Assume we have a zul file like this:

<zk>
	<div apply="DemoComposer">
		<div id="host1"></div>
	</div>
	<template name="labels">
		<label value="zul label"/>
		<x:label>xhtml label</x:label>
		<n:span>native span</n:span>
	</template>
</zk>

and in DemoComposer.java

1 @Wire
2 Div host1;
3 
4 public void doAfterCompose(Component comp) throws Exception {
5 	super.doAfterCompose(comp);
6 	ShadowTemplate st = new ShadowTemplate(true); //autodrop = true
7 	st.setTemplate("labels");
8 	st.apply(host1);
9 }
  • Line 6: we instantiate a new ShadowTemplate with autodrop which is equal to true.
  • Line 7: assign the template name to st.
  • Line 8: call apply method and shadow host is Div host1.

Then, we can see template "labels" are rendered and the created components are attached to host1.

If we have a button to change the template:

	@Listen("onClick = #btn")
	public void clickBtn() {
		st.setTemplate("othertemplate");
		st.apply(st.getShadowHost());
	}

Those components rendered before will be detached first before attaching the new ones. Note: developers have to call apply(host) method again.

If developers want to apply other shadow hosts, please apply null first and then reapply like this:

st.apply(null);
st.apply(otherHost);

And the rendered components will also be detached.

Another case is when autodrop is equal to false. Here, neither changing the template nor applying other hosts (yes, you can apply whichever hosts you want) will cause rendered components to be detached.

Use CollectionTemplate

CollectionTemplate is similar to ShadowTemplate. The difference is that developers can assign ListModel and CollectionTemplateResolver for iterative rendering.

Example

The basic usage is simple. Here we demonstrate by using the previous sample code:

<zk>
	<div apply="DemoComposer">
		<div id="host1"></div>
	</div>
	<template name="labels">
		<label value="zul one ${each} "></label>
		<x:label>xhtml one ${each} </x:label>
		<n:span>native one ${each} </n:span>
	</template>
</zk>

The each in line 6, 7, 8 represents each item in ListModel, and in DemoComposer.java

@Wire
Div host1;
ListModel model = new ListModelList(Arrays.asList(new String[]{"1", "2", "3"}));

public void doAfterCompose(Component comp) throws Exception {
	super.doAfterCompose(comp);
	CollectionTemplate ct = new CollectionTemplate(true); //autodrop = true
	ct.setModel(model);
	ct.setTemplate("labels");
	ct.apply(host1);
}

Developers have to prepare a ListModel and assign it to the CollectionTemplate instance; they will then see that the template is created multiple times. Similarly, in cases where either the template or model is changed, apply(host) must be triggered for the effect to take place. The benefit of using CollectionTemplate is that every time the model's content changes, the layout will change as well, no matter if autodrop is true or false.

CollectionTemplateResolver

More advanced usage is to assign CollectionTemplateResolver to resolve template by evaluating the variable reference from model in runtime.

<zk>
	<div id="root" apply="DemoComposer">
		<div id="host1"></div>
	
		<template name="male">
			<div>
				<label>I'm male, my name is ${each.name}</label>
			</div>
		</template>
		<template name="female">
			<div>
				<label>I'm female, my name is ${each.name}</label>
			</div>
		</template>
	</div>
</zk>

The each in line 7, 12 represents each item in ListModel, and in DemoComposer.java

@Wire
Div host1;
ListModelList<Person> model = new ListModelList<Person>(new ArrayList<Person>() {{
	add(new Person(true));
	add(new Person(false));
	add(new Person(false));
	add(new Person(true));
}});

public void doAfterCompose(Component comp) throws Exception {
	super.doAfterCompose(comp);
	CollectionTemplate ct = new CollectionTemplate(true); //autodrop = true
	ct.setModel(model);
	ct.setTemplateResolver(new MyCollectionTemplateResolver<Person>());
	ct.apply(host1);
}

public class MyCollectionTemplateResolver<E extends Person> implements CollectionTemplateResolver<E> {
	public Template resolve(E o) {
		if (o.getGender())
			return root.getTemplate("male");
		else
			return root.getTemplate("female");
	}
}

public class Person {
	String name = "old name";
	boolean isMale = true;
	.... getter and setter
}

In this example, we assign a CollectionTemplateResolver instead of template name or URI, and you will see template "male" is rendered when the gender of Person variable is male. That means, CollectionTemplate provides not only setTemplate and setTemplateURI but also supports determining template dynamically by giving CollectionTemplateResolver like line 14, so the template will be resolved by evaluating the variable reference from model in runtime.


Because these 3 methods: setTemplate(), setTemplateURI() and setTemplateResolver(), serve the same purpose, please just call one of them. If you call them all, the later one will override the previous one.

Comparison

Although the behavior of ShadowTemplate and Macro component looks similar, there are some differences.

ShadowTemplate Macro Component
change host/parent if autodrop is true, the rendered components will change parent; otherwise,

they will stick with the same parent(or host).

doesn't matter if it is in-line or not; the rendered components will change parent.
change template/uri if autodrop is true, the rendered components will be detached; otherwise,

they will stick with the same parent(or host).

doesn't matter if it is in-line or not; the rendered components will be detached.

In short, while using Macro components, we would have to instantiate more than one to achieve this goal. ShadowTemplate has more flexibility for templating; with only one ShadowTemplate instance, developers can render anywhere without losing those rendered components. CollectionTemplate, too, can render template iteratively with ListModel, a task impossible for Macro component.




Last Update : 2024/01/26

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