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