Merge pull request #432 from aarontc/development
[KiwiIRC.git] / server / plugininterface.js
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 */