diff --git a/FEATURES.md b/FEATURES.md index d30b0940770..fd99c2406c3 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -182,3 +182,42 @@ for a detailed explanation. ``` Added in [#10160](https://github.com/emberjs/ember.js/pull/10160) + +* `ember-htmlbars-scoped-helpers` + + Adds the ability for helpers to be resolved from the local component. Allowing components to + use local helpers (that do not get made available globally) AND/OR providing local helpers to + the provided block param. + + Block Params Example: + +```javascript +// app/components/form-for.js + +import Ember from "ember"; +import FormForInput from "./helpers/input"; + +var makeViewHelper = Ember.HTMLBars.makeViewHelper; + +export default Ember.Component.extend({ + formHelpers: { + input: makeViewHelper(FormForInput) + } +}); +``` + +```handlebars +{{! app/templates/components/form-for.hbs }} + +{{yield formHelpers}} +``` + +```handlebars +{{! app/templates/post/new.hbs }} + +{{#form-for as |f|}} + {{f.input label="Title" value=model.title}} +{{/form-for}} +``` + + Added in [#10244](https://github.com/emberjs/ember.js/pull/10244) diff --git a/features.json b/features.json index 8b5573546b4..0a00dac7a49 100644 --- a/features.json +++ b/features.json @@ -15,7 +15,8 @@ "ember-testing-checkbox-helpers": null, "ember-metal-stream": null, "ember-htmlbars-each-with-index": true, - "ember-application-initializer-context": null + "ember-application-initializer-context": null, + "ember-htmlbars-scoped-helpers": null }, "debugStatements": [ "Ember.warn", diff --git a/packages/ember-htmlbars/lib/system/lookup-helper.js b/packages/ember-htmlbars/lib/system/lookup-helper.js index 93b5e08df9d..1308c0b06b5 100644 --- a/packages/ember-htmlbars/lib/system/lookup-helper.js +++ b/packages/ember-htmlbars/lib/system/lookup-helper.js @@ -33,6 +33,13 @@ export default function lookupHelper(name, view, env) { return helper; } + if (Ember.FEATURES.isEnabled('ember-htmlbars-scoped-helpers')) { + helper = view.getStream(name).value(); + if (helper && helper.isHTMLBars) { + return helper; + } + } + var container = view.container; if (!container || ISNT_HELPER_CACHE.get(name)) { diff --git a/packages/ember-htmlbars/tests/integration/helper_lookup_test.js b/packages/ember-htmlbars/tests/integration/helper_lookup_test.js new file mode 100644 index 00000000000..85bc9f51160 --- /dev/null +++ b/packages/ember-htmlbars/tests/integration/helper_lookup_test.js @@ -0,0 +1,97 @@ +import Registry from "container/registry"; +import run from "ember-metal/run_loop"; +import ComponentLookup from 'ember-views/component_lookup'; +import View from "ember-views/views/view"; +import Component from "ember-views/views/component"; +import compile from "ember-template-compiler/system/compile"; +import makeBoundHelper from "ember-htmlbars/system/make_bound_helper"; +import makeViewHelper from "ember-htmlbars/system/make-view-helper"; +import { runAppend, runDestroy } from "ember-runtime/tests/utils"; + +var registry, container, view; + +var blockParamsEnabled; +if (Ember.FEATURES.isEnabled('ember-htmlbars-block-params')) { + blockParamsEnabled = true; +} + +if (Ember.FEATURES.isEnabled('ember-htmlbars-scoped-helpers')) { +// jscs:disable validateIndentation + +QUnit.module("ember-htmlbars: helper lookup -- scoped helpers", { + setup: function() { + registry = new Registry(); + container = registry.container(); + registry.optionsForType('component', { singleton: false }); + registry.optionsForType('view', { singleton: false }); + registry.optionsForType('template', { instantiate: false }); + registry.optionsForType('helper', { instantiate: false }); + registry.register('component-lookup:main', ComponentLookup); + }, + + teardown: function() { + runDestroy(container); + runDestroy(view); + + registry = container = view = null; + } +}); + +test("basic scoped helper usage", function() { + view = View.create({ + name: 'lucy', + + capitalize: makeBoundHelper(function(params) { + return params[0].toUpperCase(); + }), + + template: compile('{{view.name}} - {{view.capitalize view.name}}') + }); + + runAppend(view); + + equal(view.$().text(), "lucy - LUCY"); + + run(function() { + view.set('name', "molly"); + }); + + equal(view.$().text(), "molly - MOLLY"); +}); + +if (blockParamsEnabled) { + test("scoped helper usage through block param", function() { + registry.register('template:components/form-for', compile('{{yield formHelpers}}')); + + registry.register('component:form-for', Component.extend({ + tagName: 'form', + + formHelpers: { + input: makeViewHelper(Component.extend({ + layout: compile('{{input value=value viewName="inputId"}}') + })) + } + })); + + view = View.create({ + firstName: 'Robert', + container: container, + template: compile('{{#form-for as |f|}} {{f.input label="First Name" value=view.firstName}} {{/form-for}}') + }); + + runAppend(view); + + equal(view.$('form').length, 1, 'form was created'); + equal(view.$('label').text(), 'First Name', 'label was created'); + equal(view.$('input').val(), 'Robert', 'input was created'); + + run(function() { + view.set('firstName', 'Jacquie'); + }); + + equal(view.$('input').val(), 'Jacquie', 'input was updated'); + }); +} + +// jscs:enable validateIndentation +} diff --git a/packages/ember-htmlbars/tests/system/lookup-helper_test.js b/packages/ember-htmlbars/tests/system/lookup-helper_test.js index dcb5c860278..a92dfaca298 100644 --- a/packages/ember-htmlbars/tests/system/lookup-helper_test.js +++ b/packages/ember-htmlbars/tests/system/lookup-helper_test.js @@ -1,5 +1,6 @@ import lookupHelper from "ember-htmlbars/system/lookup-helper"; import ComponentLookup from "ember-views/component_lookup"; +import EmberComponent from "ember-views/views/component"; import Registry from "container/registry"; import Component from "ember-views/views/component"; @@ -33,7 +34,7 @@ test('looks for helpers in the provided `env.helpers`', function() { test('returns undefined if no container exists (and helper is not found in env)', function() { var env = generateEnv(); - var view = {}; + var view = EmberComponent.create(); var actual = lookupHelper('flubarb', view, env); @@ -42,13 +43,13 @@ test('returns undefined if no container exists (and helper is not found in env)' test('does not lookup in the container if the name does not contain a dash (and helper is not found in env)', function() { var env = generateEnv(); - var view = { + var view = EmberComponent.create({ container: { lookup: function() { ok(false, 'should not lookup in the container'); } } - }; + }); var actual = lookupHelper('flubarb', view, env); @@ -57,9 +58,9 @@ test('does not lookup in the container if the name does not contain a dash (and test('does a lookup in the container if the name contains a dash (and helper is not found in env)', function() { var env = generateEnv(); - var view = { + var view = EmberComponent.create({ container: generateContainer() - }; + }); function someName() {} someName.isHTMLBars = true; @@ -73,9 +74,9 @@ test('does a lookup in the container if the name contains a dash (and helper is test('wraps helper from container in a Handlebars compat helper', function() { expect(2); var env = generateEnv(); - var view = { + var view = EmberComponent.create({ container: generateContainer() - }; + }); var called; function someName() { @@ -100,9 +101,9 @@ test('wraps helper from container in a Handlebars compat helper', function() { test('asserts if component-lookup:main cannot be found', function() { var env = generateEnv(); - var view = { + var view = EmberComponent.create({ container: generateContainer() - }; + }); view.container._registry.unregister('component-lookup:main'); @@ -113,9 +114,9 @@ test('asserts if component-lookup:main cannot be found', function() { test('registers a helper in the container if component is found', function() { var env = generateEnv(); - var view = { + var view = EmberComponent.create({ container: generateContainer() - }; + }); view.container._registry.register('component:some-name', Component); @@ -123,3 +124,45 @@ test('registers a helper in the container if component is found', function() { ok(view.container.lookup('helper:some-name'), 'new helper was registered'); }); + +if (Ember.FEATURES.isEnabled('ember-htmlbars-scoped-helpers')) { + test('local helpers do not override global helpers', function() { + var env = generateEnv({ + 'flubarb': function() { } + }); + + var view = EmberComponent.create({ + flubarb: { + isHTMLBars: true + } + }); + + var actual = lookupHelper('flubarb', view, env); + + equal(actual, env.helpers.flubarb, 'helper from env wins over view local helpers'); + }); + + test('looks helpers up on the view for non-global helpers', function() { + var env = generateEnv(); + var view = EmberComponent.create({ + flubarb: { + isHTMLBars: true + } + }); + + var actual = lookupHelper('flubarb', view, env); + + equal(actual, view.flubarb, 'matches helpers in the root of the view'); + }); + + test('does not match local helpers/props without isHTMLBars: true', function() { + var env = generateEnv(); + var view = EmberComponent.create({ + flubarb: { } + }); + + var actual = lookupHelper('flubarb', view, env); + + equal(actual, undefined, 'does not match local helpers without isHTMLBars: true'); + }); +}