diff --git a/packages/ember-htmlbars/lib/node-managers/component-node-manager.js b/packages/ember-htmlbars/lib/node-managers/component-node-manager.js index f19055cfcd9..e6af331b6f2 100644 --- a/packages/ember-htmlbars/lib/node-managers/component-node-manager.js +++ b/packages/ember-htmlbars/lib/node-managers/component-node-manager.js @@ -75,8 +75,10 @@ ComponentNodeManager.create = function(renderNode, env, options) { createOptions._deprecatedFlagForBlockProvided = true; } + let proto = extractPositionalParams(renderNode, component, params, attrs); + // Instantiate the component - component = createComponent(component, isAngleBracket, createOptions, renderNode, env, attrs); + component = createComponent(component, isAngleBracket, createOptions, renderNode, env, attrs, proto); // If the component specifies its template via the `layout` or `template` // properties instead of using the template looked up in the container, get @@ -85,7 +87,6 @@ ComponentNodeManager.create = function(renderNode, env, options) { layout = result.layout || layout; templates = result.templates || templates; - extractPositionalParams(renderNode, component, params, attrs); let results = buildComponentTemplate( { layout, component, isAngleBracket }, attrs, { templates, scope: parentScope } @@ -95,30 +96,55 @@ ComponentNodeManager.create = function(renderNode, env, options) { }; function extractPositionalParams(renderNode, component, params, attrs) { - if (component.positionalParams) { - // if the component is rendered via {{component}} helper, the first - // element of `params` is the name of the component, so we need to - // skip that when the positional parameters are constructed - const paramsStartIndex = renderNode.state.isComponentHelper ? 1 : 0; - const positionalParams = component.positionalParams; - const isNamed = typeof positionalParams === 'string'; - let paramsStream; - - if (isNamed) { - paramsStream = new Stream(() => { - return readArray(params.slice(paramsStartIndex)); - }, 'params'); - - attrs[positionalParams] = paramsStream; - } + let positionalParams = component.positionalParams; + let proto; + + if (!positionalParams) { + proto = component.proto(); + positionalParams = proto.positionalParams; + + Ember.deprecate( + 'Calling `var Thing = Ember.Component.extend({ positionalParams: [\'a\', \'b\' ]});` ' + + 'is deprecated in favor of `Thing.reopenClass({ positionalParams: [\'a\', \'b\'] });', + !positionalParams, + { id: 'ember-htmlbars.component-positional-params', until: '2.0.0' } + ); + } + + if (positionalParams) { + processPositionalParams(renderNode, positionalParams, params, attrs); + } + + // returns `proto` here so that we can avoid doing this + // twice for each initial render per component (it is also needed in `createComponent`) + return proto; +} + +function processPositionalParams(renderNode, positionalParams, params, attrs) { + // if the component is rendered via {{component}} helper, the first + // element of `params` is the name of the component, so we need to + // skip that when the positional parameters are constructed + const paramsStartIndex = renderNode.state.isComponentHelper ? 1 : 0; + const isNamed = typeof positionalParams === 'string'; + let paramsStream; + if (isNamed) { + paramsStream = new Stream(() => { + return readArray(params.slice(paramsStartIndex)); + }, 'params'); + + attrs[positionalParams] = paramsStream; + } + + if (isNamed) { + for (let i = paramsStartIndex; i < params.length; i++) { + let param = params[i]; + paramsStream.addDependency(param); + } + } else { for (let i = 0; i < positionalParams.length; i++) { let param = params[paramsStartIndex + i]; - if (isNamed) { - paramsStream.addDependency(param); - } else { - attrs[positionalParams[i]] = param; - } + attrs[positionalParams[i]] = param; } } } @@ -187,13 +213,11 @@ function configureCreateOptions(attrs, createOptions) { } ComponentNodeManager.prototype.render = function(_env, visitor) { - var { component, attrs } = this; + var { component } = this; return instrument(component, function() { let env = _env.childWithView(component); - var snapshot = takeSnapshot(attrs); - env.renderer.componentInitAttrs(this.component, snapshot); env.renderer.componentWillRender(component); env.renderedViews.push(component.elementId); @@ -274,18 +298,18 @@ ComponentNodeManager.prototype.destroy = function() { component.destroy(); }; -export function createComponent(_component, isAngleBracket, _props, renderNode, env, attrs = {}) { +export function createComponent(_component, isAngleBracket, _props, renderNode, env, attrs = {}, proto = _component.proto()) { let props = assign({}, _props); + let attrsSnapshot; if (!isAngleBracket) { let hasSuppliedController = 'controller' in attrs; // 2.0TODO remove Ember.deprecate('controller= is deprecated', !hasSuppliedController, { id: 'ember-htmlbars.create-component', until: '3.0.0' }); - let snapshot = takeSnapshot(attrs); - props.attrs = snapshot; + attrsSnapshot = takeSnapshot(attrs); + props.attrs = attrsSnapshot; - let proto = _component.proto(); - mergeBindings(props, shadowedAttrs(proto, snapshot)); + mergeBindings(props, shadowedAttrs(proto, attrsSnapshot)); } else { props._isAngleBracket = true; } @@ -295,6 +319,8 @@ export function createComponent(_component, isAngleBracket, _props, renderNode, let component = _component.create(props); + env.renderer.componentInitAttrs(component, attrsSnapshot); + // for the fallback case component.container = component.container || env.container; diff --git a/packages/ember-htmlbars/tests/integration/component_invocation_test.js b/packages/ember-htmlbars/tests/integration/component_invocation_test.js index 619bba89609..b046dee19c4 100644 --- a/packages/ember-htmlbars/tests/integration/component_invocation_test.js +++ b/packages/ember-htmlbars/tests/integration/component_invocation_test.js @@ -404,7 +404,7 @@ if (isEnabled('ember-views-component-block-info')) { }); } -QUnit.test('static named positional parameters', function() { +QUnit.test('static named positional parameters [DEPRECATED]', function() { registry.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); registry.register('component:sample-component', Component.extend({ positionalParams: ['name', 'age'] @@ -415,12 +415,14 @@ QUnit.test('static named positional parameters', function() { container: container }).create(); - runAppend(view); + expectDeprecation(function() { + runAppend(view); + }, 'Calling `var Thing = Ember.Component.extend({ positionalParams: [\'a\', \'b\' ]});` is deprecated in favor of `Thing.reopenClass({ positionalParams: [\'a\', \'b\'] });'); equal(jQuery('#qunit-fixture').text(), 'Quint4'); }); -QUnit.test('dynamic named positional parameters', function() { +QUnit.test('dynamic named positional parameters [DEPRECATED]', function() { registry.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); registry.register('component:sample-component', Component.extend({ positionalParams: ['name', 'age'] @@ -435,7 +437,10 @@ QUnit.test('dynamic named positional parameters', function() { } }).create(); - runAppend(view); + expectDeprecation(function() { + runAppend(view); + }, 'Calling `var Thing = Ember.Component.extend({ positionalParams: [\'a\', \'b\' ]});` is deprecated in favor of `Thing.reopenClass({ positionalParams: [\'a\', \'b\'] });'); + equal(jQuery('#qunit-fixture').text(), 'Quint4'); run(function() { set(view.context, 'myName', 'Edward'); @@ -445,7 +450,7 @@ QUnit.test('dynamic named positional parameters', function() { equal(jQuery('#qunit-fixture').text(), 'Edward5'); }); -QUnit.test('static arbitrary number of positional parameters', function() { +QUnit.test('static arbitrary number of positional parameters [DEPRECATED]', function() { registry.register('template:components/sample-component', compile('{{#each attrs.names as |name|}}{{name}}{{/each}}')); registry.register('component:sample-component', Component.extend({ positionalParams: 'names' @@ -456,14 +461,17 @@ QUnit.test('static arbitrary number of positional parameters', function() { container: container }).create(); - runAppend(view); + expectDeprecation(function() { + runAppend(view); + }, 'Calling `var Thing = Ember.Component.extend({ positionalParams: [\'a\', \'b\' ]});` is deprecated in favor of `Thing.reopenClass({ positionalParams: [\'a\', \'b\'] });'); + equal(view.$('#args-3').text(), 'Foo4Bar'); equal(view.$('#args-5').text(), 'Foo4Bar5Baz'); equal(view.$('#helper').text(), 'Foo4Bar5Baz'); }); -QUnit.test('dynamic arbitrary number of positional parameters', function() { +QUnit.test('dynamic arbitrary number of positional parameters [DEPRECATED]', function() { registry.register('template:components/sample-component', compile('{{#each attrs.names as |name|}}{{name}}{{/each}}')); registry.register('component:sample-component', Component.extend({ positionalParams: 'names' @@ -478,7 +486,108 @@ QUnit.test('dynamic arbitrary number of positional parameters', function() { } }).create(); + expectDeprecation(function() { + runAppend(view); + }, 'Calling `var Thing = Ember.Component.extend({ positionalParams: [\'a\', \'b\' ]});` is deprecated in favor of `Thing.reopenClass({ positionalParams: [\'a\', \'b\'] });'); + + equal(view.$('#direct').text(), 'Foo4'); + equal(view.$('#helper').text(), 'Foo4'); + run(function() { + set(view.context, 'user1', 'Bar'); + set(view.context, 'user2', '5'); + }); + + equal(view.$('#direct').text(), 'Bar5'); + equal(view.$('#helper').text(), 'Bar5'); +}); + +QUnit.test('static named positional parameters', function() { + var SampleComponent = Component.extend(); + SampleComponent.reopenClass({ + positionalParams: ['name', 'age'] + }); + registry.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); + registry.register('component:sample-component', SampleComponent); + + view = EmberView.extend({ + layout: compile('{{sample-component "Quint" 4}}'), + container: container + }).create(); + + runAppend(view); + + equal(jQuery('#qunit-fixture').text(), 'Quint4'); +}); + +QUnit.test('dynamic named positional parameters', function() { + var SampleComponent = Component.extend(); + SampleComponent.reopenClass({ + positionalParams: ['name', 'age'] + }); + + registry.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); + registry.register('component:sample-component', SampleComponent); + + view = EmberView.extend({ + layout: compile('{{sample-component myName myAge}}'), + container: container, + context: { + myName: 'Quint', + myAge: 4 + } + }).create(); + runAppend(view); + + equal(jQuery('#qunit-fixture').text(), 'Quint4'); + run(function() { + set(view.context, 'myName', 'Edward'); + set(view.context, 'myAge', '5'); + }); + + equal(jQuery('#qunit-fixture').text(), 'Edward5'); +}); + +QUnit.test('static arbitrary number of positional parameters', function() { + var SampleComponent = Component.extend(); + SampleComponent.reopenClass({ + positionalParams: 'names' + }); + + registry.register('template:components/sample-component', compile('{{#each attrs.names as |name|}}{{name}}{{/each}}')); + registry.register('component:sample-component', SampleComponent); + + view = EmberView.extend({ + layout: compile('{{sample-component "Foo" 4 "Bar" id="args-3"}}{{sample-component "Foo" 4 "Bar" 5 "Baz" id="args-5"}}{{component "sample-component" "Foo" 4 "Bar" 5 "Baz" id="helper"}}'), + container: container + }).create(); + + runAppend(view); + + equal(view.$('#args-3').text(), 'Foo4Bar'); + equal(view.$('#args-5').text(), 'Foo4Bar5Baz'); + equal(view.$('#helper').text(), 'Foo4Bar5Baz'); +}); + +QUnit.test('dynamic arbitrary number of positional parameters', function() { + var SampleComponent = Component.extend(); + SampleComponent.reopenClass({ + positionalParams: 'n' + }); + registry.register('template:components/sample-component', compile('{{#each attrs.n as |name|}}{{name}}{{/each}}')); + registry.register('component:sample-component', SampleComponent); + + view = EmberView.extend({ + layout: compile('{{sample-component user1 user2 id="direct"}}{{component "sample-component" user1 user2 id="helper"}}'), + container: container, + context: { + user1: 'Foo', + user2: 4 + } + }).create(); + + runAppend(view); + equal(view.$('#direct').text(), 'Foo4'); equal(view.$('#helper').text(), 'Foo4'); run(function() { @@ -488,6 +597,13 @@ QUnit.test('dynamic arbitrary number of positional parameters', function() { equal(view.$('#direct').text(), 'Bar5'); equal(view.$('#helper').text(), 'Bar5'); + + run(function() { + set(view.context, 'user2', '6'); + }); + + equal(view.$('#direct').text(), 'Bar6'); + equal(view.$('#helper').text(), 'Bar6'); }); QUnit.test('moduleName is available on _renderNode when a layout is present', function() { @@ -533,7 +649,7 @@ QUnit.test('moduleName is available on _renderNode when no layout is present', f }); if (isEnabled('ember-htmlbars-component-helper')) { - QUnit.test('{{component}} helper works with positional params', function() { + QUnit.test('{{component}} helper works with positional params [DEPRECATED]', function() { registry.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); registry.register('component:sample-component', Component.extend({ positionalParams: ['name', 'age'] @@ -548,6 +664,37 @@ if (isEnabled('ember-htmlbars-component-helper')) { } }).create(); + expectDeprecation(function() { + runAppend(view); + }, 'Calling `var Thing = Ember.Component.extend({ positionalParams: [\'a\', \'b\' ]});` is deprecated in favor of `Thing.reopenClass({ positionalParams: [\'a\', \'b\'] });'); + + equal(jQuery('#qunit-fixture').text(), 'Quint4'); + run(function() { + set(view.context, 'myName', 'Edward'); + set(view.context, 'myAge', '5'); + }); + + equal(jQuery('#qunit-fixture').text(), 'Edward5'); + }); + + QUnit.test('{{component}} helper works with positional params', function() { + var SampleComponent = Component.extend(); + SampleComponent.reopenClass({ + positionalParams: ['name', 'age'] + }); + + registry.register('template:components/sample-component', compile('{{attrs.name}}{{attrs.age}}')); + registry.register('component:sample-component', SampleComponent); + + view = EmberView.extend({ + layout: compile('{{component "sample-component" myName myAge}}'), + container: container, + context: { + myName: 'Quint', + myAge: 4 + } + }).create(); + runAppend(view); equal(jQuery('#qunit-fixture').text(), 'Quint4'); run(function() { diff --git a/packages/ember-metal-views/lib/renderer.js b/packages/ember-metal-views/lib/renderer.js index 405dfa134e2..3e656592fed 100755 --- a/packages/ember-metal-views/lib/renderer.js +++ b/packages/ember-metal-views/lib/renderer.js @@ -136,7 +136,6 @@ Renderer.prototype.setAttrs = function (view, attrs) { }; // set attrs the first time Renderer.prototype.componentInitAttrs = function (component, attrs) { - set(component, 'attrs', attrs); component.trigger('didInitAttrs', { attrs }); component.trigger('didReceiveAttrs', { newAttrs: attrs }); }; // set attrs the first time