Shadow for MVC"
m (→Example) |
RebeccaLai (talk | contribs) m |
||
(19 intermediate revisions by 2 users not shown) | |||
Line 3: | Line 3: | ||
__TOC__ | __TOC__ | ||
+ | {{versionSince| 8.0.0}} | ||
=Introduction= | =Introduction= | ||
− | In ZK 8.0.0, we have introduced shadow elements, such as a boilerplate code, to help application developers compose | + | 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, but 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"> | ||
Line 15: | Line 16: | ||
</template> | </template> | ||
</source> | </source> | ||
− | The shadow element "apply" will not exist | + | 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 the typical shadow elements defined in zul but components you can only create 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 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 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 parameter. Note: 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 38: | Line 57: | ||
</source> | </source> | ||
and in DemoComposer.java | and in DemoComposer.java | ||
− | < | + | <syntaxhighlight lang="java" highlight="6,7,8" line> |
@Wire | @Wire | ||
Div host1; | Div host1; | ||
Line 48: | Line 67: | ||
st.apply(host1); | st.apply(host1); | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
− | + | * Line 6: we instantiate a new ShadowTemplate with '''autodrop''' which is equal to true. | |
− | + | * Line 7: assign the template name to <code>st</code>. | |
− | + | * Line 8: call apply method and shadow host is <code>Div host1</code>. | |
− | |||
− | |||
Then, we can see template "labels" are rendered and the created components are attached to <code>host1</code>. | Then, we can see template "labels" are rendered and the created components are attached to <code>host1</code>. | ||
Line 74: | Line 91: | ||
And the rendered components will also be detached. | And the rendered components will also be detached. | ||
− | Another case is when '''autodrop''' | + | 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= | =Use CollectionTemplate= | ||
Line 82: | Line 99: | ||
==Example== | ==Example== | ||
The basic usage is simple. Here we demonstrate by using the previous sample code: | The basic usage is simple. Here we demonstrate by using the previous sample code: | ||
− | <source lang="xml" | + | <source lang="xml" highlight="6,7,8"> |
<zk> | <zk> | ||
<div apply="DemoComposer"> | <div apply="DemoComposer"> | ||
Line 95: | Line 112: | ||
</source> | </source> | ||
The <code>each</code> in line 6, 7, 8 represents each item in ListModel, and in DemoComposer.java | The <code>each</code> in line 6, 7, 8 represents each item in ListModel, and in DemoComposer.java | ||
− | <source lang="java" | + | <source lang="java" highlight="3,8"> |
@Wire | @Wire | ||
Div host1; | Div host1; | ||
Line 108: | Line 125: | ||
} | } | ||
</source> | </source> | ||
− | Developers have to prepare a <javadoc>org.zkoss.zul.ListModel</javadoc> and assign to the <code>CollectionTemplate</code> instance; | + | 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. |
==CollectionTemplateResolver== | ==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. | 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. | ||
− | <source lang="xml" | + | <source lang="xml" highlight="7,12"> |
<zk> | <zk> | ||
<div id="root" apply="DemoComposer"> | <div id="root" apply="DemoComposer"> | ||
Line 131: | Line 148: | ||
</source> | </source> | ||
The <code>each</code> in line 7, 12 represents each item in ListModel, and in DemoComposer.java | The <code>each</code> in line 7, 12 represents each item in ListModel, and in DemoComposer.java | ||
− | <source lang="java" | + | <source lang="java" highlight="3,14"> |
@Wire | @Wire | ||
Div host1; | Div host1; | ||
Line 164: | Line 181: | ||
} | } | ||
</source> | </source> | ||
− | In this example, we assign | + | 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. |
+ | |||
+ | |||
− | + | 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. | |
− | |||
− | |||
− | |||
=Comparison= | =Comparison= | ||
− | Although the behavior | + | Although the behavior of ShadowTemplate and [[ZK Developer's Reference/UI Composing/Macro Component|Macro component]] looks similar, there are some differences. |
− | {| | + | {| class='wikitable' | width="100%" |
! | ! | ||
! ShadowTemplate | ! ShadowTemplate | ||
Line 182: | Line 198: | ||
| change host/parent | | change host/parent | ||
| if '''autodrop''' is true, the rendered components will change parent; otherwise, | | if '''autodrop''' is true, the rendered components will change parent; otherwise, | ||
− | they will | + | they will stick with the same parent(or host). |
− | | doesn't matter if it is | + | | doesn't matter if it is in-line or not; the rendered components will change parent. |
|- | |- | ||
| change template/uri | | change template/uri | ||
| if '''autodrop''' is true, the rendered components will be detached; otherwise, | | if '''autodrop''' is true, the rendered components will be detached; otherwise, | ||
− | they will | + | they will stick with the same parent(or host). |
− | | doesn't matter if it is | + | | doesn't matter if it is in-line or not; the rendered components will be detached. |
|} | |} | ||
− | In short, while using Macro | + | 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. |
+ | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
{{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.