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.

JS Bin

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}}

JS Bin

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.

JS Bin

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.

JS Bin

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}}

JS Bin

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.

JS Bin

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}}

JS Bin

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}}

JS Bin

(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}} 

JS Bin

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:

JS Bin

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:

JS Bin

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 });

JS Bin

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').

JS Bin

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.