diff --git a/package.json b/package.json index 682c68922a5..5d566354989 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "express": "^4.5.0", "github": "^0.2.3", "glob": "~4.3.2", - "htmlbars": "0.13.16", + "htmlbars": "0.13.17", "qunit-extras": "^1.3.0", "qunitjs": "^1.16.0", "route-recognizer": "0.1.5", 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 b2904bf12e7..245d2f8daa8 100644 --- a/packages/ember-htmlbars/lib/node-managers/component-node-manager.js +++ b/packages/ember-htmlbars/lib/node-managers/component-node-manager.js @@ -33,7 +33,7 @@ ComponentNodeManager.create = function(renderNode, env, options) { attrs, parentView, parentScope, - template } = options; + templates } = options; attrs = attrs || {}; @@ -80,9 +80,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 @@ -105,7 +105,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/node-managers/view-node-manager.js b/packages/ember-htmlbars/lib/node-managers/view-node-manager.js index 488df16cb8b..f7cf2e289f0 100644 --- a/packages/ember-htmlbars/lib/node-managers/view-node-manager.js +++ b/packages/ember-htmlbars/lib/node-managers/view-node-manager.js @@ -80,7 +80,7 @@ ViewNodeManager.create = function(renderNode, env, attrs, found, parentView, pat Ember.assert("BUG: ViewNodeManager.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 9e0b4410814..eea1421617c 100644 --- a/packages/ember-htmlbars/tests/integration/component_invocation_test.js +++ b/packages/ember-htmlbars/tests/integration/component_invocation_test.js @@ -345,3 +345,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 900bee14a4c..da57e4b6dfd 100755 --- a/packages/ember-metal-views/lib/renderer.js +++ b/packages/ember-metal-views/lib/renderer.js @@ -24,7 +24,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 0f3a9a7c861..78c08ca0c8e 100644 --- a/packages/ember-views/lib/system/build-component-template.js +++ b/packages/ember-views/lib/system/build-component-template.js @@ -10,14 +10,13 @@ export default function buildComponentTemplate({ component, layout }, attrs, con component = null; } - if (content.template) { - meta = content.template.meta; - blockToRender = createContentBlock(content.template, content.scope, content.self, component); - } - if (layout && layout.raw) { + let yieldTo = createContentBlocks(content.templates, content.scope, content.self, component); + blockToRender = createLayoutBlock(layout.raw, yieldTo, content.self, component, attrs); meta = layout.raw.meta; - blockToRender = createLayoutBlock(layout.raw, blockToRender, content.self, component, attrs); + } else if (content.templates && content.templates.default) { + blockToRender = createContentBlock(content.templates.default, content.scope, content.self, component); + meta = content.templates.default.meta; } if (component) { @@ -59,9 +58,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