Merge branch 'development'
[KiwiIRC.git] / server / plugininterface.js
CommitLineData
18c1382b
D
1/*
2 * The same functionality as EventEmitter but with the inclusion of callbacks
3 */
4a4c1049 4
18c1382b
D
5
6
7function 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
18PluginInterface.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
25PluginInterface.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
32PluginInterface.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
62PluginInterface.prototype.getListeners = function(event_name) {
63 return this._listeners[event_name] || [];
64};
65
66
67
68PluginInterface.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
78PluginInterface.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
92PluginInterface.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
125PluginInterface.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
250if (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/*
262var modules = new PluginInterface();
4a4c1049 263
18c1382b
D
264
265
266// A plugin
267modules.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
276var data = {
277 nick: 'prawnsalald',
278 command: '/dothis'
279};
280
281modules.emit('irc message', data).done(function () {
282 console.log('Your command is: ' + data.command);
283});
284*/