aframe
Version:
A web framework for building virtual reality experiences.
186 lines (163 loc) • 6.16 kB
JavaScript
/*
------------------------------------------------------------
------------- WARNING WARNING WARNING WARNING --------------
------------------------------------------------------------
This module wraps registerElement to deal with components that inherit from
`ANode` and `AEntity`. It's a pass through in any other case.
It wraps some of the prototype methods of the created element to make sure
that the corresponding functions in the base prototypes (`AEntity` and `ANode`)
are also invoked. The method in the base prototype is always called before the one
in the derived prototype.
*/
// Polyfill `document.registerElement`.
require('document-register-element');
var ANode; // Must declare before AEntity. Initialized at the bottom.
var AEntity;
var knownTags = module.exports.knownTags = {};
function addTagName (tagName) {
knownTags[tagName.toLowerCase()] = true;
}
/**
* Return whether the element type is one of our known registered ones.
*
* @param {string} node - The name of the tag to register.
* @returns {boolean} Whether the tag name matches that of our registered custom elements.
*/
module.exports.isNode = function (node) {
return node.tagName.toLowerCase() in knownTags || node.isNode;
};
/**
* @param {string} tagName - The name of the tag to register.
* @param {object} obj - The prototype of the new element.
* @returns {object} The prototype of the new element.
*/
module.exports.registerElement = function (tagName, obj) {
var proto = Object.getPrototypeOf(obj.prototype);
var newObj = obj;
var isANode = ANode && proto === ANode.prototype;
var isAEntity = AEntity && proto === AEntity.prototype;
if (isANode || isAEntity) { addTagName(tagName); }
// Wrap if element inherits from `ANode`.
if (isANode) {
newObj = wrapANodeMethods(obj.prototype);
newObj = {prototype: Object.create(proto, newObj)};
}
// Wrap if element inherits from `AEntity`.
if (isAEntity) {
newObj = wrapAEntityMethods(obj.prototype);
newObj = {prototype: Object.create(proto, newObj)};
}
// Give all functions their proper name.
Object.getOwnPropertyNames(newObj.prototype).forEach(function (propName) {
var propVal = newObj.prototype[propName];
if (typeof propVal === 'function') {
propVal.displayName = propName;
}
});
return document.registerElement(tagName, newObj);
};
/**
* Wrap some obj methods to call those on `ANode` base prototype.
*
* @param {object} obj - Object that contains the methods that will be wrapped.
* @return {object} An object with the same properties as the input parameter but
* with some of methods wrapped.
*/
function wrapANodeMethods (obj) {
var newObj = {};
var ANodeMethods = [
'attachedCallback',
'attributeChangedCallback',
'createdCallback',
'detachedCallback'
];
wrapMethods(newObj, ANodeMethods, obj, ANode.prototype);
copyProperties(obj, newObj);
return newObj;
}
/**
* This wraps some of the obj methods to call those on `AEntity` base prototype.
*
* @param {object} obj - The objects that contains the methods that will be wrapped.
* @return {object} - An object with the same properties as the input parameter but
* with some of methods wrapped.
*/
function wrapAEntityMethods (obj) {
var newObj = {};
var ANodeMethods = [
'attachedCallback',
'attributeChangedCallback',
'createdCallback',
'detachedCallback'
];
var AEntityMethods = [
'attachedCallback',
'attributeChangedCallback',
'createdCallback',
'detachedCallback'
];
wrapMethods(newObj, ANodeMethods, obj, ANode.prototype);
wrapMethods(newObj, AEntityMethods, obj, AEntity.prototype);
// Copies the remaining properties into the new object.
copyProperties(obj, newObj);
return newObj;
}
/**
* Wrap a list a methods to ensure that those in the base prototype are called
* before the derived one.
*
* @param {object} targetObj - Object that will contain the wrapped methods.
* @param {array} methodList - List of methods from the derivedObj that will be wrapped.
* @param {object} derivedObject - Object that inherits from the baseObj.
* @param {object} baseObj - Object that derivedObj inherits from.
*/
function wrapMethods (targetObj, methodList, derivedObj, baseObj) {
methodList.forEach(function (methodName) {
wrapMethod(targetObj, methodName, derivedObj, baseObj);
});
}
module.exports.wrapMethods = wrapMethods;
/**
* Wrap one method to ensure that the one in the base prototype is called before
* the one in the derived one.
*
* @param {object} obj - Object that will contain the wrapped method.
* @param {string} methodName - The name of the method that will be wrapped.
* @param {object} derivedObject - Object that inherits from the baseObj.
* @param {object} baseObj - Object that derivedObj inherits from.
*/
function wrapMethod (obj, methodName, derivedObj, baseObj) {
var derivedMethod = derivedObj[methodName];
var baseMethod = baseObj[methodName];
// Derived prototype does not define method, no need to wrap.
if (!derivedMethod || !baseMethod) { return; }
// Derived prototype doesn't override the one in the base one, no need to wrap.
if (derivedMethod === baseMethod) { return; }
// Wrap to ensure the base method is called before the one in the derived prototype.
obj[methodName] = {
value: function wrappedMethod () {
baseMethod.apply(this, arguments);
return derivedMethod.apply(this, arguments);
},
writable: window.debug
};
}
/**
* It copies the properties from source to destination object if they don't
* exist already.
*
* @param {object} source - The object where properties are copied from.
* @param {type} destination - The object where properties are copied to.
*/
function copyProperties (source, destination) {
var props = Object.getOwnPropertyNames(source);
props.forEach(function (prop) {
var desc;
if (!destination[prop]) {
desc = Object.getOwnPropertyDescriptor(source, prop);
destination[prop] = {value: source[prop], writable: desc.writable};
}
});
}
ANode = require('./a-node');
AEntity = require('./a-entity');