Composer"

From Documentation
m
 
(29 intermediate revisions by 7 users not shown)
Line 3: Line 3:
 
=Custom Controller=
 
=Custom Controller=
  
A custom controller is called a composer in ZK.  To implement it, you could extend from <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc>, or implement <javadoc type="interface">org.zkoss.zk.ui.util.Composer</javadoc> from scratch. Then, specify it in the element it wants to handle in a ZUML document.
+
A custom controller is called a composer in ZK.  To implement it, you can simply extend <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc>. Then, specify it in the UI element that it wants to handle in a ZUML document.
  
To implement the ''logic'' to glue UI and data, a composer usually does:
+
A composer usually does, but not limited to:
  
*Post-process components after ZK Loader renders a ZUML document. It can be done by overriding <javadoc method="doAfterCompose(org.zkoss.zk.ui.Component)">org.zkoss.zk.ui.util.Composer</javadoc>.
+
*Load data to components, if necessary.
 
*Handle events and manipulate components accordingly, if necessary.
 
*Handle events and manipulate components accordingly, if necessary.
 
*Provide the data, if necessary.
 
*Provide the data, if necessary.
  
In additions, a composer can be used to involve the lifecycle of ZK Loader for doing:
+
In addition, a composer can be used to involve the lifecycle of ZK Loader for doing:
  
 
*Exception handling
 
*Exception handling
 
*Component instantiation monitoring and filtering
 
*Component instantiation monitoring and filtering
  
A composer can be [[ZK Configuration Reference/zk.xml/The listener Element/The org.zkoss.zk.ui.util.Composer interface|configured at system level]], such that it will be called for each ZUML document being loaded.
+
A composer can be [[ZK Configuration Reference/zk.xml/The listener Element/The org.zkoss.zk.ui.util.Composer interface|configured as a system-level composer]], such that it will be called each time a ZUML document is loaded.
  
 
==Implement Composers==
 
==Implement Composers==
Implementing <javadoc type="interface">org.zkoss.zk.ui.util.Composer</javadoc> is straightfoward: just override <javadoc type="interface" method="doAfterCompose(org.zkoss.zk.ui.Component)">org.zkoss.zk.ui.util.Composer</javadoc> and do whatever you want.  
+
To simplify the implementation of the controller part of UI, ZK provides several skeleton implementations. For example, <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc>, as one of the most popular skeletons, wires components, variables and event listeners automatically based on Java annotations you specify. For example, in the following controller and zul,
  
However, it is suggested to extend from <javadoc>org.zkoss.zk.ui.GenericForwardComposer</javadoc> since the default implementation of <javadoc method="doAfterCompose(org.zkoss.zk.ui.Component)">org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> wires variables and event listener automatically.
+
Controller:
 
+
<source lang="java" line highlight='9-12,14-15,18-19'>
For example,
 
 
 
<source lang="java">
 
 
package foo;
 
package foo;
import org.zkoss.zk.ui.Component;
+
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.util.GenericForwardComposer;
+
import org.zkoss.zk.ui.select.annotation.Wire;
 +
import org.zkoss.zk.ui.select.annotation.Listen;
 
import org.zkoss.zul.*;
 
import org.zkoss.zul.*;
  
public class MyComposer extends GenericForwardComposer {
+
public class MyComposer extends SelectorComposer<Window> {
    Textbox input;
 
    Label output;
 
  
    public void onClick$submit() {
+
@Wire
        output.setValue(input.getValue());
+
Textbox input;
    }
+
@Wire
    public void onClick$reset() {
+
Label output;
        output.setValue("");
+
    }
+
@Listen("onClick=#ok")
 +
public void submit() {
 +
output.setValue(input.getValue());
 +
}
 +
@Listen("onClick=#cancel")
 +
public void cancel() {
 +
output.setValue("");
 +
}
 +
 
}
 
}
 
</source>
 
</source>
 +
* Line: 9-12: The member fields <code>input</code>, <code>output</code> are automatically assigned with components with identifiers of "input" and "output", respectively.
 +
* Line 14-21: The methods <code>submit()</code> and <code>cancel()</code> will be called when user clicks on the corresponding buttons.
  
where <code>input</code> will be wired to a fellow named <code>input</code>, and <code>onClick$submit</code> will be registered as an event listener for an event named <code>onClick</code> and to a fellow named <code>submit</code>.
+
ZUL:
 +
<source lang="XML" highlight='1'>
 +
<window apply="foo.MyComposer">
 +
<div>
 +
Input: <textbox id="input" />
 +
</div>
 +
<div>
 +
Output: <label id="output" />
 +
</div>
 +
<button id="ok" label="Submit" />
 +
<button id="cancel" label="Clear" />
 +
</window>
 +
</source>
  
Notice that the event handler must be declared as '''public'''. Otherwise, they will be ignored from auto-wiring.
+
 
 +
In addition to wiring components via identifiers, you could wire by a CSS3-like selector (<javadoc>org.zkoss.zk.ui.select.Selector</javadoc>), such as
 +
 
 +
* <code>@Wire("#foo")</code>
 +
* <code>@Wire("textbox, intbox, decimalbox, datebox")</code>
 +
* <code>@Wire("window > div > button")</code>
 +
 
 +
* <code>@Listen("onClick = button[label='Clear']")</code>
 +
 
 +
For more information, please refer to the following sections: [[ZK Developer's Reference/MVC/Controller/Wire Components|Wire Components]], [[ZK Developer's Reference/MVC/Controller/Wire Variables|Wire Variables]] and [[ZK Developer's Reference/MVC/Controller/Wire Event Listeners|Wire Event Listeners]].
  
 
==Apply Composers==
 
==Apply Composers==
Once a composer is implemented, you could associate it with a component, such that the composer can control the UI rooted the given component.
+
Once a composer is implemented, you usually associate it with a component, so that the composer can control the associated components and its child components.
  
 
Associating a composer to a component is straightforward: just specify the class to [[ZUML Reference/ZUML/Attributes/apply|the apply attribute]] of the XML element you want to control. For example,
 
Associating a composer to a component is straightforward: just specify the class to [[ZUML Reference/ZUML/Attributes/apply|the apply attribute]] of the XML element you want to control. For example,
  
<source lang="xml">
+
<source lang="xml" highlight="1">
 
<grid apply="foo.MyComposer">
 
<grid apply="foo.MyComposer">
 
     <rows>
 
     <rows>
Line 65: Line 92:
 
</source>
 
</source>
  
If you have to post-process the components after ZK Loader initializes them, you could override <javadoc method="doAfterCompose(org.zkoss.zk.ui.Component)">org.zkoss.zk.ui.util.GenericForwardComposer</javadoc>. It is important to call back <code>super.doAfterCompose(comp)</code>. Otherwise, the wiring won't work. It also means that none of the data members are wired before calling <tt>super.doAfterCompose(comp)</tt>.
 
  
<source lang="java" high="2">
+
===Applying Multiple Composers===
public void doAfterCompose(Component comp) {
 
  super.doAfterCompose(comp); //wire variables and event listners
 
  //do whatever you want (you could access wired variables here)
 
}
 
</source>
 
  
where <code>comp</code> is the component that the composer is applied to. In this example, it is the grid. As the name indicates, <code>doAfterCompose</code> is called after the grid and all its descendants are instantiated.
+
You could specify multiple composers; just separate them with commas. They will be called from left to right.
 
 
===More about Applying Composers===
 
 
 
If you could specify multiple composers, just separate them with comma. They will be called from left to right.
 
  
 
<source lang="xml">
 
<source lang="xml">
Line 84: Line 101:
 
</source>
 
</source>
  
In additions to the class name, you could specify an instance too. For example, suppose you have an instance called <code>fooComposer</code>, then
+
===Apply Composer Instances===
 +
In addition to the class name, you could specify an instance too. For example, suppose you have an instance called <code>fooComposer</code>, then
  
 
<source lang="xml">
 
<source lang="xml">
Line 90: Line 108:
 
</source>
 
</source>
  
If a class name is specified, each time the component is instantiated, an instance of the class is instantiated too. Thus, you don't have to worry about the concurrency issue. However, if you specify an instance, it will be used directly. Thus, you have to either create an instance for each request, or make it thread-safe.
+
If a class name is specified, each time the component is instantiated, an instance of the specified composer class is instantiated too. Thus, you don't have to worry about the concurrency issue. However, if you specify an instance, it will be used directly. Thus, you have to either create an instance for each request, or make it thread-safe.
  
 
==Retrieve Composer in EL Expressions==
 
==Retrieve Composer in EL Expressions==
If you have to retrieve the composer back later (such as use it in an EL expression), you could store the reference of the composer you implement into a component's attribute<ref>It can be done by invoking <javadoc method="setAttribute(java.lang.String, java.lang.Object)" type="interface">org.zkoss.zk.ui.Component</javadoc>, because the component's attribute can be referenced directly in EL expressions. Notice that if you want to reference it in EL expressions, you'd better to set the attribute in <javadoc method="doBeforeComposeChildren(org.zkoss.zk.ui.Component)" type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc>, because <javadoc method="doAfterCompose(org.zkoss.zk.ui.Component)" type="interface">org.zkoss.zk.ui.util.Composer</javadoc> was called after all child components are instantiated.</ref>.
+
If you have to retrieve the composer back later (such as reference it in an EL expression), you can store the composer into a component's attribute<ref>It can be done by invoking <javadoc method="setAttribute(java.lang.String, java.lang.Object)" type="interface">org.zkoss.zk.ui.Component</javadoc>, because the component's attribute can be referenced directly in EL expressions. Notice that if you want to reference it in EL expressions, you'd set the attribute in <javadoc method="doBeforeComposeChildren(T)" type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> because <javadoc method="doAfterCompose(T)" type="interface">org.zkoss.zk.ui.util.Composer</javadoc> was called after all child components were instantiated.</ref>.
  
If your composer extends from <javadoc>org.zkoss.zk.util.GenericComposer</javadoc> (and its derives, including <javadoc>org.zkoss.zk.ui.util.GenericAutowireComposer</javadoc>), it will be stored into an attribute automatically. Thus, for sake of convenience, it is recommended to extend from one of these classes, if you'd like to retrieve the composer back.
+
If the composer extends from one of ZK skeletal implementations (such as <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc> and <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc>), it will be stored into an attribute automatically. Thus, for the sake of convenience, you could extend from one of these classes, if you'd like to retrieve the composer back.
  
<javadoc>org.zkoss.zk.util.GenericComposer</javadoc> (and its derives) support several ways to name the composer as described in the following sections.
+
Every ZK skeletal implementation provides several ways to name the composer as described in the following sections.
  
{{references}}
+
<blockquote>
 +
----
 +
<references/>
 +
</blockquote>
  
 
===Default Names of Composer===
 
===Default Names of Composer===
  
If a composer extends from <javadoc>org.zkoss.zk.ui.util.GenericAutowireComposer</javadoc> and its derives, the composer is stored in two component attributes called <tt>''id''$composer</tt> and <tt>''id''$''ClassName''</tt>, where ''id'' is the component's ID (if not assigned, it is default to an empty string), and ''ClassName'' is the class name of the composer. For example,
+
If a composer extends from one of ZK skeletal implementations (such as <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc> and <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc>), the composer is stored in three component attributes called:
 +
* <code>$composer</code>
 +
* <code>''id''$composer</code>
 +
* <code>''id''$''ClassName''</code>, where ''id'' is the component's ID, and ''ClassName'' is the class name of the composer. If ID is not assigned, it defaults to an empty string, so the composer will be stored to two component attributes: <code>$composer</code> and <code>$''ClassName''</code>.
 +
 
 +
 
 +
Therefore, you can access the composer with one of the above variables e.g.
  
 
<source lang="xml">
 
<source lang="xml">
 
<window id="mywin" apply="MyComposer">
 
<window id="mywin" apply="MyComposer">
     <textbox id="mytextbox" value="${mywin$composer.title}"/>
+
     <textbox value="${mywin$composer.title}"/>
 +
    <textbox value="${$composer.title}"/> <!- also refer to MyComposer -->
 
  </window>
 
  </window>
 
</source>
 
</source>
  
If there are multiple composers applied, the second name (<tt>''id''$''ClassName''</tt>) is useful.
+
Notice that <code>$composer</code> is always assigned no matter what the ID is, so it is more convenient to use. However, if there are several components assigned with composers, you might have to use ID to distinguish them.
 +
 
 +
The second name (<code>''id''$''ClassName''</code>) is useful, if there are multiple composers applied.
  
 
<source lang="xml">
 
<source lang="xml">
Line 121: Line 151:
  
 
===Specify Name for Composer===
 
===Specify Name for Composer===
[since 5.0.8]
 
  
If you prefer to name the composer by yourself, you could specify the name in a component attribute called <tt>composerName</tt>. For example,
+
If you prefer to name the composer by yourself, you could specify the name in a component attribute called <code>composerName</code>. For example,
  
 
<source lang="xml">
 
<source lang="xml">
Line 132: Line 161:
 
  </window>
 
  </window>
 
</source>
 
</source>
 +
 +
== Prepare Data for EL Expressions in Composer ==
 +
It is a common practice to prepare some data in a composer, such that those data are available when rendering the child components. As described above, the composer will be stored as a component attribute that is accessible directly in EL expressions. Thus, you could provide the data easily by declaring a public getter method. For example,
 +
 +
<source lang="java" highlight="2">
 +
public class UsersComposer extends org.zkoss.zk.ui.select.SelectorComposer<Window> {
 +
    public ListModel<User> getUsers() {
 +
        //return a collection of users
 +
    }
 +
}
 +
</source>
 +
 +
Then, you could access it as follows.
 +
 +
<source lang="xml" highlight="2">
 +
<window title="User List" border="normal" apply="foo.UsersComposer">
 +
    <grid model="${$composer.users}>
 +
...
 +
</source>
 +
 +
===Wire Spring-managed beans===
 +
Here is another example that we wire Spring-managed beans  with the <javadoc type="interface">org.zkoss.zk.ui.select.WireVariable</javadoc> annotation.
 +
 +
<source lang="java" highlight="1,3">
 +
@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
 +
public class UsersComposer extends SelectorComposer<Window> {
 +
    @WireVariable
 +
    private List<User> users;
 +
 +
    public ListModel<User> getUsers() {
 +
        return new ListModelList<User>(users);
 +
    }
 +
}
 +
</source>
 +
 +
where we register a variable resolver called <javadoc>org.zkoss.zkplus.spring.DelegatingVariableResolver</javadoc> with the <javadoc type="interface">org.zkoss.zk.ui.select.VariableResolver</javadoc> annotation. As its name suggests, <javadoc>org.zkoss.zkplus.spring.DelegatingVariableResolver</javadoc> will be used to retrieve Spring-managed beans when <code>@WireVariable</code> is encountered. For more information, please refer to [[ZK Developer's Reference/MVC/Controller/Wire Variables|the Wire Variables section]].
 +
 +
Notice that the variables will be wired before instantiating the component and its children, so it is OK to access them in the ZUML document, as below.
 +
 +
<source lang="xml">
 +
<window title="User List" border="normal" apply="foo.UsersComposer">
 +
    <grid model="${$composer.users}>
 +
...
 +
</source>
 +
 +
<blockquote>
 +
----
 +
<references/>
 +
</blockquote>
  
 
=Composer with More Control=
 
=Composer with More Control=
  
A composer could also handle the exceptions, if any, involve the life cycle of rendering, and monitor and even control how a child component is instantiated. It can be done by implementing the corresponding interfaces.
+
A composer could also handle the exceptions, if any, control the life cycle of rendering, and intercept how a child component is instantiated. It can be done by implementing the corresponding interfaces, <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> and/or <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc>.
 +
 
 +
== Initialize Components ==
 +
 
 +
If you want to initialize a component's properties with some default values, after ZK creates it, you should override <javadoc method="doAfterCompose(T)">org.zkoss.zk.ui.select.SelectorComposer</javadoc>.
 +
 
 +
<syntaxhighlight lang="java" line highlight='3'>
 +
public class MyComposer extends SelectorComposer<Grid> {
 +
  public void doAfterCompose(Grid comp) {
 +
      super.doAfterCompose(comp); //wire variables and event listeners
 +
      //initialize wired components here e.g. myLabel.setValue("default value")
 +
  }
 +
...
 +
</syntaxhighlight>
 +
* Line 2: The passed argument, <code>comp</code>, is the component that the composer is applied to. In this example, it is the grid. As the name indicates, <code>doAfterCompose</code> is called after the grid and all its descendants are instantiated.
 +
* Line 3: Calling  <code>super.doAfterCompose(comp)</code> first is required to make <code>@Wire</code> and <code>@Listen</code> work.
  
 
== Exception and Lifecycle Handling with ComposerExt ==
 
== Exception and Lifecycle Handling with ComposerExt ==
  
If you want a composer to handle the exception and/or involve the life cycle of rendering, you could also implement <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc>. Since <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> already implements this interface, you only need to override the method you care if you extends from it.
+
If you want a composer to handle the exception and/or control the life cycle of rendering, you could also implement <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc>. Since <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc> already implements this interface, you only need to override the method you care about if you extend from it.
  
 
For example, we could handle the exception by overriding <javadoc method="doCatch(java.lang.Throwable)">org.zkoss.zk.ui.util.ComposerExt</javadoc> and/or <javadoc method="doFinally()">org.zkoss.zk.ui.util.ComposerExt</javadoc>.
 
For example, we could handle the exception by overriding <javadoc method="doCatch(java.lang.Throwable)">org.zkoss.zk.ui.util.ComposerExt</javadoc> and/or <javadoc method="doFinally()">org.zkoss.zk.ui.util.ComposerExt</javadoc>.
  
<source lang="xml">
+
<source lang="java">
public class MyComposer extends GenericForwardComposer {
+
public class MyComposer<T extends Component> extends SelectorComposer<T> {
 
     public boolean doCatch(Throwable ex) {
 
     public boolean doCatch(Throwable ex) {
         return _ignorable(ex); //return true if ex could be ingored
+
         return ignorable(ex); //return true if ex could be ignored
 
     }
 
     }
 
}
 
}
 
</source>
 
</source>
  
For involving the life cycle, you could override <javadoc method="doBeforeCompose(org.zkoss.zk.ui.Page, org.zkoss.zk.ui.Component, org.zkoss.zk.ui.metainfo.ComponentInfo)" type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> and/or <javadoc method="doBeforeComposeChildren(org.zkoss.zk.ui.Component)" type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc>.
+
For involving the life cycle, you could override <javadoc method="doBeforeCompose(org.zkoss.zk.ui.Page, org.zkoss.zk.ui.Component, org.zkoss.zk.ui.metainfo.ComponentInfo)" type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> and/or <javadoc method="doBeforeComposeChildren(T)" type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc>.
  
 
<blockquote>
 
<blockquote>
Line 160: Line 253:
 
== Fine-grained Full Control with FullComposer ==
 
== Fine-grained Full Control with FullComposer ==
  
In addition to controlling the given component, a composer can monitor the instantiation and exceptions for each child and the descendant component. It can be done by implementing <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc>. <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> does not implement this interface by default. Thus, you have to implement it explicitly.
+
In addition to controlling the given component, a composer can monitor the instantiation and exceptions for each child and the descendant component. It is done by implementing <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc>. <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc> does not implement this interface by default. Thus, you have to implement it explicitly.
  
 
There is no implementation method needed for this interface. It is like a decorative interface to indicate that it requires the fine-grained full control. In other words, all methods declared in <javadoc type="interface">org.zkoss.zk.ui.util.Composer</javadoc> and <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> will be invoked one-by-one against each child and the descendant component.
 
There is no implementation method needed for this interface. It is like a decorative interface to indicate that it requires the fine-grained full control. In other words, all methods declared in <javadoc type="interface">org.zkoss.zk.ui.util.Composer</javadoc> and <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> will be invoked one-by-one against each child and the descendant component.
Line 166: Line 259:
 
For example, suppose we have a composer implementing both <javadoc type="interface">org.zkoss.zk.ui.util.Composer</javadoc> and <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc>, and it is assigned as followed
 
For example, suppose we have a composer implementing both <javadoc type="interface">org.zkoss.zk.ui.util.Composer</javadoc> and <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc>, and it is assigned as followed
  
<source lang="java">
+
<source lang="xml">
<panel apply="foo.MyComposer">
+
    <panel apply="foo.MyFullComposer">
    <div>
+
        <panelchildren>
        <datebox/>
+
        <div>
        <textbox/>
+
            <datebox/>
    </div>
+
            <textbox/>
</panel>
+
        </div>
 +
        </panelchildren>
 +
    </panel>
 
</source>
 
</source>
  
then, <javadoc type="interface" method="doAfterCompose(org.zkoss.zk.ui.Component)">org.zkoss.zk.ui.util.Composer</javadoc> will be called for datebox, textbox, div and then panel (in the order of ''child-first-parent-last''). If <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc> is not implemented, only the panel will be called.
+
Then, <javadoc type="interface" method="doAfterCompose(T)">org.zkoss.zk.ui.util.Composer</javadoc> will be called for datebox, textbox, div and then panel (in the order of ''child-first-parent-last''). If <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc> is not implemented, only the panel will be called.
 
 
== Lifecycle ==
 
  
Here is a lifecylce of the invocation of a composer:
+
Notice that, because <javadoc type="interface" method="doAfterCompose(T)">org.zkoss.zk.ui.util.Composer</javadoc> will be called for each child, the generic type should be <javadoc type="interface">org.zkoss.zk.ui.Component</javadoc> rather than the component's type to which the composer is applied. For example,
  
[[Image:Composer.PNG]]
+
<source lang="java">
 
+
public class MyFullComposer extends SelectorComposer<Component> implements FullComposer {
= Prepare Variables for EL Expressions in Composer =
 
It is a common practice to prepare some variables in a composer, such as that they can be accessed in EL expressions. Since a component's attributes are visible to EL expressions, the preparation of variables can be done easily with <javadoc method="setAttribute(java.lang.String, java.lang.Object)" type="interface">org.zkoss.zk.ui.Component</javadoc>. For example,
 
 
 
<source lang="java" high="4">
 
public class FooComposer extends org.zkoss.zk.ui.GenericForwardComposer {
 
    public void doBeforeComposeChildren(Component comp) throws Exception {
 
        super.doBeforeComposeChildren(comp);
 
        comp.setAttribute("whatever", prepareWhatever());
 
  }
 
}
 
</source>
 
 
 
The you could access <code>whatever</code> prepared in the composer as described above in EL expressions, such as
 
 
 
<source lang="xml" high="3">
 
<grid apply="foo.FooComposer">
 
    <rows>
 
        <row forEach="${whatever}"> <!-- assume whatever is a collection of items -->
 
        ${each.name} <textbox value="${each.value}"/> <!-- assume each item has name and value -->
 
 
...
 
...
 
</source>
 
</source>
  
Notice that we assign the attribute in <javadoc type="interface" method="doBeforeComposeChildren(org.zkoss.zk.ui.Component)">org.zkoss.zk.ui.util.ComposerExt</javadoc>, such that it can be accessed by the children of the component. If we assign in <javadoc type="interface" method="doAfterCompose(org.zkoss.zk.ui.Component)">org.zkoss.zk.ui.util.Composer</javadoc>, it won't be available to its children. Please refer to [[#Lifecycle|the Lifecycle section]] for details.
+
== Lifecycle ==
  
Another way  is to use the autowiring capabilities of <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc> and access variables defined in the composer directly:
+
Here is a lifecycle of the invocation of a composer:
<source lang="java" >
 
public class FooComposer extends org.zkoss.zk.ui.GenericForwardComposer {
 
    private String name;
 
  
    public String getName(){
+
[[Image:Composer.PNG]]
        return name;
 
    }
 
    public void setName(String name){
 
        this.name = name;
 
  }
 
}
 
</source>
 
 
 
<source lang="xml">
 
<window id="win" apply="foo.FooComposer">
 
    <label value="Name:"/><textbox value="${win$composer.name}"/> 
 
...
 
</source>
 
  
 
=System-level Composer=
 
=System-level Composer=
  
If you have a composer that shall be invoked for every page, you could register a system-level composer rather than specifying it on every page.
+
If you have a composer that shall be invoked for every page, you can register a system-level composer rather than applying it on every page. For example, handling logout logic, or receiving a common event fired by every page.
  
It could be done by specifying the composer you implemented in <code>WEB-INF/zk.xml</code><ref>For more information, please refer to [[ZK Configuration Reference/zk.xml/The listener Element|ZK Configuration Reference]]</ref>:
+
To register it, specify the composer you implemented in <code>WEB-INF/zk.xml</code>:
  
 
<source lang="xml">
 
<source lang="xml">
Line 238: Line 296:
 
</listener>
 
</listener>
 
</source>
 
</source>
 +
For more information, please refer to [[ZK Configuration Reference/zk.xml/The listener Element|ZK Configuration Reference/zk.xml]].
  
Each time a ZK page, including ZK pages and richlets, is created, ZK will instantiate one instance for each registered system-level composer and the invoke <javadoc method="doAfterCompose(org.zkoss.zk.ui.Component)" type="interface">org.zkoss.zk.ui.util.Composer</javadoc> for each root component. The system-level composer is usually used to post-process ZK pages, such as adding a trademark. If you want to process only certain pages, you can check the request path by calling <javadoc method="getRequestPath()" type="interface">org.zkoss.zk.ui.Desktop</javadoc> (the desktop instance can be found through the given component).
+
Each time a ZK page, including ZK pages and richlets, is created, ZK will instantiate one instance for each registered system-level composer and then invoke <javadoc method="doAfterCompose(T)" type="interface">org.zkoss.zk.ui.util.Composer</javadoc> for each root component. The system-level composer is usually used to process ZK pages after all components are instantiated successfully, such as adding a trademark. If you want to process only certain pages, you can check the request path by calling <javadoc method="getRequestPath()" type="interface">org.zkoss.zk.ui.Desktop</javadoc> (the desktop instance can be found through the given component).
  
 
If the system-level composer also implements <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc>, it can be used to handle more situations, such as exceptions, like any other composer can do.
 
If the system-level composer also implements <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc>, it can be used to handle more situations, such as exceptions, like any other composer can do.
Line 245: Line 304:
 
If the system-level composer also implements <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc>, it will be invoked when each component is created. It provides the finest grain of control but a wrong implementation might degrade the performance.
 
If the system-level composer also implements <javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc>, it will be invoked when each component is created. It provides the finest grain of control but a wrong implementation might degrade the performance.
  
Notice that since a new instance of the composer is created for each page, there is no concurrency issues.
+
Notice that since a new instance of the composer is created for each page, there are no concurrency issues.
  
<blockquote>
 
<references/>
 
</blockquote>
 
  
 
== Richlet ==
 
== Richlet ==
  
A system-level composer can implement <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> to handle exceptions for a richlet, such as <tt>doCatch</tt> and <tt>doFinally</tt>. However, <tt>doBeforeCompose</tt> and <tt>doBeforeComposeChildren</tt>  won't be called.
+
A system-level composer can implement <javadoc type="interface">org.zkoss.zk.ui.util.ComposerExt</javadoc> to handle exceptions for a richlet, such as <code>doCatch</code> and <code>doFinally</code>. However, <code>doBeforeCompose</code> and <code>doBeforeComposeChildren</code>  won't be called.
  
 
<javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc> is not applicable to richlets. In other words, system-level composers are called only for root components.
 
<javadoc type="interface">org.zkoss.zk.ui.util.FullComposer</javadoc> is not applicable to richlets. In other words, system-level composers are called only for root components.
  
 
=Version History=
 
=Version History=
{{LastUpdated}}
+
 
{| border='1px' | width="100%"
+
{| class='wikitable' | width="100%"
 
! Version !! Date !! Content
 
! Version !! Date !! Content
 
|-
 
|-
 
| 5.0.8
 
| 5.0.8
 
| June, 2011
 
| June, 2011
| <javadoc>org.zkoss.zk.ui.util.GenericAutowireComposer</javadoc> and its derives allow developers to specify a custom name by use of a component attribute called <tt>composerName</tt>.
+
| <javadoc>org.zkoss.zk.ui.util.GenericAutowireComposer</javadoc> and its derives allow developers to specify a custom name by use of a component attribute called <code>composerName</code>.
 
|}
 
|}
  
 
{{ZKDevelopersReferencePageFooter}}
 
{{ZKDevelopersReferencePageFooter}}

Latest revision as of 04:17, 29 January 2024

Custom Controller

A custom controller is called a composer in ZK. To implement it, you can simply extend SelectorComposer. Then, specify it in the UI element that it wants to handle in a ZUML document.

A composer usually does, but not limited to:

  • Load data to components, if necessary.
  • Handle events and manipulate components accordingly, if necessary.
  • Provide the data, if necessary.

In addition, a composer can be used to involve the lifecycle of ZK Loader for doing:

  • Exception handling
  • Component instantiation monitoring and filtering

A composer can be configured as a system-level composer, such that it will be called each time a ZUML document is loaded.

Implement Composers

To simplify the implementation of the controller part of UI, ZK provides several skeleton implementations. For example, SelectorComposer, as one of the most popular skeletons, wires components, variables and event listeners automatically based on Java annotations you specify. For example, in the following controller and zul,

Controller:

 1 package foo;
 2 import org.zkoss.zk.ui.select.SelectorComposer;
 3 import org.zkoss.zk.ui.select.annotation.Wire;
 4 import org.zkoss.zk.ui.select.annotation.Listen;
 5 import org.zkoss.zul.*;
 6 
 7 public class MyComposer extends SelectorComposer<Window> {
 8 
 9 	@Wire
10 	Textbox input;
11 	@Wire
12 	Label output;
13 	
14 	@Listen("onClick=#ok")
15 	public void submit() {
16 		output.setValue(input.getValue());
17 	}
18 	@Listen("onClick=#cancel")
19 	public void cancel() {
20 		output.setValue("");
21 	}
22 	
23 }
  • Line: 9-12: The member fields input, output are automatically assigned with components with identifiers of "input" and "output", respectively.
  • Line 14-21: The methods submit() and cancel() will be called when user clicks on the corresponding buttons.

ZUL:

<window apply="foo.MyComposer">
	<div>
		Input: <textbox id="input" />
	</div>
	<div>
		Output: <label id="output" />
	</div>
	<button id="ok" label="Submit" />
	<button id="cancel" label="Clear" />
</window>


In addition to wiring components via identifiers, you could wire by a CSS3-like selector (Selector), such as

  • @Wire("#foo")
  • @Wire("textbox, intbox, decimalbox, datebox")
  • @Wire("window > div > button")
  • @Listen("onClick = button[label='Clear']")

For more information, please refer to the following sections: Wire Components, Wire Variables and Wire Event Listeners.

Apply Composers

Once a composer is implemented, you usually associate it with a component, so that the composer can control the associated components and its child components.

Associating a composer to a component is straightforward: just specify the class to the apply attribute of the XML element you want to control. For example,

<grid apply="foo.MyComposer">
    <rows>
        <row>
            <textbox id="input"/>
            <button label="Submit" id="submit"/>
            <button label="Reset" id="reset"/>
        </row>
    </rows>
</grid>


Applying Multiple Composers

You could specify multiple composers; just separate them with commas. They will be called from left to right.

<div apply="foo.Composer1, foo2.Composer2">

Apply Composer Instances

In addition to the class name, you could specify an instance too. For example, suppose you have an instance called fooComposer, then

<grid apply="${fooComposer}">

If a class name is specified, each time the component is instantiated, an instance of the specified composer class is instantiated too. Thus, you don't have to worry about the concurrency issue. However, if you specify an instance, it will be used directly. Thus, you have to either create an instance for each request, or make it thread-safe.

Retrieve Composer in EL Expressions

If you have to retrieve the composer back later (such as reference it in an EL expression), you can store the composer into a component's attribute[1].

If the composer extends from one of ZK skeletal implementations (such as SelectorComposer and GenericForwardComposer), it will be stored into an attribute automatically. Thus, for the sake of convenience, you could extend from one of these classes, if you'd like to retrieve the composer back.

Every ZK skeletal implementation provides several ways to name the composer as described in the following sections.


  1. It can be done by invoking Component.setAttribute(String, Object), because the component's attribute can be referenced directly in EL expressions. Notice that if you want to reference it in EL expressions, you'd set the attribute in ComposerExt.doBeforeComposeChildren(T) because Composer.doAfterCompose(T) was called after all child components were instantiated.

Default Names of Composer

If a composer extends from one of ZK skeletal implementations (such as SelectorComposer and GenericForwardComposer), the composer is stored in three component attributes called:

  • $composer
  • id$composer
  • id$ClassName, where id is the component's ID, and ClassName is the class name of the composer. If ID is not assigned, it defaults to an empty string, so the composer will be stored to two component attributes: $composer and $ClassName.


Therefore, you can access the composer with one of the above variables e.g.

<window id="mywin" apply="MyComposer">
     <textbox value="${mywin$composer.title}"/>
     <textbox value="${$composer.title}"/> <!- also refer to MyComposer -->
 </window>

Notice that $composer is always assigned no matter what the ID is, so it is more convenient to use. However, if there are several components assigned with composers, you might have to use ID to distinguish them.

The second name (id$ClassName) is useful, if there are multiple composers applied.

<window apply="foo.Handle1, foo.Handle2">
    <textbox value="${$Handle1.title}"/>
    <textbox value="${$Handle2.name}"/>
 </window>

Specify Name for Composer

If you prefer to name the composer by yourself, you could specify the name in a component attribute called composerName. For example,

<window apply="MyComposer">
    <custom-attributes composerName="mc"/> <!-- name the composer as mc -->

    <textbox value="${mc.title}"/>
 </window>

Prepare Data for EL Expressions in Composer

It is a common practice to prepare some data in a composer, such that those data are available when rendering the child components. As described above, the composer will be stored as a component attribute that is accessible directly in EL expressions. Thus, you could provide the data easily by declaring a public getter method. For example,

public class UsersComposer extends org.zkoss.zk.ui.select.SelectorComposer<Window> {
    public ListModel<User> getUsers() {
        //return a collection of users
    }
}

Then, you could access it as follows.

<window title="User List" border="normal" apply="foo.UsersComposer">
    <grid model="${$composer.users}>
...

Wire Spring-managed beans

Here is another example that we wire Spring-managed beans with the WireVariable annotation.

@VariableResolver(org.zkoss.zkplus.spring.DelegatingVariableResolver.class)
public class UsersComposer extends SelectorComposer<Window> {
    @WireVariable
    private List<User> users;

    public ListModel<User> getUsers() {
        return new ListModelList<User>(users);
    }
}

where we register a variable resolver called DelegatingVariableResolver with the VariableResolver annotation. As its name suggests, DelegatingVariableResolver will be used to retrieve Spring-managed beans when @WireVariable is encountered. For more information, please refer to the Wire Variables section.

Notice that the variables will be wired before instantiating the component and its children, so it is OK to access them in the ZUML document, as below.

<window title="User List" border="normal" apply="foo.UsersComposer">
    <grid model="${$composer.users}>
...

Composer with More Control

A composer could also handle the exceptions, if any, control the life cycle of rendering, and intercept how a child component is instantiated. It can be done by implementing the corresponding interfaces, ComposerExt and/or FullComposer.

Initialize Components

If you want to initialize a component's properties with some default values, after ZK creates it, you should override SelectorComposer.doAfterCompose(T).

1 public class MyComposer extends SelectorComposer<Grid> {
2    public void doAfterCompose(Grid comp) {
3       super.doAfterCompose(comp); //wire variables and event listeners
4       //initialize wired components here e.g. myLabel.setValue("default value")
5    }
6 ...
  • Line 2: The passed argument, comp, is the component that the composer is applied to. In this example, it is the grid. As the name indicates, doAfterCompose is called after the grid and all its descendants are instantiated.
  • Line 3: Calling super.doAfterCompose(comp) first is required to make @Wire and @Listen work.

Exception and Lifecycle Handling with ComposerExt

If you want a composer to handle the exception and/or control the life cycle of rendering, you could also implement ComposerExt. Since SelectorComposer already implements this interface, you only need to override the method you care about if you extend from it.

For example, we could handle the exception by overriding ComposerExt.doCatch(Throwable) and/or ComposerExt.doFinally().

public class MyComposer<T extends Component> extends SelectorComposer<T> {
    public boolean doCatch(Throwable ex) {
        return ignorable(ex); //return true if ex could be ignored
    }
}

For involving the life cycle, you could override ComposerExt.doBeforeCompose(Page, Component, ComponentInfo) and/or ComposerExt.doBeforeComposeChildren(T).


Fine-grained Full Control with FullComposer

In addition to controlling the given component, a composer can monitor the instantiation and exceptions for each child and the descendant component. It is done by implementing FullComposer. SelectorComposer does not implement this interface by default. Thus, you have to implement it explicitly.

There is no implementation method needed for this interface. It is like a decorative interface to indicate that it requires the fine-grained full control. In other words, all methods declared in Composer and ComposerExt will be invoked one-by-one against each child and the descendant component.

For example, suppose we have a composer implementing both Composer and FullComposer, and it is assigned as followed

    <panel apply="foo.MyFullComposer">
        <panelchildren>
        <div>
            <datebox/>
            <textbox/>
        </div>
        </panelchildren>
    </panel>

Then, Composer.doAfterCompose(T) will be called for datebox, textbox, div and then panel (in the order of child-first-parent-last). If FullComposer is not implemented, only the panel will be called.

Notice that, because Composer.doAfterCompose(T) will be called for each child, the generic type should be Component rather than the component's type to which the composer is applied. For example,

public class MyFullComposer extends SelectorComposer<Component> implements FullComposer {
...

Lifecycle

Here is a lifecycle of the invocation of a composer:

Composer.PNG

System-level Composer

If you have a composer that shall be invoked for every page, you can register a system-level composer rather than applying it on every page. For example, handling logout logic, or receiving a common event fired by every page.

To register it, specify the composer you implemented in WEB-INF/zk.xml:

<listener>
    <listener-class>foo.MyComposer</listener-class>
</listener>

For more information, please refer to ZK Configuration Reference/zk.xml.

Each time a ZK page, including ZK pages and richlets, is created, ZK will instantiate one instance for each registered system-level composer and then invoke Composer.doAfterCompose(T) for each root component. The system-level composer is usually used to process ZK pages after all components are instantiated successfully, such as adding a trademark. If you want to process only certain pages, you can check the request path by calling Desktop.getRequestPath() (the desktop instance can be found through the given component).

If the system-level composer also implements ComposerExt, it can be used to handle more situations, such as exceptions, like any other composer can do.

If the system-level composer also implements FullComposer, it will be invoked when each component is created. It provides the finest grain of control but a wrong implementation might degrade the performance.

Notice that since a new instance of the composer is created for each page, there are no concurrency issues.


Richlet

A system-level composer can implement ComposerExt to handle exceptions for a richlet, such as doCatch and doFinally. However, doBeforeCompose and doBeforeComposeChildren won't be called.

FullComposer is not applicable to richlets. In other words, system-level composers are called only for root components.

Version History

Version Date Content
5.0.8 June, 2011 GenericAutowireComposer and its derives allow developers to specify a custom name by use of a component attribute called composerName.



Last Update : 2024/01/29

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