Skip to content

Commit 7ddf054

Browse files
committed
mixin composition + string split cache util (fixes aframevr#3270) (aframevr#3305)
* mixin composition * fix mixin composition tests for firefox
1 parent a9978b7 commit 7ddf054

File tree

5 files changed

+281
-48
lines changed

5 files changed

+281
-48
lines changed

src/core/a-entity.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,9 @@ var proto = Object.create(ANode.prototype, {
602602
return;
603603
}
604604
if (attr === 'mixin') {
605+
// Ignore if `<a-node>` code is just updating computed mixin in the DOM.
606+
if (newVal === this.computedMixinStr) { return; }
605607
this.mixinUpdate(newVal, oldVal);
606-
return;
607608
}
608609
}
609610
},
@@ -643,8 +644,8 @@ var proto = Object.create(ANode.prototype, {
643644

644645
// Not a component. Normal set attribute.
645646
if (!COMPONENTS[componentName]) {
646-
ANode.prototype.setAttribute.call(this, attrName, arg1);
647647
if (attrName === 'mixin') { this.mixinUpdate(arg1); }
648+
ANode.prototype.setAttribute.call(this, attrName, arg1);
648649
return;
649650
}
650651

src/core/a-mixin.js

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
var ANode = require('./a-node');
22
var registerElement = require('./a-register-element').registerElement;
33
var components = require('./component').components;
4+
var utils = require('../utils');
45

56
var MULTIPLE_COMPONENT_DELIMITER = '__';
67

@@ -14,6 +15,7 @@ module.exports = registerElement('a-mixin', {
1415
value: function () {
1516
this.componentCache = {};
1617
this.id = this.getAttribute('id');
18+
this.isMixin = true;
1719
}
1820
},
1921

@@ -37,8 +39,8 @@ module.exports = registerElement('a-mixin', {
3739
*/
3840
setAttribute: {
3941
value: function (attr, value) {
40-
this.cacheAttribute(attr, value);
4142
window.HTMLElement.prototype.setAttribute.call(this, attr, value);
43+
this.cacheAttribute(attr, value);
4244
}
4345
},
4446

@@ -47,8 +49,12 @@ module.exports = registerElement('a-mixin', {
4749
*/
4850
cacheAttribute: {
4951
value: function (attr, value) {
50-
var componentName = attr.split(MULTIPLE_COMPONENT_DELIMITER)[0];
51-
var component = components[componentName];
52+
var component;
53+
var componentName;
54+
55+
// Get component data.
56+
componentName = utils.split(attr, MULTIPLE_COMPONENT_DELIMITER)[0];
57+
component = components[componentName];
5258
if (!component) { return; }
5359
if (value === undefined) {
5460
value = window.HTMLElement.prototype.getAttribute.call(this, attr);
@@ -89,15 +95,17 @@ module.exports = registerElement('a-mixin', {
8995
*/
9096
updateEntities: {
9197
value: function () {
98+
var entity;
99+
var entities;
100+
var i;
101+
92102
if (!this.sceneEl) { return; }
93-
var entities = this.sceneEl.querySelectorAll('[mixin~=' + this.id + ']');
94-
for (var i = 0; i < entities.length; i++) {
95-
var entity = entities[i];
96-
if (!entity.hasLoaded) { continue; }
97-
entity.registerMixin(this.id);
98-
Object.keys(this.componentCache).forEach(function updateComponent (componentName) {
99-
entity.updateComponent(componentName);
100-
});
103+
104+
entities = this.sceneEl.querySelectorAll('[mixin~=' + this.id + ']');
105+
for (i = 0; i < entities.length; i++) {
106+
entity = entities[i];
107+
if (!entity.hasLoaded || entity.isMixin) { continue; }
108+
entity.mixinUpdate(this.id);
101109
}
102110
}
103111
}

src/core/a-node.js

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ var registerElement = require('./a-register-element').registerElement;
33
var isNode = require('./a-register-element').isNode;
44
var utils = require('../utils/');
55

6-
var bind = utils.bind;
76
var warn = utils.debug('core:a-node:warn');
87

8+
var MIXIN_OBSERVER_CONFIG = {attributes: true};
9+
910
/**
1011
* Base class for A-Frame that manages loading of objects.
1112
*
@@ -16,6 +17,7 @@ module.exports = registerElement('a-node', {
1617
prototype: Object.create(window.HTMLElement.prototype, {
1718
createdCallback: {
1819
value: function () {
20+
this.computedMixinStr = '';
1921
this.hasLoaded = false;
2022
this.isNode = true;
2123
this.mixinEls = [];
@@ -37,15 +39,25 @@ module.exports = registerElement('a-node', {
3739
this.hasLoaded = false;
3840
this.emit('nodeready', undefined, false);
3941

40-
mixins = this.getAttribute('mixin');
41-
if (mixins) { this.updateMixins(mixins); }
42+
if (!this.isMixin) {
43+
mixins = this.getAttribute('mixin');
44+
if (mixins) { this.updateMixins(mixins); }
45+
}
4246
},
4347
writable: window.debug
4448
},
4549

50+
/**
51+
* Handle mixin.
52+
*/
4653
attributeChangedCallback: {
4754
value: function (attr, oldVal, newVal) {
48-
if (attr === 'mixin') { this.updateMixins(newVal, oldVal); }
55+
// Ignore if `<a-node>` code is just updating computed mixin in the DOM.
56+
if (newVal === this.computedMixinStr) { return; }
57+
58+
if (attr === 'mixin' && !this.isMixin) {
59+
this.updateMixins(newVal, oldVal);
60+
}
4961
}
5062
},
5163

@@ -128,32 +140,76 @@ module.exports = registerElement('a-node', {
128140
},
129141

130142
/**
131-
* Remove old mixins and mixin listeners.
132-
* Add new mixins and mixin listeners.
143+
* Unregister old mixins and listeners.
144+
* Register new mixins and listeners.
145+
* Registering means to update `this.mixinEls` with listeners.
133146
*/
134147
updateMixins: {
135-
value: function (newMixins, oldMixins) {
136-
var newMixinIds = newMixins ? newMixins.trim().split(/\s+/) : [];
137-
var oldMixinIds = oldMixins ? oldMixins.trim().split(/\s+/) : [];
148+
value: (function () {
149+
var newMixinIdArray = [];
150+
var oldMixinIdArray = [];
138151

139-
// Unregister old mixins.
140-
oldMixinIds.filter(function (i) {
141-
return newMixinIds.indexOf(i) < 0;
142-
}).forEach(bind(this.unregisterMixin, this));
152+
return function (newMixins, oldMixins) {
153+
var i;
154+
var newMixinIds;
155+
var oldMixinIds;
143156

144-
// Register new mixins.
145-
this.mixinEls = [];
146-
newMixinIds.forEach(bind(this.registerMixin, this));
147-
}
157+
newMixinIdArray.length = 0;
158+
oldMixinIdArray.length = 0;
159+
newMixinIds = newMixins ? utils.split(newMixins.trim(), /\s+/) : newMixinIdArray;
160+
oldMixinIds = oldMixins ? utils.split(oldMixins.trim(), /\s+/) : oldMixinIdArray;
161+
162+
// Unregister old mixins.
163+
for (i = 0; i < oldMixinIds.length; i++) {
164+
if (newMixinIds.indexOf(oldMixinIds[i]) === -1) {
165+
this.unregisterMixin(oldMixinIds[i]);
166+
}
167+
}
168+
169+
// Register new mixins.
170+
this.computedMixinStr = '';
171+
this.mixinEls.length = 0;
172+
for (i = 0; i < newMixinIds.length; i++) {
173+
this.registerMixin(document.getElementById(newMixinIds[i]));
174+
}
175+
176+
// Update DOM. Keep track of `computedMixinStr` to not recurse back here after
177+
// update.
178+
if (this.computedMixinStr) {
179+
this.computedMixinStr = this.computedMixinStr.trim();
180+
window.HTMLElement.prototype.setAttribute.call(this, 'mixin',
181+
this.computedMixinStr);
182+
}
183+
};
184+
})()
148185
},
149186

187+
/**
188+
* From mixin ID, add mixin element to `mixinEls`.
189+
*
190+
* @param {Element} mixinEl
191+
*/
150192
registerMixin: {
151-
value: function (mixinId) {
152-
if (!this.sceneEl) { return; }
153-
var mixinEl = this.sceneEl.querySelector('a-mixin#' + mixinId);
193+
value: function (mixinEl) {
194+
var compositedMixinIds;
195+
var i;
196+
var mixin;
197+
154198
if (!mixinEl) { return; }
155-
this.attachMixinListener(mixinEl);
199+
200+
// Register composited mixins (if mixin has mixins).
201+
mixin = mixinEl.getAttribute('mixin');
202+
if (mixin) {
203+
compositedMixinIds = utils.split(mixin.trim(), /\s+/);
204+
for (i = 0; i < compositedMixinIds.length; i++) {
205+
this.registerMixin(document.getElementById(compositedMixinIds[i]));
206+
}
207+
}
208+
209+
// Register mixin.
210+
this.computedMixinStr = this.computedMixinStr + ' ' + mixinEl.id;
156211
this.mixinEls.push(mixinEl);
212+
this.attachMixinListener(mixinEl);
157213
}
158214
},
159215

@@ -166,9 +222,9 @@ module.exports = registerElement('a-node', {
166222

167223
unregisterMixin: {
168224
value: function (mixinId) {
225+
var i;
169226
var mixinEls = this.mixinEls;
170227
var mixinEl;
171-
var i;
172228
for (i = 0; i < mixinEls.length; ++i) {
173229
mixinEl = mixinEls[i];
174230
if (mixinId === mixinEl.id) {
@@ -189,19 +245,27 @@ module.exports = registerElement('a-node', {
189245
}
190246
},
191247

248+
/**
249+
* Add mutation observer from entity to mixin.
250+
*/
192251
attachMixinListener: {
193252
value: function (mixinEl) {
253+
var currentObserver;
254+
var mixinId;
255+
var observer;
194256
var self = this;
195-
var mixinId = mixinEl.id;
196-
var currentObserver = this.mixinObservers[mixinId];
257+
197258
if (!mixinEl) { return; }
259+
260+
mixinId = mixinEl.id;
261+
currentObserver = this.mixinObservers[mixinId];
198262
if (currentObserver) { return; }
199-
var observer = new MutationObserver(function (mutations) {
200-
var attr = mutations[0].attributeName;
201-
self.handleMixinUpdate(attr);
263+
264+
// Add observer.
265+
observer = new MutationObserver(function (mutations) {
266+
self.handleMixinUpdate(mutations[0].attributeName);
202267
});
203-
var config = { attributes: true };
204-
observer.observe(mixinEl, config);
268+
observer.observe(mixinEl, MIXIN_OBSERVER_CONFIG);
205269
this.mixinObservers[mixinId] = observer;
206270
}
207271
},

src/utils/index.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,3 +302,19 @@ module.exports.findAllScenes = function (el) {
302302

303303
// Must be at bottom to avoid circular dependency.
304304
module.exports.srcLoader = require('./src-loader');
305+
306+
/**
307+
* String split with cached result.
308+
*/
309+
module.exports.split = (function () {
310+
var splitCache = {};
311+
312+
return function (str, delimiter) {
313+
if (!(delimiter in splitCache)) { splitCache[delimiter] = {}; }
314+
315+
if (str in splitCache[delimiter]) { return splitCache[delimiter][str]; }
316+
317+
splitCache[delimiter][str] = str.split(delimiter);
318+
return splitCache[delimiter][str];
319+
};
320+
})();

0 commit comments

Comments
 (0)