diff --git a/packages/ember-htmlbars/lib/hooks/component.js b/packages/ember-htmlbars/lib/hooks/component.js index 78662caf531..fda3956001b 100644 --- a/packages/ember-htmlbars/lib/hooks/component.js +++ b/packages/ember-htmlbars/lib/hooks/component.js @@ -1,6 +1,6 @@ import ComponentNodeManager from "ember-htmlbars/node-managers/component-node-manager"; -export default function componentHook(renderNode, env, scope, tagName, params, attrs, template, visitor) { +export default function componentHook(renderNode, env, scope, tagName, params, attrs, templates, visitor) { var state = renderNode.state; // Determine if this is an initial render or a re-render @@ -17,7 +17,7 @@ export default function componentHook(renderNode, env, scope, tagName, params, a params, attrs, parentView, - template, + templates, parentScope: scope }); diff --git a/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js b/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js index 6ff0aacc675..37d18b45030 100644 --- a/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js +++ b/packages/ember-htmlbars/lib/hooks/create-fresh-scope.js @@ -1,7 +1,7 @@ export default function createFreshScope() { return { self: null, - block: null, + blocks: {}, component: null, view: null, attrs: null, diff --git a/packages/ember-htmlbars/lib/hooks/get-root.js b/packages/ember-htmlbars/lib/hooks/get-root.js index b271ddb7e8b..452dd24bec4 100644 --- a/packages/ember-htmlbars/lib/hooks/get-root.js +++ b/packages/ember-htmlbars/lib/hooks/get-root.js @@ -11,9 +11,9 @@ export default function getRoot(scope, key) { if (key === 'this') { return [scope.self]; } else if (key === 'hasBlock') { - return [!!scope.block]; + return [!!scope.blocks.default]; } else if (key === 'hasBlockParams') { - return [!!(scope.block && scope.block.arity)]; + return [!!(scope.blocks.default && scope.blocks.default.arity)]; } else if (isGlobal(key) && Ember.lookup[key]) { return [getGlobal(key)]; } else if (scope.locals[key]) { diff --git a/packages/ember-htmlbars/lib/keywords/component.js b/packages/ember-htmlbars/lib/keywords/component.js index e384245e3fa..6bc4bffaa22 100644 --- a/packages/ember-htmlbars/lib/keywords/component.js +++ b/packages/ember-htmlbars/lib/keywords/component.js @@ -27,5 +27,5 @@ function render(morph, env, scope, params, hash, template, inverse, visitor) { return; } - env.hooks.component(morph, env, scope, componentPath, params, hash, template, visitor); + env.hooks.component(morph, env, scope, componentPath, params, hash, { default: template, inverse }, visitor); } diff --git a/packages/ember-htmlbars/lib/keywords/input.js b/packages/ember-htmlbars/lib/keywords/input.js index be32c47e94e..dcf88dbddba 100644 --- a/packages/ember-htmlbars/lib/keywords/input.js +++ b/packages/ember-htmlbars/lib/keywords/input.js @@ -13,7 +13,7 @@ export default { }, render(morph, env, scope, params, hash, template, inverse, visitor) { - env.hooks.component(morph, env, scope, morph.state.componentName, params, hash, template, visitor); + env.hooks.component(morph, env, scope, morph.state.componentName, params, hash, { default: template, inverse }, visitor); }, rerender(...args) { diff --git a/packages/ember-htmlbars/lib/keywords/legacy-yield.js b/packages/ember-htmlbars/lib/keywords/legacy-yield.js index 45008aaef09..6b3825b7665 100644 --- a/packages/ember-htmlbars/lib/keywords/legacy-yield.js +++ b/packages/ember-htmlbars/lib/keywords/legacy-yield.js @@ -3,7 +3,7 @@ import ProxyStream from "ember-metal/streams/proxy-stream"; export default function legacyYield(morph, env, _scope, params, hash, template, inverse, visitor) { let scope = _scope; - if (scope.block.arity === 0) { + if (scope.blocks.default.arity === 0) { // Typically, the `controller` local is persists through lexical scope. // However, in this case, the `{{legacy-yield}}` in the legacy each view // needs to override the controller local for the template it is yielding. @@ -14,9 +14,9 @@ export default function legacyYield(morph, env, _scope, params, hash, template, scope.locals.controller = new ProxyStream(hash.controller, "controller"); scope.overrideController = true; } - scope.block(env, [], params[0], morph, scope, visitor); + scope.blocks.default(env, [], params[0], morph, scope, visitor); } else { - scope.block(env, params, undefined, morph, scope, visitor); + scope.blocks.default(env, params, undefined, morph, scope, visitor); } return true; diff --git a/packages/ember-htmlbars/lib/keywords/textarea.js b/packages/ember-htmlbars/lib/keywords/textarea.js index 656096ae2b2..daf57bcc087 100644 --- a/packages/ember-htmlbars/lib/keywords/textarea.js +++ b/packages/ember-htmlbars/lib/keywords/textarea.js @@ -4,6 +4,6 @@ */ export default function textarea(morph, env, scope, originalParams, hash, template, inverse, visitor) { - env.hooks.component(morph, env, scope, '-text-area', originalParams, hash, template, visitor); + env.hooks.component(morph, env, scope, '-text-area', originalParams, hash, { default: template, inverse }, visitor); return true; } 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 a6e97a5598b..6db4516b26b 100644 --- a/packages/ember-htmlbars/lib/node-managers/component-node-manager.js +++ b/packages/ember-htmlbars/lib/node-managers/component-node-manager.js @@ -32,7 +32,7 @@ ComponentNodeManager.create = function(renderNode, env, options) { attrs, parentView, parentScope, - template } = options; + templates } = options; attrs = attrs || {}; @@ -79,9 +79,9 @@ ComponentNodeManager.create = function(renderNode, env, options) { // There is no block template provided but the component has a // `template` property. - if (!template && componentTemplate) { + if ((!templates || !templates.default) && componentTemplate) { Ember.deprecate("Using deprecated `template` property on a Component."); - template = componentTemplate.raw; + templates = { default: componentTemplate.raw }; } } else if (componentTemplate) { // If the component has a `template` but no `layout`, use the template @@ -104,7 +104,7 @@ ComponentNodeManager.create = function(renderNode, env, options) { } var results = buildComponentTemplate({ layout: layout, component: component }, attrs, { - template: template, + templates, scope: parentScope }); diff --git a/packages/ember-htmlbars/lib/system/component-node.js b/packages/ember-htmlbars/lib/system/component-node.js index 4981934901e..2bec75eddcd 100644 --- a/packages/ember-htmlbars/lib/system/component-node.js +++ b/packages/ember-htmlbars/lib/system/component-node.js @@ -79,7 +79,7 @@ ComponentNode.create = function(renderNode, env, attrs, found, parentView, path, Ember.assert("BUG: ComponentNode.create can take a scope or a self, but not both", !(contentScope && found.self)); var results = buildComponentTemplate(componentInfo, attrs, { - template: contentTemplate, + templates: { default: contentTemplate }, scope: contentScope, self: found.self }); diff --git a/packages/ember-htmlbars/tests/integration/component_invocation_test.js b/packages/ember-htmlbars/tests/integration/component_invocation_test.js index 6db127e635b..2036bd3c282 100644 --- a/packages/ember-htmlbars/tests/integration/component_invocation_test.js +++ b/packages/ember-htmlbars/tests/integration/component_invocation_test.js @@ -303,3 +303,88 @@ if (Ember.FEATURES.isEnabled('ember-htmlbars-component-helper')) { equal(jQuery('#qunit-fixture').text(), 'Edward5'); }); } + +QUnit.test('yield to inverse', function() { + registry.register('template:components/my-if', compile('{{#if predicate}}Yes:{{yield someValue}}{{else}}No:{{yield to="inverse"}}{{/if}}')); + + view = EmberView.extend({ + layout: compile('{{#my-if predicate=activated someValue=42 as |result|}}Hello{{result}}{{else}}Goodbye{{/my-if}}'), + container: container, + context: { + activated: true + } + }).create(); + + runAppend(view); + equal(jQuery('#qunit-fixture').text(), 'Yes:Hello42'); + run(function() { + Ember.set(view.context, 'activated', false); + }); + + equal(jQuery('#qunit-fixture').text(), 'No:Goodbye'); +}); + +QUnit.test('parameterized hasBlock inverse', function() { + registry.register('template:components/check-inverse', compile('{{#if (hasBlock "inverse")}}Yes{{else}}No{{/if}}')); + + view = EmberView.extend({ + layout: compile('{{#check-inverse id="expect-no"}}{{/check-inverse}} {{#check-inverse id="expect-yes"}}{{else}}{{/check-inverse}}'), + container: container + }).create(); + + runAppend(view); + equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); + equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); +}); + +QUnit.test('parameterized hasBlock default', function() { + registry.register('template:components/check-block', compile('{{#if (hasBlock)}}Yes{{else}}No{{/if}}')); + + view = EmberView.extend({ + layout: compile('{{check-block id="expect-no"}} {{#check-block id="expect-yes"}}{{/check-block}}'), + container: container + }).create(); + + runAppend(view); + equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); + equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); +}); + +QUnit.test('non-expression hasBlock ', function() { + registry.register('template:components/check-block', compile('{{#if hasBlock}}Yes{{else}}No{{/if}}')); + + view = EmberView.extend({ + layout: compile('{{check-block id="expect-no"}} {{#check-block id="expect-yes"}}{{/check-block}}'), + container: container + }).create(); + + runAppend(view); + equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); + equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); +}); + +QUnit.test('parameterized hasBlockParams', function() { + registry.register('template:components/check-params', compile('{{#if (hasBlockParams)}}Yes{{else}}No{{/if}}')); + + view = EmberView.extend({ + layout: compile('{{#check-params id="expect-no"}}{{/check-params}} {{#check-params id="expect-yes" as |foo|}}{{/check-params}}'), + container: container + }).create(); + + runAppend(view); + equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); + equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); +}); + +QUnit.test('non-expression hasBlockParams', function() { + registry.register('template:components/check-params', compile('{{#if hasBlockParams}}Yes{{else}}No{{/if}}')); + + view = EmberView.extend({ + layout: compile('{{#check-params id="expect-no"}}{{/check-params}} {{#check-params id="expect-yes" as |foo|}}{{/check-params}}'), + container: container + }).create(); + + runAppend(view); + equal(jQuery('#qunit-fixture #expect-no').text(), 'No'); + equal(jQuery('#qunit-fixture #expect-yes').text(), 'Yes'); +}); diff --git a/packages/ember-metal-views/lib/renderer.js b/packages/ember-metal-views/lib/renderer.js index 90374e26a54..a7a93f2370a 100755 --- a/packages/ember-metal-views/lib/renderer.js +++ b/packages/ember-metal-views/lib/renderer.js @@ -28,7 +28,7 @@ Renderer.prototype.prerenderTopLevelView = var block = buildComponentTemplate(componentInfo, {}, { self: view, - template: template && template.raw + templates: template ? { default: template.raw } : undefined }).block; view.renderBlock(block, renderNode); diff --git a/packages/ember-routing-htmlbars/lib/keywords/link-to.js b/packages/ember-routing-htmlbars/lib/keywords/link-to.js index 3c456822cb7..6e77f009619 100644 --- a/packages/ember-routing-htmlbars/lib/keywords/link-to.js +++ b/packages/ember-routing-htmlbars/lib/keywords/link-to.js @@ -297,7 +297,7 @@ export default { attrs.escaped = !morph.parseTextAsHTML; - env.hooks.component(morph, env, scope, '-link-to', params, attrs, template, visitor); + env.hooks.component(morph, env, scope, '-link-to', params, attrs, { default: template }, visitor); }, rerender(morph, env, scope, params, hash, template, inverse, visitor) { diff --git a/packages/ember-views/lib/system/build-component-template.js b/packages/ember-views/lib/system/build-component-template.js index 4d2f2af8ac4..3570c7c6ee1 100644 --- a/packages/ember-views/lib/system/build-component-template.js +++ b/packages/ember-views/lib/system/build-component-template.js @@ -10,12 +10,11 @@ export default function buildComponentTemplate({ component, layout }, attrs, con component = null; } - if (content.template) { - blockToRender = createContentBlock(content.template, content.scope, content.self, component); - } - if (layout && layout.raw) { - blockToRender = createLayoutBlock(layout.raw, blockToRender, content.self, component, attrs); + let yieldTo = createContentBlocks(content.templates, content.scope, content.self, component); + blockToRender = createLayoutBlock(layout.raw, yieldTo, content.self, component, attrs); + } else if (content.templates && content.templates.default) { + blockToRender = createContentBlock(content.templates.default, content.scope, content.self, component); } if (component) { @@ -56,9 +55,25 @@ function createContentBlock(template, scope, self, component) { }); } +function createContentBlocks(templates, scope, self, component) { + if (!templates) { + return; + } + var output = {}; + for (var name in templates) { + if (templates.hasOwnProperty(name)) { + var template = templates[name]; + if (template) { + output[name] = createContentBlock(templates[name], scope, self, component); + } + } + } + return output; +} + function createLayoutBlock(template, yieldTo, self, component, attrs) { return blockFor(template, { - yieldTo: yieldTo, + yieldTo, // If we have an old-style Controller with a template it will be // passed as our `self` argument, and it should be the context for