diff --git a/polymer-micro.html b/polymer-micro.html
index cbc4f0c5c3..3cfa935ef8 100644
--- a/polymer-micro.html
+++ b/polymer-micro.html
@@ -23,6 +23,8 @@
_registerFeatures: function() {
// identity
this._prepIs();
+ // attributes
+ this._prepAttributes();
// shared behaviors
this._prepBehaviors();
// inheritance
@@ -31,18 +33,20 @@
this._prepConstructor();
},
- _prepBehavior: function() {},
+ _prepBehavior: function(b) {
+ this._addHostAttributes(b.hostAttributes);
+ },
+
+ _marshalBehavior: function(b) {
+ },
_initFeatures: function() {
+ // install host attributes
+ this._marshalHostAttributes();
// setup debouncers
this._setupDebouncers();
// acquire behaviors
this._marshalBehaviors();
- },
-
- _marshalBehavior: function(b) {
- // publish attributes to instance
- this._installHostAttributes(b.hostAttributes);
}
});
diff --git a/polymer-mini.html b/polymer-mini.html
index 389bc3c7c8..8290035cb1 100644
--- a/polymer-mini.html
+++ b/polymer-mini.html
@@ -24,6 +24,8 @@
_registerFeatures: function() {
// identity
this._prepIs();
+ // attributes
+ this._prepAttributes();
// shared behaviors
this._prepBehaviors();
// inheritance
@@ -36,7 +38,9 @@
this._prepShady();
},
- _prepBehavior: function() {},
+ _prepBehavior: function(b) {
+ this._addHostAttributes(b.hostAttributes);
+ },
_initFeatures: function() {
// manage local dom
@@ -47,6 +51,8 @@
this._stampTemplate();
// host stack
this._popHost();
+ // install host attributes
+ this._marshalHostAttributes();
// setup debouncers
this._setupDebouncers();
// instance shared behaviors
@@ -56,8 +62,6 @@
},
_marshalBehavior: function(b) {
- // publish attributes to instance
- this._installHostAttributes(b.hostAttributes);
}
});
diff --git a/polymer.html b/polymer.html
index bc5517c396..916c9ff6b5 100644
--- a/polymer.html
+++ b/polymer.html
@@ -28,6 +28,8 @@
_registerFeatures: function() {
// identity
this._prepIs();
+ // attributes
+ this._prepAttributes();
// inheritance
this._prepExtends();
// factory
@@ -51,8 +53,9 @@
},
_prepBehavior: function(b) {
- this._addPropertyEffects(b.properties || b.accessors);
+ this._addPropertyEffects(b.properties);
this._addComplexObserverEffects(b.observers);
+ this._addHostAttributes(b.hostAttributes);
},
_initFeatures: function() {
@@ -70,6 +73,8 @@
this._popHost();
// concretize template references
this._marshalAnnotationReferences();
+ // install host attributes
+ this._marshalHostAttributes();
// setup debouncers
this._setupDebouncers();
// concretize effects on instance
@@ -83,8 +88,6 @@
},
_marshalBehavior: function(b) {
- // publish attributes to instance
- this._installHostAttributes(b.hostAttributes);
// establish listeners on instance
this._listenListeners(b.listeners);
}
diff --git a/src/micro/attributes.html b/src/micro/attributes.html
index 62b97247da..564ff42634 100644
--- a/src/micro/attributes.html
+++ b/src/micro/attributes.html
@@ -60,21 +60,25 @@
Polymer.Base._addFeature({
- _marshalAttributes: function() {
- this._takeAttributes();
+ _prepAttributes: function() {
+ this._aggregatedAttributes = {};
},
- _installHostAttributes: function(attributes) {
+ _addHostAttributes: function(attributes) {
if (attributes) {
- this._applyAttributes(this, attributes);
+ this.mixin(this._aggregatedAttributes, attributes);
}
},
+ _marshalHostAttributes: function() {
+ this._applyAttributes(this, this._aggregatedAttributes);
+ },
+
/* apply attributes to node but avoid overriding existing values */
_applyAttributes: function(node, attr$) {
for (var n in attr$) {
// NOTE: never allow 'class' to be set in hostAttributes
- // since shimming classes would make it work
+ // since shimming classes would make it work
// inconsisently under native SD
if (!this.hasAttribute(n) && (n !== 'class')) {
this.serializeValueToAttribute(attr$[n], n, this);
@@ -82,7 +86,7 @@
}
},
- _takeAttributes: function() {
+ _marshalAttributes: function() {
this._takeAttributesToModel(this);
},
@@ -106,6 +110,7 @@
},
_serializing: false,
+
reflectPropertyToAttribute: function(name) {
this._serializing = true;
this.serializeValueToAttribute(this[name],
diff --git a/src/micro/behaviors.html b/src/micro/behaviors.html
index c3420ff47a..443863e309 100644
--- a/src/micro/behaviors.html
+++ b/src/micro/behaviors.html
@@ -46,7 +46,7 @@
if (this.behaviors.length) {
this.behaviors = this._flattenBehaviorsList(this.behaviors);
}
- this._prepAllBehaviors();
+ this._prepAllBehaviors(this.behaviors);
},
_flattenBehaviorsList: function(behaviors) {
@@ -54,35 +54,41 @@
behaviors.forEach(function(b) {
if (b instanceof Array) {
flat = flat.concat(this._flattenBehaviorsList(b));
- } else {
+ }
+ // filter out null entries so other iterators don't need to check
+ else if (b) {
flat.push(b);
+ } else {
+ this._warn(this._logf('_flattenBehaviorsList', 'behavior is null, check for missing or 404 import'));
}
}, this);
return flat;
},
- _prepAllBehaviors: function() {
- // filter so other iterators don't need null check
- this.behaviors = this.behaviors.filter(function(b) {
- if (b) {
- this._mixinBehavior(b);
- this._prepBehavior(b);
- return true;
- }
- this._warn(this._logf('_prepAllBehaviors', 'behavior is null, check for missing or 404 import'));
- }, this);
+ _prepAllBehaviors: function(behaviors) {
+ // traverse the behaviors in _reverse_ order (youngest first) because
+ // `_mixinBehavior` has _first property wins_ behavior, this is done
+ // to optimize # of calls to `_copyOwnProperty`
+ for (var i=behaviors.length-1; i>=0; i--) {
+ this._mixinBehavior(behaviors[i]);
+ }
+ // we iterate a second time so that `_prepBehavior` goes in natural order
+ // otherwise, it's a tricky detail for implementors of `_prepBehavior`
+ for (var i=0, l=behaviors.length; i
-
diff --git a/test/unit/behaviors-elements.html b/test/unit/behaviors-elements.html
index d186c4d7ec..6e99d45780 100644
--- a/test/unit/behaviors-elements.html
+++ b/test/unit/behaviors-elements.html
@@ -28,10 +28,20 @@
value: false
},
+ overridablePropertyB: {
+ value: false
+ },
+
hasBehaviorA: {
value: true
}
+ },
+
+ _simpleProperty: 'A',
+ hostAttributes: {
+ behavior: 'A',
+ user: 'A'
},
listeners: {
@@ -76,10 +86,21 @@
hasBehaviorB: {
value: true
- }
+ },
+
+ overridablePropertyB: {
+ value: true
+ },
},
+ hostAttributes: {
+ behavior: 'B',
+ user: 'B'
+ },
+
+ _simpleProperty: 'B',
+
_disabledChanged: function(disabled) {
this.__disabled = disabled
},
@@ -118,6 +139,7 @@
behaviors: [
Polymer.BehaviorA,
+ null,
Polymer.BehaviorB
],
@@ -158,7 +180,9 @@
value: true
}
- }
+ },
+
+ _simpleProperty: 'C'
};
@@ -170,7 +194,9 @@
value: true
}
- }
+ },
+
+ _simpleProperty: 'D'
};
diff --git a/test/unit/behaviors.html b/test/unit/behaviors.html
index 9e45423334..54b1b1116d 100644
--- a/test/unit/behaviors.html
+++ b/test/unit/behaviors.html
@@ -59,7 +59,9 @@
var el;
setup(function() {
- el = document.createElement('multi-behaviors');
+ var div = document.createElement('div');
+ div.innerHTML = '';
+ el = div.firstElementChild;
document.body.appendChild(el);
});
@@ -103,14 +105,21 @@
assert.equal(typeof el._setHasOptionsB, 'function');
});
- test('behavior overrides are last', function() {
- assert.equal(el._toOverride, Polymer._toOverride, 'Behavior method was not overridden');
- assert(el.overridableProperty, 'Behavior property was not overridden');
+ test('multi-behavior overrides ordering', function() {
+ assert.equal(el._toOverride, Polymer._toOverride, 'Behavior method was not overridden by prototype');
+ assert(el.overridableProperty, 'Behavior property was not overridden by prototype');
+ assert(el.overridablePropertyB, 'Behavior config-property was not overridden by sub-behavior');
});
+
+ test('hostAttributes ordering', function() {
+ assert.equal(el.attributes.behavior.value, 'B', 'Behavior hostAttribute not overridden by subclass');
+ assert.equal(el.attributes.user.value, 'user', 'Behavior hostAttribute overrode user attribute');
+ });
+
});
-suite('nexted-behaviors element', function() {
+suite('nested-behaviors element', function() {
var el;
@@ -123,11 +132,12 @@
document.body.removeChild(el);
});
- test('properties from nested behaviors', function() {
+ test('nested-behavior overrides ordering', function() {
assert.ok(el.hasBehaviorA, "missing BehaviorA");
assert.ok(el.hasBehaviorB, "missing BehaviorB");
assert.ok(el.hasBehaviorC, "missing BehaviorC");
assert.ok(el.hasBehaviorD, "missing BehaviorD");
+ assert.equal(el._simpleProperty, 'D', 'Behavior simple property was not overridden by sub-behavior');
});
});