diff --git a/packages/ember-htmlbars/tests/integration/component_invocation_test.js b/packages/ember-htmlbars/tests/integration/component_invocation_test.js index 3d2efba8edd..b5c778b76bf 100644 --- a/packages/ember-htmlbars/tests/integration/component_invocation_test.js +++ b/packages/ember-htmlbars/tests/integration/component_invocation_test.js @@ -802,6 +802,29 @@ QUnit.test('specifying classNames results in correct class', function(assert) { ok(button.is('.foo.bar.baz.ember-view'), 'the element has the correct classes: ' + button.attr('class')); }); +QUnit.test('specifying custom concatenatedProperties avoids clobbering', function(assert) { + expect(1); + + let clickyThing; + registry.register('component:some-clicky-thing', Component.extend({ + concatenatedProperties: ['blahzz'], + blahzz: ['blark', 'pory'], + init() { + this._super(...arguments); + clickyThing = this; + } + })); + + view = EmberView.extend({ + template: compile('{{#some-clicky-thing blahzz="baz"}}Click Me{{/some-clicky-thing}}'), + container: container + }).create(); + + runAppend(view); + + assert.deepEqual(clickyThing.get('blahzz'), ['blark', 'pory', 'baz'], 'property is properly combined'); +}); + // jscs:disable validateIndentation if (isEnabled('ember-htmlbars-component-generation')) { diff --git a/packages/ember-views/lib/compat/attrs-proxy.js b/packages/ember-views/lib/compat/attrs-proxy.js index edd7ae4683d..164a155dfdb 100644 --- a/packages/ember-views/lib/compat/attrs-proxy.js +++ b/packages/ember-views/lib/compat/attrs-proxy.js @@ -2,6 +2,7 @@ import { Mixin } from 'ember-metal/mixin'; import { symbol } from 'ember-metal/utils'; import { PROPERTY_DID_CHANGE } from 'ember-metal/property_events'; import { on } from 'ember-metal/events'; +import EmptyObject from 'ember-metal/empty_object'; export function deprecation(key) { return `You tried to look up an attribute directly on the component. This is deprecated. Use attrs.${key} instead.`; @@ -13,9 +14,37 @@ function isCell(val) { return val && val[MUTABLE_CELL]; } +function setupAvoidPropagating(instance) { + // This caches the list of properties to avoid setting onto the component instance + // inside `_propagateAttrsToThis`. We cache them so that every instantiated component + // does not have to pay the calculation penalty. + let constructor = instance.constructor; + if (!constructor.__avoidPropagating) { + constructor.__avoidPropagating = new EmptyObject(); + let i, l; + for (i = 0, l = instance.concatenatedProperties.length; i < l; i++) { + let prop = instance.concatenatedProperties[i]; + + constructor.__avoidPropagating[prop] = true; + } + + for (i = 0, l = instance.mergedProperties.length; i < l; i++) { + let prop = instance.mergedProperties[i]; + + constructor.__avoidPropagating[prop] = true; + } + } +} + let AttrsProxyMixin = { attrs: null, + init() { + this._super(...arguments); + + setupAvoidPropagating(this); + }, + getAttr(key) { let attrs = this.attrs; if (!attrs) { return; } @@ -42,14 +71,7 @@ let AttrsProxyMixin = { let attrs = this.attrs; for (let prop in attrs) { - if (prop !== 'attrs' && - // These list of properties are concatenated and merged properties of - // Ember.View / Ember.Component. Setting them here results in them being - // completely stomped and not handled properly, BAIL OUT! - prop !== 'actions' && - prop !== 'classNames' && - prop !== 'classNameBindings' && - prop !== 'attributeBindings') { + if (prop !== 'attrs' && !this.constructor.__avoidPropagating[prop]) { this.set(prop, this.getAttr(prop)); } }