diff --git a/FEATURES.md b/FEATURES.md index 8991afde5c8..685ff97cdb0 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -80,3 +80,19 @@ for a detailed explanation. * `ember-metal-ember-assign` Add `Ember.assign` that is polyfill for `Object.assign`. + +* `ember-contextual-components` + + Introduce a helper that creates closures over attrs and its own path, then + allow the closed over cell to be invoked via the `{{component` helper or + any reference with a dot in the path. + + For example: + + ```js + {{#with (hash profile=(component "user-profile")) as |userComponents|}} + {{userComponents.profile}} + {{/with}} + ``` + + Implements RFC [#64](https://github.com/emberjs/rfcs/blob/master/text/0064-contextual-component-lookup.md) diff --git a/packages/ember-htmlbars/lib/hooks/component.js b/packages/ember-htmlbars/lib/hooks/component.js index 6cb90739a40..e7b0b67d539 100644 --- a/packages/ember-htmlbars/lib/hooks/component.js +++ b/packages/ember-htmlbars/lib/hooks/component.js @@ -3,15 +3,21 @@ import ComponentNodeManager from 'ember-htmlbars/node-managers/component-node-ma import buildComponentTemplate, { buildHTMLTemplate } from 'ember-views/system/build-component-template'; import lookupComponent from 'ember-htmlbars/utils/lookup-component'; import Cache from 'ember-metal/cache'; +import { + CONTAINS_DASH_CACHE, + CONTAINS_DOT_CACHE +} from 'ember-htmlbars/system/lookup-helper'; +import { + COMPONENT_PATH, + COMPONENT_HASH, + isComponentCell, + mergeHash, +} from 'ember-htmlbars/keywords/closure-component'; var IS_ANGLE_CACHE = new Cache(1000, function(key) { return key.match(/^(@?)<(.*)>$/); }); -var CONTAINS_DASH = new Cache(1000, function(key) { - return key.indexOf('-') !== -1; -}); - export default function componentHook(renderNode, env, scope, _tagName, params, attrs, templates, visitor) { var state = renderNode.getState(); @@ -22,6 +28,15 @@ export default function componentHook(renderNode, env, scope, _tagName, params, } let tagName = _tagName; + if (CONTAINS_DOT_CACHE.get(tagName)) { + let stream = env.hooks.get(env, scope, tagName); + let componentCell = stream.value(); + if (isComponentCell(componentCell)) { + tagName = componentCell[COMPONENT_PATH]; + attrs = mergeHash(componentCell[COMPONENT_HASH], attrs); + } + } + let isAngleBracket = false; let isTopLevel = false; let isDasherized = false; @@ -34,7 +49,7 @@ export default function componentHook(renderNode, env, scope, _tagName, params, isTopLevel = !!angles[1]; } - if (CONTAINS_DASH.get(tagName)) { + if (CONTAINS_DASH_CACHE.get(tagName)) { isDasherized = true; } diff --git a/packages/ember-htmlbars/lib/system/lookup-helper.js b/packages/ember-htmlbars/lib/system/lookup-helper.js index 7e961084608..361187f7299 100644 --- a/packages/ember-htmlbars/lib/system/lookup-helper.js +++ b/packages/ember-htmlbars/lib/system/lookup-helper.js @@ -10,6 +10,10 @@ export var CONTAINS_DASH_CACHE = new Cache(1000, function(key) { return key.indexOf('-') !== -1; }); +export var CONTAINS_DOT_CACHE = new Cache(1000, function(key) { + return key.indexOf('.') !== -1; +}); + export function validateLazyHelperName(helperName, container, keywords) { return container && !(helperName in keywords); } diff --git a/packages/ember-htmlbars/lib/utils/is-component.js b/packages/ember-htmlbars/lib/utils/is-component.js index 37cdce9cd96..5ce2b08e26e 100644 --- a/packages/ember-htmlbars/lib/utils/is-component.js +++ b/packages/ember-htmlbars/lib/utils/is-component.js @@ -3,7 +3,12 @@ @submodule ember-htmlbars */ -import { CONTAINS_DASH_CACHE } from 'ember-htmlbars/system/lookup-helper'; +import { + CONTAINS_DASH_CACHE, + CONTAINS_DOT_CACHE +} from 'ember-htmlbars/system/lookup-helper'; +import { isComponentCell } from 'ember-htmlbars/keywords/closure-component'; +import { isStream } from 'ember-metal/streams/utils'; /* Given a path name, returns whether or not a component with that @@ -12,7 +17,18 @@ import { CONTAINS_DASH_CACHE } from 'ember-htmlbars/system/lookup-helper'; export default function isComponent(env, scope, path) { var container = env.container; if (!container) { return false; } - if (!CONTAINS_DASH_CACHE.get(path)) { return false; } - return container.registry.has('component:' + path) || - container.registry.has('template:components/' + path); + if (typeof path === 'string') { + if (CONTAINS_DOT_CACHE.get(path)) { + let stream = env.hooks.get(env, scope, path); + if (isStream(stream)) { + let cell = stream.value(); + if (isComponentCell(cell)) { + return true; + } + } + } + if (!CONTAINS_DASH_CACHE.get(path)) { return false; } + return container.registry.has('component:' + path) || + container.registry.has('template:components/' + path); + } } diff --git a/packages/ember-htmlbars/tests/helpers/closure_component_test.js b/packages/ember-htmlbars/tests/helpers/closure_component_test.js index a974494b232..5b521a05503 100644 --- a/packages/ember-htmlbars/tests/helpers/closure_component_test.js +++ b/packages/ember-htmlbars/tests/helpers/closure_component_test.js @@ -339,4 +339,50 @@ if (isEnabled('ember-contextual-components')) { runAppend(component); }, `The component helper cannot be used without a valid component name. You used "not-a-component" via (component compName)`); }); + + QUnit.test('renders with dot path', function() { + let expectedText = 'Hodi'; + registry.register( + 'template:components/-looked-up', + compile(expectedText) + ); + + let template = compile('{{#with (hash lookedup=(component "-looked-up")) as |object|}}{{object.lookedup}}{{/with}}'); + component = Component.extend({ container, template }).create(); + + runAppend(component); + equal(component.$().text(), expectedText, '-looked-up component rendered'); + }); + + QUnit.test('renders with dot path and attr', function() { + let expectedText = 'Hodi'; + registry.register( + 'template:components/-looked-up', + compile('{{expectedText}}') + ); + + let template = compile('{{#with (hash lookedup=(component "-looked-up")) as |object|}}{{object.lookedup expectedText=expectedText}}{{/with}}'); + component = Component.extend({ container, template }).create({ + expectedText + }); + + runAppend(component); + equal(component.$().text(), expectedText, '-looked-up component rendered'); + }); + + QUnit.test('renders with dot path curried over attr', function() { + let expectedText = 'Hodi'; + registry.register( + 'template:components/-looked-up', + compile('{{expectedText}}') + ); + + let template = compile('{{#with (hash lookedup=(component "-looked-up" expectedText=expectedText)) as |object|}}{{object.lookedup}}{{/with}}'); + component = Component.extend({ container, template }).create({ + expectedText + }); + + runAppend(component); + equal(component.$().text(), expectedText, '-looked-up component rendered'); + }); }