Created
January 24, 2018 00:49
-
-
Save plong0/7e015409692ec389348c8f3722d06c2a to your computer and use it in GitHub Desktop.
Simple JavaScript events system (with handler filtering on data objects)
This file contains 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
/* 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