Skip to content

Instantly share code, notes, and snippets.

@rwaldron
Created April 20, 2012 23:23
Show Gist options
  • Save rwaldron/2432622 to your computer and use it in GitHub Desktop.
Save rwaldron/2432622 to your computer and use it in GitHub Desktop.
(function (exports) {
// These aliases can be safely removed.
var toString = {}.toString, hasOwn = {}.hasOwnProperty,
// **defer** attempts to execute a callback function asynchronously in supported
// environments.
defer = function (callback, context) {
// `process.nextTick` is an efficient alternative to `setTimeout(..., 0)`.
// As of Node 0.6.9, neither `process.nextTick` nor `setTimeout` isolate
// execution; if the `callback` throws an exception, subsequent deferred
// callbacks **will not execute**. This is an unfortunate incompatibility
// with both the `setTimeout` function exposed in Browsers and Phantom,
// and the Java `Timer` API exposed via LiveConnect in Rhino.
if (typeof process == "object" && process && typeof process.nextTick == "function") {
defer = function (callback, context) {
function run() {
callback.call(context);
}
process.nextTick(run);
};
// Browsers and Phantom provide the `setTimeout` function.
} else if (typeof setTimeout != "undefined") {
defer = function (callback, context) {
function run() {
callback.call(context);
}
setTimeout(run, 0);
};
// Mozilla Rhino's LiveConnect interface exposes the Java `Timer` API for
// executing tasks in a background thread.
} else if (typeof java != "undefined" && java && toString.call(java) == "[object JavaPackage]" && typeof JavaAdapter == "function") {
defer = function (callback, context) {
var timer = new java.util.Timer();
function run() {
// Terminate the background thread once the task runs. If the thread
// is not terminated, the Rhino process will persist even after
// execution is completed.
timer.cancel();
callback.call(context);
}
// Schedule the timer task for background execution. A new scheduler is
// created for each task to ensure that exceptions do not leak between
// tasks.
timer.schedule(new JavaAdapter(java.util.TimerTask, { "run": run }), 0);
};
// Execute the callback function synchronously in other environments.
} else {
defer = function (callback, context) {
callback.call(context);
};
}
return defer(callback, context);
};
// Custom Events
// -------------
// `Spec.Events` provides an interface for managing custom events. You can
// add and remove individual event handlers; triggering an event executes its
// handlers in succession. Based on work by Jeremy Ashkenas.
exports.Events = Events;
function Events() {
this.events = {};
}
// **addListener** attaches a `callback` function to an `event`. The callback
// will be invoked whenever the event, specified by a string identifier, is
// fired. If the `event` contains spaces, it is treated as a list of multiple
// event types. If the optional `context` argument is provided, the `callback`
// will be bound to it. Callbacks attached to the special `all` event will be
// invoked for **all** triggered events.
Events.prototype.on = Events.prototype.addListener = addListener;
function addListener(event, callback, context) {
var events, index, length, type, callbacks, target, previous;
if (event && callback) {
events = event.split(" ");
for (index = 0, length = events.length; index < length; index += 1) {
type = events[index];
callbacks = hasOwn.call(this.events, type) && this.events[type];
target = callbacks ? callbacks.previous : {};
target.next = previous = {};
// Store the event handler and context.
target.callback = callback;
target.context = context;
// Create a new event target node.
this.events[type] = {
"previous": previous,
"next": callbacks ? callbacks.next : target
};
}
}
return this;
}
// **removeListener** removes a previously-bound event handler. If the
// `context` is omitted, all versions of the handler, including those bound to
// different contexts, will be removed. If the `callback` is omitted, all
// registered handlers for the given `event` will be removed. If both the
// `callback` and `event` are omitted, **all** listeners for all events will
// be removed.
Events.prototype.removeListener = removeListener;
function removeListener(event, callback, context) {
var events, index, length, type, target, previous;
if (!event) {
// Remove all event listeners.
this.events = {};
} else if (this.events) {
events = event.split(" ");
for (index = 0, length = events.length; index < length; index += 1) {
type = events[index];
target = hasOwn.call(this.events, type) && this.events[type];
if (target) {
// Remove the event listener registry.
delete this.events[type];
if (callback) {
// Create a new registry without the given listener.
previous = hasOwn.call(target, "previous") && target.previous;
for (; (target = hasOwn.call(target, "next") && target.next) != previous;) {
if (hasOwn.call(target, "callback") && target.callback != callback || (context && (hasOwn.call(target, "context") && target.context != context))) {
this.on(type, target.callback, target.context);
}
}
}
}
}
}
return this;
}
// **emit** fires an event, specified by either a string identifier or an
// event object with a `type` property. Multiple event types are not
// supported for string identifiers.
Events.prototype.emit = emit;
function emit(event) {
var target, type, previous, all, error;
function raise() {
throw error;
}
// Convert a string identifier into an event object.
if (typeof event == "string" || toString.call(event) == "[object String]") {
event = { "type": event };
}
type = hasOwn.call(event, "type") && event.type;
// Capture a reference to the current event target.
if (!hasOwn.call(event, "target")) {
event.target = this;
}
// Capture a reference to the callback registry for the `all` event.
all = type != "all" && hasOwn.call(this.events, "all") && this.events.all;
if ((target = hasOwn.call(this.events, type) && this.events[type])) {
previous = hasOwn.call(target, "previous") && target.previous;
for (; (target = hasOwn.call(target, "next") && target.next) != previous;) {
// Execute the callbacks in succession.
try {
call.call(target.callback, hasOwn.call(target, "context") && target.context || this, event);
} catch (exception) {
error = exception;
// Re-throw exceptions asynchronously, allowing all subsequent
// callbacks to fire.
defer(raise);
}
}
}
// Fire the `all` event.
if (all) {
previous = hasOwn.call(all, "previous") && all.previous;
for (; (all = hasOwn.call(all, "next") && all.next) != previous;) {
try {
call.call(all.callback, hasOwn.call(all, "context") && all.context || this, event);
} catch (exception) {
error = exception;
defer(raise);
}
}
}
return this;
}
}(this));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment