Client-side UI Composing
Overview
A UI object visible to a user at the client is hosted by a JavaScript object[1] called a widget (Widget). On the other hand, a component is a Java object (Component) representing the UI object at the server that an application manipulates directly. Once a component is attached to a page, a widget is created at client automatically. Furthermore, any state change of the component at the server will be updated to the widget at the client.
Generally, you need not to know the existence of widgets. Ajax requests and the state synchronization are handled automatically by ZK and the components automatically. However, you are allowed to instantiate or alert any client-side widgets directly at the client (in JavaScript). It is the so-called Server+client fusion.
The rule of thumb is to compose and manipulate UI at the server first since it is easier. Then, you could reduce the load of the server by composing some UI at the client when it is appropriate. Notice that JavaScript is readable by any user, so be careful not to expose sensitive data or business logic when migrating some code from server to client.
Here we describe how to compose UI in JavaScript at the client. For client-side event handling, please refer to the Client-side Event Handling section. For XML-based UI composing at the client, please refer to the iZUML section.
- ↑ It actually depends on the device. For Ajax, it is a JavaScript object. For Android devices, it is a Java object.
Modify Widget's State at Client
While the states of a widget is maintained automatically if you update the corresponding component at the server, you could modify the widget state directly at the server. The modification is straightforward: call the correct method with the arguments you want. Notice that it is JavaScript for Ajax browsers.
var foo = zk.Widget.$('foo');
foo.setValue("What's Up?");
For a complete API available to the client-side fusion, please refer to JavaScript API.
Fusion with Server-side ZUML and Java
It is suggested that the client-side UI composing is better designed to minimize the network round-trip, provide effects and other enhancement, while the most, if not all, of the application is better to be done at the server. Thus, here we only discuss this kind of addon, aka., fusion. For pure-client approach, please refer to Small Talk: ZK 5.0 and Client-centric Approach.
Depending on your requirement, there are typically two situations we could fuse the client-side code:
- Register a client-side event listener.
- Override widget's default behavior
For example, suppose we want to open the drop down when a commbox gains the focus, then we register a client-side event listener for the onFocus event as follows.
<div>
<combobox xmlns:w="client" w:onFocus="this.open()"/>
</div>
As shown, we have to use the client namespace to indicate the onFocus attribute is for the client-side event listener. It is done by apply XML namespace:
- Add the
xmlns:w="client"
attribute - Prefix
w:
before onFocus
For more information about the client-side event listener, please refer to the Client-side Event Listening section.
The other typical situation to fuse the client-side code is to override the default behavior of a widget. We will discuss it later.
Identify Widget at Client
When the client event is invoked, you can reference the widget using this and the event using event. In the following example, this refers to the label.
<window xmlns:w="client">
<label value="change me by click" w:onClick="this.setValue('clicked');"/>
</window>
To retrieve a fellow[1], you could use Widget.$f(String). It works in a similar manner as Component.getFellow(String). For example,
this.$f('foo').setValue('found');
this.$().foo.setValue('found'); //equivalent to the above statement
If you don't have a widget as a reference, you could use Widget.$(Object, Map). Notice it assumes there is only one widget with the given ID in all ID spaces of the desktop. For example,
zk.Widget.$('foo').setValue('found');
In additions, you can use jQuery to select a DOM element of a widget[2]. For example jq("@window")
will select DOM elements of all window widget. And, jq("$win1")
will select DOM elements of all widgets whose ID is win1
. (see jq).
<window xmlns:w="http://www.zkoss.org/2005/zk/client">
<vbox>
<label id="labelone" value="click to change"
w:onClick="this.setValue('changed by click label');" />
<button label="button"
w:onClick="this.$f('labelone').setValue('changed by button');" />
<html><![CDATA[
<a href="javascript:;" onclick="zk.Widget.$(jq('$labelone')[0]).setValue('changed with jq');">not widget</a>
]]></html>
</vbox>
</window>
Instantiate Widget at Client
A widget has to be create to make a component visible at the client (once it has been attached to a page). However, you could instantiate a widget at client, without the corresponding component at the server. To extreme extent, you could create all widgets at client (of course, it is costly and less secure).
To instantiate a widget is similar to a widget, except we can pass all initial values into the constructor. For example,
new zul.wnd.Window({
title: 'Hello, World',
border: 'normal',
children: [
new zul.wgt.Label({value: 'Hi, '}),
new zul.wgt.Button({
label: 'Click Me!',
listeners: {
onClick: function (evt) {
alert('Hi, you clicked me');
}
}
})
]
});
As shown, the initial values can be passed as a map. In additions, the children
property could be used to specify an array of child widgets, and the listeners
property to specify a map of listeners.
In additions to instantiate widgets in JavaScript, you could use a markup language called iZUML. Please refer to the iZUML section for more information.
Attach Widget to DOM
Once a widget is instantiated, you could attach it to the browser's DOM tree to make it visible to users[1]. It can be done in one of two ways:
- Make it as a child of another widget that already being attached
- Replace or insert it to a DOM element
You could use Widget.appendChild(Widget) or Widget.insertBefore(Widget, Widget). For example,
<vlayout>
<button label="Click Me" xmlns:w="client"
w:onClick="this.parent.appendChild(new zul.wgt.Label({value: 'Clicked'}))"/>
</vlayout>
In additions, we could replace an existent DOM element with a widget (not attached yet). For example,
<zk>
<n:div id="anchor" xmlns:n="native"/>
<button label="Click Me" xmlns:w="client"
w:onClick="new zul.wgt.Label({value: 'Clicked'}).replaceHTML('#anchor')"/>
</zk>
where we use the native namespace to create a DOM element and then replace it with the label widgt.
- ↑ Notice that a widget is not visible to users unless it is attached to the browser's DOM tree.
When to Run Your JavaScript Code
ZK Client Engine loads a JavaScript package only when it is required. It minimizes the memory footprint at the client. However, it also mean that you cannot run your JavaScript code until the required packages have been loaded. It can be done by use of zk.load(String, Function). For example, suppose you're not sure if the zul.wnd
and zul.grid
package has been loaded, when you are going to instantiate Window and Grid, you could do as follows.
zk.load("zul.wnd,zul.grid", function () { //load zul.wnd and zul.grid if they aren't loaded yet
//In this function, you could access zul.wnd.Window and zul.grid.Grid whatever you want
new zul.wnd.Window({children: [new zul.grid.Grid()]});
});
where zk.load(String, Function) loads the zul.wnd
and zul.grid
packages and then invokes the function when they have been loaded.
Notice that there is another method for similar purpose called zk.aferLoad(String, Function). Unlike zk.load(String, Function), zk.afterLoad(String, Function) won't load the packages. Rather, it queues the given function and invokes it when the packages have been loaded. It is useful when you want to override the default behavior of a widget. We will discuss it later.
Override Widget's Default Behavior
There are many ways to override the default behavior of widgets and even ZK Client Engine. JavaScript is a dynamic language and you could override almost any method you want.
Override a Widget Method
For example, suppose we want to change the CSS style of a label when its value is changed, then we might have the code as follows.
<window xmlns:w="http://www.zkoss.org/2005/zk/client">
<label>
<attribute w:name="setValue">
function (value) {
this.$setValue(value); //call the original method
if (this.desktop) {
this._flag = !this._flag;
this.setStyle('background:'+(this._flag ? 'red':'green'));
}
}
</attribute>
</label>
</window>
where
- We specify client namespace to the
setValue
attribute to indicate it is the method to override - The content of the attribute is a complete function definition of the method, including
function ()
- You can access the widget by
this
in the function - You can access the original method by
this.$xxx
, where xxx is the method name being overridden. If the method doesn't exist, it is null. - To retrieve another widget, use
this.$f('anotherWidgetId')
or other methods as described in the previous section - You can specify EL expressions[1] in the content of the attribute, such as
w:setValue='function (value) { this.$setValue(value + "${whatever}")}';
Notice EL expressions are evaluated at the server before sent back to the client. Thus, you could any Java class or variables in EL expressions.
- ↑ EL expressions are allowed since ZK 5.0.2
Override a Default Widget Method
In previous section, we showed how to override method of a particular widget we declared. However, it only affects the behavior of a particular instance. If you want to modify the behavior of all instances of a widget class, you have to override the method in prototype
[1].
For example,
<window xmlns:w="http://www.zkoss.org/2005/zk/client">
<label id="labelone" value="label one"/>
<label id="labeltwo" value="label two"/>
<script defer="true">
var oldSV = zul.wgt.Label.prototype.setValue;
zul.wgt.Label.prototype.setValue = function (){
arguments[0]="modified prototype"+arguments[0];
oldSV.apply(this, arguments);
}
</script>
<button label="change" onClick="labelone.setValue((new Date()).toString());
labeltwo.setValue((new Date()).toString());"/>
</window>
where we assign a new method to zul.wgt.Label.prototype.setValue
. Since it is prototype
, the setValue method of all instances are modified.
- ↑ For more information about JavaScript's prototype, please refer to Using Prototype Property in JavaScript and JavaScript prototype Property
Override a Widget Field
You can override a method or a field no matter it exists or not. For example, it is easy to pass the application-specific data to the client, such as
<label value="hello" w:myval="'${param.foo}'"/>
Notice that the content of the attribute must be a valid JavaScript snippet. To specify a string (as shown above), you have to enclose it with ' or " if you want to pass a string. It also means you can pass anything, such as new Date()
.
Override a Widget Method in Java
In additions to ZUML, you could override a Widget method or field by use of Component.setWidgetMethod(String, String) at the server. For example,
label.setWidgetOverride("setValue",
"function (value) {this.$setValue('overloaded setValue');}");
Specify Your Own Widget Class
You could specify your own implementation instead of the default widget class (at the client) as follows.
<zk xmlns:w="http://www.zkoss.org/2005/zk/client">
...
<button w:use="foo.MyButton"/>
</zk>
where foo.MyButton
is a widget you implement. For example,
zk.afterLoad("zul.wgt", function () {
zk.$package("foo").MyButton = zk.$extends(zul.wgt.Button, {
setLabel: function (label) {
this.$supers("setLabel", arguments);
//do whatever you want
}
});
});
Notice that zk.afterLoad(String, Function) is used to defer the declaration of foo.MyButton
until zul.wgt
has been loaded.
Load Additional JavaScript Files
You could use Script, HTML SCRIPT tag or script to load additional JavaScript files. Please refer to [[1]] for more information.
The Client-Attribute Namespace
[since 5.0.3]
The [[ZUML Reference/ZUML/Namespaces/Client Attribute|client-attribute namespace (http://www.zkoss.org/2005/zk/client/attribute; shortcut, client/attribute
) is used to specify additional DOM attributes that are not generated by widgets. In other words, whatever attributes you specify with the client-attribute namespace will be generated directly to the browser's DOM tree. Whether it is meaningful, it is really up to the browser -- ZK does not handle or filter it at all.
For example, you want to listen to the onload
event, and then you can do as follows.
<iframe src="http://www.google.com" width="100%" height="300px"
xmlns:ca="client/attribute" ca:onload="do_whater_you_want()"/>
If the attribute contains colon or other special characters, you can use the attribute
element as follows.
<div xmlns:ca="http://www.zkoss.org/2005/zk/client/attribute">
<attribute ca:name="ns:whatever">
whatever_value_you_want
</attribute>
</div>
The other use of the client-attribute namespace is to specify attributes that are available only to certain browsers, such as accessibility and Section 508.
Version History
Last Update : 2010/12/1
Version | Date | Content |
---|---|---|