Created
July 30, 2013 21:43
-
-
Save oconnore/6117278 to your computer and use it in GitHub Desktop.
Backbone style events
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
(function(exports) { | |
'use strict'; | |
/* | |
activecontexts is a two level map | |
Map(object -> Map(context -> true)) | |
The second map is simply used for efficient set lookup (has) | |
*/ | |
var activecontexts = new WeakMap(); | |
function EventContext() { | |
/* | |
Here we have a two level map: | |
Map(object -> Map(type -> callbackList)) | |
*/ | |
this.registered = new Map(); | |
} | |
EventContext.prototype = { | |
// --------------------------------------------------------- | |
// Maintain active context WeakMap | |
registerActive: function(object) { | |
// register this context as active on this object | |
var innermap = activecontexts.get(object); | |
if (!innermap) { | |
innermap = new Map(); | |
innermap.set(this, true); | |
activecontexts.set(object, innermap); | |
} else { | |
innermap.set(this, true); | |
} | |
}, | |
cleanActive: function(object) { | |
// check if we are active on this object, and update | |
// the activecontexts map appropriately | |
var innermap = activecontexts.get(object); | |
if (innermap) { | |
if (!this.checkActive(object)) { | |
innermap.delete(this); | |
var size; | |
// FF18 defines size to be a method, so we need to test here: | |
// Remove with FF18 support | |
if (typeof innermap.size === 'function') { | |
size = innermap.size(); | |
} else { | |
size = innermap.size; | |
} | |
// If we have no active callbacks, remove this object | |
// from the WeakMap | |
if (size === 0) { | |
activecontexts.delete(object); | |
} | |
} | |
} | |
}, | |
// --------------------------------------------------------- | |
// Event type splits | |
spliteventtype: function(type) { | |
if (typeof type === 'string') { | |
var unique = new Map(); | |
return (type || '').split(' ').filter(function(x) { | |
var hit = unique.has(x); | |
unique.set(x, true); | |
return !hit && x !== ''; | |
}); | |
} | |
return [type]; | |
}, | |
// --------------------------------------------------------- | |
// Register events on this context | |
on: function(object, typeset, callback) { | |
if (!callback) { | |
return; | |
} | |
var innermap = this.registered.get(object); | |
if (!innermap) { | |
innermap = new Map(); | |
this.registered.set(object, innermap); | |
} | |
for (var type of this.spliteventtype(typeset)) { | |
var callbackList = innermap.get(type); | |
if (!callbackList) { | |
callbackList = [callback]; | |
innermap.set(type, callbackList); | |
} else if (callbackList.indexOf(callback) === -1) { | |
callbackList.push(callback); | |
} | |
} | |
this.registerActive(object); | |
}, | |
off: function(object, typeset, callback) { | |
var innermap = this.registered.get(object); | |
if (innermap) { | |
if (typeset) { | |
// remove event callbacks of a certain type | |
for (var type of this.spliteventtype(typeset)) { | |
if (innermap.has(type)) { | |
if (callback) { | |
// search for the callback | |
var callbackList = innermap.get(type); | |
for (var i = 0; i < callbackList.length; i++) { | |
if (callbackList[i] === callback) { | |
callbackList.splice(i, 1); | |
return; | |
} | |
} | |
} else { | |
// remove all callbacks of this type | |
innermap.delete(type); | |
} | |
} | |
} | |
} else { | |
// remove all event callbacks | |
this.registered.delete(object); | |
} | |
} | |
this.cleanActive(object); | |
}, | |
checkActive: function(object) { | |
return this.registered.has(object); | |
}, | |
// --------------------------------------------------------- | |
// Trigger events for this context | |
trigger: function(object, typeset, args) { | |
var innermap = this.registered.get(object); | |
if (innermap) { | |
for (var type of this.spliteventtype(typeset) || []) { | |
var callbackList = innermap.get(type); | |
if (callbackList) { | |
for (var callback of callbackList) { | |
setTimeout((function(type, callback) { | |
return function() { | |
callback && callback.apply(object, [type].concat(args)); | |
}; | |
})(type, callback), 0); | |
} | |
} | |
} | |
} | |
} | |
}; | |
var defaultContext = new EventContext(); | |
// --------------------------------------------------------- | |
// Mixin this object! | |
var EventMixin = { | |
on: function em_on(type, callback, context) { | |
if (!context) { | |
context = defaultContext; | |
} | |
context.on(this, type, callback); | |
}, | |
off: function em_off(type, callback, context) { | |
if (!context) { | |
context = defaultContext; | |
} | |
context.off(this, type, callback); | |
}, | |
trigger: function em_trigger(type, args) { | |
args = Array.prototype.slice.call(arguments, 1); | |
for (var ctx of activecontexts.get(this) || []) { | |
ctx[0].trigger(this, type, args); | |
} | |
} | |
}; | |
// --------------------------------------------------------- | |
// Exports | |
exports.EventMixin = EventMixin; | |
exports.EventContext = EventContext; | |
exports.EventContext.default = defaultContext; | |
})(this); | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment