Skip to content

Instantly share code, notes, and snippets.

@oconnore
Created July 30, 2013 21:43
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oconnore/6117278 to your computer and use it in GitHub Desktop.
Save oconnore/6117278 to your computer and use it in GitHub Desktop.
Backbone style events
(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