Component-based UI
Overview
Each UI object is represented with a component (Component). Thus, composing UI is all about assembling components. To alter UI is all about modifying the states and relationship of components.
For example, as shown below, we declared a Window component, enabled the border (border="normal"), and set its width to a definite 250 pixels. Enclosed in the Window are two Button components.
As shown, there are two ways to declare UI: XML-based approach and pure-Java approach. Furthermore, you could mix them if you like.
Forest of Trees of Components
Like a tree structure, a component has at most one parent, while it might have multiple children.
Some components accept only certain types of components as children. Some don't allow any child at all. For example, Grid in XUL accepts Columns and Rows as children only.
A component without any parent is called a root component. Multiple root components are allowed in each page, though not common.
Notice that, if you are using ZUML, there is a XML limitation: exactly one document root is allowed. To specify multiple roots, you have to enclose the root components with the zk tag. It is a special tag that does not create components. For example,
<zk>
<window/> <!-- the first root component -->
<div/> <!-- the second root component -->
</zk>
getChildren()
Most of collections returned by a component, such as Component.getChildren(), are live structures. It means you can add, remove or clear it. For example, to detach all children, you could do it in one statement:
comp.getChildren().clear();
It is equivalent to
for (Iterator it = comp.getChildren().iterator(); it.hasNext();) {
it.next();
it.remove();
}
However, it also means the following code won't work, since it will cause ConcurrentModificationException.
for (Iterator it = comp.getChildren().iterator(); it.hasNext();)
((Component)it.next()).detach();
Sorting
A less subtle example: sorting the children. The following statement will fail since the list is live and a component will be detached first when we move it to different location.
Collections.sort(comp.getChildren());
More precisely, a component has at most one parent and it has only one spot in the list of children. It means, the list is actually a set (no no duplicate elements allowed). On the other hand, Collections.sort()
cannot handle a set correctly.
Thus, we have to copy the list to another list or array, and sort it. Components.sort(List, Comparator) is a utility to simply the job.
Desktop, Page and Component
A page (Page) is a collections of components. It represents a portion of the browser window. Only components attached to a page are available at the client. They are removed when they are detached from a page.
A desktop (Desktop) is a collection of pages. It represents a browser window (or a tab or a frame of the browser)[1]. You might image a desktop represents an independent HTTP request.
A desktop is also the logic scope that an application can access in a request. Each time a request is sent from the client, it is associated with the desktop it belongs. The request is passed to DesktopCtrl.service(AuRequest, boolean), and then forwarded to ComponentCtrl.service(AuRequest, boolean). It also means, the application can not access components in multiple desktops at the same time.
Both a desktop and a page is created automatically when ZK Loader loads a ZUML page or calls a richlet (Richlet.service(Page)). The second page is created when the Include component includes another page with the defer mode. For example, two pages will be created if the following is visited.
<!-- the main page -->
<window>
<include src="another.zul" mode="defer"/> <!-- creates another page -->
</window>
Notice, if the mode is not specified (i.e., the instant mode), Include won't create a new page. Rather, it append all components created by another.zul
as its own child components. For example,
<window>
<include src="another.zul"/> <!-- default: instant mode -->
</window>
is equivalent to the following (except div is not a space owner, see below)
<window>
<div>
<zscript>
execution.createComponents("another.zul", self, null);
</zscript>
</div>
</window>
- ↑ Under portal environment, there might be multiple desktops in one browser window. However, it is really important in the developer's viewpoint.
Attach Component to Page
A component is available at the client only if it is attached to a page. For example, the window created below will not be available at the client.
Window win = new Window();
win.appendChild(new Label("foo"));
A component is a POJO object. If you don't have any reference to it, it will be recycled when JVM starts garbage collection.
There are two ways to attach a component to a page:
- Append it as a child of another component that is already attached to a page (Component.appendChild(Component), Component.insertBefore(Component, Component), or Component.setParent(Component)).
- Invoke Component.setPage(Page) to attach it to a page directly. It is also the way to make a component become a root component.
Since a component can have at most one parent and be attached at most one page, it is detached automatically from the previous parent or page when it is attached to another component or page. For example, b
will be a child of win2
and win1
has no child at the end.
Window win1 = new Window;
Button b = new Button();
win1.appendChild(b);
win2.appendChild(b); //implies detach b from win1
Detach Component from Page
To detach from the page, you could invoke comp.setParent(null)
, if not a root component, or comp.setPage(null)
, if a root component. Component.detach() is a shortcut to detach a component without knowing if it is a root component.
Invalidate
When a component is attached to a page, the component and all of its descendants will be rendered. On the other hand, when a state of a attached component is changed, only the changed state is sent to client for update (for better performance). Though rare, you could invoke Component.invalidate() to force the component and its descendants to be rerendered[1].
There are only a few reasons to invalidate a component, but worth to note:
- If you are adding a lot of child components, say, more than 20, the performance is better if you invalidate the parent. Though the result Ajax response might be larger, the browser is more effective to replace a DOM tree than adding DOM elements.
- If a component has a bug that an update does not update the DOM tree correctly, you could invalidate its parent, which usually could resolve the problem[2].
Don't Cache Components Attached to Page in Static Fields
As described above, a desktop is a logical scope that the application can access when serving a request. In other words, the application cannot detach a component from one desktop from another desktop. It typically happens when you cache a component accidentally. For example, the following code will cause an exception if the URL is loaded multiple times.
package foo;
import org.zkoss.zk.ui.*;
import org.zkoss.zul.*;
public class Foo implements org.zkoss.zk.ui.util.Composer {
private static Window main; //WRONG! don't cache it in a static field
public void doAfterCompose(Component comp) {
if (main == null)
main = new Window();
comp.appendChild(main);
}
}
The exception is similar to the following:
org.zkoss.zk.ui.UiException: The parent and child must be in the same desktop: <Window u1EP0>
org.zkoss.zk.ui.AbstractComponent.checkParentChild(AbstractComponent.java:1057)
org.zkoss.zk.ui.AbstractComponent.insertBefore(AbstractComponent.java:1074)
org.zkoss.zul.Window.insertBefore(Window.java:833)
org.zkoss.zk.ui.AbstractComponent.appendChild(AbstractComponent.java:1232)
foo.Foo.doAfterCompose(Foo.java:10)
ID Space
It is common to decompose a visual presentation into several subsets or ZUML pages. For example, a page for displaying a purchase order, and a modal dialog for entering the payment term. If all components are uniquely identifiable in the same desktop, developers have to maintain the uniqueness of all identifiers for all pages that might created to the same desktop. It is tedious, if not impossible, for a sophisticated application.
The concept of ID space is hence introduced to resolve this issue. An ID space is a subset of components of a desktop. The uniqueness is guaranteed only in the scope of an ID space. Thus, developers could maintain the subset of components separately without worrying any conflict with other subsets.
The simplest form of an ID space is a window (Window ). All descendant components of a window (including the window itself) forms an independent ID space. Thus, you could use a window as the topmost component to group components, such that developers need only to maintain the uniqueness of each subset separately.
By and large, any component could form an ID space as long as it implements IdSpace, and the component is called the space owner of the ID space it forms. Components in the same ID space are called fellows.
A page also implements IdSpace, so it is also a space owner. Besides window and page, the macro component and the include component (Include) are all space owners.
You could make a standard component as a space owner by extending it to implement IdSpace. There is no method need to implement at all. For example,
public class IdGrid extends Grid implements IdSpace {
//no method implementation required
}
Tree of ID Space
If an ID space has a child ID space, the components of the child space are not part of the parent ID space, except the space owner of the child ID space. For example, if an ID space, say X, is a descendant of another ID space, say Y, then space X's owner is part of space Y. However, descendants of X is not part of space Y.
For example, the following ZUML page
<?page id="P"?>
<zk>
<window id="A">
<hbox id="B">
<button id="D" />
</hbox>
<window id="C">
<button id="E" />
</window>
</window>
<hbox id="F">
<button id="G" />
</hbox>
</zk>
will form ID spaces as follows:
As depicted in the figure, there are three spaces: P, A and C. Space P includes P, A, F and G. Space A includes A, B, C and D. Space C includes C and E.
Components in the same ID spaces are called fellows. For example, A, B, C and D are fellows of the same ID space.
getFellow and getSpaceOwner
The owner of an ID space could be retrieved by Component.getSpaceOwner(). Furthermore, any component in an ID space could be retrieved by Component.getFellow(String), if it is assigned with an ID (Component.setId(String)).
Notice that the getFellow method can be invoked against any components in the same ID space, not just the space owner. Similarly, the getSpaceOwner method returns the same object for any components in the same ID space, no matter it is the space owner or not. In the example above, if C calls getSpaceOwner will get C itself, if C calls getSpaceOwnerOfParent will get A.
Composer and Fellow Auto-wiring
With ZK Developer's Reference/MVC, you generally don't need to look up fellows manually. Rather, they could be wired automatically by use of the auto-wiring feature of a composer. For example,
public class MyComposer extends GenericForwardComposer {
private Textbox input; //will be wired automatically if there is a fellow named input
public void onOK() {
Messsagebox.show("You entered " + input.getValue());
}
public void onCancel() {
input.setValue("");
}
}
Then, you could associate this composer to a component by specifying the apply attribute as shown below.
<window apply="MyComposer">
<textbox id="input"/>
</window>
Once the ZUML document above is rendered, an instance of MyComposer will be instantiated, and the input
member will be initialized with the fellow called input
. This process is called "auto-wiring". For more information, please refer to the Wire Variables section.
Path
ZK provides a utility class called Path class to simplify the location of a component among ID spaces. The way of using it is similar to java.io.File. For example,
//Two different ways to get the same component E
Path.getComponent("/A/C/E");//if call Path.getComponent under the same page.
new Path("/A/C", "E").getComponent(); //the same as new Path("/A/C/E").getComponent()
If a component belongs to another page, then we can retrieve it by starting with the page's ID. Notice that double slashes have to be specified in front of the page's ID.
Path.getComponent("//P/A/C/E");//for page, you have to use // as prefix
Notice that the page's ID can be assigned by use of the page directive as follows.
<?page id="foo"?>
<window/>
Version History
Last Update : 2010/11/12
Version | Date | Content |
---|---|---|