An In-Depth Introduction To Ember.js

Advertisement

With the release of Ember.js 1.0, it’s just about time to consider giving it a try. This article aims to introduce Ember.js to newcomers who want to learn about this framework.

Users often say that the learning curve is steep, but once you’ve overcome the difficulties, then Ember.js is tremendous. This happened to me as well. While the official guides1 are more accurate and up to date than ever (for real!), this post is my attempt to make things even smoother for beginners.

First, we will clarify the main concepts of the framework. Next, we’ll go in depth with a step-by-step tutorial that teaches you how to build a simple Web app with Ember.js and Ember-Data, which is Ember’s data storage layer. Then, we will see how views and components help with handling user interactions. Finally, we will dig a little more into Ember-Data and template precompiling.

An In-Depth Introduction To Ember.js
Ember’s famous little mascot, Tomster. (Image credits2)

The unstyled demo below will help you follow each step of the tutorial. The enhanced demo is basically the same but with a lot more CSS and animations and a fully responsive UX when displayed on small screens.

Unstyled demo3 Source code4 Enhanced demo5

Table of Contents

Definitions Of Main Concepts

The diagram below illustrates how routes, controllers, views, templates and models interact with each other.

ember-sketch45

Let’s define these concepts. And if you’d like to learn more, check the relevant section of the official guides:

Models

Suppose our application handles a collection of users. Well, those users and their informations would be the model. Think of them as the database data. Models may be retrieved and updated by implementing AJAX callbacks inside your routes, or you can rely on Ember-Data (a data-storage abstraction layer) to greatly simplify the retrieval, updating and persistence of models over a REST API.

The Router

There is the Router, and then there are routes. The Router is just a synopsis of all of your routes. Routes are the URL representations of your application’s objects (for example, a route’s posts will render a collections of posts). The goal of routes is to query the model, from their model hook, to make it available in the controller and in the template. Routes can also be used to set properties in controllers, to execute events and actions, and to connect a particular template to a particular controller. Last but not least, the model hook can return promises so that you can implement a LoadingRoute, which will wait for the model to resolve asynchronously over the network.

Controllers

At first, a controller gets a model from a route. Then, it makes the bridge between the model and the view or template. Let’s say you need a convenient method or function for switching between editing mode to normal mode. A method such as goIntoEditMode() and closeEditMode() would be perfect, and that’s exactly what controllers can be used for.

Controllers are auto-generated by Ember.js if you don’t declare them. For example, you can create a user template with a UserRoute; and, if you don’t create a UserController (because you have nothing special to do with it), then Ember.js will generate one for you internally (in memory). The Ember Inspector53 extension for Chrome can help you track those magic controllers.

Views

Views represent particular parts of your application (the visual parts that the user can see in the browser). A View is associated with a Controller, a Handlebars template and a Route. The difference between views and templates can be tricky. You will find yourself dealing with views when you want to handle events or handle some custom interactions that are impossible to manage from templates. They have a very convenient didInsertElement hook, through which you can play with jQuery very easily. Furthermore, they become extremely useful when you need to build reusable views, such as modals, popovers, date-pickers and autocomplete fields.

Components

A Component is a completely isolated View that has no access to the surrounding context. It’s a great way to build reusable components for your apps. A Twitter Button54, a custom select box55 and those reusable charts56 are all great examples of components. In fact, they’re such a great idea that the W3C is actually working with the Ember team on a custom element57 specification.

Templates

Simply put, a template is the view’s HTML markup. It prints the model data and automatically updates itself when the model changes. Ember.js uses Handlebars58, a lightweight templating engine also maintained by the Ember team. It has the usual templating logic, like if and else, loops and formatting helpers, that kind of stuff. Templates may be precompiled (if you want to cleanly organize them as separate .hbs or .handlebars files) or directly written in <script type="text/x-handlebars"></script> tags in your HTML page. Jump to the section on templates precompiling59 to dig into the subject.

Helpers

Handlebars helpers are functions that modify data before it is rendered on the screen — for example, to format dates better than Mon Jul 29 2013 13:37:39 GMT+0200 (CEST). In your template, the date could be written as {{date}}. Let’s say you have a formatDate helper (which converts dates into something more elegant, like “One month ago” or “29 July 2013”). In this case, you could use it like so: {{formatDate date}}.

Components? Helpers? Views? HELP!

The Ember.js forum has an answer60 and StackOverflow has a response61 that should alleviate your headache.

Let’s Build An App

In this section, we’ll build a real app, a simple interface for managing a group of users (a CRUD62 app). Here’s what we’ll do:

  • look at the architecture we’re aiming for;
  • get you started with the dependencies, files structure, etc.;
  • set up the model with Ember-Data’s FixtureAdapter;
  • see how routes, controllers, views and templates interact with each other;
  • finally, replace the FixtureAdapter with the LSAdapter to persist data in the browser’s local storage.

Sketch Our App

We need a basic view to render a group of users (see 1 below). We need a single-user view to see its data (2). We need to be able to edit and delete a given user’s data (3). Finally, we need a way to create a new user; for this, we will reuse the edit form.

app-sketch63

Ember.js strongly relies on naming conventions. So, if you want the page /foo in your app, you will have the following:

  • a foo template,
  • a FooRoute,
  • a FooController,
  • and a FooView.

Learn more about Ember’s naming conventions64 in the guides.

What You’ll Need to Get Started

You will need:

  • jQuery,
  • Ember.js itself (obviously),
  • Handlebars (i.e. Ember’s templating engine),
  • Ember-Data (i.e. Ember’s data-persistence abstraction layer).
/* /index.html
*/
 …
 <script src="//code.jquery.com/jquery-2.0.3.min.js"></script>
 <script src="//builds.emberjs.com/handlebars-1.0.0.js"></script>
 <script src="//builds.emberjs.com/tags/v1.1.2/ember.js"></script>
 <script src="//builds.emberjs.com/tags/v1.0.0-beta.3/ember-data.js"></script>
 <script>
   // your code
 </script>
</body>
</html>

Ember’s website has a builds section65, where you can find all of the links for Ember.js and Ember-Data. Currently, Handlebars is not there; you will find it on the official Handlebars66 website.

Once we have loaded the required dependencies, we can get started building our app. First, we create a file named app.js, and then we initialize Ember:

/* /app.js
*/
window.App = Ember.Application.create();

Just to be sure everything is OK, you should see Ember’s debugging logs in the browser’s console.

console-log-1

Our Files Directory Structure

There’s not much of a convention on how to organize files and folders. The Ember App Kit67 (a Grunt-based environment to scaffold Ember apps) provides a kind of standard for this because it is maintained by the Ember team. Even simpler, you could put everything in a single app.js file. In the end, it’s really up to you.

For this tutorial, we will simply put controllers in a controllers folder, views in a views folder and so on.

components/
controllers/
helpers/
models/
routes/
templates/
views/
app.js
router.js
store.js

Precompile Templates or Not?

There are two ways to declare templates. The easiest way is to add special script tags to your index.html file.

<script type="text/x-handlebars" id="templatename">
  <div>I'm a template</div>
</script>

Each time you need a template, you’d add another script tag for it. It’s fast and easy but can become a real mess if you have too many templates.

The other way is to create an .hbs (or .handlebars) file for each of your templates. This is called “template precompiling,” and a complete section68 is dedicated to it later in this article.

Our unstyled demo69 uses <script type="text/x-handlebars"> tags, and all of the templates for our enhanced demo70 are stored in .hbs files, which are precompiled with a Grunt13171 task. This way, you can compare the two techniques.

Set Up the Model With Ember-Data’s FixtureAdapter

Ember-Data is a library that lets you retrieve records from a server, hold them in a Store, update them in the browser and, finally, save them back to the server. The Store can be configured with various adapters (for example, the RESTAdapter interacts with a JSON API, and the LSAdapter persists your data in the browser’s local storage). An entire section72 is dedicated to Ember-Data later in this article.

Here, we are going to use the FixtureAdapter. So, let’s instantiate it:

/* /store.js
*/
App.ApplicationAdapter = DS.FixtureAdapter;

In previous versions of Ember, you had to subclass the DS.Store. We don’t need to do that anymore to instantiate adapters.

The FixtureAdapter is a great way to start with Ember.js and Ember-Data. It lets you work with sample data in the development stage. At the end, we will switch to the LocalStorage adapter73 (or LSAdapter).

Let’s define our model. A user would have a name, an email address, a short bio, an avatarUrl and a creationDate.

/* /models/user.js
*/
App.User = DS.Model.extend({
  name         : DS.attr(),
  email        : DS.attr(),
  bio          : DS.attr(),
  avatarUrl    : DS.attr(),
  creationDate : DS.attr()
});

Now, let’s feed our Store with the sample data. Feel free to add as many users as you need:

/* /models/user.js
*/
App.User.FIXTURES = [{
  id: 1,
  name: 'Sponge Bob',
  email: 'bob@sponge.com',
  bio: 'Lorem ispum dolor sit amet in voluptate fugiat nulla pariatur.',
  avatarUrl: 'http://jkneb.github.io/ember-crud/assets/images/avatars/sb.jpg',
  creationDate: 'Mon, 26 Aug 2013 20:23:43 GMT'
}, {
  id: 2,
  name: 'John David',
  email: 'john@david.com',
  bio: 'Lorem ispum dolor sit amet in voluptate fugiat nulla pariatur.',
  avatarUrl: 'http://jkneb.github.io/ember-crud/assets/images/avatars/jk.jpg',
  creationDate: 'Fri, 07 Aug 2013 10:10:10 GMT'
}
…
];

Learn more about models in the documentation74.

Instantiate the Router

Let’s define our Router with the routes we want (based on the diagram we made earlier75).

/* /router.js
*/
App.Router.map(function(){
  this.resource('users', function(){
    this.resource('user', { path:'/:user_id' }, function(){
      this.route('edit');
    });
    this.route('create');
  });
});

This Router will generate exactly this:

URL Route Name Controller Route Template
N/A N/A ApplicationController ApplicationRoute application
/ index IndexController IndexRoute index
N/A users UsersController UsersRoute users
/users users.index UsersIndexController UsersIndexRoute users/index
N/A user UserController UserRoute user
/users/:user_id user.index UserIndexController UserIndexRoute user/index
/users/:user_id/edit user.edit UserEditController UserEditRoute user/edit
/users/create users.create UsersCreateController UsersCreateRoute users/create

The :user_id part is called a dynamic segment because the corresponding user ID will be injected into the URL. So, it will look like /users/3/edit, where 3 is the user with the ID of 3.

You can define either a route or a resource. Keep in mind that a resource is a group of routes and that it allows routes to be nested.

A resource also resets the nested naming convention to the last resource name, which means that, instead of having UsersUserEditRoute, you would have UserEditRoute. In other words, in case this confuses you, if you have a resource nested inside another resource, then your files name would be:

  • UserEditRoute instead of UsersUserEditRoute;
  • UserEditControler instead of UsersUserEditController;
  • UserEditView instead of UsersUserEditView;
  • for templates, user/edit instead of users/user/edit.

Learn more about how to define routes76 in the guides.

The Application Template

Each Ember.js app needs an Application template, with an {{outlet}} tag that holds all other templates.

/* /templates/application.hbs
*/
<div class="main">
  <h1>Hello World</h1>
  {{outlet}}
</div>

If you’ve decided to follow this tutorial without precompiling templates, here’s what your index.html should look like:

/* /index.html
*/
  …
  <script type="text/x-handlebars" id="application">
    <div class="main">
      <h1>Hello World</h1>
      {{outlet}}
    </div>
  </script>

  <script src="dependencies.js"></script>
  <script src="your-app.js"></script>
</body>
</html>

The Users Route

This route deals with our group of users. Remember we saw earlier77, in the definitions, that a route is responsible for querying the model. Well, routes have a model hook through which you can perform AJAX requests (for retrieving your models, if you don’t use Ember-Data) or for querying your Store (if you do use Ember-Data). If you’re interested in retrieving models without Ember-Data, you can jump to the section78 in which I briefly explain how to do it.

Now, let’s create our UsersRoute:

/* /routes/usersRoute.js
*/
App.UsersRoute = Ember.Route.extend({
  model: function(){
    return this.store.find('user');
  }
});

Learn more about how to specify the routes model hook79 in the guides.

If you visit your app at the URL http://localhost/#/users, nothing will happen, because we need a users template. Here it is:

/* /templates/users.hbs
*/
<ul class="users-listing">
  {{#each user in controller}}
    <li>{{user.name}}</li>
  {{else}}
    <li>no users… :-(</li>
  {{/each}}
</ul>

The each loop iterates over the users collection; here, controller equals UsersController. Notice that the {{#each}} loop has an {{else}} statement; so, if the model is empty, then no users… :-( will be printed.

Because we’ve followed Ember’s naming conventions, we can omit the declaration of the UsersController. Ember will guess that we are dealing with a collection because we’ve used the plural of “user.”

Object vs. Array Controller

An ObjectController deals with a single object, and an ArrayController deals with multiple objects (such as a collection). We just saw that, in our case, we don’t need to declare the ArrayController. But for the purpose of this tutorial, let’s declare it, so that we can set some sorting properties on it:

/* /controllers/usersController.js
*/
App.UsersController = Ember.ArrayController.extend({
  sortProperties: ['name'],
  sortAscending: true // false = descending
});

Here, we’ve simply sorted our users alphabetically. Learn more about controllers in the guides80.

Displaying the Number of Users

Let’s use UsersController to create our first computed property81. This will display the number of users, so that we can see changes when adding or deleting users.

In the template, we just need something as simple as this:

/* /templates/users.hbs
*/
…
<div>Users: {{usersCount}}</div>
…

In UsersController, let’s declare the usersCount property — but not like a regular property, because this one will be a function that returns the model’s length.

/* /controllers/usersController.js
*/
App.UsersController = Em.ArrayController.extend({
  …
  usersCount: function(){
    return this.get('model.length');
  }.property('@each')
});

Basically, usersCount takes the .property('@each') method, which tells Ember.js that this function is in fact a property that is watching for any changes to one of the models in the collection (i.e. the users). Later, we will see usersCount incrementing and decrementing as we create and delete users.

Computed Properties

Computed properties are powerful. They let you declare functions as properties. Let’s see how they work.

App.Person = Ember.Object.extend({
  firstName: null,
  lastName: null,

  fullName: function() {
    return this.get('firstName') + ' ' + this.get('lastName');
  }.property('firstName', 'lastName')
});

var ironMan = App.Person.create({
  firstName: "Tony",
  lastName:  "Stark"
});

ironMan.get('fullName') // "Tony Stark"

In this example, the Person object has two static properties, which are firstName and lastName. It also has a fullName computed property, which concatenates a full name by retrieving the value of the two static properties. Note that the .property('firstName', 'lastName') method tells the function to re-execute if either firsName or lastName changes.

Properties (whether static or computed) are retrieved with .get('property') and can be set with .set('property', newValue).

If you find yourself setting multiple properties consecutively, a better way to do it is with one single .setProperties({}), rather than with multiple instances of .set(). So, instead of doing this…

this.set('propertyA', 'valueA');
this.set('propertyB', valueB);
this.set('propertyC', 0);
this.set('propertyD', false);

… you would do this:

this.setProperties({
  'propertyA': 'valueA',
  'propertyB': valueB,
  'propertyC': 0,
  'propertyD': false
});

The documentation has so much more information about how to bind data with computed properties82, observers83 and bindings84.

Redirecting From the Index Page

If you go to the home page of your app (http://localhost/), you might be asking yourself why nothing is happening. That’s because you are viewing the index page, and we don’t have an index template. Let’s add one, then. We’ll call it index.hbs.

Ember.js will notice that you are creating the index template for IndexRoute; so, no need to tell it anything else about the index in the Router. This is called an initial route. Three of them are available: ApplicationRoute, IndexRoute and LoadingRoute. Learn more about them in the guides85.

Now, let’s add a link to the user’s page with the {{#link-to}}…{{/link-to}} block helper. Why a block helper? Because you’re able to write text between the opening and closing tags, as if it were a real custom HTML element.

/* /templates/index.hbs
*/
{{#link-to "users"}} Go to the users page {{/link-to}}

This takes the route’s name that you want to link to as the first argument (the second optional argument is a model). Under the hood, it’s just a regular <a> element, although Ember also handles for us the active class name when reaching the matching route. Those link-to’s are perfect for navigation menus. Learn more about them in the guides86.

Another approach would be to tell IndexRoute to redirect to UsersRoute. Again, pretty easy:

/* /routes/indexRoute.js
*/
App.IndexRoute = Ember.Route.extend({
  redirect: function(){
    this.transitionTo('users');
  }
});

Now, when you visit the home page, you will immediately be redirected to the /#/users URL.

Single User Route

Before getting our hands dirty with building the dynamic segment, we need a way to link to each user from the users template. Let’s use the {{#link-to}} block helper inside the user’s each loop.

/* /templates/users.hbs
*/
…
{{#each user in controller}}
  <li>
    {{#link-to "user" user}}
      {{user.name}}
    {{/link-to}}
  </li>
{{/each}}

The second argument of link-to is the model that will be passed to UserRoute.

OK, let’s get back to our single user template. It looks like this:

/* /templates/user.hbs
*/
<div class="user-profile">
  <img {{bind-attr src="avatarUrl"}} alt="User's avatar" />
  <h2>{{name}}</h2>
  <span>{{email}}</span>
  <p>{{bio}}</p>
  <span>Created {{creationDate}}</span>
</div>

Note that you can’t use <img src="{{avatarUrl}}">, because data inside attributes are bound with the bind-attr helper. For instance, you could do something like <img {{bind-attr height="imgHeight}}"/>, where imgHeight is a computed property in the current controller.

You’ll find all you need to know about binding attributes87 and class names88 in the guides.

So far, so good. But nothing happens when you click on the user’s links, because we told the Router that we want UserRoute to be nested in UsersRoute. So, we need an {{outlet}} in which to render the user template.

/* /templates/users.hbs
*/
…
{{#each user in controller}}
…
{{/each}}

{{outlet}}

An {{outlet}} is like a dynamic placeholder into which other templates can be injected when {{#link-to}} tags are clicked. It allows for views to be nested.

Now, you should be able to view the user template injected in the page when visiting the page at the URL /#/users/1.

Hey, wait a minute! We have declared neither UserRoute nor UserController, but it’s still working! Why is that? Well, UserRoute is the singular of UsersRoute, so Ember has generated the route and the controller for us (in memory). Thank goodness for naming conventions!

For the sake of consistency, let’s declare them anyway, so that we can see how they look:

/* /routes/userRoute.js
*/
App.UserRoute = Ember.Route.extend({
  model: function(params) {
    return this.store.find('user', params.user_id);
  }
});
/* /controllers/userController.js
*/
App.UserController = Ember.ObjectController.extend();

Learn more about dynamic segments89 in the guides.

Edit a User

Moving on to the edit user form nested in the single user, the template looks like this:

/* /templates/user/edit.hbs
*/
<div class="user-edit">
  <label>Choose user avatar</label>
  {{input value=avatarUrl}}

  <label>User name</label>
  {{input value=name}}

  <label>User email</label>
  {{input value=email}}

  <label>User short bio</label>
  {{textarea value=bio}}
</div>

Let’s talk about those {{input}}90 and {{textarea}}91 tags. This form’s goal is to enable us to edit the user’s data, and these custom input tags take the model’s properties as parameters to enable data-binding.

Note that it’s value=model, without the " ". The {{input}} helper is a shorthand for {{Ember.TextField}}. Ember.js has those built-in views92 especially for form elements.

If you visit your app at the URL /#/users/1/edit, nothing will happen, because, again, we need an {{outlet}} to nest the edit template into the single user template.

/* /templates/user.hbs
*/
…
{{outlet}}

Now, the template is correctly injected in the page. But the fields are still empty, because we need to tell the route which model to use.

/* /routes/userEditRoute.js
*/
App.UserEditRoute = Ember.Route.extend({
  model: function(){
    return this.modelFor('user');
  }
});

The modelFor93 method lets you use the model of another route. Here, we’ve told UserEditRoute to get the model of UserRoute. The fields are now correctly populated with the model data. Try to edit them — you will see the changes occur in the parent templates as well!

Our First Action

OK, now we need a button to click that redirects us from UserRoute to UserEditRoute.

/* /templates/user.hbs
*/
<div class="user-profile">
  <button {{action "edit"}}>Edit</button>
  …

We just added a simple button that triggers our first {{action}}. Actions are events that trigger associated methods in their current controller. If no method is found in the controller, then the action bubbles up through routes until it matches something. This is explained well in the guides94.

In other words, if we click on the button, then it will trigger the edit action found in the controller. So, let’s add it to UserController:

/* /controllers/userController.js
*/
App.UserController = Ember.ObjectController.extend({
  actions: {
    edit: function(){
      this.transitionToRoute('user.edit');
    }
  }
});

Actions, whether in controllers or in routes, are stored in an actions hash. But this is not the case for default actions, such as click, doubleClick, mouseLeave and dragStart. The Ember.js website has a complete list95.

Here, basically, our edit action says, “Go to the user.edit route.” That’s pretty much it.

TransitionTo or TransitionToRoute?

On a side note, transitioning from routes is slightly different from transitioning from controllers:

// from a route
this.transitionTo('your.route')
// from a controller
this.transitionToRoute('your.route')

Saving User Modifications

Let’s see how to save modifications after a user’s data has been edited. By saving, we mean persisting the changes. With Ember-Data, this means telling Store to save() the new record of the modified user. The Store will then tell the adapter to perform an AJAX PUT request (if our adapter is the RESTAdapter).

From our application’s point of view, this would be an “OK” button that saves modifications and then transitions to the parent route. Again, we’ll use an {{action}}.

/* /templates/user/edit.hbs
*/
<button {{action "save"}}> ok </button>
/* /controllers/userEditController.js
*/
App.UserEditController = Ember.ObjectController.extend({
  actions: {
    save: function(){
      var user = this.get('model');
      // this will tell Ember-Data to save/persist the new record
      user.save();
      // then transition to the current user
      this.transitionToRoute('user', user);
    }
  }
});

Our edit mode is working well. Now, let’s see how to delete a user.

Delete a User

We can add a delete button beside the edit button in the user template — again, with a delete {{action}}, this time defined in UserController.

/* /templates/user.hbs
*/
<button {{action "delete"}}>Delete</button>
/* /controllers/userController.js
*/
…
actions: {
  delete: function(){
    // this tells Ember-Data to delete the current user
    this.get('model').deleteRecord();
    this.get('model').save();
    // then transition to the users route
    this.transitionToRoute('users');
  }
}

Now, when you click on the “Delete” button, the user is instantly trashed. A bit rough. We should add a confirm state, something like “Are you sure?” with “Yes” and “No” buttons. To do this, we need to change {{action "delete"}} to make it show confirm-box instead of immediately deleting the user. Then, we obviously need to put confirm-box in the user template.

/* /templates/user.hbs
*/
{{#if deleteMode}}
<div class="confirm-box">
  <h4>Really?</h4>
  <button {{action "confirmDelete"}}> yes </button>
  <button {{action "cancelDelete"}}> no </button>
</div>
{{/if}}

We’ve just written our first Handlebars {{if}} statement. It prints div.confirm-box only if the deleteMode property is true. We need to define this deleteMode in the current controller and then change the delete action to make it toggle deleteMode’s value to true or false. Now, our UserController looks like this:

/* /controllers/userController.js
*/
App.UserController = Ember.ObjectController.extend({
  // the deleteMode property is false by default
  deleteMode: false,

  actions: {
    delete: function(){
      // our delete method now only toggles deleteMode's value
      this.toggleProperty('deleteMode');
    },
    cancelDelete: function(){
      // set deleteMode back to false
      this.set('deleteMode', false);
    },
    confirmDelete: function(){
      // this tells Ember-Data to delete the current user
      this.get('model').deleteRecord();
      this.get('model').save();
      // and then go to the users route
      this.transitionToRoute('users');
      // set deleteMode back to false
      this.set('deleteMode', false);
    },
    // the edit method remains the same
    edit: function(){
      this.transitionToRoute('user.edit');
    }
  }
});

Deletion now works perfectly with the “Yes” and “No” buttons. Awesome! Finally, the last thing to build is the create route.

Create a User

To create a user, let’s do something fun: Let’s reuse the edit template, because the create form will be exactly the same as the edit user form. First, we declare the create route, which will return an empty object in its model hook:

/* /routes/usersCreateRoute.js
*/
App.UsersCreateRoute = Ember.Route.extend({
  model: function(){
    // the model for this route is a new empty Ember.Object
    return Em.Object.create({});
  },

  // in this case (the create route), we can reuse the user/edit template
  // associated with the usersCreateController
  renderTemplate: function(){
    this.render('user.edit', {
      controller: 'usersCreate'
    });
  }
});

Note the renderTemplate method; it enables us to associate a particular template with a route. Here, we’re telling UsersCreateRoute to use the user and edit template with UsersCreateController. Learn more about renderTemplate in the guides96.

Now, let’s define another save action, but this time in UsersCreateController. (Remember that an action first tries to match a corresponding method in the current controller.)

/* /controllers/usersCreateController.js
*/
App.UsersCreateController = Ember.ObjectController.extend({
  actions: {
    save: function(){
      // just before saving, we set the creationDate
      this.get('model').set('creationDate', new Date());

      // create a record and save it to the store
      var newUser = this.store.createRecord('user', this.get('model'));
      newUser.save();

      // redirects to the user itself
      this.transitionToRoute('user', newUser);
    }
  }
});

Finally, let’s add the {{#link-to}} helper in the users templates, so that we can access the creation form:

/* /templates/users.hbs
*/
{{#link-to "users.create" class="create-btn"}} Add user {{/link-to}}
…

That’s all there is to creating users!

Format Data With Helpers

We’ve already defined97 what helpers are. Now, let’s see how to create one that will format an ugly date into a nice clean formatted one. The Moment.js9998 library is awesome for this purpose.

Grab Moment.js9998 and load it in the page. Then, we’ll define our first helper:

/* /helpers/helpers.js
*/
Ember.Handlebars.helper('formatDate', function(date){
  return moment(date).fromNow();
});

Modify the user template so that it uses the formatDate helper on the {{creationDate}} property:

/* /templates/user.hbs
*/
…
<span>Created {{formatDate creationDate}}</span>
…

That’s it! You should see the dates nicely formatted: “2 days ago,” “One month ago,” etc.

Format Data With Bound Helpers

In this case, our date is static data because it’s not going to change in the future. But if you have data that needs to be updated (for example, a formatted price), then you would have to use a BoundHelper instead of the regular helper.

/* /helpers/helpers.js
*/
Ember.Handlebars.registerBoundHelper('formatDate', function(date){
  return moment(date).fromNow();
});

A bound helper is able to automatically update itself if the data changes. Learn more about bound helpers in the guides100.

Switch to the LocalStorage Adapter

Our app looks to be working fine, so we are ready to switch to the real thing. We could enable the RESTAdapter, but then we would need a REST server on which we could perform GET, PUT, POST and DELETE requests. Instead, let’s use LSAdapter, a third-party adapter that you can download on GitHub101. Load it in your page (just after Ember-Data), comment out all of the FIXTURE data, and change ApplicationAdapter to DS.LSAdapter:

/* /store.js
*/
App.ApplicationAdapter = DS.LSAdapter;

Now, your users’ data will persist in local storage. That’s all! Seriously, it’s that easy. Just to be sure, open the Developer Tools in your browser and go into the “Resource” panel. In the “Local Storage” tab, you should find an entry for LSAdapter with all of your users’ data.

console-localstorage

Playing With Views

So far, we haven’t declared any views in our simple CRUD, only templates. Why would we care about views? Well, they are powerful for events handling, animations and reusable components.

jQuery and the didInsertElement

How can we use jQuery the way we are used to for Ember.js’ views? Each view and component has a didInsertElement hook, which assures us that the view has indeed been inserted into the DOM. With that, you have secure jQuery access to elements in the page.

App.MyAwesomeComponent = Em.Component.extend({
  didInsertElement: function(){
    // this = the view
    // this.$() = $(the view)
    this.$().on('click', '.child .elem', function(){
      // do stuff with jQuery
    });
  }
});

If you’ve registered jQuery-like events from inside didInsertElement, then you can use willDestroyElement to clean them up after the view has been removed from the DOM, like so:

App.MyAwesomeComponent = Em.Component.extend({
  didInsertElement: function(){
    this.$().on('click', '.child .elem', function(){
      // do stuff with jQuery
    });
  },
  willDestroyElement: function(){
    this.$().off('click');
  }
});

Side Panel Components With className Bindings

The combination of computed property and className binding sounds like a scary technique, but it’s really not that bad. The idea is that we add or remove a CSS class on an element if a property is either true or false. Of course, the CSS class contains a CSS transition.

Suppose we have a hidden div in the DOM. When this div has a class of opened, it slides in. When it has a class of closed, it slides out. A side panel is a perfect example for this, so let’s build one.

Here’s a JS Bin so that you can test the code:

Reusable Ember.js side panels102

Let’s go through each tab in turn:

  • JavaScript tab
    First, we declare our SidePanelComponent with default classNames. Then, classNameBindings is used to test whether isOpen is true or false, so that it returns closed or opened. Finally, component has a toggleSidepanel action that simply toggles the isOpen boolean.
  • HTML tab
    This is the side panel’s markup. Note the {{#side-panel}}…{{/side-panel}} block tags; we can put whatever we want between them, which makes our side panel incredibly reusable. The btn-toggle button calls the toggleSidepanel action located in the component. The {{#if isOpen}} adds some logic by checking the value of the isOpen property.
  • CSS tab
    Here, we are basically putting the side panel off screen. The opened class slides it in, and closed slides it out. The animation is possible because we are listening for translate2D changes (transition:transform .3s ease).

The guides have a lot more examples on how to bind class names from components103 and from inside templates104.

Modals With Layout and Event Bubbling

This technique is way more complicated than the previous one, because a lot of Ember.js features are involved. The idea is to make an event bubble from a view to a route so that we can toggle a property located in a controller somewhere in the app. Also, here we are using a View instead of a Component (remember that, under the hood, a component is an isolated view).

Reusable Ember.js modals105

  • JavaScript tab
    The modalView is the default layout for all of our modals. It has two methods, showModal and hideModal. The showModal method is called with an action that bubbles up, first through controller, then through routes, until it finds a corresponding showModal action. We’ve stored showModal in the highest route possible, the applicationRoute. Its only goal is to set the modalVisible property inside the controller that was passed in the action’s second argument. And yes, creating a property at the same time as we set it is possible.
  • HTML tab
    Each modal has its own template, and we’ve used the convenient {{#view App.ModalView}}…{{/view}} block tags to encapsulate them in modal_layout. The modal’s controllers are not even declared because Ember.js has them in memory. Note that the {{render}} helper takes parameters, which are the template’s name and a generated controller for this template. So, here we are calling a modal01 template and a modal01 controller (auto-generated).
  • CSS tab
    For the purpose of this example, modals need to be present in the DOM. This can feel like a constraint, but the main benefit is the reduced paint cost; otherwise, Ember has to inject and remove them every time we call them. The second benefit is CSS transitions. The shown class applies two transitions: first, the top position (because the modal is off screen by default), then, with a little delay, it transitions the opacity (which also has a reduced106 paint cost107 when transitioning). The hidden class does the same in reverse. Obviously, you can apply a lot of cool transitions108 to your modals if they stay in the DOM.

The guides have a lot more information about events109, event bubbling110, layouts111 and the {{render}}112 helper tag.

What Is Ember-Data?

Ember-Data is in beta as of the time of writing, so please use it with caution.

It is a library that lets you retrieve records from a server, hold them in a store, update them in the browser and, finally, save them back to the server. The store may be configured with various adapters, depending on your back end. Here’s a diagram of Ember-Data’s architecture.

ember-data-sketch

The Store

The store holds data loaded from the server (i.e. records). Routes and controllers can query the store for records. If a given record is called for the first time, then the store tells the adapter to load it over the network. Then, the store caches it for the next time you ask for it.

Adapters

The application queries the store, and the adapter queries the back end. Each adapter is made for a particular back end. For example, the RESTAdapter deals with JSON APIs, and LSAdapter deals with local storage.

The idea behind Ember-Data is that, if you have to change the back end, then you simply plug another adapter, without having to touch a single line of your application’s code.

  • FixtureAdapter
    FixtureAdapter is perfect for testing Ember and Ember-Data. Fixtures are just sample data that you can work with until your app reaches the production phase. We went over how to configure it in an earlier part of this article113.
  • RESTAdapter
    RESTAdapter is the default adapter in Ember-Data. It lets you perform GET, PUT, POST and DELETE requests over a REST API. It also requires some specific JSON conventions114 in return. Enabling RESTAdapter looks like this:

    App.ApplicationAdapter = DS.RESTAdapter.extend({
      host: 'https://your.api.com'
    });
    

    There’s a lot more to discover about RESTAdapter in the guides115.

  • Custom adapter
    You could use something other than the two default adapters (FixtureAdapter and RESTAdapter). A bunch of them are on GitHub116. For instance, there’s the LocalStorage Adapter117, which is demonstrated in the guides’ sample Todos118 app and is also the one I use in the demo119.

What About Not Using Ember-Data?

In this article, I’ve chosen to cover Ember-Data because it’s almost stable and is probably one of the coolest thing happening these days in the JavaScript world. But perhaps you’re wondering whether getting rid of it is possible. The answer is yes! In fact, using Ember.js without Ember-Data is pretty easy.

There are two ways to do it.

You could use another library for your model’s retrieval and persistence. Ember-Model120Ember-Resource121Ember-Restless122 and the recent EPF123 are good alternatives. EmberWatch has written a great little article that sums up “Alternatives to Ember Data124.”

The other way would be to not rely on a library, in which case you would have to implement methods to retrieve models with AJAX calls. “Ember Without Ember Data125,” by Robin Ward (the guy behind Discourse126), is a great read. “Getting Into Ember.js, Part 3127” by Rey Bango on Nettuts+ deals specifically with models.

For instance, here’s a static method with reopenClass on a model:

/* /models/user.js
*/
// our own findStuff method inside the User model
App.User.reopenClass({
  findStuff: function(){
    // use regular AJAX / Promises calls
    return $.getJSON("http://your.api.com/api").then(function(response) {
      var users = [];
      // creates new Ember objects and store them into the users Array
      response.users.forEach(function(user){
        users.push( App.User.create(user) );
      });
      // finally returns the array full of Ember Objects
      return users;
    });
  }
});

You would use your findStuff method in your routes’ model hook:

/* /routes/usersRoute.js
*/
App.UsersRoute = Em.Route.extend({
  model: function(){
    return App.User.findStuff();
  }
});

What Is Handlebars Template Precompiling?

Basically, template precompiling entails grabbing all Handlebars templates, transposing them into JavaScript strings, and then storing them in Ember.TEMPLATES. It also entails an additional JavaScript file to load in your page, which will contain the JavaScript-compiled versions of all of your Handlebars templates.

For very simple apps, precompiling can be avoided. But if you have too many <script type="text/x-handlebars"> templates in your main HTML file, then precompiling will help to organize your code.

Furthermore, precompiling your templates will enable you to use the runtime version of Handlebars, which is lighter than the regular one. You can find both the runtime and standard versions on the Handlebars website128.

Template Naming Conventions

Partials129 have to begin with a _. So, you will have to declare a _yourpartial.hbs file or, if you don’t precompile your templates, a <script type="text/x-handlebars" id="_yourpartial"> tag.

Components130 have to begin with components/. So, you will have to store them in a components/ folder or, if you don’t precompile templates, declare a <script type="text/x-handlebars" id="components/your-component"> tag. Component names are hyphenated.

Otherwise, views have a templateName property in which you can specify which template to associate with the view. Take this declaration of a template:

<script type="text/x-handlebars" id="folder/some-template">
  Some template
</script>

You can associate it with a particular view:

App.SomeView = Em.View.extend({
  templateName: 'folder/some-template'
});

Precompiling With Grunt

If you use Grunt13171, then you probably use it for other building-related tasks (concatenation, compression, that kind of stuff), in which case you should be familiar with the package.json file, which comes with Node.js and Node Packaged Modules. I’ll assume you are already familiar with Grunt.

As of the time of writing, two plugins are available for Grunt to transpose your .hbs files to a templates.js file: grunt-ember-handlebars132 and grunt-ember-templates133. The latter seems a bit more up to date than the former.

I’ve made a Gist for each of them, in case you need help with configuration:

Once it’s configured, you should be able to run grunt in a command-line editor, which should produce the templates.js file. Load it into index.html (after ember.js), and then go into the browser’s console and type Em.TEMPLATES. You should see a hash containing all of the compiled templates.

Be aware that Ember.js doesn’t need the template file’s complete path, nor the file’s extension. In other words, the template’s name should be users/create, not /assets/js/templates/users/create.hbs.

Both plugins have options to handle this. Simply refer to the respective guide, or look at the Gists linked to above. You should end up with something like this:

console-templates

And this is exactly what we need to make everything work as intended. It’s all you need to know about precompiling with Grunt.

Precompiling With Rails

Precompiling with Rails is surely the easiest way to do it. The Ember-Rails gem136 handles pretty much everything for you. It almost works out of the box. Carefully follow the installation instructions in the readme file on GitHub, and you should be all good. Right now, in my humble opinion, Rails has the best Ember and Handlebars integration available.

Tools, Tips And Resources

Chrome Ember Extension

Ember Extension137 is a very convenient Chrome extension. Once installed, an “Ember” tab will appear near the “Console” tab. Then, you can navigate through controllers, routes and views. And the “Data” tab will greatly help you to explore your records if you are using Ember-Data.

console-ember-extension138
Exploring your app’s objects has never been so easy.

Ember App Kit

Maintained by the Ember team, the Ember App Kit139 lets you easily scaffold Ember.js apps. It contains Grunt140 for compiling assets, JSHint141, QUnit142, the Kharma143 test runner, Bower144 and ES6 Modules145 support.

Ember Tools

This GitHub project, Ember Tools146, is a basic command-line interface for creating and scaffolding Ember apps. Take a minute to watch the animated GIF in the readme file, and you’ll see why it’s so cool.

Development and Minified Version

Always use the development build when developing because it contains a lot of comments, a unit-testing package and a ton of helpful error messages, all of which has been removed in the minified build. Find links to both in the builds section of the Ember.js website147.

Debugging Tips

Ember.js usually gives you cool human-readable errors in the browser’s console (remember to use the development version). Sometimes, though, figuring out what’s going on is tricky. Some convenient methods are {{log something}} and {{controller}}, which helpfully prints the current controller for the template to which you’ve added this helper.

Or you could log each Router transition like so:

window.App = Ember.Application.create({
  LOG_TRANSITIONS: true
});

The guides have an exhaustive list148 of these handy little methods.

Properly Comment Your Handlebars

This one can be frustrating. Never ever comment a Handlebars tag with a regular HTML comment. If you do, you’ll completely break the app, without getting a clue about what’s happening.

// never do this
<!-- {{foo}} -->

// instead do this
{{!foo}}

Conclusion

I hope this long article has given you a better understanding of this awesome framework. But the truth is, we’ve only scratched the surface. There’s so much more to cover. For instance, we have the router and its asynchronous nature, which resolves model requests with promises (so that you can easily implement loading spinners). There is also the object model, with its class and instances inheritance, mixins, observers, filters, macros, collectionViews, components, dependencies managed between controllers, and testing package. And so much more!

Obviously, I couldn’t cover everything. Fortunately, the guides will take you through all of these topics very well.

Happy Ember.js coding, folks!

Resources

Acknowledgments

Huge thanks to Mathieu Breton154 and Philippe Castelli155, who both taught me everything they know about Ember.js while I was learning it. Also, a big thank you to Tom Dale156, who helped me to revise this very long article.

(al, il)

Footnotes

  1. 1 http://emberjs.com/guides/
  2. 2 http://emberjs.com/
  3. 3 http://jkneb.github.io/ember-crud/unstyled
  4. 4 https://github.com/jkneb/ember-crud
  5. 5 http://jkneb.github.io/ember-crud
  6. 6 #main_concepts
  7. 7 #lets_build_an_app
  8. 8 #sketch_our_app
  9. 9 #what_you_need_to_get_started
  10. 10 #set_up_our_files_structure
  11. 11 #precompile_templates_or_not
  12. 12 #set_up_the_model_with_ember_data_fixtureadapter
  13. 13 #instantiate_the_router
  14. 14 #the_application_template
  15. 15 #the_users_route
  16. 16 #object_vs_array_controller
  17. 17 #displaying_the_number_of_users
  18. 18 #computed_properties
  19. 19 #redirecting_from_the_index_page
  20. 20 #single_user_route
  21. 21 #edit_a_user
  22. 22 #our_first_action
  23. 23 #transitionTo_or_transitionToRoute
  24. 24 #saving_user_modifications
  25. 25 #delete_a_user
  26. 26 #create_a_user
  27. 27 #format_data_with_helpers
  28. 28 #format_data_with_bound_helpers
  29. 29 #switch_to_the_localstorage_adapter
  30. 30 #playing_with_views
  31. 31 #jquery_and_the_didinsertelement
  32. 32 #side_panel_components_with_classname_bindings
  33. 33 #modals_with_layout_and_event_bubbling
  34. 34 #what_is_ember_data
  35. 35 #the_store
  36. 36 #adapters
  37. 37 #what_about_not_using_emberdata
  38. 38 #what_is_handlebars_template_precompiling
  39. 39 #templates_naming_conventions
  40. 40 #precompiling_with_grunt
  41. 41 #precompiling_with_rails
  42. 42 #conclusion
  43. 43 #tools_tips_resources
  44. 44 #acknowledgments
  45. 45 http://www.smashingmagazine.com/wp-content/uploads/2013/09/ember-sketch.png
  46. 46 http://emberjs.com/guides/models
  47. 47 http://emberjs.com/guides/routing
  48. 48 http://emberjs.com/guides/controllers
  49. 49 http://emberjs.com/guides/views
  50. 50 http://emberjs.com/guides/components/
  51. 51 http://emberjs.com/guides/templates/handlebars-basics
  52. 52 http://emberjs.com/guides/templates/writing-helpers
  53. 53 https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi
  54. 54 http://jsbin.com/OMOgUzo/1/edit?html,js,output
  55. 55 http://pixelhandler.com/posts/create-a-custom-select-box-using-ember-component
  56. 56 http://jsbin.com/odosoy/132/edit?html,js,output
  57. 57 https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/custom/index.html
  58. 58 http://handlebarsjs.com
  59. 59 #what_is_handlebars_template_precompiling
  60. 60 http://discuss.emberjs.com/t/whats-the-difference-between-ember-helpers-components-and-views/2201/2
  61. 61 http://stackoverflow.com/questions/18593424/views-vs-components-in-ember-js
  62. 62 http://en.wikipedia.org/wiki/Create,_read,_update_and_delete
  63. 63 http://www.smashingmagazine.com/wp-content/uploads/2013/09/app-sketch.png
  64. 64 http://emberjs.com/guides/concepts/naming-conventions
  65. 65 http://emberjs.com/builds
  66. 66 http://handlebarsjs.com
  67. 67 https://github.com/stefanpenner/ember-app-kit
  68. 68 #what_is_handlebars_template_precompiling
  69. 69 http://jkneb.github.io/ember-crud/unstyled
  70. 70 http://jkneb.github.io/ember-crud
  71. 71 http://gruntjs.com
  72. 72 #what_is_ember_data
  73. 73 https://github.com/rpflorence/ember-localstorage-adapter
  74. 74 http://emberjs.com/guides/models/
  75. 75 #sketch_our_app
  76. 76 http://emberjs.com/guides/routing/defining-your-routes
  77. 77 #router
  78. 78 #what_about_not_using_emberdata
  79. 79 http://emberjs.com/guides/routing/specifying-a-routes-model
  80. 80 http://emberjs.com/guides/controllers/
  81. 81 http://emberjs.com/guides/object-model/computed-properties/
  82. 82 http://emberjs.com/guides/object-model/computed-properties/
  83. 83 http://emberjs.com/guides/object-model/observers
  84. 84 http://emberjs.com/guides/object-model/bindings
  85. 85 http://emberjs.com/guides/routing/defining-your-routes/#toc_initial-routes
  86. 86 http://emberjs.com/guides/templates/links
  87. 87 http://emberjs.com/guides/templates/binding-element-attributes/
  88. 88 http://emberjs.com/guides/templates/binding-element-class-names/
  89. 89 http://emberjs.com/guides/routing/specifying-a-routes-model/#toc_dynamic-models
  90. 90 http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_input
  91. 91 http://emberjs.com/api/classes/Ember.Handlebars.helpers.html#method_textarea
  92. 92 http://emberjs.com/guides/views/built-in-views
  93. 93 http://emberjs.com/api/classes/Ember.Route.html#method_modelFor
  94. 94 http://emberjs.com/guides/templates/actions/#toc_action-bubbling
  95. 95 http://emberjs.com/api/classes/Ember.View.html#toc_event-names
  96. 96 http://emberjs.com/guides/routing/rendering-a-template/
  97. 97 #helpers
  98. 98 http://momentjs.com
  99. 99 http://momentjs.com
  100. 100 http://emberjs.com/api/classes/Ember.Handlebars.html#method_registerBoundHelper
  101. 101 https://github.com/rpflorence/ember-localstorage-adapter/blob/master/localstorage_adapter.js
  102. 102 http://emberjs.jsbin.com/utimiZI/12/embed?js,output
  103. 103 http://emberjs.com/guides/components/customizing-a-components-element
  104. 104 http://emberjs.com/guides/templates/binding-element-class-names
  105. 105 http://emberjs.jsbin.com/aKUWUF/8/embed?js,output
  106. 106 https://speakerdeck.com/ariya/fluid-user-interface-with-hardware-acceleration?slide=36
  107. 107 http://css-tricks.com/w3conf-ariya-hidayat-fluid-user-interface-with-hardware-acceleration
  108. 108 http://tympanus.net/Development/ModalWindowEffects
  109. 109 http://emberjs.com/guides/views/handling-events
  110. 110 http://emberjs.com/guides/understanding-ember/the-view-layer/#toc_event-bubbling
  111. 111 http://emberjs.com/guides/views/adding-layouts-to-views
  112. 112 http://emberjs.com/guides/templates/rendering-with-helpers/#toc_the-code-render-code-helper
  113. 113 #set_up_the_model_with_ember_data_fixtureadapter
  114. 114 http://emberjs.com/guides/models/the-rest-adapter/#toc_json-conventions
  115. 115 http://emberjs.com/guides/models/the-rest-adapter
  116. 116 https://github.com/search?q=ember+adapter&ref=reposearch
  117. 117 https://github.com/rpflorence/ember-localstorage-adapter
  118. 118 http://emberjs.com/guides/getting-started/using-other-adapters
  119. 119 http://jkneb.github.io/ember-crud
  120. 120 https://github.com/ebryn/ember-model
  121. 121 https://github.com/zendesk/ember-resource
  122. 122 https://github.com/endlessinc/ember-restless
  123. 123 http://epf.io/
  124. 124 http://blog.emberwatch.com/2013/06/19/alternatives-ember-data.html
  125. 125 http://eviltrout.com/2013/03/23/ember-without-data.html
  126. 126 http://www.discourse.org/
  127. 127 http://net.tutsplus.com/tutorials/javascript-ajax/getting-into-ember-js-part-3
  128. 128 http://handlebarsjs.com/
  129. 129 http://emberjs.com/guides/templates/rendering-with-helpers/
  130. 130 http://emberjs.com/guides/components
  131. 131 http://gruntjs.com
  132. 132 https://github.com/yaymukund/grunt-ember-handlebars
  133. 133 https://github.com/dgeb/grunt-ember-templates
  134. 134 https://gist.github.com/jkneb/6072299
  135. 135 https://gist.github.com/jkneb/6599001
  136. 136 https://github.com/emberjs/ember-rails
  137. 137 https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi
  138. 138 http://www.smashingmagazine.com/wp-content/uploads/2013/08/console-ember-extension1.png
  139. 139 http://iamstef.net/ember-app-kit/
  140. 140 http://gruntjs.com/
  141. 141 http://www.jshint.com/
  142. 142 http://qunitjs.com/
  143. 143 http://karma-runner.github.io/0.10/index.html
  144. 144 http://bower.io/
  145. 145 http://wiki.ecmascript.org/doku.php?id=harmony:modules
  146. 146 https://github.com/rpflorence/ember-tools
  147. 147 http://emberjs.com/builds
  148. 148 http://emberjs.com/guides/understanding-ember/debugging
  149. 149 http://emberjs.com/guides/
  150. 150 http://emberjs.com/guides/cookbook/
  151. 151 http://emberwatch.com
  152. 152 http://emberweekly.com/issues.html
  153. 153 http://discuss.emberjs.com
  154. 154 https://twitter.com/MatBreton
  155. 155 https://twitter.com/ficastelli
  156. 156 https://twitter.com/tomdale

↑ Back to top Tweet itShare on Facebook

Julien is a self-taught interface designer and a front-end developer living in Paris where he is freelancing for some of the biggest French companies. When he is not bringing his 10 years of Web experience to his clients, you'll probably find him writing on his blog or trying to create cool CSS stuff on Codepen.

Advertisement
  1. 1

    Good amount of work/effort put into it. Haven’t been bale to read the entire entry but hats off for putting in this much effort.
    Truly appreciate it.. :)

    0
  2. 52

    Wow, one of the best introduction/guide on Ember.js I have ever read, too good for all level of JavaScript developers.

    -3
  3. 103

    Hi I tried to implement the above example in grails using ember but I’m getting Error while processing route: users.index

    -3
  4. 154

    Something must have changed with the Fixture Adaptor since this article was written. Whenever I tried to load /users route Ember would try and make an Ajax request to /users. I replaced:

    App.ApplicationAdapter = DS.FixtureAdapter;

    with:

    App.Store = DS.Store.extend({
    adapter: DS.FixtureAdapter
    });

    and voila. No more ajax requests and the fixtures were correctly loaded.

    -1
  5. 205

    Boss Two Words, “Well done”.

    You have taken readers from very start but one thing that lacks in this article is that you have taken it with pre compiling concept. When a new one on ember comes on your article he should be taught in quite simple way.

    But else It is really a piece that can be referred to others to understand ember.

    Thanks a lot for this.

    0
  6. 256

    Having spent the last 3 years writing a very complex app in Ember, I’d have to say that this is one of the best intros to Ember I have come across. However, I have one beef with it: for a new user to Ember, requiring jQuery in the how-to is a bad practice to encourage.

    When I first started using Ember, I used jQuery to help augment what I needed done with my views. The more I learned Ember, the less jQuery I used, to the point where I ripped jQuery almost entirely out of Ember. Also, jQuery is going to become an optional library within the next major release of Ember. I discourage any new users to Ember from touching jQuery. Ember can do pretty much anything you want.

    The example you provided for components, I would say *maybe* that is an edge case. Adding click events to child elements can easily be done with actions inside the component (or view or controller). I have yet to find myself in a predicament within the Ember framework where I needed to bind an event using jQuery, even with components.

    0

Leave a Comment

Yay! You've decided to leave a comment. That's fantastic! Please keep in mind that comments are moderated and rel="nofollow" is in use. So, please do not use a spammy keyword or a domain as your name, or else it will be deleted. Let's have a personal and meaningful conversation instead. Thanks for dropping by!

↑ Back to top