diff --git a/text/0000-attribute-actions.md b/text/0000-attribute-actions.md new file mode 100644 index 0000000000..db80f5c4f5 --- /dev/null +++ b/text/0000-attribute-actions.md @@ -0,0 +1,251 @@ +- Start Date: 2015-10-17 +- RFC PR: (leave this empty) +- Ember Issue: (leave this empty) + +# Summary + +Ember's `action` helper has two inconsistent forms. The goal of this RFC is to +allow the deprecation or removal of the classic action helper by adding a new +DOM event listener API designed for closure actions. + +The RFC suggests three phases of implementation: + +* In Ember 2.x (after this RFC is merged), attribute actions like + ``. + +Closure actions pluck a function from the immediate context of a template, +and return another function closing over that function and arguments. These +are used where subexpressions are valid in Ember, for example +`{{my-button onChange=(action 'save')}}` or +` +``` + +For comparison, to attach the `save` action with classic actions this syntax +is used today: + +```hbs + +``` + +Attribute actions are more verbose in the above case, but +have the advantage of a clear and extensible syntax. Consider attaching a +`mouseover` event: + +```hbs +
Save
+``` + +vs. + +```hbs +
Save
+``` + +The `onmouseover` attribute action is an evolution of the `onclick` usage. + +A second advantage this change is its parity with Ember component +action passing. For example consider these components: + +```hbs +{{my-button save=(action 'save')}}Save{{/my-button}} +Save +``` + +This common usage of action passing is visually very similar to the new +attribute attachment syntax. Or course an `on*` attribute passed to an +`Ember.Component` is simply a function with no special behavior. + +Passing non-actions to `on*` attributes will be discouraged. + +* Literals should be permitted, such as `onclick="window.alert('foo')"`. +* Non-function bindings are not permitted, and will throw from an assertion. + For example `onclick={{someBoundString}}` will cause an assertion to throw. +* Non-action functions such as `onclick={{someBoundFunction}}` should cause + a warning to be logged. This warning will encourage the user to use + `onclick={{action someBoundFunction}}` or use actions as normally + documented. + +#### Handling an Attribute Action + +`on*` attributes will be invoked with the raw DOM event (not the jQuery event) +as an argument. For example: + +```js +// app/components/my-button.js +export Ember.Component.extend({ + actions: { + save(event) { + console.log('saved with click on ', event.target); + this.get('model').save(); + } + } +}); +``` + +```hbs +{{! app/templates/components/my-button.hbs }} + +``` + +Actions create a closure over arguments, thus the event may not always be +the first argument. For example: + +```js +// app/components/my-input.js +export Ember.Component.extend({ + actions: { + log(prefix, event) { + console.log(prefix, Ember.$(event.target).val()); + } + } +}); +``` + +```hbs +{{! app/templates/components/my-input.hbs }} + +``` + +Additionally, the `value` argument to `action` is useful when passing the +event. + + +```js +// app/components/my-input.js +export Ember.Component.extend({ + actions: { + log(value) { + console.log(target); + } + } +}); +``` + +```hbs +{{! app/templates/components/my-input.hbs }} + +``` + +Passing the event is useful for interacting with the originating DOM node, +but additionally important for allowing event propagation to be controlled +(cancelled). + +#### Event Dispatching + +Attribute actions utilize a native API for dispatching that places them on +the bubbling event dispatcher. Browsers supported by Ember 2.x have three +kind of DOM event attachment: + +* `el.addEventListener('click', handlerFn, false)` add a handler to the bubbling + phase. +* `el.addEventListener('click', handlerFn, true)` add a handler to the capture + phase. +* `el.onclick = handlerFn;` add a handler to the bubbling phase. + +Attribute actions will be dispatched on the bubbling phase, and attached via +`addEventListener`. This ensures that if they are multiple handler making +their way onto an element they do not stomp on each other. + +Glimmer components may (and this is specilation more than design) permit +multiple events to be attached via reflection of invocation attrs to the +root element. For example: + +```hbs +{{! app/routes/index/template.hbs }} +{{! Invoke the component 'my-button' }} + +``` + +```hbs +{{! app/components/my-button/template.hbs }} +{{! Create a div for the root element, with a logging action attached }} +
+``` + +The `logClick` handler should be attached prior to the `save` handler being +attached. + +# Drawbacks + +Other events managed by Ember use a bespoke solution which delegates to +handlers from a single listener on the document body. Because this manager +is on the body, an event dispatched by that system will always execute after +all attribute actions. + +[This JSBin](http://emberjs.jsbin.com/refanumatu/1/edit?html,js,output) demonstrates using action and onclick, and you can see that the +order of the wrapper click event and the action/onclick attached function +are not consistent. Events that will still execute via the event manager are: + +* Those on `Ember.Component` components +* Those attached via element space `{{action 'foo' on="click"}}` usage + +This RFC schedules the deprecation the second API, however to ensure the +ordering problems of `Ember.Component` do not continue into glimmer components +it also recommends that `Ember.GlimmerComponent` have no `save() {` event +attachment API. Instead attribute actions on the root element should be +used. + +Additionally, Ember's [current event naming schema](http://emberjs.com/api/classes/Ember.View.html#toc_event-names) +for handlers on components and to the `on=` argument of `{{action}}` does not +stay consistantly lowercase as the native attribute names do. This inconsistancy +is unfortunate. + +# Alternatives + +Considered (and rejected) was the idea of using `on-*` instead of the native +action attribute name. This option would make clear that native semantics do +not apply, and we could perhaps opt-in to some consistency features such as +delegating attribute actions from the event manager. + +# Unresolved questions + +None. diff --git a/text/0000-kebab-actions.md b/text/0000-kebab-actions.md deleted file mode 100644 index 9fb7af8916..0000000000 --- a/text/0000-kebab-actions.md +++ /dev/null @@ -1,208 +0,0 @@ -- Start Date: 2015-10-17 -- RFC PR: (leave this empty) -- Ember Issue: (leave this empty) - -# Summary - -Ember's `action` helper has two inconsistent forms. The goal of this RFC is to -allow the deprecation or removal of the classic action helper by adding a new -DOM event listener API designed for closure actions. - -The RFC suggests three phases of implementation: - -* In Ember 2.x, kebab actions are released as an API -* In Ember 2.x+1, classic actions will be deprecated -* In Ember 3.0, classic actions will be removed - -# Motivation - -Classic actions bubble a string action through handlers, and are used in the -space of an element. For example: ``. - -Closure actions pluck a function from the immediate context of a template, -and return another function closing over that function and arguments. These -are used where subexpressions are valid in Ember, for example -`{{my-button onChange=(action 'save')}}` or -` -``` - -For comparison, to attach the `save` action with classic actions this syntax -would be used instead: - -```hbs - -``` - -Kebabs are more verbose in the above case, but have the -advantage of a clear and extensible syntax. Consider attaching a -`mouseover` event: - -```hbs -
Save
-``` - -vs. - -```hbs -
Save
-``` - -The kebab version is an basic evolution of the `on-click` usage. - -The second advantage of kebab syntax is its parity with Ember component -action passing. For example consider these components: - -```hbs -{{my-button on-click=(action 'save')}}Save{{/my-button}} -Save -``` - -Kebab syntax is much closer to the above than classic action syntax. Of course -a kebab action passed to a component (or angle bracket component) is simply -a function with no special behavior. - -If any non-function is passed to a kebab, such as in -`on-click="alert('blah');"` an assertion should fail. Only functions are -permitted. - -#### Handling a Kebab Action - -Kebab actions will be invoked with the raw DOM event (not the jQuery event) -as an argument. For example: - -```js -// app/components/my-button.js -export Ember.Component.extend({ - actions: { - save(event) { - console.log('saved with click on ', event.target); - this.get('model').save(); - } - } -}); -``` - -```hbs -{{! app/templates/components/my-button.hbs }} - -``` - -Actions curry arguments, thus the event may not always be the first argument. For -example: - -```js -// app/components/my-input.js -export Ember.Component.extend({ - actions: { - log(prefix, event) { - console.log(prefix, Ember.$(event.target).val()); - } - } -}); -``` - -```hbs -{{! app/templates/components/my-input.hbs }} - -``` - -Additionally, the `value` argument to `action` is useful when passing the -event. - - -```js -// app/components/my-input.js -export Ember.Component.extend({ - actions: { - log(value) { - console.log(target); - } - } -}); -``` - -```hbs -{{! app/templates/components/my-input.hbs }} - -``` - -Passing the event is useful for interacting with the originating DOM node, -but additionally important for allowing event propagation to be controlled -(cancelled). - -#### Event Management - -Kebab actions are lazy. If a kebab action of `on-flummux` is used, then Ember -should listen for the event of `flummux` on the root element and dispatch -that action when it fires. - -If an event from the [list of events Ember listens for](http://emberjs.com/api/classes/Ember.View.html#toc_event-names) -is used, then kebabs can use the already existing listeners. - -# Drawbacks - -Obviously, if this is an RFC, there cannot be drawbacks. - -j/k. - -Kebab actions, as described here, use a dasherized multi-word format. For -example `on-mouse-over`. This corresponds to how Ember already camelizes -event listeners (for example `mouseOver` is the method for a component -listener), but does *not* correspond to the native browser APIs which would -imply `on-mouseover`. As what form of camelization, dasherization, or single-word-ness -to use can be extremely confusing to a newcomer, I am inclined to suggest -matching the native browser instead of following Ember's convention of splitting -the words. - -# Alternatives - -One immediate alternative making the rounds today is using event listeners -directly. For example: - -```hbs - -``` - -The above passes a function to the `onclick` property of the `