Ember.js tutorial for beginners

 What is Ember.js?

Ember is a JavaScript framework for creating ambitious web applications that eliminates boilerplate and provides a standard application architecture.

Eliminate Boilerplate

There are some tasks that are common to every web application. For example, taking data from the server, rendering it to the screen, then updating that information when it changes.
Since the tools provided to do this by the browser are quite primitive, you end up writing the same code over and over. Ember.js provides tools that let you focus on your app instead of writing the same code you've written a hundred times.
Because we've built dozens of applications ourselves, we've gone beyond the obvious low-level event-driven abstractions, eliminating much of the boilerplate associated with propagating changes throughout your application, and especially into the DOM itself.
To help manage changes in the view, Ember.js comes with a templating engine that will automatically update the DOM when the underlying objects change.
For a simple example, consider this template of a Person:

{{person.name}} is {{person.age}}.
As with any templating system, when the template is initially rendered, it will reflect the current state of the person. To avoid boilerplate, though, Ember.js will also update the DOM automatically for you if the person's name or age changes.
You specify your template once, and Ember.js makes sure it's always up to date.

Provides Architecture

Since web applications evolved from web pages, which were nothing more than static documents, browsers give you just enough rope to hang yourself with.
Ember makes it easy to divide your application into models, views, and controllers, which improves testability, makes code more modular, and helps new developers on the project quickly understand how everything fits together. The days of callback spaghetti are over.
Ember also supplies built-in support for state management, so you'll have a way to describe how your application moves through various nested states (like signed-out, signed-in, viewing-post, and viewing-comment) out of the box.

How is Ember.js Different?

Traditional web applications make the user download a new page every time they interact with the server. This means that every interaction is never faster than the latency between you and the user, and usually slower. Using AJAX to replace only parts of the page helps somewhat, but still requires a roundtrip to your server every time your UI needs to update. And if multiple parts of the page need to update all at once, most developers just resort to loading the page over again, since keeping everything in sync is tricky.
Ember.js, like some other modern JavaScript frameworks, works a little differently. Instead of the majority of your application's logic living on the server, an Ember.js application downloads everything it needs to run in the initial page load. That means that while your user is using your app, she never has to load a new page and your UI responds quickly to their interaction.
One advantage of this architecture is that your web application uses the same REST API as your native apps or third-party clients. Back-end developers can focus on building a fast, reliable, and secure API server, and don't have to be front-end experts, too.

Ember.js at a Glance

These are the three features that make Ember a joy to use:
  1. Bindings
  2. Computed properties
  3. Auto-updating templates

Bindings

Use bindings to keep properties between two different objects in sync. You just declare a binding once, and Ember will make sure changes get propagated in either direction.
Here's how you create a binding between two objects:
1
2
3
4
5
6
7
8
9
10
11
MyApp.president = Ember.Object.create({
  name: "Barack Obama"
});
MyApp.country = Ember.Object.create({
  // Ending a property with 'Binding' tells Ember to
  // create a binding to the presidentName property.
  presidentNameBinding: 'MyApp.president.name'
});
// Later, after Ember has resolved bindings...
MyApp.country.get('presidentName');
// "Barack Obama"
Bindings allow you to architect your application using the MVC (Model-View-Controller) pattern, then rest easy knowing that data will always flow correctly from layer to layer.

Computed Properties

Computed properties allow you to treat a function like a property:
1
2
3
4
5
6
7
8
9
10
MyApp.president = Ember.Object.create({
  firstName: "Barack",
  lastName: "Obama",
  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  // Call this flag to mark the function as a property
  }.property()
});
MyApp.president.get('fullName');
// "Barack Obama"
Computed properties are useful because they can work with bindings, just like any other property.
Many computed properties have dependencies on other properties. For example, in the above example, the fullName property depends on firstName and lastName to determine its value. You can tell Ember about these dependencies like this:
1
2
3
4
5
6
7
8
9
MyApp.president = Ember.Object.create({
  firstName: "Barack",
  lastName: "Obama",
  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  // Tell Ember that this computed property depends on firstName
  // and lastName
  }.property('firstName', 'lastName')
});
Make sure you list these dependencies so Ember knows when to update bindings that connect to a computed property.

Auto-updating Templates

Ember uses Handlebars, a semantic templating library. To take data from your JavaScript application and put it into the DOM, create a <script> tag and put it into your HTML, wherever you'd like the value to appear:
1
2
3
<script type="text/x-handlebars">
  The President of the United States is {{MyApp.president.fullName}}.
</script>
Here's the best part: templates are bindings-aware. That means that if you ever change the value of the property that you told us to display, we'll update it for you automatically. And because you've specified dependencies, changes to those properties are reflected as well.
Hopefully you can see how all three of these powerful tools work together: start with some primitive properties, then start building up more sophisticated properties and their dependencies using computed properties. Once you've described the data, you only have to say how it gets displayed once, and Ember takes care of the rest. It doesn't matter how the underlying data changes, whether from an XHR request or the user performing an action; your user interface always stays up-to-date. This eliminates entire categories of edge cases that developers struggle with every day.

Getting Started

Depending on your needs, there are several ways to get started creating your first Ember.js app.
If your needs are simple or you're interested in just playing around, you can download the Ember.js Starter Kit. The Starter Kit is based on HTML5 Boilerplate and does not require any build tools or other dependencies. To begin, download the Starter Kit and unzip it. You can edit the Handlebars templates directly inside the index.html file, and the Ember.js app itself lives in javascripts/app.js.
For larger apps, you may want to consider using Ruby on Rails. Rails helps you manage your source code and other assets, while also providing the REST API that your application will talk to.

The Ember Object Model

Ember enhances the simple JavaScript object model to support bindings and observers, as well as to support a more powerful mixin-based approach to code sharing.
At its most basic, you create a new Ember class by using the extend method on Ember.Object.
1
2
3
4
5
Person = Ember.Object.extend({
  say: function(thing) {
    alert(thing);
 }
});
Once you have built a new class, you can create new instances of the class by using the create method. Any properties defined on the class will be available to instances.
1
2
var person = Person.create();
person.say("Hello") // alerts "Hello"
When creating an instance, you can also add additional properties to the instance by passing in an object.
1
2
3
4
5
6
7
var tom = Person.create({
  name: "Tom Dale",
  helloWorld: function() {
    this.say("Hi my name is " + this.get('name'));
  }
});
tom.helloWorld() // alerts "Hi my name is Tom Dale"
Because of Ember's support for bindings and observers, you will always access properties using the get method, and set properties using the set method.
When creating a new instance of an object, you can also override any properties or methods defined on the class. For instance, in this case, you could override the say method from the Person class.
1
2
3
4
5
6
7
var yehuda = Person.create({
  name: "Yehuda Katz",
  say: function(thing) {
    var name = this.get('name');
    this._super(name + " says: " + thing);
  }
});
You can use the _super method on the object (super is a reserved word in JavaScript) to call the original method you overrode.

Subclassing Classes

You can also create subclasses of classes you create by using the extend method. In fact, when we created a new class above by calling extend on Ember.Object, we were subclassing Ember.Object.
1
2
3
4
5
var LoudPerson = Person.extend({
  say: function(thing) {
    this._super(thing.toUpperCase());
  }
});
When subclassing, you can use this._super to invoke methods you are overriding.

Reopening Classes and Instances

You don't need to define a class all at once. You can reopen a class and define new properties using the reopen method.
1
2
3
4
Person.reopen({
  isPerson: true
});
Person.create().get('isPerson') // true
When using reopen, you can also override existing methods and call this._super.
1
2
3
4
5
6
Person.reopen({
  // override `say` to add an ! at the end
  say: function(thing) {
    this._super(thing + "!");
  }
});
As you can see, reopen is used to add properties and methods to an instance. But when you need to create class method or add the properties to the class itself you can use reopenClass.
1
2
3
4
5
6
Person.reopenClass({
  createMan: function() {
    return Person.create({isMan: true})
  }
});
Person.createMan().get('isMan') // true

Computed Properties (Getters)

Often, you will want a property that is computed based on other properties. Ember's object model allows you to define computed properties easily in a normal class definition.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Person = Ember.Object.extend({
  // these will be supplied by `create`
  firstName: null,
  lastName: null,
  fullName: function() {
    var firstName = this.get('firstName');
    var lastName = this.get('lastName');
   return firstName + ' ' + lastName;
  }.property('firstName', 'lastName')
});
var tom = Person.create({
  firstName: "Tom",
  lastName: "Dale"
});
tom.get('fullName') // "Tom Dale"
The property method defines the function as a computed property, and defines its dependencies. Those dependencies will come into play later when we discuss bindings and observers.
When subclassing a class or creating a new instance, you can override any computed properties.

Computed Properties (Setters)

You can also define what Ember should do when setting a computed property. If you try to set a computed property, it will be invoked with the key and value you want to set it to.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Person = Ember.Object.extend({
  // these will be supplied by `create`
  firstName: null,
  lastName: null,
  fullName: function(key, value) {
    // getter
    if (arguments.length === 1) {
      var firstName = this.get('firstName');
      var lastName = this.get('lastName');
      return firstName + ' ' + lastName;
    // setter
    } else {
      var name = value.split(" ");
      this.set('firstName', name[0]);
      this.set('lastName', name[1]);
      return value;
    }
  }.property('firstName', 'lastName')
});
var person = Person.create();
person.set('fullName', "Peter Wagenet");
person.get('firstName') // Peter
person.get('lastName') // Wagenet
Ember will call the computed property for both setters and getters, and you can check the number of arguments to determine whether it is being called as a getter or a setter.

Observers

Ember supports observing any property, including computed properties. You can set up an observer on an object by using the addObserver method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Person = Ember.Object.extend({
  // these will be supplied by `create`
  firstName: null,
  lastName: null,
  fullName: function() {
    var firstName = this.get('firstName');
    var lastName = this.get('lastName');
    return firstName + ' ' + lastName;
  }.property('firstName', 'lastName')
});
var person = Person.create({
  firstName: "Yehuda",
  lastName: "Katz"
});
person.addObserver('fullName', function() {
  // deal with the change
});
person.set('firstName', "Brohuda"); // observer will fire
Because the fullName computed property depends on firstName, updating firstName will fire observers on fullName as well.
Because observers are so common, Ember provides a way to define observers inline in class definitions.
1
2
3
4
5
Person.reopen({
  fullNameChanged: function() {
    // this is an inline version of .addObserver
  }.observes('fullName')
});
You can define inline observers by using the Ember.observer method if you are using Ember without prototype extensions:
1
2
3
4
5
Person.reopen({
  fullNameChanged: Ember.observer(function() {
    // this is an inline version of .addObserver
  }, 'fullName')
});

Changes in Arrays

Often, you may have a computed property that relies on all of the items in an array to determine its value. For example, you may want to count all of the todo items in a controller to determine how many of them are completed.
Here's what that computed property might look like:
1
2
3
4
5
6
7
8
9
App.todosController = Ember.Object.create({
  todos: [
    Ember.Object.create({ isDone: false })
  ],
  remaining: function() {
    var todos = this.get('todos');
    return todos.filterProperty('isDone', false).get('length');
  }.property('todos.@each.isDone')
});
Note here that the dependent key (todos.@each.isDone) contains the special key @each. This instructs Ember.js to update bindings and fire observers for this computed property when one of the following four events occurs:
  1. The isDone property of any of the objects in the todos array changes.
  2. An item is added to the todos array.
  3. An item is removed from the todos array.
  4. The todos property of the controller is changed to a different array.
In the example above, the remaining count is 1:
1
2
App.todosController.get('remaining');
// 1
If we change the todo's isDone property, the remaining property is updated automatically:
1
2
3
4
5
6
7
8
9
var todos = App.todosController.get('todos');
var todo = todos.objectAt(0);
todo.set('isDone', true);
App.todosController.get('remaining');
// 0
todo = Ember.Object.create({ isDone: false });
todos.pushObject(todo);
App.todosController.get('remaining');
// 1

Bindings

A binding creates a link between two properties such that when one changes, the other one is updated to the new value automatically. Bindings can connect properties on the same object, or across two different objects. Unlike most other frameworks that include some sort of binding implementation, bindings in Ember.js can be used with any object, not just between views and models.
The easiest way to create a two-way binding is by creating a new property with the string Binding at the end, then specifying a path from the global scope:
1
2
3
4
5
6
7
8
9
10
App.wife = Ember.Object.create({
  householdIncome: 80000
});
App.husband = Ember.Object.create({
  householdIncomeBinding: 'App.wife.householdIncome'
});
App.husband.get('householdIncome'); // 80000
// Someone gets raise.
App.husband.set('householdIncome', 90000);
App.wife.get('householdIncome'); // 90000
Note that bindings don't update immediately. Ember waits until all of your application code has finished running before synchronizing changes, so you can change a bound property as many times as you'd like without worrying about the overhead of syncing bindings when values are transient.

One-Way Bindings

A one-way binding only propagates changes in one direction. Usually, one-way bindings are just a performance optimization and you can safely use the more concise two-way binding syntax (as, of course, two-way bindings are de facto one-way bindings if you only ever change one side).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
App.user = Ember.Object.create({
  fullName: "Kara Gates"
});
App.userView = Ember.View.create({
  userNameBinding: Ember.Binding.oneWay('App.user.fullName')
});
// Changing the name of the user object changes
// the value on the view.
App.user.set('fullName', "Krang Gates");
// App.userView.userName will become "Krang Gates"
// ...but changes to the view don't make it back to
// the object.
App.userView.set('userName', "Truckasaurus Gates");
App.user.get('fullName'); // "Krang Gates"

What Do I Use When?

Sometimes new users are confused about when to use computed properties, bindings and observers. Here are some guidelines to help:
  1. Use computed properties to build a new property by synthesizing other properties. Computed properties should not contain application behavior, and should generally not cause any side-effects when called. Except in rare cases, multiple calls to the same computed property should always return the same value (unless the properties it depends on have changed, of course.)
  2. Observers should contain behavior that reacts to changes in another property. Observers are especially useful when you need to perform some behavior after a binding has finished synchronizing.
  3. Bindings are most often used to ensure objects in two different layers are always in sync. For example, you bind your views to your controller using Handlebars. You may often bind between two objects in the same layer. For example, you might have an App.selectedContactController that binds to the selectedContact property of App.contactsController.

Creating a Namespace

Every Ember app should have an instance of Ember.Application. This object will serve as the globally-accessible namespace for all of the other classes and instances in your app. Additionally, it sets up event listeners on the page so that your views receive events when users interact with your user interface (which you'll learn about later.)
Here's an example of an application:

window.App = Ember.Application.create();
You can call your namespace whatever you'd like, but it must begin with a capital letter in order for bindings to find it.
If you are embedding an Ember application into an existing site, you can have event listeners set up for a specific element by providing a rootElement property:
1
2
3
window.App = Ember.Application.create({
  rootElement: '#sidebar'
});

Describing Your UI with Handlebars

Handlebars

Ember supports templates for rendering views. The default ( and best ingegrated ) for Ember is Handlebars, a semantic templating language. These templates look like regular HTML, with embedded expressions. Ember does not come with Handlebars, but you can easily download Handlebars and include it with your Ember application. Ember and Handlebars work great together, but you can substitute other rendering engines by pointing the Ember.View's template property at a function that will use the Ember.View's context to produce a string of html. For now, let's cover the great integration between Ember and Handlebars.
You should store your Handlebars templates inside your application's HTML file. At runtime, Ember will compile these templates so they are available for you to use in your views.
To immediately insert a template into your document, place it inside a <script> tag within your <body> tag:
1
2
3
4
5
6
7
<html>
  <body>
    <script type="text/x-handlebars" data-template-name="application">
      Hello, <b>{{MyApp.name}}</b>
    </script>
  </body>
</html>
Adding a data-template-name attribute makes the template available to use later:
1
2
3
4
5
6
7
<html>
  <head>
    <script type="text/x-handlebars" data-template-name="say-hello">
      Hello, <b>{{MyApp.name}}</b>
    </script>
  </head>
</html>

Ember.View

You can use Ember.View to render a Handlebars template and insert it into the DOM.
To tell the view which template to use, set its templateName property. For example, if I had a <script> tag like this:
1
2
3
4
5
6
7
<html>
  <head>
    <script type="text/x-handlebars" data-template-name="say-hello">
      Hello, <b>{{name}}</b>
    </script>
  </head>
</html>
I would set the templateName property to "say-hello".
1
2
3
4
var view = Ember.View.create({
  templateName: 'say-hello',
  name: "Bob"
});
Note: For the remainder of the guide, the templateName property will be omitted from most examples. You can assume that if we show a code sample that includes an Ember.View and a Handlebars template, the view has been configured to display that template via the templateName property.
You can append views to the document by calling appendTo:

  view.appendTo('#container');
As a shorthand, you can append a view to the document body by calling append:

  view.append();
To remove a view from the document, call remove:

  view.remove();

Handlebars Basics

As you've already seen, you can print the value of a property by enclosing it in a Handlebars expression, or a series of braces, like this:

My new car is {{color}}.
This will look up and print the View's color property. For example, if your view looks like this:
1
2
3
App.CarView = Ember.View.extend({
  color: 'blue'
});
Your view would appear in the browser like this:

My new car is blue.
You can also specify global paths:

My new car is {{App.carController.color}}.
(Ember determines whether a path is global or relative to the view by checking whether the first letter is capitalized, which is why your Ember.Application instance should start with a capital letter.)
All of the features described in this guide are bindings aware. That means that if the values used by your templates ever change, your HTML will be updated automatically. It's like magic.
In order to know which part of your HTML to update when an underlying property changes, Handlebars will insert marker elements with a unique ID. If you look at your application while it's running, you might notice these extra elements:
1
2
3
4
My new car is
<script id="metamorph-0-start" type="text/x-placeholder"></script>
blue
<script id="metamorph-0-end" type="text/x-placeholder"></script>.
Because all Handlebars expressions are wrapped in these markers, make sure each HTML tag stays inside the same block. For example, you shouldn't do this:
1
2
<!-- Don't do it! -->
<div {{#if isUrgent}}class="urgent"{{/if}}>
If you want to avoid your property output getting wrapped in these markers, use the unbound helper:

My new car is {{unbound color}}.
Your output will be free of markers, but be careful, because the output won't be automatically updated!

My new car is blue.

{{#if}}, {{else}}, and {{#unless}}

Sometimes you may only want to display part of your template if a property exists. For example, let's say we have a view with a person property that contains an object with firstName and lastName fields:
1
2
3
4
5
6
App.SayHelloView = Ember.View.extend({
  person: Ember.Object.create({
    firstName: "Joy",
    lastName: "Clojure"
  })
});
In order to display part of the template only if the person object exists, we can use the {{#if}} helper to conditionally render a block:
1
2
3
{{#if person}}
  Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>!
{{/if}}
Handlebars will not render the block if the argument passed evaluates to false, undefined, null or [] (i.e., any "falsy" value).
If the expression evaluates to falsy, we can also display an alternate template using {{else}}:
1
2
3
4
5
{{#if person}}
  Welcome back, <b>{{person.firstName}} {{person.lastName}}</b>!
{{else}}
  Please log in.
{{/if}}
To only render a block if a value is falsy, use {{#unless}}:
1
2
3
{{#unless hasPaid}}
  You owe: ${{total}}
{{/unless}}
{{#if}} and {{#unless}} are examples of block expressions. These allow you to invoke a helper with a portion of your template. Block expressions look like normal expressions except that they contain a hash (#) before the helper name, and require a closing expression.

{{#with}}

Sometimes you may want to invoke a section of your template with a context different than the Ember.View. For example, we can clean up the above template by using the {{#with}} helper:
1
2
3
{{#with person}}
  Welcome back, <b>{{firstName}} {{lastName}}</b>!
{{/with}}
{{#with}} changes the context of the block you pass to it. The context is the object on which properties are looked up. By default, the context is the Ember.View to which the template belongs.

Binding Element Attributes with {{bindAttr}}

In addition to text, you may also want your templates to dictate the attributes of your HTML elements. For example, imagine a view that contains a URL:
1
2
3
App.LogoView = Ember.View.extend({
  logoUrl: 'http://www.mycorp.com/images/logo.png'
});
The best way to display the URL as an image in Handlebars is like this:
1
2
3
<div id="logo">
  <img {{bindAttr src="logoUrl"}} alt="Logo">
</div>
This generates the following HTML:
1
2
3
<div id="logo">
  <img src="http://www.mycorp.com/images/logo.png" alt="Logo">
</div>
If you use {{bindAttr}} with a Boolean value, it will add or remove the specified attribute. For example, given this Ember view:
1
2
3
App.InputView = Ember.View.extend({
  isDisabled: true
});
And this template:

<input type="checkbox" {{bindAttr disabled="isDisabled"}}>
Handlebars will produce the following HTML element:

<input type="checkbox" disabled>

Binding Class Names with {{bindAttr}}

The class attribute can be bound like any other attribute, but it also has some additional special behavior. The default behavior works like you'd expect:
1
2
3
4
App.AlertView = Ember.View.extend({
  priority: "p4",
  isUrgent: true
});
1
2
3
<div {{bindAttr class="priority"}}>
  Warning!
</div>
This template will emit the following HTML:
1
2
3
<div class="p4">
  Warning!
</div>
If the value to which you bind is a Boolean, however, the dasherized version of that property will be applied as a class:
1
2
3
<div {{bindAttr class="isUrgent"}}>
  Warning!
</div>
This emits the following HTML:
1
2
3
<div class="is-urgent">
  Warning!
</div>
Unlike other attributes, you can also bind multiple classes:
1
2
3
<div {{bindAttr class="isUrgent priority"}}>
  Warning!
</div>
You can also specify an alternate class name to use, instead of just dasherizing.
1
2
3
<div {{bindAttr class="isUrgent:urgent"}}>
  Warning!
</div>
In this case, if the isUrgent property is true, the urgent class will be added. If it is false, the urgent class will be removed.
You can also specify a class name which shall be used when the property is false:
1
2
3
<div {{bindAttr class="isEnabled:enabled:disabled"}}>
  Warning!
</div>
In this case, if the isEnabled property is true, the enabled class will be added. If the property is false, the class disabled will be added.
This syntax allows the shorthand for only adding a class when a property is false, so this:
1
2
3
<div {{bindAttr class="isEnabled::disabled"}}>
  Warning!
</div>
Will add the class disabled when isEnabled is false and add no class if isEnabled is true.

Handling Events with {{action}}

Use the {{action}} helper to attach a handler in your view class to an event triggered on an element.
To attach an element's click event to the edit() handler in the current view:

<a href="#" {{action "edit" on="click"}}>Edit</a>
Because the default event is click, this could be written more concisely as:

<a href="#" {{action "edit"}}>Edit</a>
Although the view containing the {{action}} helper will be targeted by default, it is possible to target a different view:

<a href="#" {{action "edit" target="parentView"}}>Edit</a>
The action handler can optionally accept a jQuery event object, which will be extended to include view and context properties. These properties can be useful when targeting a different view with your action. For instance:
1
2
3
4
5
6
App.ListingView = Ember.View.extend({
  templateName: 'listing',
  edit: function(event) {
    event.view.set('isEditing', true);
  }
});
Any of the templates discussed above will produce an HTML element like this:

<a href="#" data-ember-action="3">Edit</a>
Ember will delegate the event you specified to your target view's handler based upon the internally assigned data-ember-action id.

Building a View Hierarchy

So far, we've discussed writing templates for a single view. However, as your application grows, you will often want to create a hierarchy of views to encapsulate different areas on the page. Each view is responsible for handling events and maintaining the properties needed to display it.

{{view}}

To add a child view to a parent, use the {{view}} helper, which takes a path to a view class.
1
2
3
4
5
6
7
8
9
10
11
12
// Define parent view
App.UserView = Ember.View.extend({
  templateName: 'user',
  firstName: "Albert",
  lastName: "Hofmann"
});
// Define child view
App.InfoView = Ember.View.extend({
  templateName: 'info',
  posts: 25,
  hobbies: "Riding bicycles"
});
1
2
User: {{firstName}} {{lastName}}
{{view App.InfoView}}
1
2
3
<b>Posts:</b> {{view.posts}}
<br>
<b>Hobbies:</b> {{view.hobbies}}
If we were to create an instance of App.UserView and render it, we would get a DOM representation like this:
1
2
3
4
5
6
User: Albert Hofmann
<div>
  <b>Posts:</b> 25
  <br>
  <b>Hobbies:</b> Riding bicycles
</div>

Relative Paths

Instead of specifying an absolute path, you can also specify which view class to use relative to the parent view. For example, we could nest the above view hierarchy like this:
1
2
3
4
5
6
7
8
9
10
App.UserView = Ember.View.extend({
  templateName: 'user',
  firstName: "Albert",
  lastName: "Hofmann",
  infoView: Ember.View.extend({
    templateName: 'info',
    posts: 25,
    hobbies: "Riding bicycles"
  })
});
1
2
User: {{firstName}} {{lastName}}
{{view infoView}}
When nesting a view class like this, make sure to use a lowercase letter, as Ember will interpret a property with a capital letter as a global property.

Setting Child View Templates

If you'd like to specify the template your child views use inline in the main template, you can use the block form of the {{view}} helper. We might rewrite the above example like this:
1
2
3
4
5
6
7
8
9
App.UserView = Ember.View.extend({
  templateName: 'user',
  firstName: "Albert",
  lastName: "Hofmann"
});
App.InfoView = Ember.View.extend({
  posts: 25,
  hobbies: "Riding bicycles"
});
1
2
3
4
5
6
User: {{firstName}} {{lastName}}
{{#view App.InfoView}}
  <b>Posts:</b> {{view.posts}}
  <br>
  <b>Hobbies:</b> {{view.hobbies}}
{{/view}}
When you do this, it may be helpful to think of it as assigning views to portions of the page. This allows you to encapsulate event handling for just that part of the page.

Setting Up Bindings

So far in our examples, we have been setting static values directly on the views. But to best implement an MVC architecture, we should actually be binding the properties of our views to the controller layer.
Let's set up a controller to represent our user data:
1
2
3
4
5
6
7
8
App.userController = Ember.Object.create({
  content: Ember.Object.create({
    firstName: "Albert",
    lastName: "Hofmann",
    posts: 25,
    hobbies: "Riding bicycles"
  })
});
Now let's update App.UserView to bind to App.userController:
1
2
3
4
5
App.UserView = Ember.View.extend({
  templateName: 'user',
  firstNameBinding: 'App.userController.content.firstName',
  lastNameBinding: 'App.userController.content.lastName'
});
When we only have a few bindings to configure, like with App.UserView, it is sometimes useful to be able to declare those bindings in the template. You can do that by passing additional arguments to the {{#view}} helper. If all you're doing is configuring bindings, this often allows you to bypass having to create a new subclass.
1
2
3
4
5
6
7
User: {{firstName}} {{lastName}}
{{#view App.UserView postsBinding="App.userController.content.posts"
        hobbiesBinding="App.userController.content.hobbies"}}
  <b>Posts:</b> {{view.posts}}
  <br>
  <b>Hobbies:</b> {{view.hobbies}}
{{/view}}
NOTE: You can actually pass any property as a parameter to {{view}}, not just bindings. However, if you are doing anything other than setting up bindings, it is generally a good idea to create a new subclass.

Modifying a View's HTML

When you append a view, it creates a new HTML element that holds its content. If your view has any child views, they will also be displayed as child nodes of the parent's HTML element.
By default, new instances of Ember.View create a <div> element. You can override this by passing a tagName parameter:

{{view App.InfoView tagName="span"}}
You can also assign an ID attribute to the view's HTML element by passing an id parameter:

{{view App.InfoView id="info-view"}}
This makes it easy to style using CSS ID selectors:
1
2
3
4
/** Give the view a red background. **/
  #info-view {
    background-color: red;
  }
You can assign class names similarly:

{{view App.InfoView class="info urgent"}}
You can bind class names to a property of the view by using classBinding instead of class. The same behavior as described in bindAttr applies:
1
2
3
4
App.AlertView = Ember.View.extend({
  priority: "p4",
  isUrgent: true
});

{{view App.AlertView classBinding="isUrgent priority"}}
This yields a view wrapper that will look something like this:

<div id="sc420" class="sc-view is-urgent p4"></div>

Displaying a List of Items

If you need to enumerate over a list of objects, use Handlebar's {{#each}} helper:
1
2
3
4
App.PeopleView = Ember.View.extend({
  people: [ { name: 'Yehuda' },
            { name: 'Tom' } ]
});
1
2
3
4
5
<ul>
  {{#each people}}
    <li>Hello, {{name}}!</li>
  {{/each}}
</ul>
This will print a list like this:
1
2
3
4
<ul>
  <li>Hello, Yehuda!</li>
  <li>Hello, Tom!</li>
</ul>
If you want to create a view for every item in a list, just set it up as follows:
1
2
3
4
5
{{#each App.peopleController}}
  {{#view App.PersonView}}
    {{firstName}} {{lastName}}
  {{/view}}
{{/each}}

Writing Custom Helpers

Sometimes, you may use the same HTML in your application multiple times. In those case, you can register a custom helper that can be invoked from any Handlebars template.
For example, imagine you are frequently wrapping certain values in a <span> tag with a custom class. You can register a helper from your JavaScript like this:
1
2
3
4
Handlebars.registerHelper('highlight', function(property, options) {
  var value = Ember.Handlebars.get(this, property, options);
  return new Handlebars.SafeString('<span class="highlight">'+value+'</span>');
});
If you return HTML from a helper, and you don't want it to be escaped, make sure to return a new SafeString.
Anywhere in your Handlebars templates, you can now invoke this helper:

{{highlight name}}
and it will output the following:

<span class="highlight">Peter</span>
NOTE: Parameters to helper functions are passed as names, not their current values. This allows you to optionally set up observers on the values. To get the current value of the parameter, use Ember.get, as shown above.

Included Views

Ember comes pre-packaged with a set of views for building a few basic controls like text inputs, check boxes, and select lists.
They are:

Ember.Checkbox

1
2
3
4
    <label>
      {{view Ember.Checkbox checkedBinding="content.isDone"}}
      {{content.title}}
    </label>

Ember.TextField

1
2
3
4
5
6
    App.MyText = Ember.TextField.extend({
        formBlurredBinding: 'App.adminController.formBlurred',
        change: function(evt) {
          this.set('formBlurred', true);
        }
      });

Ember.Select

1
2
3
4
5
6
    {{view Ember.Select viewName="select"
                          contentBinding="App.peopleController"
                          optionLabelPath="content.fullName"
                          optionValuePath="content.id"
                          prompt="Pick a person:"
                          selectionBinding="App.selectedPersonController.person"}}

Ember.TextArea

1
2
3
    var textArea = Ember.TextArea.create({
            valueBinding: 'TestObject.value'
            });
If you would like to add one of these controls to your view, you are encouraged to extend from these controls.
Events do not bubble from a subview to a parent view so extending these views is the only way to capture those events.
Example:
1
2
3
4
5
6
App.MyText = Ember.TextField.extend({
    formBlurredBinding: 'App.adminController.formBlurred',
    change: function(evt) {
      this.set('formBlurred', true);
    }
  });
You can then use this view as a sub view and capture the events. In the following example, a change to the Name input would blurr the form and cause the save button to appear.
1
2
3
4
5
6
7
8
9
10
11
12
<script id="formDetail" data-template-name='formDetail' type="text/x-handlebars">
    <form>
        <fieldset>
           <legend>Info:</legend>                 
                   {{view App.MyText name="Name" id="Name"  valueBinding="myObj.Name"}} 
                   <label for="Name">Name</label><br/>
                   {{#if formBlurred}}
                    <a href="#" {{action "syncData" on="click"}}>Save</a>
                    {{/if}}
        </fieldset>
    </form>
</script>

Views In-Depth

Now that you're familiar with using Handlebars, let's go more in-depth on how to both handle events, and customize views to your needs.

Handling Events

Instead of having to register event listeners on elements you'd like to respond to, simply implement the name of the event you want to respond to as a method on your view.
For example, imagine we have a template like this:
1
2
3
{{#view App.ClickableView}}
This is a clickable area!
{{/view}}
Let's implement App.ClickableView such that when it is clicked, an alert is displayed:
1
2
3
4
5
App.ClickableView = Ember.View.extend({
  click: function(evt) {
    alert("ClickableView was clicked!");
  }
});
Events bubble up from the target view to each parent view in succession, until the root view. These values are read-only. If you want to manually manage views in JavaScript (instead of creating them using the {{view}} helper in Handlebars), see the Ember.ContainerView documentation below.

Manually Managed Views with Ember.ContainerView

Usually, views create their child views by using the {{view}} helper. Sometimes it is useful to manually manage a view's child views. If you create an instance of Ember.ContainerView, the childViews array is editable. Views that you add are rendered to the page, and views that you remove are removed from the DOM.
1
2
3
4
5
var container = Ember.ContainerView.create();
container.append();
var coolView = App.CoolView.create(),
    childViews = container.get('childViews');
childViews.pushObject(coolView);
As a shorthand, you can specify the child views as properties and the child views as a list of keys. When the container view is created, these views will be instantiated and added to the child views array:
1
2
3
4
5
var container = Ember.ContainerView.create({
  childViews: ['firstView', 'secondView'],
  firstView: App.FirstView,
  secondView: App.SecondView
});

Render Pipeline

Before your views are turned into DOM elements, they first exist as a string representation. As views render, they turn each of their child views into strings and concatenate them together.
If you'd like to use something other than Handlebars, you can override a view's render method to generate a custom string of HTML.
1
2
3
4
5
App.CoolView = Ember.View.create({
  render: function(buffer) {
    buffer.push("<b>This view is so cool!</b>");
  }
});
This makes it easy to support template engines other than Handlebars; though do note that if you override rendering, values will not update automatically. Any updates will be your responsibility.

Customizing the HTML Element

A view is represented by a single DOM element on the page. You can change what kind of element is created by changing the tagName property.
1
2
3
App.MyView = Ember.View.extend({
  tagName: 'span'
});
You can also specify which class names are applied to the view by setting its classNames property to an array of strings:
1
2
3
App.MyView = Ember.View.extend({
  classNames: ['my-view']
});
If you want class names to be determined by the state of properties on the view, 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
2
3
4
App.MyView = Ember.View.extend({
  classNameBindings: ['isUrgent'],
  isUrgent: true
});
This would render a view like this:

<div class="ember-view is-urgent">
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
2
3
4
App.MyView = Ember.View.extend({
  classNameBindings: ['isUrgent:urgent'],
  isUrgent: true
});
This would render this HTML:

<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
2
3
4
App.MyView = Ember.View.extend({
  classNameBindings: ['isEnabled:enabled:disabled'],
  isEnabled: false
});
This would render this HTML:

<div class="ember-view disabled">
You can also specify to only add a class when the property is false by declaring classNameBindings like this:
1
2
3
4
App.MyView = Ember.View.extend({
  classNameBindings: ['isEnabled::disabled'],
  isEnabled: false
});
This would render this HTML:

<div class="ember-view disabled">
If the isEnabled property is set to true, no class name is added:

<div class="ember-view">
If the bound value is a string, that value will be added as a class name without modification:
1
2
3
4
App.MyView = Ember.View.extend({
  classNameBindings: ['priority'],
  priority: 'highestPriority'
});
This would render this HTML:

<div class="ember-view highestPriority">

Attribute Bindings on a View

You can bind attributes to the DOM element that represents a view by using attributeBindings:
1
2
3
4
5
App.MyView = Ember.View.extend({
  tagName: 'a',
  attributeBindings: ['href'],
  href: "http://emberjs.com"
});

The Ember Enumerable API

What Are Enumerables?

In Ember, an Enumerable is any object that contains a number of child objects, and which allows you to work with those children using the Enumerable interface. The most basic Enumerable is the built-in JavaScript Array.
For instance, all Enumerables support the standard forEach method:
1
2
3
[1,2,3].forEach(function(item) {
  console.log(item);
});
In general, Enumerable methods, like forEach, take an optional second parameter, which will become the value of this in the callback function:
1
2
3
4
var array = [1,2,3];
array.forEach(function(item) {
  console.log(item, this.indexOf(item));
}, array)
Among other reasons, you will find this useful when using another Enumerable method as a callback to forEach:
1
2
var array = [1,2,3];
array.forEach(array.removeObject, array);
NOTE: This second parameter helps work around a limitation of JavaScript which sets the value of this to window in methods used this way.

Enumerables in Ember

In general, Ember objects that represent lists implement the Enumerable interface. Some examples:
  • Array: Ember extends the native JavaScript Array with the Enumerable interface.
  • ArrayProxy: A construct that wraps a native Array and adds additional functionality for the view layer.
  • Set: An object that can quickly answer whether it includes an object.

The Enumerable Interface

Parameters

The callbacks to Enumerable methods take three arguments:
  • item: the item for the current iteration.
  • index: an Integer, counting up from 0.
  • self: the Enumerable itself.

Enumeration

To enumerate all the values of an enumerable object, use the forEach method:
1
2
3
enumerable.forEach(function(item, index, self) {
  console.log(item);
});
To invoke some method on each element of an enumerable object, use the invoke method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Person = Ember.Object.extend({
  sayHello: function() {
    console.log("Hello from " + this.get('name'));
  }
});
var people = [
  Person.create({ name: "Juan" }),
  Person.create({ name: "Charles" }),
  Person.create({ name: "Majd" })
]
people.invoke('sayHello');
// Hello from Juan
// Hello from Charles
// Hello from Majd

First and Last

You can get the first or last object from an Enumerable by getting firstObject or lastObject.
1
2
[1,2,3].get('firstObject') // 1
[1,2,3].get('lastObject')  // 3

Converting to Array

This one is simple. To convert an Enumerable into an Array, simply call its toArray method.

Transforming

You can transform an Enumerable into a derived Array by using the map method:
1
2
3
4
['Goodbye', 'cruel', 'world'].map(function(item, index, self) {
  return item + "!";
});
// returns ["Goodbye!", "cruel!", "world!"]

Setting and Getting on Each Object

A very common use of forEach and map is to get (or set) a property on each element. You can use the getEach and setEach methods to accomplish these goals.
1
2
3
4
var arr = [Ember.Object.create(), Ember.Object.create()];
// we now have an Array containing two Ember.Objects
arr.setEach('name', 'unknown');
arr.getEach('name') // ['unknown', 'unknown']

Filtering

Another common task to perform on an Enumerable is to take the Enumerable as input, and return an Array after filtering it based on some criteria.
For arbitrary filtering, use the (you guessed it) filter method. The filter method expects the callback to return true if Ember should include it in the final Array, and false or undefined if Ember should not.
1
2
3
4
5
var arr = [1,2,3,4,5];
arr.filter(function(item, index, self) {
  if (item < 4) { return true; }
})
// returns [1,2,3]
When working with a collection of Ember objects, you will often want to filter a set of objects based upon the value of some property. The filterProperty method provides a shortcut.
1
2
3
4
5
6
7
8
9
10
Todo = Ember.Object.extend({
  title: null,
  isDone: false
});
todos = [
  Todo.create({ title: 'Write code', isDone: true }),
  Todo.create({ title: 'Go to sleep' })
];
todos.filterProperty('isDone', true);
// returns an Array containing just the first item
If you want to return just the first matched value, rather than an Array containing all of the matched values, you can use find and findProperty, which work just like filter and filterProperty, but return only one item.

Aggregate Information (All or Any)

If you want to find out whether every item in an Enumerable matches some condition, you can use the every method:
1
2
3
4
5
6
7
8
9
10
11
12
Person = Ember.Object.extend({
  name: null,
  isHappy: false
});
var people = [
  Person.create({ name: 'Yehuda', isHappy: true }),
  Person.create({ name: 'Majd', isHappy: false })
];
people.every(function(person, index, self) {
  if(person.get('isHappy')) { return true; }
});
// returns false
If you want to find out whether at least one item in an Enumerable matches some conditions, you can use the some method:
1
2
3
4
people.some(function(person, index, self) {
  if(person.get('isHappy')) { return true; }
});
// returns true
Just like the filtering methods, the every and some methods have analogous everyProperty and someProperty methods.
1
2
people.everyProperty('isHappy', true) // false
people.someProperty('isHappy', true)  // true

Comments

Popular posts from this blog

create pdf by using javascript

yii framework simple shopping cart tutorial for beginners

yii2 arrayhelper::map vs array_map