To notify what happens at the client, the view has to send an AU request to the server. At the server the AU request is encapulated as an instance of the org.zkoss.zk.au.AuRequest class. At the client side, the AU request is an object with uuid, cmd, data and other properties. For example,
{uuid: cmp.id, cmd: "onClick", data: [10, 20], ctrl: true}
The following is a list of the properties that an AU request might have.
|
Property |
Description |
|---|---|
|
uuid |
[Optional but exactly one of uuid and dtid must be specified] The UUID of the component that shall receive this request. It is also known as the targeted component. |
|
dtid |
[Optional but exactly one of uuid and dtid must be specified] The desktop ID that shall receive this request. |
|
cmd |
[Required] The command ID, such as onClick and onSelect. It is used to search for the command. ZK Update Engine first invokes the getCommand method of the targeted component to look for the component-specific command. If not found, it then invokes the static getCommand method of the AuRequest class to look for the global command. |
|
data |
[Optional; Default: null] An array of data to pass to the server. It depends on the command. For example, the onClick command accepts a two-element array to indicate the position of the mouse pointer. |
|
ctl |
[Optional; Default: false] Whether the request is a control command, such as onClick, onOK and so on. To avoid re-execute the long operation twice when an impatient user clicks a button repeatedly, ZK ignores the second control command if two consecutive control commands target the same component and the first one is still in execution. If any doubt, don't specify it (with true). |
|
ignorable |
[Optional, Default: false] Whether the request can be ignored if the system is busy or it causes an error. Typical example is the onChanging and onScrolling commands. It is too annoying if not to set ignorable to true. |
zkau.sendasap and zkau.send are two of most common methods to send an AU request to the server.
|
zkau.send(evt, timeout) Sends an AU request after the specified milliseconds expires. If there are any pending AU requests, this request will be appended to the end. evt The AU request to send. timeout The delay before sending the request (unit: milliseconds). If not specified, it is delayed for 0 milliseconds (i.e., calling setTimeout with 0). If a negative value is specified, it means it is a deferrable command and it won't be sent back to the server until another invocation of zkau.send with a non-negative timeout. To have better performance, it is better to specify -1 if no non-deferrable event listener is registered for this command. It is easy to do this by use of zkau.asapTimeout as shown below. zkau.send({uuid: cmp.id, cmd: "onChange", data: [cmp.value]},
zkau.asapTimeout(cmp, "onChange"));
|
|
zkau.sendasap(evt, timeout) Sends an AU request by checking if there is a non-deferrable event listener registered for it. It is a shortcut of the following. zkau.send(evt, zkau.asapTimeout(evt.uuid, evt.cmd, timeout)); evt The AU request to send. timeout The delay before sending the request (unit: milliseconds) if non-deferrable. Notice that it is the timeout used only if there is a non-deferrable event listener registered for it. |
|
zkau.sendAhead(evt, timeout) Sends an AU request by placing it in front of any other pending AU requests. evt The AU request to send. timeout The delay before sending the request (unit: milliseconds). If not specified, it is delayed for 0 milliseconds (i.e., calling setTimeout with 0). If a negative value is specified, it means it is a deferrable command and it won't be sent back to the server until another invocation of zkau.send with a non-negative timeout. |
There are two callbacks you can register to do something before sending AU requests, and after receiving the responses.
|
zkau.addOnSend(func);zkau.removeOnSend(func); Register a callback function that will be called before AU requests are sending to the server. It is useful if you are using timer or other mechanism to monitor the DOM element's value. FCKeditor is a typical example. By using the onSend callback, we can check the value again and send back the correct value if changed with other AU requests. zkau.addOnSend(function() {
//check any change and call zkau.sendAhead to carry the change back
});
|
|
zkau.addOnResponse(script) Register a callback function that will be called after processing all responses. script It could be a function or a string containing JavaScript codes. |
Here is a list of methods to simply the sending of particular AU requests, such as onClose. They are all based on zkau.send.
|
zkau.sendRemove(uuid) Sends the remove command to remove the specified component (at the server). |
|
zkau.sendOnMove Sends the onMove command to denote a component is moved. |
|
zkau.sendOnSize Sends the onSize command to denote a component's size is changed. |
|
zkau.sendOnZIndex Sends the onZIndex command to denote the Z-index of a component is changed. |
|
zkau.sendOnClose Sends the onClose command that causes the onClose event being sent to the component. How the onClose event is handled depends on the component. For window, it is detached. |
When an AU request arrives at the server, it is packed as an instance of org.zkoss.zk.au.AuRequest by associating with a command (org.zkoss.zk.au.Command). The command is responsible to process the associated request. ZK Update Engine doesn't assume anything but invoking the command's process method. Up to the component developer, the command might then update the component's state, post an event or others when process is invoked.
There are two kinds of commands: global commands and component-specific commands. The global commands are available to all kind of components, while component-specific commands are available only to a particular kind of components. More precisely, global commands are maintained in a global map, while component-specific commands is maintained in an individual component.
ZK takes two steps to look for the command for an AU request:
ZK first check whether any component-specific command is defined for the command ID of the AU request. This is done by invoking the getCommand method of the ComponentCtrl interface.
Then, ZK check whether any global command is defined for the command ID of the AU request, if no component-specific command is found. This is done by invoking the static method called getCommand of the AuRequest class.
There are a lot of built-in AU responses such as InputCommand, MouseCommand, KeyCommand and so on in the org.zkoss.zk.au.in package. All of them are global commands. In other words, they are available to all components.
To minimize the development effort, use the built-in commands if possible.
If none of built-in command fulfills your requirement, you can develop your own command.
First, you have to decide if the command is available to two or more different kinds of components. If it is unique to particular kind of components, you shall develop a component-specific command:
Extend from ComponentCommand and implement the process method.
Return the command when the getCommand method of the component is called.
For example,
public class SuperButton extends AbstractComponent {
private static Command _flycmd = new ComponentCommand("onFly", 0) {
protected void process(AuRequest request) {
final SuperButton btn = (SuperButton)request.getComponent();
btn.doFly();
Events.postEvent(new Event(getId(), btn, request.getData()));}
}; //note: it won't register itself to the global map
//because it is extended from ComponentCommand)
public Command getCommand(String cmdId) {
return "fly".equals(cmdId) ? _flycmd: null;
}
void doFly() {
//whatever
}
}
If the command is available to two or more other kinds of components, you might consider to implement a global command[13]. Since it is available to all components, make sure the naming doesn't conflict with others.
A global command is extended from the org.zkoss.zk.au.Command class.
class FlyCommand extends Command {
public FlyCommand(String id, int flags) {
super(id, flags);
}
protected process(AuRequest request) {
final SuperButton btn = (SuperButton)request.getComponent();
btn.doFly();
Events.postEvent(new Event(getId(), btn, request.getData()));
}
}
Since it is global, you have to instantiate an instance at startup. A typical usage is to declare a static instance of a class. For example,
public class SuperButton extends AbstractComponent {
static {
new FlyCommand("onFly", 0); //register itself automatically}
...
It is interesting to note you don't need to keep a reference to a global command. It is registered automatically as soon as it is instantiated.
Also notice you must instantiate them in a class that will be loaded – the component's class is always loaded (if it is registered in lang.xml or lang-addon.xml).
When implementing a command, you can call any method you want. However, ZK introduces a special method to encapsulate the implementation to be invisible to application developers. This method is called getExtraCtrl (in the ComponentCtrl interface) – aka., extra control.
public Object getExtraCtrl();
Note: You don't have to follow this. First, it is simply a design pattern we use to implement components. Second, command related methods can be encapsulated well if you use only component-specific commands.
Here is how it use. Let us assume you don't want doFly to be accessed by applications. The easiest way is to declare it as private or protected and then implement it as a component-specific command as we did in the Develop Your Own Component-specific Command section.
However, what if FlyCommand has to be a global command? First, you can design an interface, say, Flyable.
public interface Flyable {
public void doFly();
}
Then, in the component implementation, we can do as follows.
public class SuperButton extends AbstractComponent {
public Object newExtraCtrl() {
return new ExtraCtrl();
}
private void doFly() { //make it inaccessible
//whatever
}
private class ExtraCtrl implements Flyable {
public void doFly() {
SuperButto.this.doFly();
}
}
...
where we override newExtraCtrl rather than getExtraCtrl, since getExtraCtrl handles the lifecycle of the extra control and will invoke newExtraCtrl to instantiate the object.
Finally, you can implement FlyCommand this way:
class FlyCommand extends Command {
public FlyCommand(String id, int flags) {
super(id, flags);
}
protected process(AuRequest request) {
final Component comp = request.getComponent();
if (comp instanceof Flyable)
((Flyable)comp).doFly();
Events.postEvent(new Event(getId(), comp, request.getData()));
}
}
Handling the smart updates sent from the server is straightforward. Just declare a callback called setAttr. For example, assume the component type is SuperButton, then the setAttr callback is as follows.
zkSuperButton.setAttr = function (cmp, name, value) {
if ("fly" == name) {
//whatever
}
return false;
}
The return value is whether you want the default behavior to take place, i.e., whether to update the attribute. More precisely, zkau.setAttr(cmp, name, value) will be called if false is returned.
If you prefer to update the attribute first, you can invoke zkau.setAttr manually. For example,
zkSuperButton.setAttr = function (cmp, name, value) {
if ("disabled" == name) {
zkau.setAttr(cmp, name, value); //update cmp.disabled first
//whatever
return true; //attribute has been updated
}
return false;
}
When implementing a sophisticated component, you might want to pass multiple values (i.e., an array of values) to an attribute. At the server side, you invoke the smartUpdateValues method. At the client, the setAttr callback will be called with additional arguments. For example, at the server, you invoke
smartUpdateValues("fly", new String[] {"low", "fast"});
Then, at the client, you can handle it as follows.
zkSuperButton.setAttr = function (cmp, name, value1, value2) {
if ("fly" == name) {
fly(value1, value2);
...
JavaScript Tip: You can use arguments.length to determine how many arguments are passed.
There are many built-in responses you can use (in the org.zkoss.zk.au.out package). However, the invoke (AuInvoke) and script (AuScript) responses are almost all you need.
The invoke response is used to invoke a callback. For example, you can send an AU response at the server as follows.
public void fly() {response("ctrl", new AuInvoke(this, "fly", "low", "fast"));}
Then, at the client, the following callback will be called.
zkSuperButton.fly = function (cmp, height, speed) {//whatever
}
Tip: As shown above, we can use a AU response and a smart update to do the similar task (fly in this example). But, there is a difference: the AU response is sent even if the component is invalidated, while the smart update won't.
Thus, if the init callback will handle everything, the smart update shall be used (otherwise, you will do it twice when it is invalidated).
The script response is used to execute any JavaScript codes at the client, not just the callbacks. For example,
public void fly() {
response("ctrl",
new AuScript(this, "zkSuperButton.fly('" + getUuid() + "','low','fast')"));
}
Since the JavaScript codes are hardcoded, it is sometimes not easy to support multiple molds. For example, assume you want to use the same Java class to implement another mold. Then, the JavaScript codes that implements fly might be different. So, you might have to do as follows.
public void fly() {
response("ctrl",
new AuScript(this,
("SuperButton".equals(getMold()) ? "zkSuperButton": "zkGiantButton")
+ ".fly('" + getUuid() + "','low','fast')"));
}
On the other hand, if you use the invoke response, you can have the same response but define a different component type, say GiantButton. Then, you don't modify Java class. Rather, provide another callback:
zkGiantButton.fly = function (cmp, height, speed) {
//whatever
}
In general, the invoke response is recommended if applicable.
Unlike the AU request's commands, you rarely need to develop your own AU response since the invoke response can serve almost everything. However, if you prefer to provide your own response, you can follow the instructions described in this section.
First, you have to implement a Java class extending from the org.zkoss.zk.au.AuResponse class.
public AuFly extends AuResponse {
public AuFly(SuperButton comp, String height, String speed) {
super("fly", comp, new String[] {comp.getUuid(), height, speed});
}
}
where "fly" is the response name, and the third argument (String[]) of AuResponse's constructor will be passed to the client. The response name must be unique. The third argument is called the response data.
Then, at the client side, you shall provide a method called zkau.cmd1.fly to handle it.
zkau.cmd1.fly = function (uuid, cmp, height, speed) {
//whatever
}
There are two set of response handlers: zkau.cmd0 and zkau.cmd1. zkau.cmd0 is used to handle responses that are applied to the whole browser window, while zkau.cmd1 is for individual component.
If zkau.cmd1 is overridden, the first element of the response data must be the component UUID (comp.getUuid() in the above example). Then, ZK Client Engine will convert it to a reference to a component, and then invoke the response's handler with UUID and the reference as the first two arguments (refer to zkau.cmd.fly in the above example).