UIZE JavaScript Framework

GUIDES Javascript Event System

1. Introduction

The JavaScript language does not provide a built-in system for event-driven programming, so the UIZE JavaScript Framework provides an event system.

The UIZE JavaScript Framework provides a robust event system that enables applications to utilize custom events - disctinct from the system of DOM events that is provided by the Web browser context, allowing applications to be built around the principles of event-driven programming.

1.1. JavaScript Has No Events

Technically speaking, the JavaScript language does not provide a built-in event mechanism to facilitate event based programming and the wiring of JavaScript object instances together through events.

The events that we may think of as being JavaScript events are, in fact, browser events (or DOM events) available to JavaScript code that runs inside the browser. So, the events (and the event mechanism) available to JavaScript code depend on the host / context for the language.

To complement the JavaScript language, UIZE provides a JavaScript event system that can be used by all JavaScript classes built on this framework, and any JavaScript applications that use classes built on the framework. This event mechanism operates in any context in which your JavaScript code executes - whether it be a Web browser or Windows Script Host.

1.2. DOM events vs. UIZE events

In contrast to UIZE events, DOM events are events that originate from HTML elements / DOM nodes in a document.

DOM events are unique to the Web browser environment, whereas UIZE events are supported in any environment that is supported by the UIZE JavaScript Framework, such as Microsoft's Windows Script Host and Adobe's ExtendScript environments. For more information on dealing with DOM events in the context of the UIZE JavaScript Framework, consult the guide JavaScript DOM Events.

1.3. All Classes are Invited to the Party

The UIZE JavaScript Framework provides an event mechanism that is available for all classes created using the framework.

The event mechanism is implemented in the Uize.Class base class, so any class that derives - however indirectly - from this base class can leverage this mechanismm. This, of course, also applies to widgets, since widget classes derive from the Uize.Widget base class, which derives from the Uize.Class base class.

1.4. It's Easy

Events can easily be fired by any instance of any class created in the UIZE JavaScript Framework - at any arbitrary place in its code - by simply using the fire instance method, as follows...

myInstance.fire ('My Custom Event Name');

Code can wire event handlers for events by using the wire method, as follows...

myInstance.wire (
  'My Custom Event Name',
  function () {/* do something here */}
);

1.5. Static Events and Instance Events

The Uize.Class base class provides a convenient infrastructure for supporting both static (ie. class) and instance events.

Events can conveniently be fired for a class or an instance of a class, and methods are provided to every class that subclasses the Uize.Class base class to allow code to manage the wiring and unwiring of event handlers at both the instance and class levels.

The syntax for wiring handlers on static events is identical to wiring handlers on instance events, except that the method is called on the class rather than an instance.

EXAMPLE

MyClass.wire (
  'My Static Event',
  function () {
    // do something
  }
);

1.6. Fire at Will

Events are fired, using the fire instance and static methods.

They can be fired from anywhere in your code, even from deep within nested closures, so you can provide further hooks as needed without having to radically restructure your code around hook points.

2. The Event Object

When an event is fired, all handlers wired for that event are called sequentially - in the order in which they were wired.

Each handler function can expect to receive a single parameter, being an object that represents the event being fired. The event object contains properties that describe the event, including some standard properties that are set by the event mechanism itself, and any arbitrary custom properties set by the code firing the event.

2.1. Standard Event Properties

The event mechanism supports standard properties of the event object that are set by the event mechanism itself, such as...

PROPERTIES

name - a string, representing the name of the event being fired
source - an object reference, representing the instance or class that originated the event
bubble - a boolean, specifying whether or not the event should bubble up the parent chain of a widget tree (only application to widget instances)

EXAMPLE

myInstance.fire ('My Custom Event Name');

For a handler that is wired for the above event, the event object will have the value 'My Custom Event Name' for its name property, and a reference to the instance myInstance for its source property. The event object is passed as the single parameter to wired handlers when they are called, so the following handler function would alert the value true, for example...

myInstance.wire (
  'My Custom Event Name',
  function (_event) {
    alert (_event.name == 'My Custom Event Name' && _event.source == myInstance);
  }
);

Not really a useful handler, but it illustrates the point.

2.2. Custom Event Properties

In addition to the standard event properties, your code can add any aribitrary properties to the event object when firing an event.

This is done by using the form of the fire method that takes an event object as a parameter. To extend on the previous example, we could add a custom property to the event object for the 'My Custom Event Name' event, as follows...

EXAMPLE

myInstance.fire ({name:'My Custom Event Name',customProperty:'some value'});

Now, the handlers for this event will be able to access the customProperty property on the event object in the same way as the standard name and source properties (as shown in the previous example). When using the above form of the fire method, you must specify the name of the event you're firing by using the name property of the event object.

3. Relaying Events

Because any handler for an event receives a reference to the event object, and because the fire method can take a reference to an event object, it is possible to "relay" an event from one instance to another.

3.1. Manually Relaying an Event

You can relay an event to another instance quite easily by just calling the fire instance on that other instance, and passing it a reference to the event object to be relayed.

EXAMPLE

myWidget.wire (
  'Some Crazy Event',
  function (_event) {myWidget.parent.fire (_event)}
);

In the above example, whenever 'Some Crazy Event' is fired on the myWidget instance, the wired handler will relay it to its parent by calling the fire method on its parent and passing the event object.

3.2. Dedicated Event Relay Facility

In addition to being able to manually relay events using the fire instance method and the Uize.Class.fire static method, the UIZE JavaScript Framework provides a dedicated facility for relaying events.

To relay an event to a Uize.Class subclass or an instance of a Uize.Class subclass, simple specify a reference to the class or instance as the handler for the event you wish to relay, as shown in the example below.

INSTEAD OF...

myWidget.wire (
  'Some Crazy Event',
  function (_event) {myWidget.parent.fire (_event)}
);

USE...

myWidget.wire ('Some Crazy Event',myWidget.parent);

So, why use the manual approach when there's this dedicated facility? Well, the manual approach could come in handy if you wish to modify the event object before relaying it, or if you wish to perform other actions before the event is relayed.

3.3. Knowing the Source of Relayed Events

When passing an event object reference to the fire method, a previously set value for the source property is not overwritten.

This has the implication that subsequent handlers receiving a relayed event can always know the originating source, since events are relayed by firing the event to be relayed on the target, and the event being relayed already has a value set for the source property.

EXAMPLE

var
  instance1 = Uize.Class (),
  instance2 = Uize.Class ()
;
instance1.wire ('Some Crazy Event',instance2);
instance2.wire (
  'Some Crazy Event',
  function (eventObj) {
    alert (eventObj.source == instance1); // alerts the text "true"
  }
);
instance1.fire ('Some Crazy Event');

3.4. Event Bubbling

Another built-in facility for relaying events is event bubbling.

Event bubbling allows events originating from widgets (instances of a Uize.Widget subclass) to bubble up the parent chain, until the root of the widget tree is reached, or the event is handled and bubbling is canceled. Bubbling is both activated and canceled by using the bubble property of the event object. Consider the following example...

EXAMPLE

var
  widget = Uize.Widget (),
  childWidget = widget.addChild ('child',Uize.Widget),
  grandchildWidget = childWidget.addChild ('grandchild',Uize.Widget)
;
widget.wire (
  'Some Crazy Event',
  function () {alert ('HELLO?')} // doesn't get executed, because bubbling is canceled
);
childWidget.wire (
  'Some Crazy Event',
  function (eventObj) {
    alert (eventObj.source.get ('name')); // alerts the text "grandchild"
    eventObj.bubble = false;
  }
);
grandchildWidget.fire ({name:'Some Crazy Event',bubble:true});

In the above example, three plain vanilla widget instances are created (instances of the Uize.Widget base class). The first instance (widget) serves as the root of a widget tree, where the second instance (childWidget) is a child, and the third instance (grandchildWidget) is a grandchild. Then, a handler is wired for the Some Crazy Event instance event on the root widget, and a handler is also wired for this event on the child widget.

The fire instance method is called on the grandchild widget in order to fire the event Some Crazy Event. The object form is used when specifying the event to fire, so that the value true can be specified for the bubble property. This causes the event to bubble up to grandchild's parent widget (childWidget in this case), where it is handled by the event handler that was wired on childWidget. This handler alerts the value of the name state property for the originating source of the event, which is obtained by using the source property of the event object. This causes the text 'grandchild' to be displayed in the alert dialog.

Finally, further bubbling of the event is canceled by setting the bubble property of the event object to false. As a result, the handler wired for this event on the root widget never gets executed.