Created
April 20, 2012 23:23
-
-
Save rwaldron/2432622 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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