JSignal - A signal handling library

A loose Javascript port of the GLib signal system used by the GTK

Requires Javascript v1.5
Author: jpbarto (jpbarto.freeshell.org)

This page is meant as a temporary placeholder until I can get around to authoring a proper webpage. In the meantime if you like here is a link to JSignal's project page and here is a link to the library's JSDoc documentation.

Table of Contents

  1. About - Brief Introduction to JSignal
  2. Introduction - Information about JSignal
  3. About GTK - Brief 'what is it' regarding the GTK
  4. Terminology - Terms & Definitions
  5. Features - List of features implemented by JSignal
  6. Getting Started - Quickstart Guide for using JSignal
  7. Examples - Illustration of JSignal Usage

ABOUT

JSignal is a loose port of the signal system implemented by GTK's GLib. It uses signals and handlers to allow for the registration of callback functions to events.

To use JSignal those classes which will emit signals (cause events) must register the signals that each class will emit. Note that if a class extends another class (has a superclass) it will inherit any signals registered by the parent class and can emit those signals as well. In addition a new 'global'-type of signal can be registered which is class non-specific. Any class can emit a global signal.

After you have a class with registered signals you can begin connecting callback functions to those signals. Callback functions are connected (registered to listen for) to signals by specifying the signal and the object instance that will emit that signal; this registration results in the creation of a handler. Multiple handlers can be configured for the same signal on the same object instance. In addition to instance-specific signal handlers a handler can also be registered to execute when a global signal (created using j_signal_new_global) is emitted; again, regardless of emitting instance. Note that when a global signal is emitted by an instance those handlers that are registered globally (using j_signal_handler_connect_global) will be executed BEFORE any instance-specific handlers.

To emit a signal - thus causing an event - the class which registered the signal should use the j_signal_emit function and pass any additional information to the callback function via the event detail argument. This detail object should be an array of name => value pairs.

Illustrative examples are provided below.

Note: In order for signal inheritance to work properly it is recommended that a class's prototype be given a 'parent' member which points to the constructor of the class's superclass.

INTRODUCTION

JSignal is a Javascript port of the signaling system used by the GTK and implemented as part of the GTK's GLib library. (To learn more about the GTK see the section ABOUT GTK.

The signaling system - event system - as implemented by GLib allows for the customization of object behaviour and provides a general purpose mechanism for notification. The basic concept of the GLib signal system is the 'emission' of a signal which causes the creation and handling of an event. Signals are introduced - registered - on a per-class basis and are made available to derived types. This results in an inheritance model whereby a child class inherits the signals registered by its parent superclass.

All signal handlers are called and executed in the order in which they were registered or connected to the emitted signal. Thus the three callback functions connected by

  j_signal_handler_connect (obj, "signal_name", signal_cb01);
  j_signal_handler_connect (obj, "signal_name", signal_cb03);
  j_signal_handler_connect (obj, "signal_name", signal_cb02);
will be executed in the order signal_cb01, signal_cb03, signal_cb02. The emission of a signal can be cancelled at any time by any executed handler that returns a value of boolean false.

In addition to cancelling signal emission by function return value, handlers can also be blocked, unblocked, and disconnected during signal emission. Placing a block on an individual handler will prevent the handler from firing during the current signal emission and future emissions until the handler has been unblocked. In addition the blocks against a handler are cumulative such that the handler with ID 23 in the following code block:

  j_signal_handler_block (obj, 23);
  j_signal_handler_block (obj, 23);
  j_signal_handler_unblock (obj, 23);
will still be blocked until the j_signal_handler_unblock has been called a second time. A handler has to get unblocked exactly the same amount of times it has been blocked before.

Some features of the signal system defined by GLib have not been - yet? - implemented in JSignal. These features are as follows:

Such features may be implemented in the future but for my immediate purposes they are not necessary and will be implemented on an as-needed basis. If you require one of the following to be implemented you can either contact me to make the request or you can make the change yourself - just please submit the changes to me for incorporation.

The rest of this document attempts to further illustrate the features implemented by JSignal and provide you with the information you need to get you started using the JSignal library.

ABOUT GTK

GTK (GIMP Toolkit) is a software library which provides users a widget set for the development of GUI applications. Originally written in C to support the development of the GNU Image Manipulation Program (GIMP) the GTK has since enjoyed widespread use in multiple languages, on multiple platforms, and in multiple applications.

The GTK as is typically referred to actually refers to a set of three libraries: the GTK, the GDK, and GLib. The first is the highly abstracted set of windowing objects (such as GtkButton, GtkWindow, and GtkLabel). The GDK library is an abstraction & wrapper library for window system calls. The GDK allows the GTK to operate in various windowing environments such as the X window system (XFree86), MS Windows, wxWindows, and V. And finally the GLib library provides common system functions to the GTK and GDK libraries. Again to provide both a layer of abstraction and also to fill in for missing system calls when those calls are not available on a ported platform.

The GTK has been released (as of this writing it is at version 2.8) and is a mature toolkit. It has bindings to many programming languages such as C++, Perl, Python, Ada95, and Java. In addition to the GIMP the GTK has been used to implement many software packages such as GNU Network Object Model Environment (better known as the GNOME Desktop Manager). The GTK has proven itself to be a mature and capable software library for the development of GUI applications using various languages and operating in various environments and windowing systems.

To learn more about the GTK you can visit the project's homepage located on the world wide web at http://www.gtk.org.

TERMINOLOGY

Emission / Emit
The action of initiating a signal transmission to any listening objects or handlers.
Event
An instance of a signal emission. A single event results from the emission of a signal by a specific object instance.
Handler
A registered callback function. A handler references a function that has been registered to recieve the emission of a particular signal when emitted by a specified object instance.
Signal
A signal is a class or type of message described by a signal name. A signal must be registered under a specified class type such that only instances of the registered type or descendants of the class type can emit the signal.
Global Signal
A signal which is registered globally and can be emitted by any object regardless of the object's type.
Global Handler
A handler which fires whenever the global signal it is registered to is fired - regardless of the emitting object instance.

FEATURES

- Multiple Handlers per Event
Multiple callback functions can be registered per signal per object instance.
- Signal Inheritance
The signals registered by an objects superclass are inherited by any extending child classes.
- Ordered Handler Execution
Like the DOM event handling system all handlers set to fire in response to a particular event fire in the order they were registered.
- Chained Handler Execution
Also like the DOM event handling model, when a handler returns a value of false any subsequent handlers will NOT be executed.
- Temporary Handler Blocking
Handlers can have blocks placed against them cumulatively; allowing for the selectively temporary disabling of handlers.
- Global Signals
Signals (as used in GLib) are class-specific. A signal registered under the class type of Integer can only be emitted by Integer instances. A global signal (registered using j_signal_new_global) can be emitted by any and all instances regardless of their class. This is essentially equivelant to registering a signal using the Javascript Object class type.
- Global Handlers
As signals are traditionally class specific it follows that handlers are instance specific. However global signals can be handled by both instance-specific handlers AND global handlers. A global handler is executed whenever a global signal is emitted, regardless of the emitting instance and its class type. Note that global handlers are executed BEFORE instance-specific handlers.

GETTING STARTED

The following code example defines a parent class which registers three signals and a child class that registers one signal of its own. Two callback functions are also defined. Then in the main function the callback functions are connected to the signals the user is interested in - followed by manipulation of an instance of the class defined earlier.

Begin by creating the base class - Vehicle. This class will register three signals, one which is emitted when an instance is created, a second that is emitted when the vehicle's speed changes, and a third which is emitted when the vehicle is destroyed.

/* define a base class - Vehicle */
function Vehicle (mph) {
  /* register my signals */
  /* store the signal id of the 'created' signal for future use */
  var create_sid = j_signal_new ("created", Vehicle);
  j_signal_new ("speed_set", Vehicle);
  j_signal_new ("destroyed", Vehicle);
	
  /* if an initial speed was provided then set it */
  if (mph)
    this.setSpeed (mph);
  else
    this.speed = 0;
		
  /* signal that the object has been instantiated - no details provided */
  j_signal_emit (this, create_sid, null);
}
Vehicle.prototype.speed = 0;

Create a function setSpeed that changes the vehicles speed and emits a signal to notify handlers that the objects speed has been changed.

Vehicle.prototype.setSpeed = function (mph) {
  /* this function will throw the speed_set signal */
  /* build event details for callback funcs */
  var detail = new Array ();
  detail['old_speed'] = this.speed;
	
  /* ... change the speed ... */
	
  detail['new_speed'] = this.speed;
	
  j_signal_emit_by_name (this, "speed_set", detail);
}

Now create an extending class - Motercycle - which begins to specialize the Vehicle class. Much like Vehicle the constructor registers a signal specific to the Motorcycle class and also emits the 'created' signal to notify that an instance has been created.

Note: Notice that the Motorcycle prototype is given a variable 'parent' which is set to the constructor of its superclass Vehicle. This is required in order for JSignal to be able to properly traverse the class inheritance. If signal inheritance is of no interest to you then you do not need to concern yourself with setting the class's parent property.

/* declare an extending class - Motorcycle */
function Motorcycle (gear) {
  /* register an additional signal - not an automatic after all */
  j_signal_new ("gear_set", Motorcycle);
	
  /* if an initial gear was provided then set it */
  if (gear)
    this.setGear (gear);
  else
    this.gear = 'N';
	
  /* send word that an instance has been created */
  j_signal_emit_by_name (this, "created", null);
}
Motorcycle.prototype = new Vehicle ();
Motorcycle.prototype.constructor = Motorcycle;
Motorcycle.prototype.parent = Vehicle;
Motorcycle.prototype.gear = 'N';

Again the setGear function - like setSpeed - emits a signal to notify any handlers that something about the object has been altered.

Motorcycle.prototype.setGear = function (setting) {
  var detail = new Array ();
  detail['old_gear'] = this.gear;
	
  /* ... change gears ... */
	
  detail['new_gear'] = this.gear;
  j_signal_emit_by_name (this, "gear_set", detail);
}

Define callback functions to be registered as handlers. The second callback, handle_gearChange, accepts two parameters - the event object AND an object that is defined by the user when the callback function is registered as a handler.

function handle_speedChange (event) {
  /* ... access event object to obtain event details ... */
  /* ... if speed has increased set gear appropriately ... */
}

function handle_gearChange (event, udata) {
  /* ... access event object and do work ... */
}

So now you have two classes with a class hierarchy like:

 - Object
 |
 -- Vehicle
   |
   -- Motorocycle
The Vehicle class has registered some signals and thanks to class inheritance the Motorcylce has access to those signals in addition to the signal(s) it has registered. Each class instance - when it's data changes - issues a signal to notify any registered handlers. Now instantiate a class and manipulate it to watch the handlers fire.

function main () {
  var m = new Motorcycle ('N');
	var rider = "Bill Mann";
	
  /* register callback functions as handlers */
  j_signal_handler_connect (m, "speed_set", handle_speedChange);
  /* this will pass the value 'rider' to the callback function as 'udata' */
  /* this object could be anything - an array, a DOM object, another function, etc */
  j_signal_handler_connect (m, "gear_set", handle_gearChange, rider);
	
  /* this will emit a signal which will cause the handle_speedChange */
  /* callback to increase the gear setting.                          */
  m.setSpeed (35);
}

EXAMPLES

Example Callback

A callback function should be prepared to accept up to two parameters. The first will be a reference to a JEvent object which encapsulates information about the signal which was emitted, the object which emitted the signal (initiated the event), and any event details provided by the initiating object instance. The second parameter is the user-specified data object which was registered alongside the handler when the callback was connected to the signal via j_signal_handler_connect. The callback function should return a true or false value. If the value returned is false execution of all additional handlers will cease - preventing subsequent callback functions from being called; if no value is returned or if a value of true is returned any additionally registered handlers will fire. The following is an example of a callback function's definition:

function evt_callback (event, udata) {
  var signal_id = event.getSignalId ();
  var signal_name = event.getSignalName ();
  var origin = event.getOrigin ();
  var ev_detail = event.getEventDetail ();
  ... do some work ...
  
  // stop any additional handlers from firing
  return false
}