Created
December 6, 2012 18:53
-
-
Save pgraham/4227071 to your computer and use it in GitHub Desktop.
Make any object observable with subtyped events
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
test( "test interface", function () { | |
var eventRegistry = window.eventRegistry(); | |
ok(eventRegistry.hasOwnProperty('on'), "No `on` method found"); | |
ok(eventRegistry.hasOwnProperty('trigger'), "No `trigger` method found"); | |
}); | |
test( "test binding", function () { | |
var eventRegistry = window.eventRegistry(); | |
expect( 1 ); | |
eventRegistry.on('event', function () { | |
ok( true, "This needs to be called"); | |
}); | |
eventRegistry.trigger('event'); | |
}); | |
test( "sub event binding", function () { | |
var eventRegistry = window.eventRegistry(); | |
expect( 3 ); | |
eventRegistry.on('event', function () { | |
ok( true, "This should be called"); | |
}); | |
eventRegistry.on('event.triggered', function () { | |
ok( true, "This should be called"); | |
}); | |
eventRegistry.on('event.triggered.triggered', function () { | |
ok( true, "This should be called"); | |
}); | |
eventRegistry.on('event.triggered.notTriggered', function () { | |
ok( false, "This should not be called"); | |
}); | |
eventRegistry.on('event.notTriggered', function () { | |
ok( false, "This should not be called"); | |
}); | |
eventRegistry.trigger('event.triggered.triggered'); | |
}); | |
test( "sub event invocation order", function () { | |
var eventRegistry = window.eventRegistry(); | |
var level1 = false, level2 = false, level3 = false; | |
eventRegistry.on('leastSpecific', function () { | |
ok(!level1, "Level 1 handler should not have been invoked"); | |
ok(level2, "Level 2 handler should have been invoked"); | |
ok(level3, "Level 3 handler should have been invoked"); | |
level1 = true; | |
}); | |
eventRegistry.on('leastSpecific.moreSpecific', function () { | |
ok(!level1, "Level 1 handler should not have been invoked"); | |
ok(!level2, "Level 2 handler should not have been invoked"); | |
ok(level3, "Level 3 handler should have been invoked"); | |
level2 = true; | |
}); | |
eventRegistry.on('leastSpecific.moreSpecific.mostSpecific', function () { | |
ok(!level1, "Level 1 handler should not have been invoked"); | |
ok(!level2, "Level 2 handler should not have been invoked"); | |
ok(!level3, "Level 3 handler should not have been invoked"); | |
level3 = true; | |
}); | |
eventRegistry.trigger('leastSpecific.moreSpecific.mostSpecific'); | |
ok( level1 && level2 && level3 ); | |
}); | |
test( "event data: one parameter", function () { | |
var eventRegistry = window.eventRegistry(); | |
var evData = { value: 'some data' }; | |
expect( 2 ); | |
eventRegistry.on('a', function (ev, data) { | |
ok(ev.type === 'a', "Event object does not contain proper type"); | |
ok(data === evData, "Data is not the right object"); | |
}); | |
eventRegistry.trigger('a', [ evData ]); | |
}); | |
test( "event data: multiple parameters", function () { | |
var eventRegistry = window.eventRegistry(); | |
var evData1 = { value: 'some data' }, evData2 = { value: 'some more data' }; | |
expect( 3 ); | |
eventRegistry.on('a', function (ev, data1, data2) { | |
ok(ev.type === 'a', "Event object does not contain proper type"); | |
ok(data1 === evData1, "`data1` is not the right object"); | |
ok(data2 === evData2, "`data2` is not the right object"); | |
}); | |
eventRegistry.trigger('a', [ evData1, evData2 ]); | |
}); | |
test( "observable.one", function () { | |
var eventRegistry = window.eventRegistry(); | |
var evData = { value: 'some data' }; | |
var invoked = false; | |
expect( 2 ); | |
eventRegistry.one('a', function (ev, data) { | |
ok( data === evData, "`data` is not passed" ); | |
ok( !invoked, "This should only be called once" ); | |
invoked = true; | |
}); | |
eventRegistry.trigger('a', [ evData ]); | |
eventRegistry.trigger('a', [ evData ]); | |
}); | |
test( "no handlers", function () { | |
var eventRegistry = window.eventRegistry(); | |
eventRegistry.trigger('event'); | |
ok( true, "As long as there is no error everything is good" ); | |
}); | |
test( "no subtype handlers", function () { | |
var eventRegistry = window.eventRegistry(); | |
eventRegistry.on('event', function () { | |
ok( true, "This should be called but that's not what is being tested" ); | |
}); | |
eventRegistry.trigger('event.subtype'); | |
ok( true, "As long as there is no error everything is good" ); | |
}); |
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
/** | |
* Make object observable with subtyped events. | |
* | |
* Event hierarchy is specified using dots (.) | |
* | |
* observable({}).on('type.child.leaf', function () {}); | |
* | |
* When an event of type `type.child.leaf` is triggered, then handlers for | |
* events of type `type` and `type.child` will also be triggered. Event | |
* handlers registered for a deeper event type are considered to be "more | |
* specific" since they will be triggered by fewer event types. When an event | |
* with a substype is triggered, more specific handlers will be invoked first. | |
*/ | |
(function (exports, undefined) { | |
"use strict"; | |
function eventRegistry() { | |
var handlers = [], registry = {}, that = {}; | |
function removeHandler(handler) { | |
var i, len; | |
for (i = 0, len = handlers.length; i < len; i++) { | |
if (handlers[i] === handler) { | |
handlers.splice(i, 1); | |
break; | |
} | |
} | |
} | |
that.addHandler = function (handler, subtypes) { | |
if (subtypes.length === 0) { | |
handlers.push(handler); | |
return; | |
} | |
var basetype = subtypes.shift(); | |
if (!registry.hasOwnProperty(basetype)) { | |
registry[basetype] = eventRegistry(); | |
} | |
registry[basetype].addHandler(handler, subtypes); | |
}; | |
that.getHandlers = function (subtypes) { | |
if (subtypes.length === 0) { | |
return [].concat(handlers); | |
} | |
var basetype = subtypes.shift(); | |
if (!registry.hasOwnProperty(basetype)) { | |
return [].concat(handlers); | |
} | |
return registry[basetype].getHandlers(subtypes).concat(handlers); | |
}; | |
that.removeHandler = function (handler, subtypes) { | |
if (subtypes.length === 0) { | |
removeHandler(handler); | |
return; | |
} | |
registry[subtypes.shift()].removeHandler(handler, subtypes); | |
}; | |
return that; | |
} | |
exports.observable = function (that) { | |
var registry = eventRegistry(); | |
that.trigger = function (event, data) { | |
var handlers = registry.getHandlers(event.split('.')), i, len; | |
for (i = 0, len = handlers.length; i < len; i++) { | |
handlers[i].apply(this, [ { type: event } ].concat(data)); | |
} | |
return this; | |
}; | |
that.on = function (type, handler) { | |
var events = type.split(' '), i, len; | |
for (i = 0, len = events.length; i < len; i++) { | |
registry.addHandler(handler, events[i].split('.')); | |
} | |
return this; | |
}; | |
that.one = function (type, handler) { | |
var wrapper = function () { | |
handler.apply(this, arguments); | |
that.off(type, wrapper); | |
}; | |
that.on(type, wrapper); | |
}; | |
that.off = function (type, handler) { | |
var events = type.split(' '), i, len; | |
for (i = 0, len = events.length; i < len; i++) { | |
registry.removeHandler(handler, events[i].split('.')); | |
} | |
return this; | |
}; | |
return that; | |
}; | |
/** | |
* Convenience function for creating an empty observable object that can be | |
* used as an EventBus. | |
*/ | |
exports.eventRegistry = function () { | |
return observable({}); | |
}; | |
} (window)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment