Composer"

From Documentation
Line 75: Line 75:
 
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.
 
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.
  
==More about Applying Composers==
+
===More about Applying Composers===
  
 
If you could specify multiple composers, just separate them with comma. They will be called from left to right.
 
If you could specify multiple composers, just separate them with comma. They will be called from left to right.
Line 90: Line 90:
  
 
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 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.
 +
 +
==Identify a Composer==
 +
If your application is required to retrieve the composer back, you could store the reference of the composer you implemented into a component's attribute. However, if you are using or extending from <javadoc>org.zkoss.zk.ui.util.GenericAutowireComposer</javadoc> and its derives, there are a few ways to name the composer as described in the following sections.
 +
 +
===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,
 +
 +
<source lang="xml">
 +
<window id="mywin" apply="MyComposer">
 +
    <textbox id="mytextbox" value="${mywin$composer.title}"/>
 +
</window>
 +
</source>
 +
 +
If there are multiple composers applied, the second name (<tt>''id''$''ClassName''</tt>) is useful.
 +
 +
<source lang="xml">
 +
<window apply="foo.Handle1, foo.Handle2">
 +
    <textbox value="${$Handle1.title}"/>
 +
    <textbox value="${$Handle2.name}"/>
 +
</window>
 +
</source>
 +
 +
===Specify Name for Composer===
 +
[since 5.0.8]
  
 
=Composer with More Control=
 
=Composer with More Control=

Revision as of 07:45, 24 June 2011

Custom Controller

A custom controller is called a composer in ZK. To implement it, you could extend from GenericForwardComposer, or implement Composer from scratch. Then, specify it in the element it wants to handle in a ZUML document.

To implement the logic to glue UI and data, a composer usually does:

  • Post-process components after ZK Loader renders a ZUML document. It can be done by overriding Composer.doAfterCompose(Component).
  • Handle events and manipulate components 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 be configured as system-level, such that it will be called when ZK Loader has processed a ZUML document.

Implement Composers

Implementing Composer is straightfoward: just override Composer.doAfterCompose(Component) and do whatever you want.

However, it is suggested to extend from GenericForwardComposer since the default implementation of GenericForwardComposer.doAfterCompose(Component) wires variables and event listener automatically.

For example,

package foo;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.util.GenericForwardComposer;
import org.zkoss.zul.*;

public class MyComposer extends GenericForwardComposer {
    Textbox input;
    Label output;

    public void onClick$submit() {
        output.setValue(input.getValue());
    }
    public void onClick$reset() {
        output.setValue("");
    }
}

where input will be wired to a fellow named input, and onClick$submit will be registered as an event listener for an event named onClick and to a fellow named submit.

Notice that the event handler must be declared as public. Otherwise, they will be ignored from auto-wiring.

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 post-process the components after ZK Loader initializes them, you could override GenericForwardComposer.doAfterCompose(Component). It is important to call back super.doAfterCompose(comp). Otherwise, the wiring won't work. It also means none of data members are wired before calling super.doAfterCompose(comp).

public void doAfterCompose(Component 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.

More about Applying 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">

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.

Identify a Composer

If your application is required to retrieve the composer back, you could store the reference of the composer you implemented into a component's attribute. However, if you are using or extending from GenericAutowireComposer and its derives, there are a few ways to name the composer as described in the following sections.

Default Names of Composer

If a composer extends from GenericAutowireComposer and its derives, the composer is stored in two component attributes called id$composer and id$ClassName, 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,

<window id="mywin" apply="MyComposer">
     <textbox id="mytextbox" value="${mywin$composer.title}"/>
 </window>

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

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

Specify Name for Composer

[since 5.0.8]

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.

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 ComposerExt. Since GenericForwardComposer 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 extends GenericForwardComposer {
    public boolean doCatch(Throwable ex) {
        return _ignorable(ex); //return true if ex could be ingored
    }
}

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


Fine-grained Full Control with FullComposer

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

There is no method need to implement in 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 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(Component) will be called for datebox, textbox, div and then panel (in the order of child-first-parent-last). If FullComposer is not implemented, only panel will be called.

Lifecycle

Here is a lifecylce of the invocation of a composer:

Composer.PNG

Retrieve Composer in EL Expressions

When GenericComposer is applied to a component, it stores itself in the component's attribute called id$composer, where id is the ID of the component that a composer is applied to. Thus, it is easy to retrieve it in an EL expression by referencing to this name directly. For example,

<div id="main" apply="foo.MyComposer">
   ${main$composer.whatever} <!-- retrieve a property of the composer -->
</div>

If you have multiple composers applied to the same component, you could retrieve them by attributes called and id$ClassName. For example,

<borderlayout id="main" apply="foo.MyComposer, foo2.AnotherComposer">
   ${main$MyComposer.whatever} <!-- retrieve a property of foo.MyComposer -->
   ${main$AnotherComposer.somethingelse} <!-- retrieve a property of foo.AnotherComposer -->
</borderlayout>

However, if you implement the composer from Composer, you have to store it manually if necessary. Otherwise, it will be garbage-collected after applied (since ZK does not maintain any reference to it). It can be done easily by calling Component.setAttribute(String, Object). Notice it is suggested to assign the attribute in ComposerExt.doBeforeComposeChildren(Component). Please refer to the following section for more information.

Prepare Variables for EL Expressions in Composer

It is a common practice to prepare some variables in a composer, such that they can be accessed in EL expressions. Since component's attributes are visible to EL expressions, the preparation of variables can be done easily with Component.setAttribute(String, Object). For example,

public class FooComposer extends org.zkoss.zk.ui.GenericForwardComposer {
    public void doBeforeComposeChildren(Component comp) throws Exception {
        super.doBeforeComposeChildren(comp);
        comp.setAttribute("whatever", prepareWhatever());
   }
}

The you could access whatever prepared in the composer as described above in EL expressions, such as

<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 -->
...

Notice that we assign the attribute in ComposerExt.doBeforeComposeChildren(Component), such that it can be accessed by the children of the component. If we assign in Composer.doAfterCompose(Component), it won't be available to its children. Please refer to the Lifecycle section for details.

Another way is to use the autowiring capabilities of GenericForwardComposer and access variables defined in the composer directly:

public class FooComposer extends org.zkoss.zk.ui.GenericForwardComposer {
    private String name;

    public String getName(){
        return name;
    }
    public void setName(String name){
        this.name = name;
   }
}
<window id="win" apply="foo.FooComposer">
    <label value="Name:"/><textbox value="${win$composer.name}"/>   
...

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(Component) with 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 Desktop.getRequestPath() (the desktop instance can be found thru the give 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.

  1. 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 the root components.

Version History

Last Update : 2011/06/24


Version Date Content
     



Last Update : 2011/06/24

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