Skip to content

Commit

Permalink
Merge pull request #11257 from rwjblue/fix-input-on
Browse files Browse the repository at this point in the history
[BUGFIX beta] Fix `{{input on="foo" action="bar"}}`.
  • Loading branch information
rwjblue committed May 23, 2015
2 parents 10f772a + 6b909b5 commit 37f4325
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 0 deletions.
42 changes: 42 additions & 0 deletions packages/ember-htmlbars/tests/helpers/input_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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'}}", {
Expand All @@ -43,6 +48,7 @@ QUnit.module("{{input type='text'}}", {

teardown() {
runDestroy(view);
runDestroy(container);
}
});

Expand Down Expand Up @@ -160,6 +166,7 @@ QUnit.module("{{input type='text'}} - static values", {

teardown() {
runDestroy(view);
runDestroy(container);
}
});

Expand Down Expand Up @@ -195,6 +202,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();
Expand All @@ -214,6 +247,7 @@ QUnit.module("{{input type='text'}} - dynamic type", {

teardown() {
runDestroy(view);
runDestroy(container);
}
});

Expand Down Expand Up @@ -248,6 +282,7 @@ QUnit.module("{{input}} - default type", {

teardown() {
runDestroy(view);
runDestroy(container);
}
});

Expand Down Expand Up @@ -276,6 +311,8 @@ QUnit.module("{{input type='checkbox'}}", {

teardown() {
runDestroy(view);
runDestroy(container);

}
});

Expand Down Expand Up @@ -320,6 +357,8 @@ QUnit.module("{{input type='checkbox'}} - prevent value= usage", {

teardown() {
runDestroy(view);
runDestroy(container);

}
});

Expand Down Expand Up @@ -349,6 +388,7 @@ QUnit.module("{{input type=boundType}}", {

teardown() {
runDestroy(view);
runDestroy(container);
}
});

Expand Down Expand Up @@ -383,6 +423,7 @@ QUnit.module("{{input type='checkbox'}} - static values", {

teardown() {
runDestroy(view);
runDestroy(container);
}
});

Expand All @@ -409,6 +450,7 @@ QUnit.module("{{input type='text'}} - null/undefined values", {

teardown() {
runDestroy(view);
runDestroy(container);
}
});

Expand Down
2 changes: 2 additions & 0 deletions packages/ember-template-compiler/lib/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -30,6 +31,7 @@ registerPlugin('ast', TransformItemClass);
registerPlugin('ast', TransformComponentAttrsIntoMut);
registerPlugin('ast', TransformComponentCurlyToReadonly);
registerPlugin('ast', TransformAngleBracketComponents);
registerPlugin('ast', TransformInputOnToOnEvent);

export {
_Ember,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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.`);
});

0 comments on commit 37f4325

Please sign in to comment.