Components
Introduction
HTML was designed in a time when the browser was a simple document viewer. Developers building great web apps need something more.
Instead of trying to replace HTML, however, Ember.js embraces it, then adds powerful new features that modernize it for building web apps.
Currently, you are limited to the tags that are created for you by the W3C. Wouldn’t it be great if you could define your own, application-specific HTML tags, then implement their behavior using JavaScript?
That’s exactly what components let you do. In fact, it’s such a good idea that the W3C is currently working on the Custom Elements spec.
Ember’s implementation of components hews as closely to the Web Components specification as possible. Once Custom Elements are widely available in browsers, you should be able to easily migrate your Ember components to the W3C standard and have them be usable by other frameworks.
This is so important to us that we are working closely with the standards bodies to ensure our implementation of components matches the roadmap of the web platform.
To highlight the power of components, here is a short example of turning a blog post into a reusable
blog-post custom element that you could use again and again in your
application. Keep reading this section for more details on building
components.
Defining A Component
To define a component, create a template whose name starts with
components/. To define a new component, {{blog-post}} for example,
create a components/blog-post template.
If you are including your Handlebars templates inside an HTML file via
<script> tags, it would look like this:
1 <script type="text/x-handlebars" id="components/blog-post">
2 <h1>Blog Post</h1>
3 <p>Lorem ipsum dolor sit amet.</p>
4 </script>
If you’re using build tools, create a Handlebars file at
templates/components/blog-post.handlebars.
Having a template whose name starts with components/ creates a
component of the same name. Given the above template, you can now use the
{{blog-post}} custom element:
1 <h1>My Blog</h1>
2 {{#each}}
3 {{blog-post}}
4 {{/each}}
Each component, under the hood, is backed by an element. By default
Ember will use a <div> element to contain your component’s template.
To learn how to change the element Ember uses for your component, see
Customizing a Component’s
Element.
Defining a Component Subclass
Often times, your components will just encapsulate certain snippets of Handlebars templates that you find yourself using over and over. In those cases, you do not need to write any JavaScript at all. Just define the Handlebars template as described above and use the component that is created.
If you need to customize the behavior of the component you’ll
need to define a subclass of Ember.Component. For example, you would
need a custom subclass if you wanted to change a component’s element,
respond to actions from the component’s template, or manually make
changes to the component’s element using JavaScript.
Ember knows which subclass powers a component based on its name. For
example, if you have a component called blog-post, you would create a
subclass called App.BlogPostComponent. If your component was called
audio-player-controls, the class name would be
App.AudioPlayerControlsComponent.
In other words, Ember will look for a class with the camelized name of
the component, followed by Component.
Passing Properties To A Component
By default a component does not have access to properties in the template scope in which it is used.
For example, imagine you have a blog-post component that is used to
display a blog post:
1 <script type="text/x-handlebars" id="components/blog-post">
2 <h1>Component: {{title}}</h1>
3 <p>Lorem ipsum dolor sit amet.</p>
4 </script>
You can see that it has a {{title}} Handlebars expression to print the
value of the title property inside the <h1>.
Now imagine we have the following template and route:
1 App.IndexRoute = Ember.Route.extend({
2 model: function() {
3 return {
4 title: "Rails is omakase"
5 };
6 }
7 });
1 {{! index.handlebars }}
2 <h1>Template: {{title}}</h1>
3 {{blog-post}}
Running this code, you will see that the first <h1> (from the outer
template) displays the title property, but the second <h1> (from
inside the component) is empty.
We can fix this by making the title property available to the
component:
1 {{blog-post title=title}}
This will make the title property in the outer template scope
available inside the component’s template using the same name, title.
If, in the above example, the model’s title property was instead
called name, we would change the component usage to:
1 {{blog-post title=name}}
In other words, you are binding a named property from the outer scope to
a named property in the component scope, with the syntax
componentProperty=outerProperty.
It is important to note that the value of these properties is bound. Whether you change the value on the model or inside the component, the values stay in sync. In the following example, type some text in the text field either in the outer template or inside the component and note how they stay in sync.
You can also bind properties from inside an {{#each}} loop. This will
create a component for each item and bind it to each model in the loop.
1 {{#each}}
2 {{blog-post title=title}}
3 {{/each}}
Wrapping Content in a Component
Sometimes, you may want to define a component that wraps content provided by other templates.
For example, imagine we are building a blog-post component that we can
use in our application to display a blog post:
1 <script type="text/x-handlebars" id="components/blog-post">
2 <h1>{{title}}</h1>
3 <div class="body">{{body}}</div>
4 </script>
Now, we can use the {{blog-post}} component and pass it properties
in another template:
1 {{blog-post title=title body=body}}
(See Passing Properties to a Component for more.)
In this case, the content we wanted to display came from the model. But what if we want the developer using our component to be able to provide custom HTML content?
In addition to the simple form you’ve learned so far, components also
support being used in block form. In block form, components can be
passed a Handlebars template that is rendered inside the component’s
template wherever the {{yield}} expression appears.
To use the block form, add a # character to the
beginning of the component name, then make sure to add a closing tag.
(See the Handlebars documentation on block expressions for more.)
In that case, we can use the {{blog-post}} component in block form
and tell Ember where the block content should be rendered using the
{{yield}} helper. To update the example above, we’ll first change the component’s
template:
1 <script type="text/x-handlebars" id="components/blog-post">
2 <h1>{{title}}</h1>
3 <div class="body">{{yield}}</div>
4 </script>
You can see that we’ve replaced {{body}} with {{yield}}. This tells
Ember that this content will be provided when the component is used.
Next, we’ll update the template using the component to use the block form:
1 {{#blog-post title=title}}
2 <p class="author">by {{author}}</p>
3 {{body}}
4 {{/blog-post}}
It’s important to note that the template scope inside the component block is the same as outside. If a property is available in the template outside the component, it is also available inside the component block.
This JSBin illustrates the concept:
Customizing A Component’s Element
By default, each component is backed by a <div> element. If you were
to look at a rendered component in your developer tools, you would see
a DOM representation that looked something like:
1 <div id="ember180" class="ember-view">
2 <h1>My Component</h1>
3 </div>
You can customize what type of element Ember generates for your
component, including its attributes and class names, by creating a
subclass of Ember.Component in your JavaScript.
Customizing the Element
To use a tag other than div, subclass Ember.Component and assign it
a tagName property. This property can be any valid HTML5 tag name as a
string.
1 App.NavigationBarComponent = Ember.Component.extend({
2 tagName: 'nav'
3 });
1 {{! templates/components/navigation-bar }}
2 <ul>
3 <li>{{#link-to 'home'}}Home{{/link-to}}</li>
4 <li>{{#link-to 'about'}}About{{/link-to}}</li>
5 </ul>
Customizing Class Names
You can also specify which class names are applied to the component’s
element by setting its classNames property to an array of strings:
1 App.NavigationBarComponent = Ember.Component.extend({
2 classNames: ['primary']
3 });
If you want class names to be determined by properties of the component, you can use class name bindings. If you bind to a Boolean property, the class name will be added or removed depending on the value:
1 App.TodoItemComponent = Ember.Component.extend({
2 classNameBindings: ['isUrgent'],
3 isUrgent: true
4 });
This component would render the following:
1 <div class="ember-view is-urgent"></div>
If isUrgent is changed to false, then the is-urgent class name will be removed.
By default, the name of the Boolean property is dasherized. You can customize the class name applied by delimiting it with a colon:
1 App.TodoItemComponent = Ember.Component.extend({
2 classNameBindings: ['isUrgent:urgent'],
3 isUrgent: true
4 });
This would render this HTML:
1 <div class="ember-view urgent">
Besides the custom class name for the value being true, you can also specify a class name which is used when the value is false:
1 App.TodoItemComponent = Ember.Component.extend({
2 classNameBindings: ['isEnabled:enabled:disabled'],
3 isEnabled: false
4 });
This would render this HTML:
1 <div class="ember-view disabled">
You can also specify a class which should only be added when the property is
false by declaring classNameBindings like this:
1 App.TodoItemComponent = Ember.Component.extend({
2 classNameBindings: ['isEnabled::disabled'],
3 isEnabled: false
4 });
This would render this HTML:
1 <div class="ember-view disabled">
If the isEnabled property is set to true, no class name is added:
1 <div class="ember-view">
If the bound property’s value is a string, that value will be added as a class name without modification:
1 App.TodoItemComponent = Ember.Component.extend({
2 classNameBindings: ['priority'],
3 priority: 'highestPriority'
4 });
This would render this HTML:
1 <div class="ember-view highestPriority">
Customizing Attributes
You can bind attributes to the DOM element that represents a component
by using attributeBindings:
1 App.LinkItemComponent = Ember.Component.extend({
2 tagName: 'a',
3 attributeBindings: ['href'],
4 href: "http://emberjs.com"
5 });
You can also bind these attributes to differently named properties:
1 App.LinkItemComponent = Ember.Component.extend({
2 tagName: 'a',
3 attributeBindings: ['customHref:href'],
4 customHref: "http://emberjs.com"
5 });
Example
Here is an example todo application that shows completed todos with a red background:
Handling User Interaction with Actions
Components allow you to define controls that you can reuse throughout your application. If they’re generic enough, they can also be shared with others and used in multiple applications.
To make a reusable control useful, however, you first need to allow users of your application to interact with it.
You can make elements in your component interactive by using the
{{action}} helper. This is the same {{action}} helper you use in
application templates, but it has an
important difference when used inside a component.
Instead of sending an action to the template’s controller, then bubbling
up the route hierarchy, actions sent from inside a component are sent
directly to the component’s Ember.Component instance, and do not
bubble.
For example, imagine the following component that shows a post’s title. When the title is clicked, the entire post body is shown:
1 <script type="text/x-handlebars" id="components/post-summary">
2 <h3 {{action "toggleBody"}}>{{title}}</h3>
3 {{#if isShowingBody}}
4 <p>{{{body}}}</p>
5 {{/if}}
6 </script>
1 App.PostSummaryComponent = Ember.Component.extend({
2 actions: {
3 toggleBody: function() {
4 this.toggleProperty('isShowingBody');
5 }
6 }
7 });
The {{action}} helper can accept arguments, listen for different event
types, control how action bubbling occurs, and more.
For details about using the {{action}} helper, see the Actions
section of the Templates chapter.
Sending Actions from Components to Your Application
When a component is used inside a template, it has the ability to send actions to that template’s controller and routes. These allow the component to inform the application when important events, such as the user clicking a particular element in a component, occur.
Like the {{action}} Handlebars helper, actions sent from components
first go to the template’s controller. If the controller does not
implement a handler for that action, it will bubble to the template’s
route, and then up the route hierarchy. For more information about this
bubbling behavior, see Action
Bubbling.
Components are designed to be reusable across different parts of your application. In order to achieve this reusability, it’s important that the actions that your components send can be specified when the component is used in a template.
In other words, if you were writing a button component, you would not
want to send a click action, because it is ambiguous and likely to
conflict with other components on the page. Instead, you would want to
allow the person using the component to specify which action to send
when the button was clicked.
Luckily, components have a sendAction() method that allows them to
send actions specified when the component is used in a template.
Sending a Primary Action
Many components only send one kind of action. For example, a button component might send an action when it is clicked on; this is the primary action.
To set a component’s primary action, set its action attribute in
Handlebars:
1 {{my-button action="showUser"}}
This tells the my-button component that it should send the showUser
action when it triggers its primary action.
So how do you trigger sending a component’s primary action? After
the relevant event occurs, you can call the sendAction() method
without arguments:
1 App.MyButtonComponent = Ember.Component.extend({
2 click: function() {
3 this.sendAction();
4 }
5 });
In the above example, the my-button component will send the showUser
action when the component is clicked.
Sending Parameters with an Action
You may want to provide additional context to the route or controller handling an action. For example, a button component may want to tell a controller not only that an item was deleted, but also which item.
To send parameters with the primary action, call sendAction() with the
string 'action' as the first argument and any additional parameters
following it:
1 this.sendAction('action', param1, param2);
For example, imagine we’re building a todo list that allows the user to delete a todo:
1 App.IndexRoute = Ember.Route.extend({
2 model: function() {
3 return {
4 todos: [{
5 title: "Learn Ember.js"
6 }, {
7 title: "Walk the dog"
8 }]
9 };
10 },
11
12 actions: {
13 deleteTodo: function(todo) {
14 var todos = this.modelFor('index').todos;
15 todos.removeObject(todo);
16 }
17 }
18 });
1 {{! index.handlebars }}
2
3 {{#each todo in todos}}
4 <p>{{todo.title}} <button {{action "deleteTodo" todo}}>Delete</button></p>
5 {{/each}}
We want to update this app so that, before actually deleting a todo, the user must confirm that this is what they intended. We’ll implement a component that first double-checks with the user before completing the action.
In the component, when triggering the primary action, we’ll pass an additional argument that the component user can specify:
1 App.ConfirmButtonComponent = Ember.Component.extend({
2 actions: {
3 showConfirmation: function() {
4 this.toggleProperty('isShowingConfirmation');
5 },
6
7 confirm: function() {
8 this.toggleProperty('isShowingConfirmation');
9 this.sendAction('action', this.get('param'));
10 }
11 }
12 });
1 {{! templates/components/confirm-button.handlebars }}
2
3 {{#if isShowingConfirmation}}
4 <button {{action "confirm"}}>Click again to confirm</button>
5 {{else}}
6 <button {{action "showConfirmation"}}>{{title}}</button>
7 {{/if}}
Now we can update our initial template and replace the {{action}}
helper with our new component:
1 {{! index.handlebars }}
2
3 {{#each todo in todos}}
4 <p>{{todo.title}} {{confirm-button title="Delete" action="deleteTodo" para\
5 m=todo}}</p>
6 {{/each}}
Note that we’ve specified the action to send by setting the component’s
action attribute, and we’ve specified which argument should be sent as
a parameter by setting the component’s param attribute.
[JS Bin]http://jsbin.com/atIgUSi)
Sending Multiple Actions
Depending on the complexity of your component, you may need to let users specify multiple different actions for different events that your component can generate.
For example, imagine that you’re writing a form component that the user can either submit or cancel. Depending on which button the user clicks, you want to send a different action to your controller or route.
You can specify which action to send by passing the name of the event
as the first argument to sendAction(). For example, you can specify two
actions when using the form component:
1 {{user-form submit="createUser" cancel="cancelUserCreation"}}
In this case, you can send the createUser action by calling
this.sendAction('submit'), or send the cancelUserCreation action by
calling this.sendAction('cancel').
Actions That Aren’t Specified
If someone using your component does not specify an action for a
particular event, calling sendAction() has no effect.
For example, if you define a component that triggers the primary action on click:
1 App.MyButtonComponent = Ember.Component.extend({
2 click: function() {
3 this.sendAction();
4 }
5 });
Using this component without assigning a primary action will have no effect if the user clicks it:
1 {{my-button}}
Thinking About Component Actions
In general, you should think of component actions as translating a
primitive event (like a mouse click or an <audio> element’s pause
event) into actions that have meaning within your application.
This allows your routes and controllers to implement action handlers
with names like deleteTodo or songDidPause instead of vague names
like click or pause that may be ambiguous to other developers when
read out of context.
Another way to think of component actions is as the public API of your component. Thinking about which events in your component can trigger actions in their application is the primary way other developers will use your component. In general, keeping these events as generic as possible will lead to components that are more flexible and reusable.