BSD and expact license modified
[KiwiIRC.git] / server / plugininterface.js
1 /*
2 * The same functionality as EventEmitter but with the inclusion of callbacks
3 */
4
5
6
7 function PluginInterface () {
8 // Holder for all the bound listeners by this module
9 this._listeners = {};
10
11 // Event proxies
12 this._parent = null;
13 this._children = [];
14 }
15
16
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 }
49 }
50 } else {
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 }
57 }
58 };
59
60
61
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
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) {
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));
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;
120 };
121
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();
195 }
196
197
198
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]();
208 }
209 }
210
211
212
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
222 return this;
223 }
224
225
226
227 function addPreventedFunc(fn) {
228 // Only accept functions
229 if (typeof fn !== 'function') return false;
230
231 prevented_fn.push(fn);
232
233 // If we have already completed the emits, call this now
234 if (completed && prevented) fn();
235
236 return this;
237 }
238
239
240 return {
241 callListeners: callListeners,
242 then: addCompletedFunc,
243 catch: addPreventedFunc
244 };
245 };
246
247
248
249 // If running a node module, set the exports
250 if (typeof module === 'object' && typeof module.exports !== 'undefined') {
251 module.exports = PluginInterface;
252 }
253
254
255
256 /*
257 * Example usage
258 */
259
260
261 /*
262 var modules = new PluginInterface();
263
264
265
266 // A plugin
267 modules.on('irc message', function (event, data) {
268 //event.wait = true;
269 setTimeout(event.callback, 2000);
270 });
271
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 */