Last active
August 29, 2015 14:01
-
-
Save jpiccari/c29b33eb757effee76aa to your computer and use it in GitHub Desktop.
AMD EventEmitter module that supports event filters (a.k.a namespaced events). Extends object passed to constructor. There is also a global emitter accessible via EventEmitter.global. (565 bytes minified, gzipped)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define( | |
'EventEmitter', | |
function() { | |
'use strict'; | |
/** | |
* Parse event types into topic and filters. | |
*/ | |
function parseEvent(event) { | |
event = event.split('.'); | |
return { | |
topic: event.shift(), | |
/* | |
* Sorting the list of filters is pretty fast (O(n log n)) and | |
* allows us to use a simple and fast algorithim for matching | |
* up two sets of filters later. | |
*/ | |
filters: event.sort() | |
}; | |
} | |
/** | |
* Checks if all filters listed in target also exist in test. | |
*/ | |
function matchFilters(target, test) { | |
var i, | |
j; | |
if (target.length <= test.length) { | |
// This for-loop is made possible by pre-sorting the filter arrays above | |
for (i = test.indexOf(target[0]), j = 0; i < test.length && j < target.length; i++) { | |
/* | |
* If the test string is less than the target string, this | |
* merely means we haven't started to any match yet and we | |
* we should increment the test index (i) but not the target | |
* index (j). We have no condition for this case, since the | |
* index for test will be incremented no matter what. | |
* | |
* If test and target strings are equal we can continue our | |
* match and increment the index for target (j), likewise the | |
* test index (i) will be implicitly incremeted at the end of | |
* the loop. | |
*/ | |
if (test[i] === target[j]) { | |
j++; | |
} | |
/* | |
* If the test string is greater (alphabetically speaking) | |
* than the target string, it means we are no longer matching | |
* successfully and we should break. | |
*/ | |
if(test[i] > target[j]) { | |
break; | |
} | |
} | |
// This is true only if all target filters were satisfied | |
return j === target.length; | |
} | |
return false; | |
} | |
/** | |
* EventEmitter method for setting an event. | |
*/ | |
function on(event, fn, one) { | |
if (typeof fn === 'function') { | |
event = parseEvent(event); | |
if (!this._topics[event.topic]) { | |
this._topics[event.topic] = []; | |
} | |
this._topics[event.topic].push({ | |
filters: event.filters, | |
fn: fn, | |
one: one | |
}); | |
} | |
return this; | |
} | |
/** | |
* EventEmitter method for setting an event which only runs once. | |
*/ | |
function one(event, fn) { | |
// Simply call EventEmitter.on() with the same options and 'one' set to true | |
return this.on(event, fn, true); | |
} | |
/** | |
* EventEmitter method for removing an event. | |
*/ | |
function off(event) { | |
event = parseEvent(event); | |
var topic = this._topics[event.topic], | |
i; | |
if (!event.filters.length) { | |
topic.length = 0; | |
} else { | |
for (i = 0; i < topic.length; i++) { | |
// If the filters match, splice out the event object. | |
if (matchFilters(event.filters, topic[i].filters)) { | |
topic.splice(i--, 1); | |
} | |
} | |
} | |
return this; | |
} | |
/** | |
* EventEmitter method for triggering an event. | |
*/ | |
function trigger(event) { | |
var triggerCall, | |
topics, | |
i, | |
j; | |
// Enqueue the trigger call | |
trigger._queue.push({ obj: this, args: Array.apply(0, arguments) }); | |
// If we aren't currently flushing the trigger queue, then flush it | |
if (!trigger.isFlushing) { | |
trigger.isFlushing = true; | |
for (i = 0; i < trigger._queue.length; i++) { | |
triggerCall = trigger._queue[i]; | |
event = parseEvent(triggerCall.args.shift()); | |
topics = triggerCall.obj._topics[event.topic]; | |
for (j = 0; topics && j < topics.length; j++) { | |
if (matchFilters(event.filters, topics[j].filters)) { | |
topics[j].fn.apply(triggerCall.obj, triggerCall.args); | |
// If the 'one' property was set, splice out the event object. | |
if (topics[j].one) { | |
topics.splice(j--, 1); | |
} | |
} | |
} | |
} | |
trigger.isFlushing = false; | |
} | |
return this; | |
} | |
trigger._queue = []; | |
function EventEmitter(obj) { | |
// Test for objects while also weeding out null | |
if (obj !== Object(obj)) { | |
throw new TypeError(Object.prototype.toString.call(obj) + ' is not an object.'); | |
} | |
// Don't overwrite obj._topics if is already an EventEmitter | |
obj._topics = obj._topics || {}; | |
/* | |
* Setting the rest of the properties to reference the functions above | |
* achieves a similar effect to a prototype, without the possiblity | |
* of overwriting an object's current prototype. This gives us the | |
* most flexibilty and efficient memory usage. | |
*/ | |
obj.on = on; | |
obj.one = one; | |
obj.off = off; | |
obj.trigger = trigger; | |
return obj; | |
} | |
// Global EventEmitter for more of a PubSub flavor | |
EventEmitter.global = EventEmitter({}); | |
// Expose the interface | |
return EventEmitter; | |
} | |
); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
define("EventEmitter",function(){function g(a){a=a.split(".");return{a:a.shift(),filters:a.sort()}}function k(a,b){var c,d;if(a.length<=b.length){c=b.indexOf(a[0]);for(d=0;c<b.length&&d<a.length&&!(b[c]===a[d]&&d++,b[c]>a[d]);c++);return d===a.length}return!1}function l(a,b,c){"function"===typeof b&&(a=g(a),this._topics[a.a]||(this._topics[a.a]=[]),this._topics[a.a].push({filters:a.filters,f:b,g:c}));return this}function m(a,b){return this.on(a,b,!0)}function n(a){a=g(a);var b=this._topics[a.a],c; | |
if(a.filters.length)for(c=0;c<b.length;c++)k(a.filters,b[c].filters)&&b.splice(c--,1);else b.length=0;return this}function e(a){var b,c,d,f;e.b.push({e:this,c:Array.apply(0,arguments)});if(!e.d){e.d=!0;for(d=0;d<e.b.length;d++)for(b=e.b[d],a=g(b.c.shift()),c=b.e._topics[a.a],f=0;f<c.length;f++)k(a.filters,c[f].filters)&&(c[f].f.apply(b.e,b.c),c[f].g&&c.splice(f--,1));e.d=!1}return this}function h(a){if(a!==Object(a))throw new TypeError({}.toString.call(a)+" is not an object.");a._topics=a._topics|| | |
{};a.on=l;a.one=m;a.off=n;a.trigger=e;return a}e.b=[];h.global=h({});return h}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment