ember.js: Input component does not accept documented event names

Is this intended behavior, or a bug?

A contributor fixed an issue with our checkbox examples in the Guides, and in testing their work, I found that none of the Input types accept dasherized names, or enter, unless you use the action handler. However, it stated in the guides that they do, and the API docs suggest it as well.

In a fresh 3.15 app:

@action
doSomething() {
  alert('something')
}
This does not work:
<Input @type="text" @key-down={{this.doSomething}} />

This also does not work, even though there are no dashes:
<Input type="text" @enter={{this.doSomething}} />

This does not work:
<Input type="text" {{on "enter" this.doSomething}} />

This works:
<Input @type="text" @keyDown={{this.doSomething}} />

This works:
<Input @type="text" @enter={{action "doSomething"}} />

This works:
<Input @type="text" @key-down={{action "doSomething"}} />

This works:
<Input type="text" {{on "keydown" this.doSomething}} />

To do:

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 7
  • Comments: 23 (16 by maintainers)

Most upvoted comments

@cah-briangantzler @kevinhinterlong

I would love to know the reason it’s needed to help my understanding of what fn is actually doing

Actually it has nothing to do with the fn helper, the problem is caused by this line:

https://github.com/emberjs/ember.js/blob/dbf4dd1fc183eaa7f1fb7ebf927c3a228fb0de0b/packages/%40ember/-internals/views/lib/mixins/text_support.js#L325

When passing actions to <Input /> without fn helper, the action is actually a “Mutable Cell Object” (correct me if I’m using a wrong term), it exists as view.attrs.enter, the view in here is the <Input /> component actually.

This line checks if a found “action” (view.attrs.enter) is actually a function, but apparently it isn’t. The real function exists on the view.attrs.enter.value property and if you call view.attrs.enter directly, it will proxy this invocation to its value instead.

If we wrap the action with fn helper, this line will pass and the action will be invoked correctly. So fn doesn’t has any problem here, the problem is in the old <Input /> component itself.

UPDATE: My english is weak, in order to help you understand, I setup a similar demo like @kevinhinterlong did, and I set a breakpoint on the line I mentioned above:

图片

(passing an action w/o fn helper)


图片

(passing an action w fn helper)

To be clear, the previously documented approach is correct – i.e. <Input @type="text" @key-down={{this.doSomething}} />. With modifiers now available, if you are listening to a native event, then the {{on}} modifier approach is probably preferable and more idiomatic.

The “camel case” approach is not correct as it “works” by “overriding” the private event handling hook as arguments are simply “splatted” onto the component instance.

And to be extra clear, there is no “magic” with the casing and they are not interchangeable – keyDown is hook inherited from Ember.Component intended for the implementation of the class to override, not for the consumer/invoker of the component. Since that name is taken (remember arguments and properties shares the same namespace in Ember.Component), the public API name for the callback arguments has to be different and key-down (etc) was chosen. It’s a completely different property from keyDown. (In fact, the implementation of the keyDown hook is what calls/dispatches the key-down callback.)

As far as passing strings (@enter="method") it part of the “sendAction” semantics and is deprecated and will be removed in 4.0.

This should be fixed by https://github.com/emberjs/ember.js/pull/18997, which should be released in 3.21 and should be backported has been released in 3.21 and backported into LTS 3.20 and 3.16.

All the event names that always worked on the classic {{input }} (prior to 3.10, which is the first release that supports <Input />) should also work on <Input/> and if one doesn’t it’s a bug.

However, this is complicated by the fact that there are patterns that worked by accident. For example, key-down is the supported event name, but it’s implemented internally with a keyDown method, so if you say keyDown= you’re actually overwriting that internal method and it just happens to do the right thing. But overwriting internal methods is… not a good idea.

(This is a great illustration for why glimmer components use this.args instead of putting all the arguments directly onto the component instance. We learned from the mistake.)

@gvocale If you change your code to:

<Input @enter={{action 'onEnter'}} @key-up={{this.onKeyUp}} />

then it would work as you expected.

And @key-up is exactly what the official document recommended way:

image

Please note what I highlighted in the screenshot.

The Additional work which mentioned in the above description is here:

https://github.com/emberjs/ember.js/blob/v3.16.3/packages/@ember/-internals/views/lib/mixins/text_support.js

and here is the reason why @key-up would trigger @enter as well:

https://github.com/emberjs/ember.js/blob/v3.16.3/packages/@ember/-internals/views/lib/mixins/text_support.js#L195-L198


I can walk through you what happened, but I can not explain why it is behaving like that. I believe this is one of a few “mysterious” things that often make people confused and ember.js should fix them ASAP.

At last, I highly recommend rewriting your code to:

{{! @enter is a "magical thing" that ember.js does for us, not a native event }}
<Input @enter={{fn this.onEnter}} {{on "keyup" (fn this.onKeyUp)}} />

and actions could be written as :

@action
onEnter(_value: string, event: KeyboardEvent): void {
  console.log('onEnter: ', event);
}

@action
onKeyUp(event: KeyboardEvent): void {
  console.log('onKeyUp: ', event);
}

Because this way should be last long enough, and I truly think it has a better consistency and easy to understand.