Events

In SiteFusion, events are the driving force. Events signify anything that happens and might require a reaction. There are two kinds of events: application events and global events. An application event is fired by a node, and exists only within the application containing the node. A global event however is fired by an application or service and distributed by the SiteFusion daemon to every running application and service process within the same application group. Before getting into global events, we'll first describe the function and use of application events.

Application events

Recall that a node consists of a client part and a server part. Both parts can fire events. When the client part fires an event it is usually the result of a user action, like clicking a button. Events are distinguished by their name. Most events triggered on the client side are standard XUL events. For instance, many XUL controls fire the 'command' event when they are activated by the user. When such an event is triggered, an event message can be generated and sent to the server part. Notifying the server part of an event fired on the client part is not always necessary or desirable, because it requires network communication. We don't want the client to tell the server about the 'mouseover' event indicating that the mouse is moving over some node in the application, or about keys being pressed in a textbox, unless we intend to do something with that information right away. We could also want to know about something happening, but only as soon as something else happens. When for instance the user has to fill out a dialog with ten textboxes, we could have the client send every keystroke in every textbox, have it send the contents of every textbox as soon as the user moves to the next, or have it send the contents of all ten at once when the user clicks the OK button in the dialog. All of these options can be achieved in SiteFusion, but which one is most desirable depends on the type of functionality we want to provide to the user. In most cases however, sending everything at once when the dialog is accepted is the most efficient and practical option.

The transfer of the contents or state of a control to its server part is done through the 'yield' event. Every node that is able to contain data (like a textbox) or have a state (like a checkbox) has a client-side yield() function. When called, it triggers a yield event carrying the data to be transferred. The resulting event message can then be sent to the server and picked up by the server part of the node, making the data available to the application.

We usually don't want the client to send the yield event of every data-containing node until we need it, but we do want it to send the command event of a button immediately. In order to facilitate these requirements, events always have a message type associated with them. There are three message types, represented by these constants:

MSG_NONE: the event does not generate any message and thus the server is not notified. This is the default for most events.

MSG_QUEUE: the event message is appended to the client-side queue waiting to be sent. This is the default for yield events.

MSG_SEND: the event message is also appended to the queue, but immediately triggers a queue flush sending everything in the queue to the server. This is the default for some events that will almost always require a server reaction, like the 'command' event of a button or the 'initialized' and 'close' events of a window.

When event messages are received by the server, they are processed in the order they were received in, by a first-in first-out principle. That means that messages of type MSG_QUEUE (i.e. the yield event) are always processed before the message of type MSG_SEND that triggered the sending. This has the practical implication that the server part of the nodes receiving the yield events will have processed the accompanying data and made it available to the application before any handlers for the MSG_SEND event are executed.

The following example application class implements the option where the textboxes get yielded when the button is pressed:

This variation implements the option where the textboxes are yielded when the user jumps to the next. (Note that the alert() call has been commented out, because you will end up in an endless loop because the alert prompt will continue to have the textboxes loose focus. Replace the alert for something more useful in a realistic use situation):

Multiple handlers can be attached to one event. They will be executed in
the order in which they were set with Node::setEventHandler()
or Node::setEvent(),
and can be removed with Node::removeEventHandler(). Event handlers always receive the Event object as the first parameter. If the event was fired with arguments that carry its data (like the yield event), these arguments are supplied to handlers as additional parameters. An event can be cancelled by a handler by setting the Event::$cancel property on the event object to TRUE. The event object always contains a reference to the node it originates from in the Event::$sourceObject property, and the name of the event in the Event::$name property.

Events can also be triggered without input from the user. There are two
methods for this. Node::fireClientEvent()
fires an event on the client side which, depending on the associated
message type, may arrive at the server again. An actual user-generated
client event and an event fired with Node::fireClientEvent()
are in fact indistinguishable to the application. Also, only valid
client events (that is, all XUL events and
some SiteFusion specific additions) and custom client events created with Node::createClientEvent() method can be fired. Node::fireLocalEvent()
fires an event on the server side, which will never reach the client
side. These local events are not bound to the predefined set of event names, and don't have to be created before they can be fired. Also, local events can be fired on unregistered nodes, allowing the use of simple Node objects as triggers for state synchonisation across an application. There are a few local events that nodes fires at times you might want to start some action.

onBeforeAttach: fires right before the node becomes registered and 'attached' to the node tree.

onAfterAttach: fires right after the nodes becomes attached.

onBeforeDetach: fires right before the node is detached from the node tree with the extractChild or removeChild functions.

onAfterDetach: fires right after the node becomes detached.

These are useful events when writing a class extending the Node class. Certain node methods require that the node is registered. For example the Node::callMethod() method, which calls a JavaScript method on the client part of the node, can only work if the node already has a client part. In an extending class you may want to automatically do things right after the node becomes registered. The onAfterAttach event is useful for that:

Running this example application you will see the alerts 'This is before attaching' followed by 'I'm a textbox' after pressing the button.

Reflexes

In some cases the required response to a client event is so simple that asking the server about it is really not necessary or even desirable. When for example hovering a box should color it red, it would be pointless to contact the server each time the mouse hovers the box. This is where reflexes come in.

Reflexes can be set with Node::setEventReflex() and removed with Node::removeEventReflex(), and are basically pieces of JavaScript that are sent to the client
beforehand and stored there. When the event fires, the reflex code is
executed in method of the client-side SiteFusion node JavaScript object (which can be referred to as this). This object contains a property element which is a reference to the XUL element associated with the node. The executing context offers several local variables:

eventObject: the JavaScript event object, or undefined if not a XUL event

eventArguments: array of argument data, can be modified before sending it to the server

eventName: the name of the event

for example by adding event arguments, you can supply custom clientside data to the event arguments received by the event handlers on the server side. This custom textbox sends its vertical scroll position along with the yield event and restores it when it is detached and reattached:

In this example, the method scrollPos is a handler for both the 'onAfterAttach' and the 'yield' events.

Global events

Apart from in-application events, applications themselves can trigger and react to global events. This kind of event is fired by an application or service, and then distributed by the daemon to all running application and service processes within the same application group. These events can be used to synchonize changes among interrelated processes, for instance to propagate changes in a datastructure so that all processes operating on it are notified of the changes others make.

Global events behave the same as application events. Their name identifies them and they carry an arbitrary amount of arguments containing any kind of data (except resources). Be careful with passing nodes through global event arguments though, as these arguments are serialized before transfer, registered nodes or any object containing references to registered nodes will cause the entire application to be serialized and sent with the event to the other processes. With application events passing registered nodes in event arguments is not a problem because the registry takes care of the translation.

The following example is MiniChat, a tiny chat application that allows anyone starting it to send messages to each other through global events.