From 6039a3efaed9921a89ec2f52c6382539ba8686b0 Mon Sep 17 00:00:00 2001 From: Robert Jackson Date: Fri, 22 May 2015 17:20:41 -0400 Subject: [PATCH] [BUGFIX beta] Fix `{{input on="foo" action="bar"}}`. --- .../tests/helpers/input_test.js | 31 ++++ packages/ember-template-compiler/lib/main.js | 2 + .../plugins/transform-input-on-to-onEvent.js | 143 ++++++++++++++++++ .../tests/plugins/transform-input-on-test.js | 43 ++++++ 4 files changed, 219 insertions(+) create mode 100644 packages/ember-template-compiler/lib/plugins/transform-input-on-to-onEvent.js create mode 100644 packages/ember-template-compiler/tests/plugins/transform-input-on-test.js diff --git a/packages/ember-htmlbars/tests/helpers/input_test.js b/packages/ember-htmlbars/tests/helpers/input_test.js index 839afa8a3a1..ed950b2414b 100644 --- a/packages/ember-htmlbars/tests/helpers/input_test.js +++ b/packages/ember-htmlbars/tests/helpers/input_test.js @@ -7,6 +7,7 @@ import Registry from "container/registry"; import ComponentLookup from "ember-views/component_lookup"; import TextField from 'ember-views/views/text_field'; import Checkbox from 'ember-views/views/checkbox'; +import EventDispatcher from 'ember-views/system/event_dispatcher'; var view; var controller, registry, container; @@ -16,7 +17,11 @@ function commonSetup() { registry.register('component:-text-field', TextField); registry.register('component:-checkbox', Checkbox); registry.register('component-lookup:main', ComponentLookup); + registry.register('event_dispatcher:main', EventDispatcher); container = registry.container(); + + var dispatcher = container.lookup('event_dispatcher:main'); + dispatcher.setup({}, '#qunit-fixture'); } QUnit.module("{{input type='text'}}", { @@ -195,6 +200,32 @@ QUnit.test("input tabindex is updated when setting tabindex property of view", f equal(view.$('input').attr('tabindex'), "5", "renders text field with the tabindex"); }); +QUnit.test('specifying `on="someevent" action="foo"` triggers the action', function() { + expect(2); + runDestroy(view); + expectDeprecation(`Using '{{input on="focus-in" action="doFoo"}} 'foo.hbs' @L1:C0 is deprecated. Please use '{{input focus-in="doFoo"}}' instead.`); + + controller = { + send(actionName, value, sender) { + equal(actionName, 'doFoo', "text field sent correct action name"); + } + }; + + view = View.create({ + container, + controller, + + template: compile('{{input type="text" on="focus-in" action="doFoo"}}', { moduleName: 'foo.hbs' }) + }); + + runAppend(view); + + run(function() { + var textField = view.$('input'); + textField.trigger('focusin'); + }); +}); + QUnit.module("{{input type='text'}} - dynamic type", { setup() { commonSetup(); diff --git a/packages/ember-template-compiler/lib/main.js b/packages/ember-template-compiler/lib/main.js index 68ee380961a..7ffeb743bf5 100644 --- a/packages/ember-template-compiler/lib/main.js +++ b/packages/ember-template-compiler/lib/main.js @@ -15,6 +15,7 @@ import TransformItemClass from "ember-template-compiler/plugins/transform-item-c import TransformComponentAttrsIntoMut from "ember-template-compiler/plugins/transform-component-attrs-into-mut"; import TransformComponentCurlyToReadonly from "ember-template-compiler/plugins/transform-component-curly-to-readonly"; import TransformAngleBracketComponents from "ember-template-compiler/plugins/transform-angle-bracket-components"; +import TransformInputOnToOnEvent from "ember-template-compiler/plugins/transform-input-on-to-onEvent"; // used for adding Ember.Handlebars.compile for backwards compat import "ember-template-compiler/compat"; @@ -30,6 +31,7 @@ registerPlugin('ast', TransformItemClass); registerPlugin('ast', TransformComponentAttrsIntoMut); registerPlugin('ast', TransformComponentCurlyToReadonly); registerPlugin('ast', TransformAngleBracketComponents); +registerPlugin('ast', TransformInputOnToOnEvent); export { _Ember, diff --git a/packages/ember-template-compiler/lib/plugins/transform-input-on-to-onEvent.js b/packages/ember-template-compiler/lib/plugins/transform-input-on-to-onEvent.js new file mode 100644 index 00000000000..1ffd8223abc --- /dev/null +++ b/packages/ember-template-compiler/lib/plugins/transform-input-on-to-onEvent.js @@ -0,0 +1,143 @@ +/** + @module ember + @submodule ember-htmlbars +*/ + +/** + An HTMLBars AST transformation that replaces all instances of + + ```handlebars + {{input on="enter" action="doStuff"}} + {{input on="key-press" action="doStuff"}} + ``` + + with + + ```handlebars + {{input enter="doStuff"}} + {{input key-press="doStuff"}} + ``` + + @private + @class TransformInputOnToOnEvent +*/ +function TransformInputOnToOnEvent(options) { + // set later within HTMLBars to the syntax package + this.syntax = null; + this.options = options || {}; +} + +/** + @private + @method transform + @param {AST} The AST to be transformed. +*/ +TransformInputOnToOnEvent.prototype.transform = function TransformInputOnToOnEvent_transform(ast) { + const pluginContext = this; + const b = pluginContext.syntax.builders; + const walker = new pluginContext.syntax.Walker(); + + walker.visit(ast, function(node) { + if (pluginContext.validate(node)) { + let action = hashPairForKey(node.hash, 'action'); + let on = hashPairForKey(node.hash, 'on'); + let onEvent = hashPairForKey(node.hash, 'onEvent'); + let normalizedOn = on || onEvent; + let moduleInfo = pluginContext.calculateModuleInfo(node.loc); + + if (normalizedOn && normalizedOn.value.type !== 'StringLiteral') { + Ember.deprecate( + `Using a dynamic value for '#{normalizedOn.key}=' with the '{{input}}' helper ${moduleInfo} is deprecated.` + ); + + normalizedOn.key = 'onEvent'; + return; // exit early, as we cannot transform further + } + + removeFromHash(node.hash, normalizedOn); + removeFromHash(node.hash, action); + + if (!action) { + Ember.deprecate( + `Using '{{input ${normalizedOn.key}="${normalizedOn.value.value}" ...}}' without specifying an action ${moduleInfo} will do nothing.` + ); + + return; // exit early, if no action was available there is nothing to do + } + + + let specifiedOn = normalizedOn ? `${normalizedOn.key}="${normalizedOn.value.value}" ` : ''; + if (normalizedOn && normalizedOn.value.value === 'keyPress') { + // using `keyPress` in the root of the component will + // clobber the keyPress event handler + normalizedOn.value.value = 'key-press'; + } + + let expected = `${normalizedOn ? normalizedOn.value.value : 'enter'}="${action.value.original}"`; + + Ember.deprecate( + `Using '{{input ${specifiedOn}action="${action.value.original}"}} ${moduleInfo} is deprecated. Please use '{{input ${expected}}}' instead.` + ); + if (!normalizedOn) { + normalizedOn = b.pair('onEvent', b.string('enter')); + } + + node.hash.pairs.push(b.pair( + normalizedOn.value.value, + action.value + )); + } + }); + + return ast; +}; + +TransformInputOnToOnEvent.prototype.validate = function TransformWithAsToHash_validate(node) { + return node.type === 'MustacheStatement' && + node.path.original === 'input' && + ( + hashPairForKey(node.hash, 'action') || + hashPairForKey(node.hash, 'on') || + hashPairForKey(node.hash, 'onEvent') + ); +}; + +TransformInputOnToOnEvent.prototype.calculateModuleInfo = function TransformInputOnToOnEvent_calculateModuleInfo(loc) { + let { column, line } = loc.start || {}; + let moduleInfo = ''; + if (this.options.moduleName) { + moduleInfo += `'${this.options.moduleName}' `; + } + + if (line !== undefined && column !== undefined) { + moduleInfo += `@L${line}:C${column}`; + } + + return moduleInfo; +}; + +function hashPairForKey(hash, key) { + for (let i = 0, l = hash.pairs.length; i < l; i++) { + let pair = hash.pairs[i]; + if (pair.key === key) { + return pair; + } + } + + return false; +} + +function removeFromHash(hash, pairToRemove) { + var newPairs = []; + for (let i = 0, l = hash.pairs.length; i < l; i++) { + let pair = hash.pairs[i]; + + if (pair !== pairToRemove) { + newPairs.push(pair); + } + } + + hash.pairs = newPairs; +} + +export default TransformInputOnToOnEvent; diff --git a/packages/ember-template-compiler/tests/plugins/transform-input-on-test.js b/packages/ember-template-compiler/tests/plugins/transform-input-on-test.js new file mode 100644 index 00000000000..09a79a3bd72 --- /dev/null +++ b/packages/ember-template-compiler/tests/plugins/transform-input-on-test.js @@ -0,0 +1,43 @@ +import { compile } from "ember-template-compiler"; + +QUnit.module('ember-template-compiler: transform-input-on'); + +QUnit.test("Using `action` without `on` provides a deprecation", function() { + expect(1); + + expectDeprecation(function() { + compile('{{input action="foo"}}', { + moduleName: 'foo/bar/baz' + }); + }, `Using '{{input action="foo"}} 'foo/bar/baz' @L1:C0 is deprecated. Please use '{{input enter="foo"}}' instead.`); +}); + +QUnit.test("Using `action` with `on` provides a deprecation", function() { + expect(1); + + expectDeprecation(function() { + compile('{{input on="focus-in" action="foo"}}', { + moduleName: 'foo/bar/baz' + }); + }, `Using '{{input on="focus-in" action="foo"}} 'foo/bar/baz' @L1:C0 is deprecated. Please use '{{input focus-in="foo"}}' instead.`); +}); + +QUnit.test("Using `on='keyPress'` does not clobber `keyPress`", function() { + expect(1); + + expectDeprecation(function() { + compile('{{input on="keyPress" action="foo"}}', { + moduleName: 'foo/bar/baz' + }); + }, `Using '{{input on="keyPress" action="foo"}} 'foo/bar/baz' @L1:C0 is deprecated. Please use '{{input key-press="foo"}}' instead.`); +}); + +QUnit.test("Using `on='foo'` without `action='asdf'` raises specific deprecation", function() { + expect(1); + + expectDeprecation(function() { + compile('{{input on="asdf"}}', { + moduleName: 'foo/bar/baz' + }); + }, `Using '{{input on="asdf" ...}}' without specifying an action 'foo/bar/baz' @L1:C0 will do nothing.`); +});