Event listening & processing

From Documentation

Jump to: navigation, search

Contents

Overview

This chapter describes how an event is processed.

Add Event Listeners by Markup Languages

The simplest way to add an event listener is to declare an attribute in a ZUML page. The value of the attribute for listening an event is any Java codes that could be interpreted by BeanShell.

<window title="Hello" border="normal">
    <button label="Say Hello" onClick="alert(&quot;Hello World!&quot;)"/>
</window>

Add and Remove Event Listeners by Program

There are two ways to add event listeners by program.

Declare a Member

When overriding a component by use of your own class, you could declare a member function to be an event listener as follows.

In a ZUML page, you declare the use attribute to specify what class you want to use instead of the default one. As illustrated below, it asks ZK to use the MyWindow class instead of org.zkoss.zul.Window[1].

<window use="MyWindow">
 ...
</window>

Then, you implement MyWindow.java by extending from the default class as follows.

 public class MyWindow extends org.zkoss.zul.Window {
     public void onOK() { //add an event listener
         ...//handles the onOK event (sent when ENTER is pressed)
     }
 }

If you want to retrieve more information about the event, you could declare as follows.

 public void onOK(org.zkoss.zk.ui.event.KeyEvent event) {
 ...
 }

or

 public void onOK(org.zkoss.zk.ui.event.Event event) {
 ...
 }

Different events might be associated with different event objects.

Add and Remove Event Listeners Dynamically

Developers could use the addEventListener and removeEventListener methods of the org.zkoss.zk.ui.Component interface to dynamically add or remove an event listener. As illustrated below, the event listener to be added dynamically must implement the org.zkoss.zk.ui.event.EventListener interface.

 void init(Component comp) {
     ...
     comp.addEventListener("onClick", new MyListener());
     ...
 }
 class MyListener implements org.zkoss.zk.ui.event.EventListener {
     public void onEvent(Event event) throws UiException {
         ...//processing the event
     }
 }

Deferrable Event Listeners

By default, events are sent to the server when it is fired at the client. However, many event listeners are just used to maintain the status at the server, rather than providing visual response to the user. In other words, the events for these listeners have no need to be sent immediately. Rather, they shall be sent at once to minimize the traffic between the client and the server, and then to improve the server's performance. For the sake of the description convenience, we call them the deferrable event listeners.

To make an event listener deferrable, you have to implement the org.zkoss.zk.ui.event.Deferrable interface (with EventListener) and return true for the isDeferrable method as follows.

 public class DeferrableListener implements EventListener, Deferrable {
     private boolean _modified;
     public void onEvent(Event event) {
         _modified = true;
     }
     public boolean isDeferrable() {
         return true;
     }
 }

When an event is fired at the client (e.g., the user selects a list item), ZK won't send the event if no event listener is registered for it or only deferrable listeners are registered. instead, the event is queued at the client.

On the hand, if at least one non-deferrable listener is registered, the event are sent immediately with all queued events to the server at once. No event is lost and the arriving order is preserved.

Tip: Use the deferrable listeners for maintaining the server status, while the non-deferrable listeners for providing the visual responses for the user.

Add and Remove Event Listeners to Pages Dynamically

Developers could add event listeners to a page (org.zkoss.zk.ui.Page) dynamically. Once added, all events of the specified name the are sent to any components of the specified page will be sent to the listener.

All page-level event listeners are non-ASAP. In other words, the isArap method is ignored.

A typical example is to use a page-level event listener to maintain the modification flag as follows.

 public class ModificationListener implements EventListener, Deferrable {
     private final Window _owner;
     private final Page _page;
     private boolean _modified;
 
     public ModificationListener(Window owner) {
         //Note: we have to remember the page because unregister might
         //be called after the owner is detached
         _owner = owner;
         _page = owner.getPage();
         _page.addEventListener("onChange", this);
         _page.addEventListener("onSelect", this);
         _page.addEventListener("onCheck", this);
     }
     /** Called to unregister the event listener.
      */
     public void unregister() {
         _page.removeEventListener("onChange", this);
         _page.removeEventListener("onSelect", this);
         _page.removeEventListener("onCheck", this);
     }
     /** Returns whether the modified flag is set.
      */
     public boolean isModified() {
         return _modified;
     }
     //-- EventListener --//
     public void onEvent(Event event) throws UiException {
         _modified = true;
     }
     //-- Deferrable --//
     public boolean isDeferrable() {
         return true;
     }
 }

Note: Whether to implement the Deferrable interface is optional in this example, because the page's event listeners are always assumed to be deferrable, no matter Deferrable is implemented or not.

The Invocation Sequence

The sequence of invoking event listeners is as follows. Let us assume the onClick event is received.

  1. Invoke event listeners for the onClick event one-by-one that are added to the targeting component, if the listeners also implement the org.zkoss.zk.ui.event.Express interface. The first added, the first called.
  2. Invoke the script specified in the onClick attribute of the targeting component, if any.
  3. Invoke event listeners for the onClick event one-by-one that are added to the targeting component, if the listeners don't implement the org.zkoss.zk.ui.event.Express interface. The first added, the first called.
  4. Invoke the onClick member method of the targeting component, if any.
  5. Invoke event listeners for the onClick event one-by-one that are added to the page that the targeting component belongs. The first added, the first called.

The org.zkoss.zk.ui.event.Express interface is a decorative interface used to alter the invocation priority of an event listener. Notice that it is meaningless if the event listener is added to pages, instead of components.

Abort the Invocation Sequence

You could abort the calling sequence by calling the stopPropagation method in the org.zkoss.zk.ui.event.Event class. Once one of the event listeners invokes this method, all following event listeners are ignored.

Send, Post and Echo Events from an Event Listener

In addition to receiving events, an application could communicate among event listeners by posting or sending events to them.

Post Events

By use of the postEvent method in the org.zkoss.zk.ui.event.Events class, the application could post an event to the end of the event queue. This method returns immediately after placing the event into the queue. The event will be processed later after all events preceding it have been processed.

Send Events

By use of the sendEvent method in the org.zkoss.zk.ui.event.Events class, the application could ask ZK to process the specified event immediately. This method won't return until all event listeners of the specified event has been processed. The event is processed at the same thread.

Echo Events

By use of the echoEvent method in the org.zkoss.zk.ui.event.Events class, the application could ask the client to echo back the event for processing later. This method returned immediately after queuing the response asking the client to echo back the event.

Notice that, unlike sendEvent and postEvent, the event won't be processed in the current execution. Rather, it is processed later after the client echoes back the event. In other words, the event is processed later after the client has updated its user interfaces. Thus, it is useful to prompt the user before starting a long operation.

For example, you can open a highlighted window and then invoke echoEvent to do the long operation after the client shows the window (and echoes back the event).

For example, we can use the org.zkoss.zk.ui.util.Clients.showBusy method to show the busy message such that the user knows the system is busy. So, the end user will see "Execute..." first and then, after two seconds, "Done." in the following example. If you use postEvent, the end user will see "Execute..." and "Done" at the same time after two seconds.

<window id="w" title="Test echoEvent">
    <attribute name="onLater">
     Thread.sleep(2000);
     Clients.showBusy(null, false);
     new Label("Done.").setParent(w);
    </attribute>
 
    <button label="echo">
    <attribute name="onClick">
     Clients.showBusy("Execute...", true);
     Events.echoEvent("onLater", w, null);
    </attribute>
    </button>
</window>

A smalltalk

Thread Model

For each desktop, events are processed sequentially, so thread model is simple. Like developing desktop applications, you don't need to worry about racing and multi-threading. All you need to do is to register an event listener and process the event when invoked.

Tip: Each event listener executes in an independent thread called event processing thread, while the ZUML page is evaluated in the servlet thread.

Tip: The use of the event processing thread can be disabled such that all events are processed in the Servlet threads. It has a little bit better performance and less integration issues. However, you can not suspend the execution. Refer to the Use the Servlet Thread to Process Events section in the Advanced Features chapter.

Suspend and Resume

For advanced applications, you might have to suspend an execution until some condition is satisfied. The wait, notify and notifyAll methods of the org.zkoss.zk.ui.Executions class are designed for such purpose.

When an event listener want to suspend itself, it could invoke wait. Another thread could then wake it up by use of notify or notifyAll, if the application-specific condition is satisfied. The modal dialog is an typical example of using this mechanism.

 public void doModal() throws InterruptedException {
 ...
     Executions.wait(_mutex); //suspend this thread, an event processing thread
 }
 public void endModal() {
 ...
     Executions.notify(_mutex); //resume the suspended event processing thread
 }

Their use is similar to the wait, notify and notifyAll methods of the java.lang.Object class. However, you cannot use the methods of java.lang.Object for suspending and resuming event listeners. Otherwise, all event processing will be stalled for the associated desktop.

Notice that, unlike Java Object's wait and notify, whether to use the synchronized block to enclose Executions' wait and notify is optional. In the above case, we don't have to, because no racing condition is possible. However, if there is an racing condition, you can use the synchronized block as what you do with Java Object's wait and notify.

 //Thread 1
 public void request() {
     ...
     synchronized (mutex) {
         ...//start another thread
         Executions.wait(mutex); //wait for its completion
     }
 }
 //Thread 2
 public void process() {
     ... //process it asynchronously
     synchronized (mutex) {
         Executions.notify(mutex);
     }
 }

Initialization and Cleanup of Event Processing Thread

Initialization Before Processing Each Event

An event listener is executed in an event processing thread. Sometimes, you have to initialize the thread before processing any event.

A typical example is to initialize the thread for the authentication. Some J2EE or Web containers store authentication information in the thread local storage, such that they could re-authenticate automatically when needed.

To initialize the event processing threads, you have to register a class, that implements the org.zkoss.zk.ui.event.EventThreadInit interface, to the listener element in the WEB-INF/zk.xml file[2].

Once registered, an instance of the specified class is constructed in the main thread (aka., the servlet thread), before starting an event processing thread. Then, the init method of the instance is called at the context of the event processing thread before doing anything else.

Notice that the constructor and the init method are invoked at different thread such that developers could retrieve thread-dependent data from one thread and pass to anther.

Here is an example for the authentication mechanism of JBoss[3]. In this example, we retrieve the information stored in the servlet thread in the constructor. Then, we initialize the event processing thread when the init method is called.

 import java.security.Principal;
 import org.jboss.security.SecurityAssociation;
 import org.zkoss.zk.ui.Component;
 import org.zkoss.zk.ui.event.Event;
 import org.zkoss.zk.ui.event.EventThreadInit;
 
 public class JBossEventThreadInit implements EventThreadInit {
     private final Principal _principal;
     private final Object _credential;
     /** Retrieve info at the constructor, which runs at the servlet thread. */
     public JBossEventThreadInit() {
         _principal = SecurityAssociation.getPrincipal();
         _credential = SecurityAssociation.getCredential();
     }
     //-- EventThreadInit --//
     /** Initial the event processing thread at this method. */
     public void init(Component comp, Event evt) {
         SecurityAssociation.setPrincipal(_principal);
         SecurityAssociation.setCredential(_credential);
     }
 }

Then, in WEB-INF/zk.xml, you have to specify as follows.

<zk>
    <listener>
        <listener-class>JBossEventThreadInit</listener-class>
    </listener>
</zk>

Cleanup After Processed Each Event

Similarly, you might have to clean up an event processing thread after it has processed an event.

A typical example is to close the transaction, if it is not closed properly.

To cleanup the event processing threads, you have to register a listener class, that implements the org.zkoss.zk.ui.event.EventThreadCleanup interface, to the listener element in the WEB-INF/zk.xml file.

<zk>
    <listener>
        <listener-class>my.MyEventThreadCleanup</listener-class>
    <listener>
</zk>