Composer
Custom Controller
A custom controller is called a composer in ZK. To implement it, you could extend from SelectorComposer, or implement Composer from scratch. Then, specify it in the UI element that it wants to handle in a ZUML document.
A composer usually does, not not limited to:
- Load data to components, if necessary.
- Handle events and manipulate components accordingly, if necessary.
- Provide the data, if necessary.
In additions, 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
Implementing Composer is straightfoward: just override Composer.doAfterCompose(T) and do whatever you want. For example,
package foo;
import org.zkoss.zk.ui.util.Composer;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.*;
public class MyComposer implements Composer<Window> {
public void doAfterCompose(Window main) throws Exception {
main.addEventListener(Events.ON_OK, new EventListener<KeyEvent>() {
public void onEvent(KeyEvent event) throws Exception {
//do whatever you want
}
});
}
}
To simplify the implementation, 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 zul and controller,
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>
Controller:
package foo;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zul.*;
public class MyComposer extends SelectorComposer<Window> {
@Wire
Textbox input;
@Wire
Label output;
@Listen("onClick=#ok")
public void submit() {
output.setValue(input.getValue());
}
@Listen("onClick=#cancel")
public void cancel() {
output.setValue("");
}
}
the member fields input
, output
are automatically assigned with components of identifiers "input" and "output", respectively. The methods submit()
and cancel()
will be called when user clicks on the corresponding buttons.
In additions to identifiers, you could wire by a CSS3-like selector (Selector), such as
@Wire("#foo")
@Wire("textbox, intbox, decimalbox, datebox")
@Listen("onClick = button[label='Clear']")
@Wire("window > div > button")
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 could associate it with a component, such that the composer can control the UI rooted the given component.
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>
If you have to handle the components after ZK Loader initializes them, you could override SelectorComposer.doAfterCompose(T). It is important to call back super.doAfterCompose(comp)
. Otherwise, the wiring won't work. It also means that none of the data members are wired before calling super.doAfterCompose(comp).
public class MyComposer extends SelectorComposer<Grid> {
public void doAfterCompose(Grid comp) {
super.doAfterCompose(comp); //wire variables and event listners
//do whatever you want (you could access wired variables here)
}
...
where 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.
Applying Multiple Composers
If you could specify multiple composers, just separate them with comma. They will be called from left to right.
<div apply="foo.Composer1, foo2.Composer2">
Apply Composer Instances
In additions 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 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 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.
- ↑ 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 better to set the attribute in ComposerExt.doBeforeComposeChildren(T). Composer.doAfterCompose(T) was called after all child components are 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 and id$ClassName, where id is the component's ID, and ClassName is the class name of the composer. If ID is not assigned, it is default to an empty string, so the composer will be stored to two component attributes: $composer and $ClassName.
For example,
<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 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>
Notice that once you assign a name to a composer, the default name (id$composer and id$Xxx) won't be assigned.
Prepare Data for EL Expressions in Composer
It is a common practice to prepare some data in a composer, such as that they are available for 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.
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 if you extends 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.MyComposer">
<div>
<datebox/>
<textbox/>
</div>
</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 is better to Compoonent rather than the component's type which the composer is applied to. For example,
public class MyFullComposer extends SelectorComposer<Component>, FullComposer {
...
Lifecycle
Here is a lifecylce of the invocation of a 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.
It could be done by specifying the composer you implemented in WEB-INF/zk.xml
[1]:
<listener>
<listener-class>foo.MyComposer</listener-class>
</listener>
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 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 is no concurrency issues.
- ↑ For more information, please refer to ZK Configuration Reference
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. |