Shadow for MVC"

From Documentation
Line 21: Line 21:
 
=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 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, the 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 to let developers to apply shadow elements in Java class. It has the similar behavior with <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 user changed template or detach from the original host, ShadowTemplate will <javadoc method="recreate()">org.zkoss.zk.ui.HtmlShadowElement</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.
  
 
==Example==
 
==Example==
Line 55: Line 55:
 
In line 8, call apply method and shadow host is <code>Div host1</code>.
 
In 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" is rendered and the created component are attached to <code>host1</code>.
  
 
If we have a button to change the template,
 
If we have a button to change the template,
Line 65: Line 65:
 
}
 
}
 
</source>
 
</source>
Those components rendered before will be detached before new ones are attached. Note: developers have to call <code>apply(host)</code> method again.
+
Those components rendered before will be detached first, and attach the new ones, note that, developers have to call <code>apply(host)</code> method again.
  
If developers wish to apply other shadow host, please apply null first and then reapply like this:
+
If developers want to apply to other shadow host, please apply null first and then apply again like this:
 
<source lang="java">
 
<source lang="java">
 
st.apply(null);
 
st.apply(null);
Line 74: Line 74:
 
And the rendered components will also be detached.
 
And the rendered components will also be detached.
  
Another case is '''autodrop''' equal to false. Here, neither changing templates nor applying to other hosts(yes, you can apply whichever hosts you want) will cause rendered components to be detached.
+
Another case is '''autodrop''' equal to false, and then neither change template nor apply to other host(yes, you can apply different host whatever you want) won't cause rendered components being detached.
  
 
=Use CollectionTemplate=
 
=Use CollectionTemplate=
  
<javadoc>org.zkoss.zuti.zul.CollectionTemplate</javadoc> is similar with <javadoc>org.zkoss.zuti.zul.ShadowTemplate</javadoc>. Developers can assign <javadoc>org.zkoss.zul.ListModel</javadoc> and <javadoc>org.zkoss.zuti.zul.CollectionTemplateResolver</javadoc> for iteratively rendering.
+
<javadoc>org.zkoss.zuti.zul.CollectionTemplate</javadoc> is similar with <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 iteratively rendering.
  
 
==Example==
 
==Example==
 +
The basic usage is simple, and here we take use of previous sample code.
 +
<source lang="xml" high="6,7,8">
 +
<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>
 +
</source>
 +
The <code>each</code> in line 6, 7, 8 represents each item in ListModel, and in DemoComposer.java
 +
<source lang="java" high="3,8">
 +
@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);
 +
}
 +
</source>
 +
Developers have to prepare a <javadoc>org.zkoss.zul.ListModel</javadoc> and assign to the <code>CollectionTemplate</code> instance, then you will see the template is created multiple times. Similarly, either template or model is changed, <code>apply(host)</code> must be triggered to take the effect. The benefit of using <code>CollectionTemplate</code> is that every time the model's content changes the layout will change as well no matter '''autodrop''' is true or false.
 +
 +
==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.
 
<source lang="xml" high="7,12">
 
<source lang="xml" high="7,12">
 
<zk>
 
<zk>
Line 133: Line 164:
 
}
 
}
 
</source>
 
</source>
Developers have to prepare a <javadoc>org.zkoss.zul.ListModel</javadoc> and assign to the <code>CollectionTemplate</code> instance, then you will see the template is created multiple times. Besides, <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. Similarly, either template or model is changed, <code>apply(host)</code> must be triggered to take the effect. The benefit of using CollectionTemplate is that every time the model's content changes the layout will change as well no matter '''autodrop''' is true or false.
+
In this example, we assign an <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.
  
 
<blockquote>
 
<blockquote>

Revision as of 07:28, 2 October 2015

Introduction

Since ZK 8.0.0, we have introduced shadow elements like a boilerplate code to help application developers compose html layouts with dynamic data. It is inspired from Shadow DOM to enable better composition of ZK components. For more details, please check out our Official ZK MVVM Book. Shadow elements cannot only be used with MVVM binding but also with 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. For Example,

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

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 favor MVC, that is, ShadowTemplate and CollectionTemplate.

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

Use ShadowTemplate

ShadowTemplate is a utility class to let developers to apply shadow elements in Java class. It has the similar behavior with 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 user changed template or detach from the original host, ShadowTemplate will HtmlShadowElement.recreate() or removed the children, otherwise, rendered children will be remained. After instantiating ShadowTemplate instance, developers can trigger ShadowTemplate.apply(Component) 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.

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

@Wire
Div host1;

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

In line 6, we instantiate a new ShadowTemplate with autodrop equal to true.

In line 7, assign the template name to st.

In line 8, call apply method and shadow host is Div host1.

Then, we can see template "labels" is rendered and the created component 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, and attach the new ones, note that, developers have to call apply(host) method again.

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

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

And the rendered components will also be detached.

Another case is autodrop equal to false, and then neither change template nor apply to other host(yes, you can apply different host whatever you want) won't cause rendered components being detached.

Use CollectionTemplate

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

Example

The basic usage is simple, and here we take use of 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 to the CollectionTemplate instance, then you will see the template is created multiple times. Similarly, either template or model is changed, apply(host) must be triggered to take the effect. The benefit of using CollectionTemplate is that every time the model's content changes the layout will change as well no matter 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 an 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.


Note that, ShadowTemplate don't support set template and template URI at the same time, any one of them should be null or empty string before set another. CollectionTemplate will only consider the last call to either setTemplate, setTemplateURI or setTemplateResolver.

Comparison

Although the behavior looks alike between ShadowTemplate and Macro component, there exist some differences.

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

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

no matter it is inline or not, the rendered components will change parent.
change template/uri if autodrop is true, the rendered components will be detached, otherwise,

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

no matter it is inline or not, the rendered components will be detached.

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

Version History

Last Update : 2015/10/02


Version Date Content
     



Last Update : 2015/10/02

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