blob: baaa3b10a7eed3975a49ff4c59218a9358395675 [file] [log] [blame]
// ==========================================
// Copyright 2013 Twitter, Inc
// Licensed under The MIT License
// http://opensource.org/licenses/MIT
// ==========================================
"use strict";
define(
[
'./utils'
],
function (util) {
function parseEventArgs(instance, args) {
var element, type, callback;
var end = args.length;
if (typeof args[end - 1] === 'function') {
end -= 1;
callback = args[end];
}
if (typeof args[end - 1] === 'object') {
end -= 1;
}
if (end == 2) {
element = args[0];
type = args[1];
} else {
element = instance.node;
type = args[0];
}
return {
element: element,
type: type,
callback: callback
};
}
function matchEvent(a, b) {
return (
(a.element == b.element) &&
(a.type == b.type) &&
(b.callback == null || (a.callback == b.callback))
);
}
function Registry() {
var registry = this;
(this.reset = function() {
this.components = [];
this.allInstances = {};
this.events = [];
}).call(this);
function ComponentInfo(component) {
this.component = component;
this.attachedTo = [];
this.instances = {};
this.addInstance = function(instance) {
var instanceInfo = new InstanceInfo(instance);
this.instances[instance.identity] = instanceInfo;
this.attachedTo.push(instance.node);
return instanceInfo;
}
this.removeInstance = function(instance) {
delete this.instances[instance.identity];
var indexOfNode = this.attachedTo.indexOf(instance.node);
(indexOfNode > -1) && this.attachedTo.splice(indexOfNode, 1);
if (!this.instances.length) {
//if I hold no more instances remove me from registry
registry.removeComponentInfo(this);
}
}
this.isAttachedTo = function(node) {
return this.attachedTo.indexOf(node) > -1;
}
}
function InstanceInfo(instance) {
this.instance = instance;
this.events = [];
this.addBind = function(event) {
this.events.push(event);
registry.events.push(event);
};
this.removeBind = function(event) {
for (var i = 0, e; e = this.events[i]; i++) {
if (matchEvent(e, event)) {
this.events.splice(i, 1);
}
}
}
}
this.addInstance = function(instance) {
var component = this.findComponentInfo(instance);
if (!component) {
component = new ComponentInfo(instance.constructor);
this.components.push(component);
}
var inst = component.addInstance(instance);
this.allInstances[instance.identity] = inst;
return component;
};
this.removeInstance = function(instance) {
var index, instInfo = this.findInstanceInfo(instance);
//remove from component info
var componentInfo = this.findComponentInfo(instance);
componentInfo && componentInfo.removeInstance(instance);
//remove from registry
delete this.allInstances[instance.identity];
};
this.removeComponentInfo = function(componentInfo) {
var index = this.components.indexOf(componentInfo);
(index > -1) && this.components.splice(index, 1);
};
this.findComponentInfo = function(which) {
var component = which.attachTo ? which : which.constructor;
for (var i = 0, c; c = this.components[i]; i++) {
if (c.component === component) {
return c;
}
}
return null;
};
this.findInstanceInfo = function(instance) {
return this.allInstances[instance.identity] || null;
};
this.findInstanceInfoByNode = function(node) {
var result = [];
Object.keys(this.allInstances).forEach(function(k) {
var thisInstanceInfo = this.allInstances[k];
if(thisInstanceInfo.instance.node === node) {
result.push(thisInstanceInfo);
}
}, this);
return result;
};
this.on = function(componentOn) {
var instance = registry.findInstanceInfo(this), boundCallback;
// unpacking arguments by hand benchmarked faster
var l = arguments.length, i = 1;
var otherArgs = new Array(l - 1);
for (; i < l; i++) otherArgs[i - 1] = arguments[i];
if (instance) {
boundCallback = componentOn.apply(null, otherArgs);
if (boundCallback) {
otherArgs[otherArgs.length-1] = boundCallback;
}
var event = parseEventArgs(this, otherArgs);
instance.addBind(event);
}
};
this.off = function(el, type, callback) {
var event = parseEventArgs(this, arguments),
instance = registry.findInstanceInfo(this);
if (instance) {
instance.removeBind(event);
}
};
//debug tools may want to add advice to trigger
if (window.DEBUG && DEBUG.enabled) {
registry.trigger = new Function;
}
this.teardown = function() {
registry.removeInstance(this);
};
this.withRegistration = function() {
this.before('initialize', function() {
registry.addInstance(this);
});
this.around('on', registry.on);
this.after('off', registry.off);
//debug tools may want to add advice to trigger
window.DEBUG && DEBUG.enabled && this.after('trigger', registry.trigger);
this.after('teardown', {obj:registry, fnName:'teardown'});
};
}
return new Registry;
}
);