aframe
Version:
A web framework for building virtual reality experiences.
198 lines (172 loc) • 6.86 kB
JavaScript
var AEntity = require('../../core/a-entity');
var components = require('../../core/component').components;
var registerElement = require('../../core/a-register-element').registerElement;
var utils = require('../../utils/');
var debug = utils.debug;
var setComponentProperty = utils.entity.setComponentProperty;
var log = debug('extras:primitives:debug');
var warn = debug('extras:primitives:warn');
var primitives = module.exports.primitives = {};
module.exports.registerPrimitive = function registerPrimitive (name, definition) {
name = name.toLowerCase();
log('Registering <%s>', name);
// Deprecation warning for defaultAttributes usage.
if (definition.defaultAttributes) {
warn("The 'defaultAttributes' object is deprecated. Use 'defaultComponents' instead.");
}
var primitive = registerElement(name, {
prototype: Object.create(AEntity.prototype, {
defaultComponentsFromPrimitive: {
value: definition.defaultComponents || definition.defaultAttributes || {}
},
deprecated: {value: definition.deprecated || null},
deprecatedMappings: {value: definition.deprecatedMappings || {}},
mappings: {value: definition.mappings || {}},
createdCallback: {
value: function () {
if (definition.deprecated) { console.warn(definition.deprecated); }
this.resolveMappingCollisions();
}
},
/**
* If a mapping collides with a registered component name
* it renames the mapping to componentname-property
*/
resolveMappingCollisions: {
value: function () {
var mappings = this.mappings;
var self = this;
Object.keys(mappings).forEach(function resolveCollision (key) {
var newAttribute;
if (key !== key.toLowerCase()) { warn('Mapping keys should be specified in lower case. The mapping key ' + key + ' may not be recognized'); }
if (components[key]) {
newAttribute = mappings[key].replace('.', '-');
mappings[newAttribute] = mappings[key];
delete mappings[key];
console.warn('The primitive ' + self.tagName.toLowerCase() + ' has a mapping collision. ' +
'The attribute ' + key + ' has the same name as a registered component and' +
' has been renamed to ' + newAttribute);
}
});
}
},
getExtraComponents: {
value: function () {
var attr;
var data;
var i;
var mapping;
var mixins;
var path;
var self = this;
// Gather component data from default components.
data = utils.clone(this.defaultComponentsFromPrimitive);
// Factor in mixins to overwrite default components.
mixins = this.getAttribute('mixin');
if (mixins) {
mixins = mixins.trim().split(' ');
mixins.forEach(function applyMixin (mixinId) {
var mixinComponents = self.sceneEl.querySelector('#' + mixinId).componentCache;
Object.keys(mixinComponents).forEach(function setComponent (name) {
data[name] = extend(data[name], mixinComponents[name]);
});
});
}
// Gather component data from mappings.
for (i = 0; i < this.attributes.length; i++) {
attr = this.attributes[i];
mapping = this.mappings[attr.name];
if (mapping) {
path = utils.entity.getComponentPropertyPath(mapping);
if (path.constructor === Array) {
data[path[0]] = data[path[0]] || {};
data[path[0]][path[1]] = attr.value.trim();
} else {
data[path] = attr.value.trim();
}
continue;
}
}
return data;
/**
* For the base to be extensible, both objects must be pure JavaScript objects.
* The function assumes that base is undefined, or null or a pure object.
*/
function extend (base, extension) {
if (isUndefined(base)) {
return copy(extension);
}
if (isUndefined(extension)) {
return copy(base);
}
if (isPureObject(base) && isPureObject(extension)) {
return utils.extendDeep(base, extension);
}
return copy(extension);
}
function isUndefined (value) {
return typeof value === 'undefined';
}
function copy (value) {
if (isPureObject(value)) {
return utils.extendDeep({}, value);
}
return value;
}
function isPureObject (value) {
return value !== null && value.constructor === Object;
}
}
},
/**
* Sync to attribute to component property whenever mapped attribute changes.
* If attribute is mapped to a component property, set the component property using
* the attribute value.
*/
attributeChangedCallback: {
value: function (attr, oldVal, value) {
var componentName = this.mappings[attr];
if (attr in this.deprecatedMappings) {
console.warn(this.deprecatedMappings[attr]);
}
if (!attr || !componentName) { return; }
// Set value.
setComponentProperty(this, componentName, value);
}
}
})
});
// Store.
primitives[name] = primitive;
return primitive;
};
/**
* Add component mappings using schema.
*/
function addComponentMapping (componentName, mappings) {
var schema = components[componentName].schema;
Object.keys(schema).map(function (prop) {
// Hyphenate where there is camelCase.
var attrName = prop.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
// If there is a mapping collision, prefix with component name and hyphen.
if (mappings[attrName] !== undefined) { attrName = componentName + '-' + prop; }
mappings[attrName] = componentName + '.' + prop;
});
}
/**
* Helper to define a primitive, building mappings using a component schema.
*/
function definePrimitive (tagName, defaultComponents, mappings) {
// If no initial mappings provided, start from empty map.
mappings = mappings || {};
// From the default components, add mapping automagically.
Object.keys(defaultComponents).map(function buildMappings (componentName) {
addComponentMapping(componentName, mappings);
});
// Register the primitive.
module.exports.registerPrimitive(tagName, utils.extendDeep({}, null, {
defaultComponents: defaultComponents,
mappings: mappings
}));
}
module.exports.definePrimitive = definePrimitive;