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.

The View Layer


This guide goes into extreme detail about the Ember.js view layer. It is intended for an experienced Ember developer, and includes details that are unnecessary for getting started with Ember.

Ember.js has a sophisticated system for creating, managing and rendering a hierarchy of views that connect to the browser’s DOM. Views are responsible for responding to user events, like clicks, drags, and scrolls, as well as updating the contents of the DOM when the data underlying the view changes.

View hierarchies are usually created by evaluating a Handlebars template. As the template is evaluated, child views are added. As the templates for those child views are evaluated, they may have child views added, and so on, until an entire hierarchy is created.

Even if you do not explicitly create child views from your Handlebars templates, Ember.js internally uses the view system to update bound values. For example, every Handlebars expression {{value}} creates a view behind-the-scenes that knows how to update the bound value if it changes.

You can also dynamically make changes to the view hierarchy at application runtime using the Ember.ContainerView class. Rather than being template-driven, a container view exposes an array of child view instances that can be manually managed.

Views and templates work in tandem to provide a robust system for creating whatever user interface you dream up. End users should be isolated from the complexities of things like timing issues while rendering and event propagation. Application developers should be able to describe their UI once, as a string of Handlebars markup, and then carry on with their application without having to worry about making sure that it remains up-to-date.

What problems does it solve?

Child Views

In a typical client-side application, views may represent elements nested inside of each other in the DOM. In the naïve solution to this problem, separate view objects represent each DOM element, and ad-hoc references help the various view object keep track of the views conceptually nested inside of them.

Here is a simple example, representing one main app view, a collection nested inside of it, and individual items nested inside of the collection.

view-hierarchy-simple in ember.js

view-hierarchy-simple in ember.js

This system works well at first glance, but imagine that we want to open Joe’s Lamprey Shack at 8am instead of 9am. In this situation, we will want to re-render the App View. Because the developer needed to build up the references to the children on an ad-hoc basis, this re-rendering process has several problems.

In order to re-render the App View, the App View must also manually re-render the child views and re-insert them into App View’s element. If implemented perfectly, this process works well, but it relies upon a perfect, ad hoc implementation of a view hierarchy. If any single view fails to implement this precisely, the entire re-render will fail.

In order to avoid these problems, Ember’s view hierarchy has the concept of child views baked in.

view-hierarchy-ember

view-hierarchy-ember

When the App View re-renders, Ember is responsible for re-rendering and inserting the child views, not application code. This also means that Ember can perform any memory management for you, such as cleaning up observers and bindings.

Not only does this eliminate quite a bit of boilerplate code, but it eliminates the possibility that an imperfectly implemented view hierarchy will cause unexpected failures.

Event Delegation

In the past, web developers have added event listeners to individual elements in order to know when the user interacts with them. For example, you might have a <div> element on which you register a function that gets called when the user clicks it.

However, this approach often does not scale when dealing with large numbers of interactive elements. For example, imagine a <ul> with 100 <li>s in it, with a delete button next to each item. Since the behavior is the same for all of these items, it would be inefficient to create 100 event listeners, one for each delete button.

undelegated

undelegated

To solve this problem, developers discovered a technique called “event delegation”. Instead of registering a listener on each element in question, you can register a single listener for the containing element and use event.target to identify which element the user clicked on.

delegated

delegated

Implementing this is a bit tricky, because some events (like focus, blur and change) don’t bubble. Fortunately, jQuery has solved this problem thoroughly; using jQuery’s on method reliably works for all native browser events.

Other JavaScript frameworks tackle this problem in one of two ways. In the first approach, they ask you to implement the naïve solution yourself, creating a separate view for each element. When you create the view, it sets up an event listener on the view’s element. If you had a list of 500 items, you would create 500 views and each would set up a listener on its own element.

In the second approach, the framework builds in event delegation at the view level. When creating a view, you can supply a list of events to delegate and a method to call when the event occurs. This leaves identifying the context of the click (for example, which item in the list) to the method receiving the event.

You are now faced with an uncomfortable choice: create a new view for each item and lose the benefits of event delegation, or create a single view for all of the items and have to store information about the underlying JavaScript object in the DOM.

In order to solve this problem, Ember delegates all events to the application’s root element (usually the document body) using jQuery. When an event occurs, Ember identifies the nearest view that handles the event and invokes its event handler. This means that you can create views to hold a JavaScript context, but still get the benefit of event delegation.

Further, because Ember registers only one event for the entire Ember application, creating new views never requires setting up event listeners, making re-renders efficient and less error-prone. When a view has child views, this also means that there is no need to manually undelegate views that the re-render process replaces.

The Rendering Pipeline

Most web applications specify their user interface using the markup of a particular templating language. For Ember.js, we’ve done the work to make templates written using the Handlebars templating language automatically update when the values used inside of them are changed.

While the process of displaying a template is automatic for developers, under the hood there are a series of steps that must be taken to go from the original template to the final, live DOM representation that the user sees.

This is the approximate lifecycle of an Ember view:

view-lifecycle-ember

view-lifecycle-ember

1. Template Compilation

The application’s templates are loaded over the network or as part of the application payload in string form. When the application loads, it sends the template string to Handlebars to be compiled into a function. Once compiled, the template function is saved, and can be used by multiple views repeatedly, each time they need to re-render.

This step may be omitted in applications where the templates are pre-compiled on the server. In those cases, the template is transferred not as the original, human-readable template string but as the compiled code.

Because Ember is responsible for template compilation, you don’t have to do any additional work to ensure that compiled templates are reused.

2. String Concatenation

A view’s rendering process is kickstarted when the application calls append or appendTo on the view. Calling append or appendTo schedules the view to be rendered and inserted later. This allows any deferred logic in your application (such as binding synchronization) to happen before rendering the element.

To begin the rendering process, Ember creates a RenderBuffer and gives it to the view to append its contents to. During this process, a view can create and render child views. When it does so, the parent view creates and assigns a RenderBuffer for the child, and links it to the parent’s RenderBuffer.

Ember flushes the binding synchronization queue before rendering each view. By syncing bindings before rendering each view, Ember guarantees that it will not render stale data it needs to replace right away.

Once the main view has finished rendering, the render process has created a tree of views (the “view hierarchy”), linked to a tree of buffers. By walking down the tree of buffers and converting them into Strings, we have a String that we can insert into the DOM.

Here is a simple example:

render-buffer

render-buffer

In addition to children (Strings and other RenderBuffers), a RenderBuffer also encapsulates the element’s tag name, id, classes, style, and other attributes. This makes it possible for the render process to modify one of these properties (style, for example), even after its child Strings have rendered. Because many of these properties are controlled via bindings (e.g. using bind-attr), this makes the process robust and transparent.

3. Element Creation and Insertion

At the end of the rendering process, the root view asks the RenderBuffer for its element. The RenderBuffer takes its completed string and uses jQuery to convert it into an element. The view assigns that element to its element property and places it into the correct place in the DOM (the location specified in appendTo or the application’s root element if the application used append).

While the parent view assigns its element directly, each child views looks up its element lazily. It does this by looking for an element whose id matches its elementId property. Unless explicitly provided, the rendering process generates an elementId property and assigns its value to the view’s RenderBuffer, which allows the view to find its element as needed.

4. Re-Rendering

After the view inserts itself into the DOM, either Ember or the application may want to re-render the view. They can trigger a re-render by calling the rerender method on a view.

Rerendering will repeat steps 2 and 3 above, with two exceptions:

  • Instead of inserting the element into an explicitly specified location, rerender replaces the existing element with the new element.
  • In addition to rendering a new element, it also removes the old element and destroys its children. This allows Ember to automatically handle unregistering appropriate bindings and observers when re-rendering a view. This makes observers on a path more viable, because the process of registering and unregistering all of the nested observers is automatic.

The most common cause of a view re-render is when the value bound to a Handlebars expression ({{foo}}) changes. Internally, Ember creates a simple view for each expression, and registers an observer on the path. When the path changes, Ember updates the area of the DOM with the new value.

Another common case is an {{#if}} or {{#with}} block. When rendering a template, Ember creates a virtual view for these block helpers. These virtual views do not appear in the publicly available view hierarchy (when getting parentView and childViews from a view), but they exist to enable consistent re-rendering.

When the path passed to an {{#if}} or {{#with}} changes, Ember automatically re-renders the virtual view, which will replace its contents, and importantly, destroy all child views to free up their memory.

In addition to these cases, the application may sometimes want to explicitly re-render a view (usually a ContainerView, see below). In this case, the application can call rerender directly, and Ember will queue up a re-rendering job, with the same semantics.

The process looks something like:

render-buffer

render-buffer

The View Hierarchy

Parent and Child Views

As Ember renders a templated view, it will generate a view hierarchy. Let’s assume we have a template form.

1
2
{{view App.Search placeholder="Search"}}
{{#view Ember.Button}}Go!{{/view}}

And we insert it into the DOM like this:

1
2
3
var view = Ember.View.create({
  templateName: 'form'
}).append();

This will create a small view hierarchy that looks like this:

simple-view-hierarchy

simple-view-hierarchy

You can move around in the view hierarchy using the parentView and childViews properties.

1
2
var children = view.get('childViews') // [ <App.Search>, <Ember.Button> ]
children.objectAt(0).get('parentView') // view

One common use of the parentView method is inside of an instance of a child view.

1
2
3
4
5
App.Search = Ember.View.extend({
  didInsertElement: function() {
    // this.get('parentView') in here references `view`
  }
})

Lifecycle Hooks

In order to make it easy to take action at different points during your view’s lifecycle, there are several hooks you can implement.

  • willInsertElement: This hook is called after the view has been rendered but before it has been inserted into the DOM. It does not provide access to the view’s element.
  • didInsertElement: This hook is called immediately after the view has been inserted into the DOM. It provides access to the view’s element and is most useful for integration with an external library. Any explicit DOM setup code should be limited to this hook.
  • willDestroyElement: This hook is called immediately before the element is removed from the DOM. This is your opportunity to tear down any external state associated with the DOM node. Like didInsertElement, it is most useful for integration with external libraries.
  • willRerender: This hook is called immediately before a view is re-rendered. This is useful if you want to perform some teardown immediately before a view is re-rendered.
  • becameVisible: This hook is called after a view’s isVisible property, or one of its ancestor’s isVisible property, changes to true and the associated element becomes visible. Note that this hook is only reliable if all visibility is routed through the isVisible property.
  • becameHidden: This hook is called after a view’s isVisible property, or one of its ancestor’s isVisible property, changes to false and the associated element becomes hidden. Note that this hook is only reliable if all visibility is routed through the isVisible property.

Apps can implement these hooks by defining a method by the hook’s name on the view. Alternatively, it is possible to register a listener for the hook on a view:

1
2
3
view.on('willRerender', function() {
  // do something with view
});

Virtual Views

As described above, Handlebars creates views in the view hierarchy to
represent bound values. Every time you use a Handlebars expression,
whether it’s a simple value or a block helper like {{#with}} or
{{#if}}, Handlebars creates a new view.

Because Ember uses these views for internal bookkeeping only,
they are hidden from the view’s public parentView and childViews
API. The public view hierarchy reflects only views created using the
{{view}} helper or through ContainerView (see below).

For example, consider the following Handlebars template:

1
2
3
4
5
6
7
8
9
10
11
12
<h1>Joe's Lamprey Shack</h1>
{{controller.restaurantHours}}

{{#view App.FDAContactForm}}
  If you are experiencing discomfort from eating at Joe's Lamprey Shack,
please use the form below to submit a complaint to the FDA.

  {{#if controller.allowComplaints}}
    {{view Ember.TextArea valueBinding="controller.complaint"}}
    <button {{action submitComplaint}}>Submit</button>
  {{/if}}
{{/view}}

Rendering this template would create a hierarchy like this:

public-view-hierarchy

public-view-hierarchy

Behind the scenes, Ember tracks additional virtual views for the
Handlebars expressions:

virtual-view-hierarchy

virtual-view-hierarchy

From inside of the TextArea, the parentView would point to the
FDAContactForm and the FDAContactForm‘s childViews would be an
array of the single TextArea view.

You can see the internal view hierarchy by asking for the _parentView
or _childViews, which will include virtual views:

1
2
3
var _childViews = view.get('_childViews');
console.log(_childViews.objectAt(0).toString());
//> <Ember._HandlebarsBoundView:ember1234>

Warning! You may not rely on these internal APIs in application code.
They may change at any time and have no public contract. The return
value may not be observable or bindable. It may not be an Ember object.
If you feel the need to use them, please contact us so we can expose a better
public API for your use-case.

Bottom line: This API is like XML. If you think you have a use for it,
you may not yet understand the problem enough. Reconsider!

Event Bubbling

One responsibility of views is to respond to primitive user events
and translate them into events that have semantic meaning for your
application.

For example, a delete button translates the primitive click event into
the application-specific “remove this item from an array.”

In order to respond to user events, create a new view subclass that
implements that event as a method:

1
2
3
4
5
6
App.DeleteButton = Ember.View.create({
  click: function(event) {
    var item = this.get('model');
    this.get('controller').send('deleteItem', item);
  }
});

When you create a new Ember.Application instance, it registers an event
handler for each native browser event using jQuery’s event delegation
API. When the user triggers an event, the application’s event dispatcher
will find the view nearest to the event target that implements the
event.

A view implements an event by defining a method corresponding to the
event name. When the event name is made up of multiple words (like
mouseup) the method name should be the camelized form of the event
name (mouseUp).

Events will bubble up the view hierarchy until the event reaches the
root view. An event handler can stop propagation using the same
techniques as normal jQuery event handlers:

  • return false from the method
  • event.stopPropagation

For example, imagine you defined the following view classes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
App.GrandparentView = Ember.View.extend({
  click: function() {
    console.log('Grandparent!');
  }
});

App.ParentView = Ember.View.extend({
  click: function() {
    console.log('Parent!');
    return false;
  }
});

App.ChildView = Ember.View.extend({
  click: function() {
    console.log('Child!');
  }
});

And here’s the Handlebars template that uses them:

1
2
3
4
5
6
7
{{#view App.GrandparentView}}
  {{#view App.ParentView}}
    {{#view App.ChildView}}
      <h1>Click me!</h1>
    {{/view}}
  {{/view}}
{{/view}}

If you clicked on the <h1>, you’d see the following output in your
browser’s console:

1
2
Child!
Parent!

You can see that Ember invokes the handler on the child-most view that
received the event. The event continues to bubble to the ParentView,
but does not reach the GrandparentView because ParentView returns
false from its event handler.

You can use normal event bubbling techniques to implement familiar
patterns. For example, you could implement a FormView that defines a
submit method. Because the browser triggers the submit event when
the user hits enter in a text field, defining a submit method on the
form view will “just work”.

1
2
3
4
5
6
7
8
App.FormView = Ember.View.extend({
  tagName: "form",

  submit: function(event) {
    // will be invoked whenever the user triggers
    // the browser's `submit` method
  }
});
1
2
3
4
5
{{#view App.FormView}}
  {{view Ember.TextField valueBinding="controller.firstName"}}
  {{view Ember.TextField valueBinding="controller.lastName"}}
  <button type="submit">Done</button>
{{/view}}

Adding New Events

Ember comes with built-in support for the following native browser
events:

Event Name Method Name
touchstart touchStart
touchmove touchMove
touchend touchEnd
touchcancel touchCancel
keydown keyDown
keyup keyUp
keypress keyPress
mousedown mouseDown
mouseup mouseUp
contextmenu contextMenu
click click
dblclick doubleClick
mousemove mouseMove
focusin focusIn
focusout focusOut
mouseenter mouseEnter
mouseleave mouseLeave
submit submit
change change
dragstart dragStart
drag drag
dragenter dragEnter
dragleave dragLeave
dragover dragOver
drop drop
dragend dragEnd

You can add additional events to the event dispatcher when you create a
new application:

1
2
3
4
5
6
7
App = Ember.Application.create({
  customEvents: {
    // add support for the loadedmetadata media
    // player event
    'loadedmetadata': "loadedMetadata"
  }
});

In order for this to work for a custom event, the HTML5 spec must define
the event as “bubbling”, or jQuery must have provided an event
delegation shim for the event.

Templated Views

As you’ve seen so far in this guide, the majority of views that you will
use in your application are backed by a template. When using templates,
you do not need to programmatically create your view hierarchy because
the template creates it for you.

While rendering, the view’s template can append views to its child views
array. Internally, the template’s {{view}} helper calls the view’s
appendChild method.

Calling appendChild does two things:

  1. Adds the child view to the childViews array.
  2. Immediately renders the child view and adds it to the parent’s render
    buffer.
template-appendChild-interaction

template-appendChild-interaction

You may not call appendChild on a view after it has left the rendering
state. A template renders “mixed content” (both views and plain text) so
the parent view does not know exactly where to insert the new child view
once the rendering process has completed.

In the example above, imagine trying to insert a new view inside of
the parent view’s childViews array. Should it go immediately
after the closing </div> of App.MyView? Or should it go after the
closing </div> of the entire view? There is no good answer that will
always be correct.

Because of this ambiguity, the only way to create a view hierarchy using
templates is via the {{view}} helper, which always inserts views
in the right place relative to any plain text.

While this works for most situations, occasionally you may want to have
direct, programmatic control of a view’s children. In that case, you can
use Ember.ContainerView, which explicitly exposes a public API for
doing so.

Container Views

Container views contain no plain text. They are composed entirely of
their child views (which may themselves be template-backed).

ContainerView exposes two public APIs for changing its contents:

  1. A writable childViews array into which you can insert Ember.View
    instances.
  2. A currentView property that, when set, inserts the new value into
    the child views array. If there was a previous value of
    currentView, it is removed from the childViews array.

Here is an example of using the childViews API to create a view that
starts with a hypothetical DescriptionView and can add a new button at
any time by calling the addButton method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
App.ToolbarView = Ember.ContainerView.create({
  init: function() {
    var childViews = this.get('childViews');
    var descriptionView = App.DescriptionView.create();

    childViews.pushObject(descriptionView);
    this.addButton();

    return this._super();
  },

  addButton: function() {
    var childViews = this.get('childViews');
    var button = Ember.ButtonView.create();

    childViews.pushObject(button);
  }
});

As you can see in the example above, we initialize the ContainerView
with two views, and can add additional views during runtime. There is a
convenient shorthand for doing this view setup without having to
override the init method:

1
2
3
4
5
6
7
8
9
10
11
12
13
App.ToolbarView = Ember.ContainerView.create({
  childViews: ['descriptionView', 'buttonView'],

  descriptionView: App.DescriptionView,
  buttonView: Ember.ButtonView,

  addButton: function() {
    var childViews = this.get('childViews');
    var button = Ember.ButtonView.create();    childViews.pushObject(button);
  }
});

As you can see above, when using this shorthand, you specify the
childViews as an array of strings. At initialization time, each of the
strings is used as a key to look up a view instance or class. That view
is automatically instantiated, if necessary, and added to the
childViews array.

Template Scopes

Standard Handlebars templates have the concept of a context–the
object from which expressions will be looked up.

Some helpers, like {{#with}}, change the context inside their block.
Others, like {{#if}}, preserve the context. These are called
“context-preserving helpers.”

When a Handlebars template in an Ember app uses an expression
({{#if foo.bar}}), Ember will automatically set up an
observer for that path on the current context.

If the object referenced by the path changes, Ember will automatically
re-render the block with the appropriate context. In the case of a
context-preserving helper, Ember will re-use the original context when
re-rendering the block. Otherwise, Ember will use the new value of the
path as the context.

1
2
3
4
5
6
7
{{#if controller.isAuthenticated}}
  <h1>Welcome {{controller.name}}</h1>
{{/if}}{{#with controller.user}}
  <p>You have {{notificationCount}} notifications.</p>
{{/with}}

In the above template, when the isAuthenticated property changes from
false to true, Ember will render the block, using the original outer
scope as its context.

The {{#with}} helper changes the context of its block to the user
property on the current controller. When the user property changes,
Ember re-renders the block, using the new value of controller.user as
its context.

View Scope

In addition to the Handlebars context, templates in Ember also have the
notion of the current view. No matter what the current context is, the
view property always references the closest view.

Note that the view property never references the internal views
created for block expressions like {{#if}}. This allows you to
differentiate between Handlebars contexts, which always work the way
they do in vanilla Handlebars, and the view hierarchy.

Because view points to an Ember.View instance, you can access any
properties on the view by using an expression like view.propertyName.
You can get access to a view’s parent using view.parentView.

For example, imagine you had a view with the following properties:

1
2
3
4
App.MenuItemView = Ember.View.create({
  templateName: 'menu_item_view',
  bulletText: '*'
});

…and the following template:

1
2
3
{{#with controller}}
  {{view.bulletText}} {{name}}
{{/with}}

Even though the Handlebars context has changed to the current
controller, you can still access the view’s bulletText by referencing
view.bulletText.

Template Variables

So far in this guide, we’ve been handwaving around the use of the
controller property in our Handlebars templates. Where does it come
from?

Handlebars contexts in Ember can inherit variables from their parent
contexts. Before Ember looks up a variable in the current context, it
first checks in its template variables. As a template creates new
Handlebars scope, they automatically inherit the variables from their
parent scope.

Ember defines these view and controller variables, so they are
always found first when an expression uses the view or controller
names.

As described above, Ember sets the view variable on the Handlebars
context whenever a template uses the {{#view}} helper. Initially,
Ember sets the view variable to the view rendering the template.

Ember sets the controller variable on the Handlebars context whenever
a rendered view has a controller property. If a view has no
controller property, it inherits the controller variable from the
most recent view with one.

Other Variables

Handlebars helpers in Ember may also specify variables. For example, the
{{#with controller.person as tom}} form specifies a tom variable
that descendent scopes can access. Even if a child context has a tom
property, the tom variable will supersede it.

This form has one major benefit: it allows you to shorten long paths
without losing access to the parent scope.

It is especially important in the {{#each}} helper, which provides
the {{#each person in people}} form.
In this form, descendent context have access to the person variable,
but remain in the same scope as where the template invoked the each.

1
2
3
4
5
6
7
8
9
{{#with controller.preferences}}
  <h1>Title</h1>
  <ul>
  {{#each person in controller.people}}
    {{! prefix here is controller.preferences.prefix }}
    <li>{{prefix}}: {{person.fullName}}</li>
  {{/each}}
  <ul>
{{/with}}

Note that these variables inherit through ContainerViews, even though
they are not part of the Handlebars context hierarchy.

Accessing Template Variables from Views

In most cases, you will need to access these template variables from
inside your templates. In some unusual cases, you may want to access the
variables in-scope from your view’s JavaScript code.

You can do this by accessing the view’s templateVariables property,
which will return a JavaScript object containing the variables that were
in scope when the view was rendered. ContainerViews also have access
to this property, which references the template variables in the most
recent template-backed view.

At present, you may not observe or bind a path containing
templateVariables.

Managing Asynchrony


Many Ember concepts, like bindings and computed properties, are designed
to help manage asynchronous behavior.

Without Ember

We’ll start by taking a look at ways to manage asynchronous behavior
using jQuery or event-based MVC frameworks.

Let’s use the most common asynchronous behavior in a web application,
making an Ajax request, as an example. The browser APIs for making Ajax
requests provide an asynchronous API. jQuery’s wrapper does as well:

1
2
3
4
jQuery.getJSON('/posts/1', function(post) {
  $("#post").html("<h1>" + post.title + "</h1>" +
    "<div>" + post.body + "</div>");
});

In a raw jQuery application, you would use this callback to make
whatever changes you needed to make to the DOM.

When using an event-based MVC framework, you move the logic out of the
callback and into model and view objects. This improves things, but
doesn’t get rid of the need to explicitly deal with asynchronous
callbacks:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Post = Model.extend({
  author: function() {
    return [this.salutation, this.name].join('')
  },

  toJSON: function() {
    var json = Model.prototype.toJSON.call(this);
    json.author = this.author();
    return json;
  }
});

PostView = View.extend({
  init: function(model) {
    model.bind('change', this.render, this);
  },

  template: _.template("<h1><%= title %></h1><h2><%= author %></h2><div><%= body %></div>"),

  render: function() {
    jQuery(this.element).html(this.template(this.model.toJSON());
    return this;
  }
});

var post = Post.create();
var postView = PostView.create({ model: post });
jQuery('#posts').append(postView.render().el);

jQuery.getJSON('/posts/1', function(json) {
  // set all of the JSON properties on the model
  post.set(json);
});

This example doesn’t use any particular JavaScript library beyond
jQuery, but its approach is typical of event-driven MVC frameworks. It
helps organize the asynchronous events, but asynchronous behavior is
still the core programming model.

Ember’s Approach

In general, Ember’s goal is to eliminate explicit forms of asynchronous
behavior. As we’ll see later, this gives Ember the ability to coalesce
multiple events that have the same result.

It also provides a higher level of abstraction, eliminating the need to
manually register and unregister event listeners to perform most common
tasks.

You would normally use ember-data for this example, but let’s see how
you would model the above example using jQuery for Ajax in Ember.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
App.Post = Ember.Object.extend({

});

App.PostController = Ember.ObjectController.extend({
  author: function() {
    return [this.get('salutation'), this.get('name')].join('');
  }.property('salutation', 'name')
});

App.PostView = Ember.View.extend({
  // the controller is the initial context for the template
  controller: null,
  template: Ember.Handlebars.compile("<h1>{{title}}</h1><h2>{{author}}</h2><div>{{body}}</div>")
});

var post = App.Post.create();
var postController = App.PostController.create({ model: post });

App.PostView.create({ controller: postController }).appendTo('body');

jQuery.getJSON("/posts/1", function(json) {
  post.setProperties(json);
});

In contrast to the above examples, the Ember approach eliminates the
need to explicitly register an observer when the post‘s properties
change.

The {{title}}, {{author}} and {{body}} template elements are bound
to those properties on the PostController. When the PostController‘s
model changes, it automatically propagates those changes to the DOM.

Using a computed property for author eliminated the need to explicitly
invoke the computation in a callback when the underlying property
changed.

Instead, Ember’s binding system automatically follows the trail from the
salutation and name set in the getJSON callback to the computed
property in the PostController and all the way into the DOM.

Benefits

Because Ember is usually responsible for propagating changes, it can
guarantee that a single change is only propagated one time in response
to each user event.

Let’s take another look at the author computed property.

1
2
3
4
5
App.PostController = Ember.ObjectController.extend({
  author: function() {
    return [this.get('salutation'), this.get('name')].join('');
  }.property('salutation', 'name')
});

Because we have specified that it depends on both salutation and
name, changes to either of those two dependencies will invalidate the
property, which will trigger an update to the {{author}} property in
the DOM.

Imagine that in response to a user event, I do something like this:

1
2
post.set('salutation', "Mrs.");
post.set('name', "Katz");

You might imagine that these changes will cause the computed property to
be invalidated twice, causing two updates to the DOM. And in fact, that
is exactly what would happen when using an event-driven framework.

In Ember, the computed property will only recompute once, and the DOM
will only update once.

How?

When you make a change to a property in Ember, it does not immediately
propagate that change. Instead, it invalidates any dependent properties
immediately, but queues the actual change to happen later.

Changing both the salutation and name properties invalidates the
author property twice, but the queue is smart enough to coalesce those
changes.

Once all of the event handlers for the current user event have finished,
Ember flushes the queue, propagating the changes downward. In this case,
that means that the invalidated author property will invalidate the
{{author}} in the DOM, which will make a single request to recompute
the information and update itself once.

This mechanism is fundamental to Ember. In Ember, you should always
assume that the side-effects of a change you make will happen later. By
making that assumption, you allow Ember to coalesce repetitions of the
same side-effect into a single call.

In general, the goal of evented systems is to decouple the data
manipulation from the side effects produced by listeners, so you
shouldn’t assume synchronous side effects even in a more event-focused
system. The fact that side effects don’t propagate immediately in Ember
eliminates the temptation to cheat and accidentally couple code together
that should be separate.

Side-Effect Callbacks

Since you can’t rely on synchronous side-effects, you may be wondering
how to make sure that certain actions happen at the right time.

For example, imagine that you have a view that contains a button, and
you want to use jQuery UI to style the button. Since a view’s append
method, like everything else in Ember, defers its side-effects, how can
you execute the jQuery UI code at the right time?

The answer is lifecycle callbacks.

1
2
3
4
5
6
7
8
9
10
11
12
App.Button = Ember.View.extend({
  tagName: 'button',
  template: Ember.Handlebars.compile("{{view.title}}"),

  didInsertElement: function() {
    this.$().button();
  }
});

var button = App.Button.create({
  title: "Hi jQuery UI!"
}).appendTo('#something');

In this case, as soon as the button actually appears in the DOM, Ember
will trigger the didInsertElement callback, and you can do whatever
work you want.

The lifecycle callbacks approach has several benefits, even if we didn’t
have to worry about deferred insertion.

First, relying on synchronous insertion means leaving it up to the
caller of appendTo to trigger any behavior that needs to run
immediately after appending. As your application grows, you may find
that you create the same view in many places, and now need to worry
about that concern everywhere.

The lifecycle callback eliminates the coupling between the code that
instantiates the view and its post-append behavior. In general, we find
that making it impossible to rely on synchronous side-effects leads to
better design in general.

Second, because everything about the lifecycle of a view is inside the
view itself, it is very easy for Ember to re-render parts of the DOM
on-demand.

For example, if this button was inside of an {{#if}} block, and Ember
needed to switch from the main branch to the else section, Ember can
easily instantiate the view and call the lifecycle callbacks.

Because Ember forces you to define a fully-defined view, it can take
control of creating and inserting views in appropriate situations.

This also means that all of the code for working with the DOM is in a
few sanctioned parts of your application, so Ember has more freedom in
the parts of the render process outside of these callbacks.

Observers

In some rare cases, you will want to perform certain behavior after a
property’s changes have propagated. As in the previous section, Ember
provides a mechanism to hook into the property change notifications.

Let’s go back to our salutation example.

1
2
3
4
5
App.PostController = Ember.ObjectController.extend({
  author: function() {
    return [this.get('salutation'), this.get('name')].join('');
  }.property('salutation', 'name')
});

If we want to be notified when the author changes, we can register an
observer. Let’s say that the view object wants to be notified:

1
2
3
4
5
6
7
8
App.PostView = Ember.View.extend({
  controller: null,
  template: Ember.Handlebars.compile("<h1>{{title}}</h1><h2>{{author}}</h2><div>{{body}}</div>"),

  authorDidChange: function() {
    alert("New author name: " + this.get('controller.author'));
  }.observes('controller.author')
});

Ember triggers observers after it successfully propagates the change. In
this case, that means that Ember will only call the authorDidChange
callback once in response to each user event, even if both of salutation
and name changed.

This gives you the benefits of executing code after the property has
changed, without forcing all property changes to be synchronous. This
basically means that if you need to do some manual work in response to a
change in a computed property, you get the same coalescing benefits as
Ember’s binding system.

Finally, you can also register observers manually, outside of an object
definition:

1
2
3
4
5
6
7
8
9
10
App.PostView = Ember.View.extend({
  controller: null,
  template: Ember.Handlebars.compile("<h1>{{title}}</h1><h2>{{author}}</h2><div>{{body}}</div>"),

  didInsertElement: function() {
    this.addObserver('controller.name', function() {
      alert("New author name: " + this.get('controller.author'));
    });
  }
});

However, when you use the object definition syntax, Ember will
automatically tear down the observers when the object is destroyed. For
example, if an {{#if}} statement changes from truthy to falsy, Ember
destroys all of the views defined inside the block. As part of that
process, Ember also disconnects all bindings and inline observers.

If you define an observer manually, you need to make sure you remove it.
In general, you will want to remove observers in the opposite callback
to when you created it. In this case, you will want to remove the
callback in willDestroyElement.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
App.PostView = Ember.View.extend({
  controller: null,
  template: Ember.Handlebars.compile("<h1>{{title}}</h1><h2>{{author}}</h2><div>{{body}}</div>"),

  didInsertElement: function() {
    this.addObserver('controller.name', function() {
      alert("New author name: " + this.get('controller.author'));
    });
  },

  willDestroyElement: function() {
    this.removeObserver('controller.name');
  }
});

If you added the observer in the init method, you would want to tear
it down in the willDestroy callback.

In general, you will very rarely want to register a manual observer in
this way. Because of the memory management guarantees, we strongly
recommend that you define your observers as part of the object
definition if possible.

Routing

There’s an entire page dedicated to managing async within the Ember
Router: Asynchronous Routing

Keeping Templates Up-to-Date


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:

1
My new car is {{unbound color}}.

Your output will be free of markers, but be careful, because the output won’t be automatically updated!

1
My new car is blue.

Keeping Templates Up-to-Date


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:

1
My new car is {{unbound color}}.

Your output will be free of markers, but be careful, because the output won’t be automatically updated!

1
My new car is blue.

Debugging


Debugging Ember and Ember Data

Here are some tips you can use to help debug your Ember application.

Also, check out the
ember-extension
project, which adds an Ember tab to Chrome DevTools that allows you
to inspect Ember objects in your application.

Routing

Log router transitions

1
2
3
4
5
6
7
8
9
10
window.App = Ember.Application.create({
  // Basic logging, e.g. "Transitioned into 'post'"
  LOG_TRANSITIONS: true, 

  // Extremely detailed logging, highlighting every internal
  // step made while transitioning into a route, including
  // `beforeModel`, `model`, and `afterModel` hooks, and
  // information about redirects and aborted transitions
  LOG_TRANSITIONS_INTERNAL: true
});

View all registered routes

1
Ember.keys(App.Router.router.recognizer.names)

Get current route name / path

Ember installs the current route name and path on your
app’s ApplicationController as the properties
currentRouteName and currentPath. currentRouteName‘s
value (e.g. "comments.edit") can be used as the destination parameter of
transitionTo and the {{linkTo}} Handlebars helper, while
currentPath serves as a full descriptor of each
parent route that has been entered (e.g.
"admin.posts.show.comments.edit").

1
2
3
4
5
6
7
8
9
10
11
// From within a Route
this.controllerFor("application").get("currentRouteName");
this.controllerFor("application").get("currentPath");

// From within a controller, after specifying `needs: ['application']`
this.get('controllers.application.currentRouteName');
this.get('controllers.application.currentPath');

// From the console:
App.__container__.lookup("controller:application").get("currentRouteName")
App.__container__.lookup("controller:application").get("currentPath")

Views / Templates

Log view lookups

1
2
3
window.App = Ember.Application.create({
  LOG_VIEW_LOOKUPS: true
});

Get the View object from its DOM Element’s ID

1
Ember.View.views['ember605']

View all registered templates

1
Ember.keys(Ember.TEMPLATES)

Handlebars Debugging Helpers

1
2
{{debugger}}
{{log record}}

Controllers

Log generated controller

1
2
3
window.App = Ember.Application.create({
  LOG_ACTIVE_GENERATION: true
});

Ember Data

View ember-data’s identity map

1
2
3
4
5
6
7
8
// all records in memory
App.__container__.lookup('store:main').recordCache // attributes
App.__container__.lookup('store:main').recordCache[2].get('data.attributes')// loaded associations
App.__container__.lookup('store:main').recordCache[2].get('comments')

Observers / Binding

See all observers for a object, key

1
Ember.observersFor(comments, keyName);

Log object bindings

1
Ember.LOG_BINDINGS = true

Miscellaneous

View an instance of something from the container

1
2
App.__container__.lookup("controller:posts")
App.__container__.lookup("route:application")

Dealing with deprecations

1
2
Ember.ENV.RAISE_ON_DEPRECATION = true
Ember.LOG_STACKTRACE_ON_DEPRECATION = true

Implement a Ember.onerror hook to log all errors in production

1
2
3
4
5
6
Ember.onerror = function(error) {
  Em.$.ajax('/error-notification', 'POST', {
    stack: error.stack,
    otherInformation: 'exception message'
  });
}

Import the console

If you are using imports with Ember, be sure to import the console:

1
2
3
4
5
6
7
Ember = {
  imports: {
    Handlebars: Handlebars,
    jQuery: $,
    console: window.console
  }
};

Errors within an RSVP.Promise

There are times when dealing with promises that it seems like any errors
are being ‘swallowed’, and not properly raised. This makes it extremely
difficult to track down where a given issue is coming from. Thankfully,
RSVP has a solution for this problem built in.

You can provide an onerror function that will be called with the error
details if any errors occur within your promise. This function can be anything
but a common practice is to call console.assert to dump the error to the
console.

1
2
3
Ember.RSVP.configure('onerror', function(error) {
  Ember.Logger.assert(false, error);
});

if the post useful to you then donate just by clicking the link http://adf.ly/MMZbv

About

myself pramodh kumar yet another php developer from India and have worked on oops,procedural,yii framework,codeigniter,wordpress,joomla,api and more.

Tagged with: , , , , , , , , , , , , , ,
Posted in Ember.js tutorial for beginners
2 comments on “Ember.js tutorial for beginners
  1. Trek Glowacki says:

    I’m reaching out because you contributed some of the earliest tutorial/opinion articles for the Ember.js community. Thanks!

    Ember has gone through many changes since you first published and we’ve finally arrived at a stable 1.0 API. This means the code samples in your article are no longer accurate.

    We’d like to help authors update their articles to the 1.0 API and reduce the confusion for new developers approaching Ember.js for the first time. We’re asking that authors place a notice at the top their older articles and have created an image and HTML snippet you can easily drop in:

    Feel free to customize this banner any way you like.

    If you want to update your article to the 1.0 API – totally optional – feel free to contact me with questions or ask for advice.

    Cheers,
    Trek

    Like

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow php tutorial on WordPress.com
categories
Calendar
November 2012
M T W T F S S
« Oct   Dec »
 1234
567891011
12131415161718
19202122232425
2627282930  
Follow me on Twitter
Blog Stats
  • 72,886 hits
%d bloggers like this: