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