| 1 | /* |
| 2 | * The same functionality as EventEmitter but with the inclusion of callbacks |
| 3 | */ |
| 4 | |
| 5 | /* |
| 6 | * Promise style object to emit events to listeners |
| 7 | */ |
| 8 | function EmitCall (event_name, event_data) { |
| 9 | var that = this, |
| 10 | completed = false, |
| 11 | completed_fn = [], |
| 12 | |
| 13 | // Has event.preventDefault() been called |
| 14 | prevented = false, |
| 15 | prevented_fn = []; |
| 16 | |
| 17 | |
| 18 | // Emit this event to an array of listeners |
| 19 | function callListeners(listeners) { |
| 20 | var current_event_idx = -1; |
| 21 | |
| 22 | // Make sure we have some data to pass to the listeners |
| 23 | event_data = event_data || undefined; |
| 24 | |
| 25 | // If no bound listeners for this event, leave now |
| 26 | if (listeners.length === 0) { |
| 27 | emitComplete(); |
| 28 | return; |
| 29 | } |
| 30 | |
| 31 | |
| 32 | // Call the next listener in our array |
| 33 | function nextListener() { |
| 34 | var listener, event_obj; |
| 35 | |
| 36 | // We want the next listener |
| 37 | current_event_idx++; |
| 38 | |
| 39 | // If we've ran out of listeners end this emit call |
| 40 | if (!listeners[current_event_idx]) { |
| 41 | emitComplete(); |
| 42 | return; |
| 43 | } |
| 44 | |
| 45 | // Object the listener ammends to tell us what it's going to do |
| 46 | event_obj = { |
| 47 | // If changed to true, expect this listener is going to callback |
| 48 | wait: false, |
| 49 | |
| 50 | // If wait is true, this callback must be called to continue running listeners |
| 51 | callback: function () { |
| 52 | // Invalidate this callback incase a listener decides to call it again |
| 53 | event_obj.callback = undefined; |
| 54 | |
| 55 | nextListener.apply(that); |
| 56 | }, |
| 57 | |
| 58 | // Prevents the default 'done' functions from executing |
| 59 | preventDefault: function () { |
| 60 | prevented = true; |
| 61 | } |
| 62 | }; |
| 63 | |
| 64 | |
| 65 | listener = listeners[current_event_idx]; |
| 66 | listener[1].call(listener[2] || that, event_obj, event_data); |
| 67 | |
| 68 | // If the listener hasn't signalled it's going to wait, proceed to next listener |
| 69 | if (!event_obj.wait) { |
| 70 | // Invalidate the callback just incase a listener decides to call it anyway |
| 71 | event_obj.callback = undefined; |
| 72 | |
| 73 | nextListener(); |
| 74 | } |
| 75 | } |
| 76 | |
| 77 | nextListener(); |
| 78 | } |
| 79 | |
| 80 | |
| 81 | |
| 82 | function emitComplete() { |
| 83 | completed = true; |
| 84 | |
| 85 | var funcs = prevented ? prevented_fn : completed_fn; |
| 86 | |
| 87 | // Call the completed/prevented functions |
| 88 | (funcs || []).forEach(function (fn) { |
| 89 | if (typeof fn === 'function') fn(); |
| 90 | }); |
| 91 | } |
| 92 | |
| 93 | |
| 94 | |
| 95 | function addCompletedFunc(fn) { |
| 96 | // Only accept functions |
| 97 | if (typeof fn !== 'function') return false; |
| 98 | |
| 99 | completed_fn.push(fn); |
| 100 | |
| 101 | // If we have already completed the emits, call this now |
| 102 | if (completed && !prevented) fn(); |
| 103 | |
| 104 | return this; |
| 105 | } |
| 106 | |
| 107 | |
| 108 | |
| 109 | function addPreventedFunc(fn) { |
| 110 | // Only accept functions |
| 111 | if (typeof fn !== 'function') return false; |
| 112 | |
| 113 | prevented_fn.push(fn); |
| 114 | |
| 115 | // If we have already completed the emits, call this now |
| 116 | if (completed && prevented) fn(); |
| 117 | |
| 118 | return this; |
| 119 | } |
| 120 | |
| 121 | |
| 122 | return { |
| 123 | callListeners: callListeners, |
| 124 | done: addCompletedFunc, |
| 125 | prevented: addPreventedFunc |
| 126 | }; |
| 127 | } |
| 128 | |
| 129 | |
| 130 | |
| 131 | |
| 132 | |
| 133 | |
| 134 | function PluginInterface () { |
| 135 | // Holder for all the bound listeners by this module |
| 136 | this._listeners = {}; |
| 137 | } |
| 138 | |
| 139 | |
| 140 | PluginInterface.prototype.on = function (event_name, fn, scope) { |
| 141 | this._listeners[event_name] = this._listeners[event_name] || []; |
| 142 | this._listeners[event_name].push(['on', fn, scope]); |
| 143 | }; |
| 144 | |
| 145 | |
| 146 | |
| 147 | PluginInterface.prototype.once = function (event_name, fn, scope) { |
| 148 | this._listeners[event_name] = this._listeners[event_name] || []; |
| 149 | this._listeners[event_name].push(['once', fn, scope]); |
| 150 | }; |
| 151 | |
| 152 | |
| 153 | |
| 154 | PluginInterface.prototype.off = function (event_name, fn, scope) { |
| 155 | var idx; |
| 156 | |
| 157 | if (typeof event_name === 'undefined') { |
| 158 | // Remove all listeners |
| 159 | this._listeners = {}; |
| 160 | |
| 161 | } else if (typeof fn === 'undefined') { |
| 162 | // Remove all of 1 event type |
| 163 | delete this._listeners[event_name]; |
| 164 | |
| 165 | } else if (typeof scope === 'undefined') { |
| 166 | // Remove a single event type + callback |
| 167 | for (idx in (this._listeners[event_name] || [])) { |
| 168 | if (this._listeners[event_name][idx][1] === fn) { |
| 169 | delete this._listeners[event_name][idx]; |
| 170 | } |
| 171 | } |
| 172 | } else { |
| 173 | // Remove a single event type + callback + scope |
| 174 | for (idx in (this._listeners[event_name] || [])) { |
| 175 | if (this._listeners[event_name][idx][1] === fn && this._listeners[event_name][idx][2] === scope) { |
| 176 | delete this._listeners[event_name][idx]; |
| 177 | } |
| 178 | } |
| 179 | } |
| 180 | }; |
| 181 | |
| 182 | |
| 183 | |
| 184 | // Call all the listeners for a certain event, passing them some event data that may be changed |
| 185 | PluginInterface.prototype.emit = function (event_name, event_data) { |
| 186 | var emitter = new EmitCall(event_name, event_data); |
| 187 | var listeners = this._listeners[event_name] || []; |
| 188 | |
| 189 | // Once emitted, remove any 'once' bound listeners |
| 190 | emitter.done(function () { |
| 191 | listeners.forEach(function (listener, idx) { |
| 192 | if (listener[0] === 'once') { |
| 193 | listeners[idx] = undefined; |
| 194 | } |
| 195 | }); |
| 196 | }); |
| 197 | |
| 198 | // Emit the event to the listeners and return |
| 199 | emitter.callListeners(listeners); |
| 200 | return emitter; |
| 201 | }; |
| 202 | |
| 203 | |
| 204 | module.exports = PluginInterface; |
| 205 | |
| 206 | |
| 207 | |
| 208 | /* |
| 209 | * Example usage |
| 210 | */ |
| 211 | |
| 212 | |
| 213 | /* |
| 214 | var modules = new PluginInterface(); |
| 215 | |
| 216 | |
| 217 | |
| 218 | // A plugin |
| 219 | modules.on('client:command', function (event, data) { |
| 220 | //event.wait = true; |
| 221 | setTimeout(event.callback, 2000); |
| 222 | }); |
| 223 | |
| 224 | |
| 225 | |
| 226 | |
| 227 | // Core code that is being extended by plugins |
| 228 | var data = { |
| 229 | nick: 'prawnsalald', |
| 230 | command: '/dothis' |
| 231 | }; |
| 232 | |
| 233 | |
| 234 | modules.emit('client:command', data).done(function () { |
| 235 | console.log('Your command is: ' + data.command); |
| 236 | }); |
| 237 | */ |