Skip to content

Commit 6fa8974

Browse files
committed
fix registerPrimitive property merging (fixes #1324)
1 parent 1b8bf9b commit 6fa8974

File tree

2 files changed

+85
-44
lines changed

2 files changed

+85
-44
lines changed

‎src/extras/primitives/registerPrimitive.js‎

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ var registerElement = require('../../core/a-register-element').registerElement;
44
var utils = require('../../utils/');
55

66
var debug = utils.debug;
7-
var log = debug('extras:primitives');
7+
var log = debug('extras:primitives:debug');
88

99
module.exports = function registerPrimitive (name, definition) {
1010
name = name.toLowerCase();
@@ -46,16 +46,10 @@ module.exports = function registerPrimitive (name, definition) {
4646
var self = this;
4747
var attributes = this.attributes;
4848

49-
// Apply default components.
50-
this.componentData = cloneObject(this.defaultAttributes);
51-
Object.keys(this.componentData).forEach(function (componentName) {
52-
if (!self.hasAttribute(componentName)) {
53-
self.setAttribute(componentName, self.componentData[componentName]);
54-
}
55-
});
49+
this.applyDefaultComponents();
5650

5751
// Apply initial attributes.
58-
Object.keys(attributes).forEach(function (attributeName) {
52+
Object.keys(attributes).forEach(function applyInitial (attributeName) {
5953
var attr = attributes[attributeName];
6054
self.syncAttributeToComponent(attr.name, attr.value);
6155
});
@@ -75,6 +69,33 @@ module.exports = function registerPrimitive (name, definition) {
7569
}
7670
},
7771

72+
applyDefaultComponents: {
73+
value: function () {
74+
var self = this;
75+
var defaultData = this.defaultAttributes;
76+
77+
// Apply default components.
78+
Object.keys(defaultData).forEach(function applyDefault (componentName) {
79+
var componentData = defaultData[componentName];
80+
81+
// Set component properties individually to not overwrite user-defined components.
82+
if (componentData instanceof Object && Object.keys(componentData).length) {
83+
Object.keys(componentData).forEach(function setProperty (propName) {
84+
// Check if component property already defined.
85+
var definedData = self.getAttribute(componentName);
86+
if (definedData && definedData[propName] !== undefined) { return; }
87+
88+
self.setAttribute(componentName, propName, componentData[propName]);
89+
});
90+
return;
91+
}
92+
93+
// Component is single-property schema, just set the attribute.
94+
self.setAttribute(componentName, componentData);
95+
});
96+
}
97+
},
98+
7899
/**
79100
* If attribute is mapped to a component property, set the component property using
80101
* the attribute value.
@@ -104,20 +125,14 @@ module.exports = function registerPrimitive (name, definition) {
104125
// Run transform.
105126
value = this.getTransformedValue(attr, value);
106127

107-
// Initialize internal component data if necessary.
108-
if (!this.componentData[componentName]) {
109-
this.componentData[componentName] = this.defaultAttributes[componentName] || {};
110-
}
111-
112-
// Update internal component data.
128+
// If multi-property schema, set as update to component to not overwrite.
113129
if (propertyName) {
114-
this.componentData[componentName][propertyName] = value;
115-
} else {
116-
this.componentData[componentName] = value;
130+
this.setAttribute(componentName, propertyName, value);
131+
return;
117132
}
118133

119-
// Put component data.
120-
this.setAttribute(componentName, this.componentData[componentName]);
134+
// Single-property schema, just set the value.
135+
this.setAttribute(componentName, value);
121136
}
122137
},
123138

@@ -133,21 +148,3 @@ module.exports = function registerPrimitive (name, definition) {
133148
})
134149
});
135150
};
136-
137-
/**
138-
* Clone an object, including inner objects one-level deep.
139-
* Used for copying defaultAttributes to componentData so primitives of the same type don't
140-
* affect each others' defaultAttributes object.
141-
*/
142-
function cloneObject (obj) {
143-
var clone = {};
144-
Object.keys(obj).forEach(function (key) {
145-
var value = obj[key];
146-
if (typeof value === 'object') {
147-
clone[key] = utils.extend({}, value);
148-
} else {
149-
clone[key] = value;
150-
}
151-
});
152-
return clone;
153-
}

‎tests/extras/primitives/registerPrimitive.test.js‎

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ var registerPrimitive = require('extras/primitives/registerPrimitive');
44

55
var primitiveId = 0;
66

7-
function primitiveFactory (def, cb) {
7+
function primitiveFactory (definition, cb) {
88
var el;
99
var entity = helpers.entityFactory();
1010
var tagName = 'a-test-' + primitiveId++;
1111

12-
registerPrimitive(tagName, def);
12+
registerPrimitive(tagName, definition);
1313
el = document.createElement(tagName);
1414
el.addEventListener('loaded', function () {
1515
cb(el, tagName);
@@ -18,20 +18,64 @@ function primitiveFactory (def, cb) {
1818
}
1919

2020
suite('registerPrimitive', function () {
21-
test('default attributes initialized', function (done) {
21+
test('initializes default attributes', function (done) {
2222
primitiveFactory({
2323
defaultAttributes: {
24-
material: { color: 'tomato' },
24+
geometry: {primitive: 'box'},
25+
material: {},
2526
position: '1 2 3'
2627
}
2728
}, function (el) {
28-
assert.equal(el.getAttribute('material').color, 'tomato');
29+
assert.equal(el.getAttribute('geometry').primitive, 'box');
30+
assert.ok('material' in el.components);
2931
assert.equal(el.getAttribute('position').x, 1);
3032
done();
3133
});
3234
});
3335

34-
test('mapping proxy attributes to components', function (done) {
36+
test('merges defined components with default components', function (done) {
37+
var entity = helpers.entityFactory();
38+
var tagName = 'a-test-' + primitiveId++;
39+
registerPrimitive(tagName, {
40+
defaultAttributes: {
41+
material: {color: '#FFF', metalness: 0.63}
42+
}
43+
});
44+
45+
// Use innerHTML to set everything at once.
46+
entity.innerHTML = '<' + tagName + ' material="color: tomato"></' + tagName + '>';
47+
entity.addEventListener('loaded', function () {
48+
var material = entity.children[0].getAttribute('material');
49+
assert.equal(material.color, 'tomato');
50+
assert.equal(material.metalness, 0.63);
51+
done();
52+
});
53+
});
54+
55+
test('does not destroy defined components when proxying attributes', function (done) {
56+
var entity = helpers.entityFactory();
57+
var tag = 'a-test-' + primitiveId++;
58+
registerPrimitive(tag, {
59+
defaultAttributes: {
60+
material: {color: '#FFF'}
61+
},
62+
63+
mappings: {
64+
color: 'material.color'
65+
}
66+
});
67+
68+
// Use innerHTML to set everything at once.
69+
entity.innerHTML = '<' + tag + ' color="red" material="fog: false"></' + tag + '>';
70+
entity.addEventListener('loaded', function () {
71+
var material = entity.children[0].getAttribute('material');
72+
assert.equal(material.color, 'red');
73+
assert.equal(material.fog, 'false');
74+
done();
75+
});
76+
});
77+
78+
test('proxies attributes to components', function (done) {
3579
primitiveFactory({
3680
mappings: {
3781
color: 'material.color',

0 commit comments

Comments
 (0)