Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE ember-glimmer-forward-modifiers-with-splattributes] enable 👌 #17901

Merged
merged 4 commits into from
Apr 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 120 additions & 39 deletions packages/@ember/-internals/glimmer/lib/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const BOUNDS = symbol('BOUNDS');
```

```app/templates/components/person-profile.hbs
<h1>{{person.title}}</h1>
<h1>{{@person.name}}</h1>
{{yield}}
```

Expand All @@ -122,37 +122,31 @@ export const BOUNDS = symbol('BOUNDS');
If you want to customize the component in order to handle events, transform
arguments or maintain internal state, you implement a subclass of `Component`.

For example, you could implement the action `hello` for the `person-profile`
component:
One example is to add computed properties to your component:

```app/components/person-profile.js
import Component from '@ember/component';

export default Component.extend({
actions: {
hello(name) {
console.log("Hello", name);
displayName: computed('person.title', 'person.firstName', 'person.lastName', function() {
let { title, firstName, lastName } = this;

if (title) {
return `${title} ${lastName}`;
} else {
return `${firstName} ${lastName};
}
}
})
});
```

And then use it in the component's template:

```app/templates/components/person-profile.hbs
<h1>{{person.title}}</h1>
{{yield}} <!-- block contents -->
<button {{action 'hello' person.name}}>
Say Hello to {{person.name}}
</button>
<h1>{{this.displayName}}</h1>
{{yield}}
```

When the user clicks the button, Ember will invoke the `hello` action,
passing in the current value of `person.name` as an argument.

For historical reasons, components must have a `-` in their name when invoked
using the `{{` syntax.

## Customizing a Component's HTML Element in JavaScript

### HTML Tag
Expand Down Expand Up @@ -206,6 +200,7 @@ export const BOUNDS = symbol('BOUNDS');
import { computed } from '@ember/object';

export default Component.extend({
classNames: ['my-class', 'my-other-class'],
classNameBindings: ['propertyA', 'propertyB'],

propertyA: 'from-a',
Expand All @@ -218,7 +213,21 @@ export const BOUNDS = symbol('BOUNDS');
Invoking this component will produce HTML that looks like:

```html
<div id="ember1" class="ember-view from-a from-b"></div>
<div id="ember1" class="ember-view my-class my-other-class from-a from-b"></div>
```

Note that `classNames` and `classNameBindings` is in addition to the `class`
attribute passed with the angle bracket invocation syntax. Therefore, if this
component was invoked like so:

```handlebars
<MyWidget class="from-invocation" />
```

The resulting HTML will look similar to this:

```html
<div id="ember1" class="from-invocation ember-view my-class my-other-class from-a from-b"></div>
```

If the value of a class name binding returns a boolean the property name
Expand Down Expand Up @@ -364,7 +373,7 @@ export const BOUNDS = symbol('BOUNDS');
[EmberObject](/api/ember/release/classes/EmberObject) documentation for more
information about concatenated properties.

## HTML Attributes
### Other HTML Attributes

The HTML attribute section of a component's tag can be set by providing an
`attributeBindings` property set to an array of property names on the component.
Expand Down Expand Up @@ -408,6 +417,24 @@ export const BOUNDS = symbol('BOUNDS');
<a id="ember1" class="ember-view" href="http://google.com"></a>
```

HTML attributes passed with angle bracket invocations will take precedence
over those specified in `attributeBindings`. Therefore, if this component was
invoked like so:

```handlebars
<MyAnchor href="http://bing.com" @url="http://google.com" />
```

The resulting HTML will looks like this:

```html
<a id="ember1" class="ember-view" href="http://bing.com"></a>
```

Note that the `href` attribute is ultimately set to `http://bing.com`,
despite it having attribute binidng to the `url` property, which was
set to `http://google.com`.

Namespaced attributes (e.g. `xlink:href`) are supported, but have to be
mapped, since `:` is not a valid character for properties in Javascript:

Expand Down Expand Up @@ -544,37 +571,54 @@ export const BOUNDS = symbol('BOUNDS');

## Handling Browser Events

Components can respond to user-initiated events in one of three ways: method
implementation, through an event manager, and through `{{action}}` helper use
in their template or layout.
Components can respond to user-initiated events in one of three ways: passing
actions with angle bracket invocation, adding event handler methods to the
component's class, or adding actions to the component's template.

### Passing Actions With Angle Bracket Invoation

### Method Implementation
For one-off events specific to particular instance of a component, it is possible
to pass actions to the component's element using angle bracket invoation syntax.

```handlebars
<MyWidget {{action 'firstWidgetClicked'}} />

<MyWidget {{action 'secondWidgetClicked'}} />
```

Components can respond to user-initiated events by implementing a method that
matches the event name. An event object will be passed as the argument to this
method.
In this case, when the first component is clicked on, Ember will invoke the
`firstWidgetClicked` action. When the second component is clicked on, Ember
will invoke the `secondWidgetClicked` action instead.

Besides `{{action}}`, it is also possible to pass any arbitrary element modifiers
using the angle bracket invocation syntax.

### Event Handler Methods

Components can also respond to user-initiated events by implementing a method
that matches the event name. This approach is appropiate when the same event
should be handled by all instances of the same component.

An event object will be passed as the argument to the event handler method.

```app/components/my-widget.js
import Component from '@ember/component';

export default Component.extend({
click(event) {
// will be called with a browser event when an instance's rendered element
// is clicked
// `event.target` is either the component's element or one of its children
let tag = event.target.tagName.toLowerCase();
console.log('clicked on a `<${tag}>` HTML element!');
}
});
```

### `{{action}}` Helper
In this example, whenever the user clicked anywhere inside the component, it
will log a message to the console.

See [Ember.Templates.helpers.action](/api/ember/release/classes/Ember.Templates.helpers/methods/yield?anchor=yield).

### Event Names

All of the event handling approaches described above respond to the same set
of events. The names of the built-in events are listed below. (The hash of
built-in events exists in `Ember.EventDispatcher`.) Additional, custom events
can be registered by using `Application.customEvents`.
It is possible to handle event types other than `click` by implementing the
following event handler methods. In addition, custom events can be registered
by using `Application.customEvents`.

Touch events:

Expand Down Expand Up @@ -610,7 +654,7 @@ export const BOUNDS = symbol('BOUNDS');
* `focusOut`
* `input`

HTML5 drag and drop events:
Drag and drop events:

* `dragStart`
* `drag`
Expand All @@ -620,6 +664,43 @@ export const BOUNDS = symbol('BOUNDS');
* `dragEnd`
* `drop`

### `{{action}}` Helper

Instead of handling all events of a particular type anywhere inside the
component's element, you may instead want to limit it to a particular
element in the component's template. In this case, it would be more
convenient to implement an action instead.

For example, you could implement the action `hello` for the `person-profile`
component:

```app/components/person-profile.js
import Component from '@ember/component';

export default Component.extend({
actions: {
hello(name) {
console.log("Hello", name);
}
}
});
```

And then use it in the component's template:

```app/templates/components/person-profile.hbs
<h1>{{@person.name}}</h1>

<button {{action 'hello' @person.name}}>
Say Hello to {{@person.name}}
</button>
```

When the user clicks the button, Ember will invoke the `hello` action,
passing in the current value of `@person.name` as an argument.

See [Ember.Templates.helpers.action](/api/ember/release/classes/Ember.Templates.helpers/methods/action?anchor=action).

@class Component
@extends Ember.CoreView
@uses Ember.TargetActionSupport
Expand Down
2 changes: 1 addition & 1 deletion packages/@ember/canary-features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const DEFAULT_FEATURES = {
EMBER_IMPROVED_INSTRUMENTATION: null,
EMBER_MODULE_UNIFICATION: null,
EMBER_METAL_TRACKED_PROPERTIES: null,
EMBER_GLIMMER_FORWARD_MODIFIERS_WITH_SPLATTRIBUTES: null,
EMBER_GLIMMER_FORWARD_MODIFIERS_WITH_SPLATTRIBUTES: true,
EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS: true,
EMBER_GLIMMER_ANGLE_BRACKET_NESTED_LOOKUP: true,
EMBER_ROUTING_BUILD_ROUTEINFO_METADATA: true,
Expand Down