/* * The same functionality as EventEmitter but with the inclusion of callbacks */ function PluginInterface () { // Holder for all the bound listeners by this module this._listeners = {}; // Event proxies this._parent = null; this._children = []; } PluginInterface.prototype.on = function (event_name, fn, scope) { this._listeners[event_name] = this._listeners[event_name] || []; this._listeners[event_name].push(['on', fn, scope]); }; PluginInterface.prototype.once = function (event_name, fn, scope) { this._listeners[event_name] = this._listeners[event_name] || []; this._listeners[event_name].push(['once', fn, scope]); }; PluginInterface.prototype.off = function (event_name, fn, scope) { var idx; if (typeof event_name === 'undefined') { // Remove all listeners this._listeners = {}; } else if (typeof fn === 'undefined') { // Remove all of 1 event type delete this._listeners[event_name]; } else if (typeof scope === 'undefined') { // Remove a single event type + callback for (idx=0; idx<(this._listeners[event_name] || []).length; idx++) { if (this._listeners[event_name][idx][1] === fn) { //delete this._listeners[event_name][idx]; this._listeners[event_name].splice(idx, 1); idx--; } } } else { // Remove a single event type + callback + scope for (idx=0; idx<(this._listeners[event_name] || []).length; idx++) { if (this._listeners[event_name][idx][1] === fn && this._listeners[event_name][idx][2] === scope) { //delete this._listeners[event_name][idx]; this._listeners[event_name].splice(idx, 1); idx--; } } } }; PluginInterface.prototype.getListeners = function(event_name) { return this._listeners[event_name] || []; }; PluginInterface.prototype.createProxy = function() { var proxy = new PluginInterface(); proxy._parent = this._parent || this; proxy._parent._children.push(proxy); return proxy; }; PluginInterface.prototype.dispose = function() { this.off(); if (this._parent) { var idx = this._parent._children.indexOf(this); if (idx > -1) { this._parent._children.splice(idx, 1); } } }; // Call all the listeners for a certain event, passing them some event data that may be changed PluginInterface.prototype.emit = function (event_name, event_data) { var emitter = new this.EmitCall(event_name, event_data), listeners = [], child_idx; // Get each childs event listeners in order of last created for(child_idx=this._children.length-1; child_idx>=0; child_idx--) { listeners = listeners.concat(this._children[child_idx].getListeners(event_name)); } // Now include any listeners directly on this instance listeners = listeners.concat(this.getListeners(event_name)); // Once emitted, remove any 'once' bound listeners emitter.then(function () { var len = listeners.length, idx; for(idx = 0; idx < len; idx++) { if (listeners[idx][0] === 'once') { listeners[idx] = undefined; } } }); // Emit the event to the listeners and return emitter.callListeners(listeners); return emitter; }; // Promise style object to emit events to listeners PluginInterface.prototype.EmitCall = function EmitCall (event_name, event_data) { var that = this, completed = false, completed_fn = [], // Has event.preventDefault() been called prevented = false, prevented_fn = []; // Emit this event to an array of listeners function callListeners(listeners) { var current_event_idx = -1; // Make sure we have some data to pass to the listeners event_data = event_data || undefined; // If no bound listeners for this event, leave now if (listeners.length === 0) { emitComplete(); return; } // Call the next listener in our array function nextListener() { var listener, event_obj; // We want the next listener current_event_idx++; // If we've ran out of listeners end this emit call if (!listeners[current_event_idx]) { emitComplete(); return; } // Object the listener ammends to tell us what it's going to do event_obj = { // If changed to true, expect this listener is going to callback wait: false, // If wait is true, this callback must be called to continue running listeners callback: function () { // Invalidate this callback incase a listener decides to call it again event_obj.callback = undefined; nextListener.apply(that); }, // Prevents the default 'done' functions from executing preventDefault: function () { prevented = true; } }; listener = listeners[current_event_idx]; listener[1].call(listener[2] || that, event_obj, event_data); // If the listener hasn't signalled it's going to wait, proceed to next listener if (!event_obj.wait) { // Invalidate the callback just incase a listener decides to call it anyway event_obj.callback = undefined; nextListener(); } } nextListener(); } function emitComplete() { completed = true; var funcs = prevented ? prevented_fn : completed_fn; funcs = funcs || []; // Call the completed/prevented functions for (var idx = 0; idx < funcs.length; idx++) { if (typeof funcs[idx] === 'function') funcs[idx](); } } function addCompletedFunc(fn) { // Only accept functions if (typeof fn !== 'function') return false; completed_fn.push(fn); // If we have already completed the emits, call this now if (completed && !prevented) fn(); return this; } function addPreventedFunc(fn) { // Only accept functions if (typeof fn !== 'function') return false; prevented_fn.push(fn); // If we have already completed the emits, call this now if (completed && prevented) fn(); return this; } return { callListeners: callListeners, then: addCompletedFunc, catch: addPreventedFunc }; }; // If running a node module, set the exports if (typeof module === 'object' && typeof module.exports !== 'undefined') { module.exports = PluginInterface; } /* * Example usage */ /* var modules = new PluginInterface(); // A plugin modules.on('irc message', function (event, data) { //event.wait = true; setTimeout(event.callback, 2000); }); // Core code that is being extended by plugins var data = { nick: 'prawnsalald', command: '/dothis' }; modules.emit('irc message', data).done(function () { console.log('Your command is: ' + data.command); }); */