JSignal

JSignal.js

Summary

JSignal - A signal handling library

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

Requires Javascript v1.5

Version: 0.2

Author: jpbarto (jpbarto.freeshell.org)

Licensed under the GNU GPL
Copyright (C) 2006 jpbarto
This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public License along with this library; if not, download a copy from the world wide web at http://www.gnu.org/copyleft/gpl.html or write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.



Class Summary
UnknownHandlerException User defined exception, thrown when a specified handler ID is not found within an instance's registry.
UnregisteredSignalException User defined exception, thrown when a signal is found registered but not registered for use by the calling object.
SignalNameExistsException User defined exception, thrown when an attempt to register a signal name as a global signal collides with an existing single class type signal.
UnknownSignalException User defined exception, thrown when a specified signal ID or name is not found within the registry.
UnknownErrorException User defined exception, thrown when a system exception is thrown.
UnknownInstanceException User defined exception, thrown when a specified instance is not found within the registry.
JEvent An object which encapsulates the details of an event where an event is defined as the emission of a signal.

Method Summary
static void j_signal_delete(<int> sid)
           Removes a signal from the registry; along with all registered handlers.
static void j_signal_emit(<Object> obj, <int> sid, <Array> data)
           Emits a signal.
static void j_signal_emit_by_name(<Object> obj, <int> name, <Array> data)
           Similiar to j_signal_emit but accepts a signal name instead of a signal identifier.
static void j_signal_handler_block(<Object> obj, <int> hid)
           Temporarily deactivates the specified handler.
static int j_signal_handler_connect(<Object> obj, <String> name, <Function> cb, <Object> data)
           Connects a callback function to a signal for a particular object.
static int j_signal_handler_connect_global(<String> name, <Function> cb, <Object> data)
           Connects a callback function to a signal that is registered as a global signal (created via j_signal_new_global).
static void j_signal_handler_disconnect(<Object> obj, <int> hid)
           Disconnects (deletes) a specified handler.
static boolean j_signal_handler_is_connected(<Object> obj, <int> hid)
           Determines if a specified handler is connected to the given instance.
static void j_signal_handler_unblock(<Object> obj, <int> hid)
           Removes a block placed upon a handler.
static Array j_signal_list_all_ids(<ObjectType> ctype)
           Retrieves a list of signal identifiers for a given class type.
static Array j_signal_list_ids(<ObjectType> ctype)
           Retrieves a list of signal identifiers registered by the given class type.
static int j_signal_lookup(<String> name, <ObjectType> ctype)
           Given the name of the signal and the class it connects to, gets the signal's identifier.
static String j_signal_name(<int> sid)
           Given the signal's identifier returns the signal's name.
static int j_signal_new(<String> name, <ObjectType> ctype)
           Registers a new signal.
static int j_signal_new_global(<String> name)
           Registers a new global signal that can be used (emitted) by any and all objects.

/**
 * @fileoverview
 * <h1>JSignal - A signal handling library</h1>
 * <h2>A loose Javascript port of the GLib signal system used by the GTK</h2>
 * <p>
 * Requires Javascript v1.5
 * @version 0.2
 * @author jpbarto (jpbarto.freeshell.org)
 * </p>
 *
 * <p>
 * Licensed under the GNU GPL<br/>
 * Copyright (C) 2006 jpbarto<br/>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * </p>
 * <p>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 * </p>
 * <p>
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, download a copy from the
 * world wide web at http://www.gnu.org/copyleft/gpl.html or write to 
 * the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 * </p>
 */
 
/*==============================================================================
 * Exception Classes
 =============================================================================*/
/** @ignore */
function __J_Exception () {}
/** Name / Type of exception. @type String */
__J_Exception.prototype.name = '';
/** Message body of exception. @type String */
__J_Exception.prototype.message = '';
/** @type String */
__J_Exception.prototype.toString = function () {
  return this.name +': '+ this.message;
}

/**
 * @class
 * User defined exception, thrown when a specified signal ID or name is 
 * not found within the registry.
 *
 * @extends __J_Exception
 */
function UnknownSignalException (id, name) {
  this.name = 'UnknownSignalException';
  if (id || name) {
    this.message = "No signal was found registered with ";
    if (id) {
      this.message += "id = "+ id;
    }else if (name) {
      this.message += "name = '"+ name +"'";
    }
  }else{
    this.message = "No registered signal was found matching the specified name or ID."
  }
}
UnknownSignalException.prototype = new __J_Exception ();

/**
 * @class
 * User defined exception, thrown when a signal is found registered but not
 * registered for use by the calling object.
 *
 * @extends __J_Exception
 */
function UnregisteredSignalException (id, name) {
  this.name = 'UnregisteredSignalException';
  if (id || name) {
    this.message = "No signal was found registered to the specified object's class type with ";
    if (id) {
      this.message += "id = "+ id;
    }else if (name) {
      this.message += "name = '"+ name +"'";
    }
  }else{
    this.message = "The specified signal was not found to be registered under the specified object's class type.";
  }
}
UnregisteredSignalException.prototype = new __J_Exception ();

/**
 * @class
 * User defined exception, thrown when an attempt to register a signal name
 * as a global signal collides with an existing single class type signal.
 * To correct this unregister all signals with the same name.
 *
 * @extends __J_Exception
 */
function SignalNameExistsException (id, name) {
  this.name = "SignalNameExistsException";
  if (id || name) {
    this.message = "The signal with ";
    if (id) {
      this.message += "id = "+ id;
    }else if (name) {
      this.message += "name = '"+ name +"'";
    }
    this.message += " already exists and is registered for one or more class types.";
  }else{
    this.message = "The specified signal already exists and is registered for one or more class types.";
  }
}
SignalNameExistsException.prototype = new __J_Exception ();

/**
 * @class
 * User defined exception, thrown when a specified handler ID is not 
 * found within an instance's registry.
 *
 * @extends __J_Exception
 */
function UnknownHandlerException (id) {
  this.name = 'UnknownHandlerException';
  if (id) {
    this.message = "No handler was found registered with id = "+ id;
  }else{
    this.message = "No registered handler was found matching the specified ID."
  }
}
UnknownHandlerException.prototype = new __J_Exception ();

/**
 * @class
 * User defined exception, thrown when a specified instance is not
 * found within the registry.
 *
 * @extends __J_Exception
 */
function UnknownInstanceException () {
  this.name = 'UnknownInstanceException';
  this.message = 'The reference object instance was not found within registries.';
}
UnknownInstanceException.prototype = new __J_Exception ();

/**
 * @class
 * User defined exception, thrown when a system exception is thrown.  May happen
 * when an object is passed to a JSignal function which is protected as a 
 * WrappedNative prototype.
 *
 * @extends __J_Exception
 */
function UnknownErrorException (exMsg) {
  this.name = 'UnknownErrorException';
  this.message = 'An error was caught by JSignal: '+ exMsg;
}
UnknownErrorException.prototype = new __J_Exception ();

/*==============================================================================
 * Supporting Classes
 *
 * These classes are kept light-weight on purpose to conserve memory - with the
 * exception of JEvent which will be used by user callback functions.
 =============================================================================*/
/**
 * @class
 * @private
 * An encapsulation object which represents a 'class' or type of signal.
 */
function JSignal (sid, name, ctype) {
  /** The id of the signal. @type int */
  this.signalId = sid;
  /** The name of the signal. @type String */
  this.signalName = name || '';
  /** The name of the class under which the signal is registered. @type Object Type */
  this.classType = ctype || null;
}
 
/**
 * @class
 * @private
 * A wrapper and management class for an event handling callback function.
 * Note that the handler constructor does not verify the signal ID passed to it
 * for existance or correctness, but just accepts it and stores it blindly.
 */
function JHandler (sid, hid, func, udata) {
  /** The id of the singal to be handled. @type int */
  this.signalId = sid;
  /** The id of the handler given an instance.  @type int */
  this.handlerId = hid;
  /** A pointer to the callback function.  @type Function */
  this.cbFunc = func || null;
  /** A pointer to the user defined data object defined when the handler was connected.  @type Object */
  this.userData = udata || null;
  /** A count of the number of blocks against the handler.  @type int */
  this.blockCount = 0;
}
 
/**
 * @class
 * An object which encapsulates the details of an event where
 * an event is defined as the emission of a signal.
 */
function JEvent (sid, name, obj, detail) {
  this.signalId = sid;
  this.signalName = name;
  this.emitter = obj;
  this.detail = detail;
}
/** The id of the signal emitted.  @type int */
JEvent.prototype.signalId = -1;
/** The name of the signal emitted.  @type String */
JEvent.prototype.signalName = '';
/** The object which emitted the signal.  @type Object */
JEvent.prototype.emitter = null;
/** The event details provided by the emitter.  @type Array */
JEvent.prototype.detail = null;

/**
 * Used to obtain the ID of the emitted signal.
 *
 * @return the ID of the issued signal
 * @type int
 */
JEvent.prototype.getSignalId = function () {
  return this.signalId;
}

/**
 * Used to obtain the name of the emitted signal.
 *
 * @return the registered name of the issued signal
 * @type String
 */
JEvent.prototype.getSignalName = function () {
  return this.signalName;
}

/**
 * Used to obtain a pointer to the object which emitted
 * or initiated the event.
 *
 * @return A pointer to the instance of the emitting object
 * @type Object
 */
JEvent.prototype.getOrigin = function () {
  return this.emitter;
}

/**
 * Used to obtain a reference to the collection of name => value
 * pairs which were emitted along with the signal.  The name / value
 * pairs are arbitrary and are populated by the originating object instance.
 *
 * @return A collection of name => value pairs which were defined / created
 *         by the originating object instance.
 * @type Array
 */
JEvent.prototype.getEventDetail = function () {
  return this.detail;
}

/*==============================================================================
 * Signal Management Functions (and variables)
 =============================================================================*/ 
/**
 * @private
 * A flat array containing a list of all registered signals.
 * @type Array
 */
var __j_signals = new Array ();
/**
 * @private
 * A flat array containing a list of all instances.
 * @type Array
 */
var __j_instances = new Array ();
/**
 * @private
 * An array containing a list of all registered handlers per instance.
 * @type Array
 */
var __j_handlers = new Array ();
/**
 * @private
 * An ever-increasing integer assigned as handler id to the next connected handler.
 * @type int
 */
var __j_handlers_next_id = 0;

/**
 * @private
 * The J Global type and class are used to support the global functions
 * which enable anonymous signal registration, handling, and emission.
 */
function __j_global_type () {
}
var __j_global_obj = new __j_global_type ();

/**
 * Registers a new signal.
 *
 * @param {String} name The name of the signal
 * @param {ObjectType} ctype The class from which this signal will originate.
 * @return The id of the newly created signal
 * @type int
 */ 
function j_signal_new (name, ctype) {
  var sig = null;
  
  for (var i in __j_signals) {
    sig = __j_signals[i];
    if ((sig.signalName == name) && (sig.classType == ctype))
      return i;
  }
  
  /* signal not found in registery, create it */
  sig = new JSignal (__j_signals.length, name, ctype);
  __j_signals.push (sig);
  return sig.signalId;
}

/**
 * Registers a new global signal that can be used (emitted) by any and all objects.
 *
 * @param {String} name The name of the signal
 * @return The id of the newly created signal
 * @type int
 */
function j_signal_new_global (name) {
  var sig = null;
  
  /* if the signal exists and IS NOT a foral signal throw an exception */
  for (var i in __j_signals) {
    sig = __j_signals[i];
    if ((sig.signalName == name) 
        && (sig.classType != __j_global_type)) throw new SignalNameExistsException (null, name);
  }
  
  return j_signal_new (name, __j_global_type);
}

/**
 * Removes a signal from the registry; along with all registered handlers.
 *
 * @param {int} sid The signal identifier to be removed.
 * @type void
 */
function j_signal_delete (sid) {
  if (sid < 0) return;
  
  var sig = __j_signals[sid];
  if (!sig) throw new UnknownSignalException (sid, null);
  
  /* delete any registered handlers */
  /* for every instance which is of the object type under */
  /* which the signal is registered */
  for (var i in __j_instances) {  
    var obj = __j_instances[i];
    if (obj instanceof sig.classType) {
      var objHlers = __j_handlers[i];
      
      for (var j in objHlers) {
        var hler = objHlers[j];
        /* if the handler is registered to receive the signal */
        if (hler.signalId == sig.signalId) {
          j_signal_handler_disconnect (obj, hler.handlerId);
        }
      }
    }
  }
  
  /* remove the signal from the registry */
  __j_signals.splice (sid, 1);
}
 
/**
 * Connects a callback function to a signal for a particular object.
 *
 * @param {Object} obj The instance to register to
 * @param {String} name The name of the signal to recieve
 * @param {Function} cb A pointer to the callback function
 * @param {Object} data Data to be passed to the callback pointer.
 * @return The handler ID
 * @type int
 * @throws UnknownSignalException If the specified signal is unregistered
 */
function j_signal_handler_connect (obj, name, cb, data) {
  /* determine if the signal is registered */
  var sid = j_signal_lookup (name, obj.constructor);
  if (sid < 0) throw new UnknownSignalException (null, name);
  
  var sig = __j_signals[sid];
  if (! sig) throw new UnknownSignalException (null, name);

  /* the signal has been found, register the handler */
  var objHlers = null;
  var ndex = __j_instances.indexOf (obj);
  if (ndex < 0) {
    /* the instance is new to the registry, add it */
    __j_instances.push (obj);
    ndex = __j_instances.indexOf (obj);
    objHlers = new Array ();
    __j_handlers[ndex] = objHlers;
  }else{
    objHlers = __j_handlers[ndex];
  }
      
  var hler = new JHandler (sig.signalId, __j_handlers_next_id++, cb, data);
  objHlers.push (hler);
  return hler.handlerId;
}

/**
 * Connects a callback function to a signal that is registered as a global
 * signal (created via j_signal_new_global).  The handler will be executed
 * whenever the signal is emitted regardless of the emitting instance.
 *
 * Note that all global handlers will be executed BEFORE instance-specific
 * handlers.
 *
 * @param {String} name The name of the signal to recieve
 * @param {Function} cb A pointer to the callback function
 * @param {Object} data Data to be passed to the callback pointer.
 * @return The handler ID
 * @type int
 * @throws UnknownSignalException If the specified signal is unregistered
 */
function j_signal_handler_connect_global (name, cb, data) {
  return j_signal_handler_connect (__j_global_obj, name, cb, data);
} 

/**
 * Temporarily deactivates the specified handler.
 *
 * @param {Object} obj The instance on which the handler is defined
 * @param {int} hid The handler's identifier
 * @type void
 * @see #j_signal_handler_unblock
 * @throws UnknownInstanceException If the specified object has no registered handlers
 * @throws UnknownHandlerException If the handler is not found within the instance's registry
 */
function j_signal_handler_block (obj, hid) {
  var ndex = __j_instances.indexOf (obj);
  if (ndex < 0) throw new  UnknownInstanceException ();
  
  var objHlers = __j_handlers[ndex];
  if (! objHlers) throw new UnknownInstanceException ();
  
  var hler = null;
  for (var i=0; i < objHlers.length; i++) {
    hler = objHlers[i];
    if (hler.handlerId = hid) {
      hler.blockCount++;
      return;
    }
  }
    
  /* the specified handler was not found */
  throw new UnknownHandlerException (hid);
}

/**
 * Removes a block placed upon a handler.
 *
 * @param {Object} obj The instance on which the handler is defined
 * @param {int} hid The handler's identifier
 * @type void
 * @see #j_signal_handler_block
 * @throws UnknownInstanceException If the specified object has no registered handlers
 * @throws UnknownHandlerException If the handler is not found within the instance's registry
 */
function j_signal_handler_unblock (obj, hid) {
  var ndex = __j_instances.indexOf (obj);
  if (ndex < 0) throw new  UnknownInstanceException ();
  
  var objHlers = __j_handlers[ndex];
  if (! objHlers) throw new UnknownInstanceException ();
  
  var hler = null;
  for (var i=0; i < objHlers.length; i++) {
    hler = objHlers[i];
    if (hler.handlerId = hid) {
      if (--hler.blockCount < 0) hler.blockCount = 0;
      return;
    }
  }
    
  /* the specified handler was not found */
  throw new UnknownHandlerException (hid);
}

/**
 * Emits a signal.
 *
 * @param {Object} obj The instance emitting the signal
 * @param {int} sid The id of the signal to emit
 * @param {Array} data Collection of name => value pairs detailing the event
 * @type void 
 * @see #j_signal_emit_by_name
 * @throws UnknownSignalException If the specified signal is unregistered
 */
function j_signal_emit (obj, sid, data) {
  /* ensure the specified signal is registered */
  var sig = __j_signals[sid];
  if (! sig) throw new UnknownSignalException (sid, null);
  /* if the signal is not registered universal and the object is not of the right type */
  if ((sig.classType != __j_global_type) 
      && ! (obj instanceof sig.classType)) throw new UnregisteredSignalException (sid, null);
  
  var objArray = new Array ();
  if (sig.classType == __j_global_type) {
    /* load global object handlers to execute first */
    objArray.push (__j_global_obj);
  }
  objArray.push (obj);
  
  for (var i in objArray) {
    var ndex = __j_instances.indexOf(objArray[i]);
    /* if instance not found then there are no registered handlers; move to next tmpobj */
    if (ndex < 0) continue;

    var objHlers = __j_handlers[ndex];
    /* if the instance has no handlers - do nothing */
    if (! objHlers) throw new UnknownInstanceException ();
	
    var evt = new JEvent (sig.signalId, sig.signalName, obj, data);
    for (var i in objHlers) {
      var hler = objHlers[i];
      if (hler.signalId == sig.signalId && hler.blockCount == 0) {
        if (hler.cbFunc (evt, hler.userData) == false) {
          /* handler returned false, cease execution of additional handlers */
          break;
        }
      }
    }
  }
}

/**
 * Similiar to j_signal_emit but accepts a signal name instead
 * of a signal identifier.  This is slower than emitting the
 * signal by the identifier as the signal must first be located
 * within the registry.
 *
 * Ancestors of the specified object instance will be searched to
 * determine if superclasses registered the specified signal.  
 * However this requires that the <pre>obj</pre> parameter has - 
 * within its prototype - a member named <pre>parent</pre> which
 * points to the constructor of the object's superclass.
 *
 * @param {Object} obj The instance emitting the signal
 * @param {int} name The name of the signal to emit
 * @param {Array} data Collection of name => value pairs detailing the event
 * @type void
 * @see #j_signal_emit
 * @throws UnknownSignalException If the specified signal is unregistered
 */
function j_signal_emit_by_name (obj, name, data) {
  var sid = j_signal_lookup (name, obj.constructor);
  
  if (sid < 0) throw new UnknownSignalException (null, name);
  
  j_signal_emit (obj, sid, data);
}

/**
 * Given the name of the signal and the class it connects 
 * to, gets the signal's identifier.  Emitting the signal 
 * by number is typically fater than by name.
 *
 * Note: In order for this function to work properly it is required that the
 * ctype argument have a member variable named 'parent' which is a reference to
 * the class's superclass constructor.
 *
 * @param {String} name The signals name
 * @param {ObjectType} ctype The type that the signal is registered under
 * @return The ID of the specified signal; -1 if the signal is not found.
 * @type int
 */
function j_signal_lookup (name, ctype) {
  var sig = null;
  
  for (var i in __j_signals) {
    sig = __j_signals[i];
    
    if (sig.signalName == name) {
      if (sig.classType == ctype || sig.classType == __j_global_type)
        return i;
        
      /* still haven't found specified class type */
      /* if a 'parent' attribute exists check against ancestors */
      try{
        if (ctype.prototype && ctype.prototype.parent) {
          var tmpCType = ctype;
          while (tmpCType) {
            if (sig.classType == tmpCType) return i;
            tmpCType = tmpCType.prototype.parent;
          }
        }
      }catch (e) {
        throw new UnknownErrorException (e.toString ());
      }
    }
  }
  
  return -1;
}

/**
 * Given the signal's identifier returns the signal's name.
 *
 * @param {int} sid Signal identifier
 * @return The name of the signal or null if the identifier is invalid.
 * @type String
 */
function j_signal_name (sid) {
  if (sid < __j_signals.length) {
    var sig = __j_signals[sid];
    return sig.signalName;
  }
  
  return null;
}

/**
 * Retrieves a list of signal identifiers registered by the given class type.
 * This function does NOT list those signals registered by the class's supertype.
 *
 * @param {ObjectType} ctype A reference to the class constructor
 * @return Array of signal IDs
 * @type Array
 * @see #j_signal_list_all_ids
 */
function j_signal_list_ids (ctype) {
  var ret = new Array ();
	var sig = null;
	for (var i in __j_signals) {
	  sig = __j_signals[i];
	  if (sig.classType == ctype) {
		  ret.push (sig.signalId);
		}
	}
	return ret;
}

/**
 * Retrieves a list of signal identifiers for a given class type.
 *
 * Note: In order for this function to work properly it is required that the
 * ctype argument have a member variable named 'parent' which is a reference to
 * the class's superclass constructor.
 *
 * @param {ObjectType} ctype A reference to the class constructor
 * @return Array of signal IDs
 * @type Array
 * @see #j_signal_list_ids
 */
function j_signal_list_all_ids (ctype) {
  var ret = new Array ();
  var sig = null;
  for (var i in __j_signals) {
    sig = __j_signals[i];
		
    if (sig.classType == ctype || sig.classType == __j_global_type) {
      ret.push (sig.signalId);
		}else	
		/* if the prototype has its parent defined check against class lineage */
		if (ctype.prototype.parent) {
		  var tmpCType = ctype;
			while (tmpCType) {
			  if (sig.classType == tmpCType) ret.push(sig.signalId);
				tmpCType = tmpCType.prototype.parent;
			}
		}
  }
	
  return ret;
}

/**
 * Disconnects (deletes) a specified handler.
 *
 * @param {Object} obj Instance the handler is currently attached to
 * @param {int} hid Handler identifier
 * @type void
 * @throw UnknownInstanceException If the specified instance has no known handlers
 * @throw UnknownHandlerExcpetion If the specified handler is not found within the instance's registry
 */
function j_signal_handler_disconnect (obj, hid) {
  var ndex = __j_instances.indexOf (obj);
  if (ndex < 0) throw new UnknownInstanceException ();
  
  var objHlers = __j_handlers[ndex];
  if (! objHlers) throw new UnknownInstanceException ();
  
  var hler = null;
  for (var i in objHlers) {
    hler = objHlers[i];
    if (hler.handlerId == hid) {
      objHlers.splice (i, 1);
      
      /* if the handler list for the instance is empty, delete the instance as well */
      if (objHlers.length == 0) {
        /* remove the array of handlers */
        __j_handlers.splice(ndex, 1);
        /* remove the instance from the list of instances */
        __j_instances.splice(ndex, 1);
      }
    
      return;
    }
  }
  
  /* the specified handler was not found */
  throw new UnknownHandlerException (hid);
}

/**
 * Determines if a specified handler is connected to the given instance.
 * @param {Object} obj handler-owning instance
 * @param {int} hid Handler identifier
 * @return true if the specified handler is connected to the given instance; false otherwise
 * @type boolean
 */
function j_signal_handler_is_connected (obj, hid) {
  var ndex = __j_instances.indexOf (obj);
  if (ndex < 0) return false;
  
  var objHlers = __j_handlers[ndex];
  if (! objHlers) return false;
  
  var hler = null;
  for (var i in objHlers) {
    hler = objHlers[i];
    if (hler.handlerId == hid) return true;
  }
  
  return false;
}

JSignal

Documentation generated by JSDoc on Fri Mar 24 20:53:22 2006