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'); }); });