Skip to content

Instantly share code, notes, and snippets.

@plong0
Created January 24, 2018 00:49
Show Gist options
  • Save plong0/7e015409692ec389348c8f3722d06c2a to your computer and use it in GitHub Desktop.
Save plong0/7e015409692ec389348c8f3722d06c2a to your computer and use it in GitHub Desktop.
Simple JavaScript events system (with handler filtering on data objects)
/* js-events-demo-01.js
Simple JavaScript events system (with handler filtering on data objects)
Author: Pat Long
Date: 2018/01/23
License: CC-BY-SA-4.0
*/
// Events manager:
var Events = {
callback: {},
on: function(type, filter, callback, id) {
if (type && typeof callback == 'function') {
if (!this.callback[type]) {
this.callback[type] = {};
}
if (!id) {
// generate unique id
id = this.Utils.ID.getUnique(type, this.callback[type]);
}
if (id) {
if (!this.callback[type][id]) {
// create new entry if id doesn't exist
this.callback[type][id] = {
id: id,
filter: filter,
callback: callback
};
}
// return entry at id
return this.callback[type][id];
}
}
return null;
},
off: function(type, id) {
if (type && id && this.callback[type] && this.callback[type][id]) {
var handler = this.callback[type][id]; // cache handler to return later
this.callback[type][id] = undefined; // detach handler from list
delete this.callback[type][id]; // remove id entry from list
var ids = Object.keys(this.callback[type]);
if (!ids || !ids.length) {
// cleanup empty callback[type]
this.callback[type] = undefined;
delete this.callback[type];
}
// return the lifted entry
return handler;
}
return null;
},
emit: function(type, args, item) {
if (type && this.callback[type]) {
for (var id in this.callback[type]) {
var handler = this.callback[type][id];
// verify the handler has a callback and that the item passes any filter the handler has
if (typeof handler.callback == 'function' && (!item || !handler.filter
|| (item && handler.filter && this.Utils.Item.passesFilter(item, handler.filter)))) {
// run the callback for the item
handler.callback(args, item);
}
}
}
},
Utils: {
ID: {
getUnique(baseID, inList, max=-1) {
var tryCount = 0;
var tryID = baseID;
while (inList[tryID] && (max < 0 || tryCount < max)) {
tryID = baseID+'-'+(++tryCount);
}
if (max >= 0 && inList[tryID]) {
return undefined;
}
return tryID;
}
},
Item: {
passesFilter: function(item, filter) {
if (!filter) {
// no filters
return true;
}
if (item) {
// basic filter is true until proven false
var result = true;
for (var prop in filter) {
// handle different cases of filter vs value to prove false
if (Array.isArray(filter[prop]) && !Array.isArray(item[prop])) {
// array filter value vs non-array data value
if (filter[prop].indexOf(item[prop]) == -1) {
// array does not contain data value
result = false;
}
}
else if (filter[prop].constructor == RegExp && typeof item[prop] == 'string') {
// regular expression filter vs string data value
if (!filter[prop].test(item[prop])) {
result = false;
}
}
else if (typeof filter[prop] == 'function') {
// function filter vs any data value
if (!filter[prop](item[prop])) {
result = false;
}
}
else if (item[prop] != filter[prop]) {
// simple non-equality check as last resort
result = false;
}
}
return result;
}
return false;
}
}
}
};
// Demonstration:
// some arbitrary event handlers
var handlers = {
demoA: function(args, data) {
console.log('[demo handler A] ( '+JSON.stringify(args)+' ) ON => '+JSON.stringify(data));
},
demoB: function(args, data) {
console.log('[demo handler B] ( '+JSON.stringify(args)+' ) ON => '+JSON.stringify(data));
}
};
// register handler with no (null) filter
var handlerA = Events.on('demo', null, handlers.demoA);
// register handler that runs for data items with property foo == 'bar'
var handlerB = Events.on('demo', {foo: 'bar'}, handlers.demoB);
// notice we track the return values - this is so they can be managed (dropped, inspected, altered, etc)
// emit some "demo" type events
console.log('Emitting first "demo" event...');
Events.emit('demo', {'message': 'Hello world!'}, {id: 42, foo: 'buz'}); // foo != 'bar' (only handler A should run)
console.log('Emitting second "demo" event');
Events.emit('demo', {'message': 'Hello again world!'}, {id: 42, foo: 'bar'}); // foo == 'bar' (both A and B should run)
// drop handlerB
console.log('Dropping "demo" handler ['+handlerB.id+']');
var handler = Events.off('demo', handlerB.id);
console.log('Dropped "demo" handler ['+handler.id+']');
// and try one more "demo" type event
console.log('Emitting third "demo" event');
Events.emit('demo', {'message': 'Hello Mars!'}, {id: 42, foo: 'bar'}); // foo == 'bar' (but B was dropped!)
// finished for now
console.log('Demo complete.');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment