|
|
Line 65: |
Line 65: |
| } | | } |
| </source> | | </source> |
− | Those components rendered before will be detached first, and attach the new ones, note that, developers have to call <code>apply</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 want to apply to other shadow host, please apply null first and then apply again like this: | | If developers want to apply to other shadow host, please apply null first and then apply again like this: |
Line 80: |
Line 80: |
| <javadoc>org.zkoss.zuti.zul.CollectionTemplate</javadoc> is similar with <javadoc>org.zkoss.zuti.zul.ShadowTemplate</javadoc>. The difference is that developers have to assign <javadoc>org.zkoss.zul.ListModel</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 have to assign <javadoc>org.zkoss.zul.ListModel</javadoc> for iteratively rendering. |
| | | |
− | | + | ==Example== |
− | | + | We can take use of previous sample code. |
− | 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.
| + | <source lang="xml" high="6,7,8"> |
− | | |
− | 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.
| |
− | | |
− | ==Implement a 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).
| |
− | | |
− | Suppose we have a template as follows, and it is placed at <code>/WEB-INF/composite/username.zul</code>.
| |
− | | |
− | <source lang="xml"> | |
| <zk> | | <zk> |
− | Usename: <textbox id="mc_who"/>
| + | <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> | | </zk> |
| </source> | | </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"})); |
| | | |
− | ==Implement a Java Class==
| + | public void doAfterCompose(Component comp) throws Exception { |
− | | + | super.doAfterCompose(comp); |
− | To implement a Java class we shall:
| + | CollectionTemplate ct = new CollectionTemplate(true); //autodrop = true |
− | | + | ct.setModel(model); |
− | # Extend from the component class you want.
| + | ct.setTemplate("labels"); |
− | # (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]].
| + | ct.apply(host1); |
− | # 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> |
− | | + | 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 CollectionTemplate is that every time the model's content changes the layout will change as well no matter '''autodrop''' is true or false. |
− | 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.
| |
− | | |
− | 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.
| |
− | | |
− | For more information, please refer to the [[ZK Developer's Reference/MVC/Controller/Wire Variables|the Wire Components section]] and
| |
− | [[ZK Developer's Reference/MVC/Controller/Wire Event Listeners|Wire Event the Listeners section]] sections.
| |
| | | |
| <blockquote> | | <blockquote> |
| ---- | | ---- |
− | 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.
| + | Note that, both ShadowTemplate and CollectionTemplate 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. |
| </blockquote> | | </blockquote> |
| | | |
− | ===Wire Spring-managed Beans=== | + | =Comparison= |
− | <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.
| |
− | | |
− | <source lang="java" high="1,3,10">
| |
− | @VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver)
| |
− | public class Username extends Row implements IdSpace {
| |
− | @WireVariable
| |
− | private User user;
| |
− | | |
− | 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>
| |
− | | |
− | <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.
| |
− | | |
− | For more information, please refer to [[ZK Developer's Reference/MVC/Controller/Wire Variables|the Wire Variables section]].
| |
− | | |
− | ===ID Space===
| |
− | | |
− | 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>
| |
| | | |
− | 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>.
| |
− |
| |
− | =Use Composite Component=
| |
− |
| |
− | 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,
| |
− |
| |
− | <source lang="xml">
| |
− | <?component name="username" extends="row" class="foo.Username"?>
| |
− |
| |
− | <grid>
| |
− | <rows>
| |
− | <username who="Joe"/>
| |
− | <username who="Hellen"/>
| |
− | </rows>
| |
− | </grid>
| |
− | </source>
| |
− |
| |
− | =Define Composite Components as Standard Components=
| |
− |
| |
− | 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]].
| |
− |
| |
− | There are basic two approaches to define a component in the application level:
| |
− |
| |
− | #Define it in a XML file which is called [[ZK Client-side Reference/Language Definition|language addon]].
| |
− | #Define it with Java annoataions.
| |
− |
| |
− | ==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>
| |
− |
| |
− | 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,
| |
− |
| |
− | <source lang="java">
| |
− | @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() {
| |
− | 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.
| |
| | | |
− | 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= | | =Version History= |
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 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 MVC pattern, developers can declare shadow tags in zul files while 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 have favor of MVC, that is, ShadowTemplate and CollectionTemplate.
They are NOT like normal shadow elements defined in zul but you can only create instances 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 Apply.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 have to assign ListModel for iteratively rendering.
Example
We can 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.
Note that, both ShadowTemplate and CollectionTemplate 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.
Comparison
Version History