Continuing with our exploration of the Javascript MVC landscape, today is the turn of Ember.js. "A framework for creating ambitious web applications" is how it describes itself on its home page, read on to find out if there is any merit to that claim.

 

A word of warning

 

At the time of this writing, the production version of ember.js contains 995 KB of javascript code (238 KB minified) which is clearly not what you would call lightweight and, in fact, may be too much for certain devices to load over the network. If you have to support Windows Mobile devices, be sure to test on real hardware before you commit to using Ember.js for an application. Including the .js file in a native RhoMobile 4 application tends to work well with the RhoElements webkit, but loading it on demand from a server can be problematic.

 

Basic concepts

 

  • Ember.js is a very opinionated framework: it dictates a structure for your code and you are expected to follow it, not fight it.
  • Your application will be structured around the concept of the URL: The URL you are currently in represents the state of the application and is the entry point.
  • Once you browse to a particular URL
    • the Router parses it and decides which Route to activate
    • the Route loads the appropriate Model for the View
    • the View renders its Template
    • the Controller handles actions that the user performs in the View
  • The name of each part of your application must follow the Ember.js naming convention: for the URL "/about", you will have App.AboutRoute and App.AboutController as well as an "about" template.

 

 

With that knowledge on hand, let's learn the absolute minimum that will let us start having fun.

 

 

Views / Templates

 

Ember.js by default uses the Handlebars templating library, which lets you create two-way bindings between your views and their underlying model. Whenever your model changes, the view is updated automatically without you having to write or manage any code. Here is an example:

 

<p>{{input type="text" value=username}}</p>
<p>Hello, {{username}}</p>



 

As you type in the input field, the greeting will update itself.

 

When you start building real applications, you will have different views and each view will have its own template. The easiest way to get started is by keeping your templates in <script> tags:

 

<script type="text/x-handlebars" id="welcome">
  Hello, {{username}}!
</script>

<script type="text/x-handlebars" id="goodbye">
  Goodbye, {{username}}
</script>



 

The type attribute tells the browser that this is not javascript, so it should not try to execute what is inside the tag, and the id is what will link a particular template with its corresponding route. By default, Ember.js looks for a script without a name (the application template) and uses it as a decoration for the rest of the views:

 

<script type="text/x-handlebars">
  <div id="header">
  Welcome to the Ember.js demo application
  </div>
  <div id="content">
  {{outlet}}
  </div>
</script>



 

See that {{outlet}} ? That's where the current view will be rendered.

 

Apart from using {{ }} to create bindings between the user interface and your model, there are other interesting features in handlebars:

 

Conditionals

 

{{#if condition}}
  Condition is true
{{else}}
  Condition is false
{{/if}}



 

Iteration

 

{{#each currentProduct in products}}
  {{currentProduct.name}}
{{/each}}



 

Tag attribute bindings

 

<img {{bind-attr src=userImage}}>



 

Creating links to routes

 

{{#link-to "product.detail" currentProduct}} Details for product {{currentProduct.name}} {{/link-to}}



 

Actions

 

<button {{action 'buyProduct'}}>Buy this product</button>



 

The above will link the <button> tag in the view to the "buyProduct" action in the controller. We will talk about controllers later in this article.

 

Routing

 

We mentioned earlier that Ember.js considers the URL as the center of your application and will parse it to figure out which part of the application should be activated. A side effect of that is that you must tell Ember.js which URLs your application will react to; there are two concepts that the Router in Ember.js knows about: routes and resources.

 

A route is just what you would expect: you tell the router that the application knows about a particular URL and Ember.js takes care of the rest. Creating a top-level route is straightforward:

 

App.Router.map(function() {
  this.route("welcome");
});



 

This creates a route named "welcome" that will load a template with the same name and be handled by App.WelcomeRoute and App.WelcomeController whenever you visit /welcome. If you do not define WelcomeRoute or WelcomeController in your code, Ember.js will generate them for you automatically, so you can create routes with simple views very easily.

 

The other construct is what Ember.js calls a "resource", which is nothing more than a route that can have sub-routes. Let's say we want to have a "/product" route that shows a list of products and we also want to show the details of a particular product at "/product/1234".

 

App.Router.map(function() {
  this.resource("product", function() {
  this.route("details", {path: "/:product_id"});
  });
});



 

That code creates two routes, named "product" and "product.details" and Ember.js expects to find them as ProductRoute and ProductDetailRoute:

 

App.products = ["product 1", "product 2"];

App.ProductRoute = Ember.Route.extend({
  model: function() {
  return App.products;
  }
});

App.ProductDetailRoute = Ember.Route.extend({
  model: function(params) {
  return App.products[params.product_id];
  }
});



 

The relationship between a resource and its routes is deeper than merely one being contained in the other in the URL: it is expected that the template of the parent route will contain an {{outlet}} so that the child route is rendered within it. That makes it very easy to build master-detail pages of arbitrary depth and reflects the hierarchical nature of the URL:

 

<script type="text/x-handlebars" id="product">
  <div id="productlist">
  {{#each product}}
  Product {{product}}
  {{/each}}
  </div>


  <div id="productdetails">
  {{outlet}}
  </div>
</script>


<script type="text/x-handlebars" id="product/details">
  Details for product {{this}}:
  - foo
  - bar
  - baz
</script>



 

Controllers

 

Controllers are where you you write the code that deals with actions from the user:

 

App.ProductDetailController = Ember.Controller.extend({
  actions: {
      buyProduct: function() {
            alert('You have bought the product. Enjoy!');
      }
  }
});



 

Think of controllers as decorators for your model that add view-related functionality.

 

 

Models

 

 

Models are where you store data that must persist across application runs. Any javascript object can be an Ember.js model, although you will typically extend from Ember.Object or, if you are using Ember-Data, from Ember.Model:

 

App.Product = Ember.Object.extend({
  name: null,
  sku: null
});


App.ProductRoute = Ember.Route.extend({
  model: function() {
      return [
            App.Product.create({
            name: "Product 1",
            sku: "11111"
      }),
      App.Product.create({
            name: "Product 2",
            sku: "22222"
      })
      ];
  }
});



 

 

With those concepts in place, you are ready to see the code for a very simple warehouse application that lets you

 

  • see which products are available
  • create new products
  • delete products

 

in approximately 65 lines of Javascript code.

 

  • Create a new RhoMobile 4 application
  • Download ember.js and handlebars-1.0.0.js and put them into /public/js
  • Edit rhoconfig.txt and set start_path = '/public/index.html'
  • Create /public/index.html with the following code:

 

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">

<head>
    <title>Sample</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"/>
        <script src="/public/api/rhoapi-modules.js" type="text/javascript"></script>
        <script src="/public/jquery/jquery-1.9.1.min.js" type="text/javascript"></script>
        <script src="/public/js/handlebars-1.0.0.js" type="text/javascript"></script>
        <script src="/public/js/ember.js" type="text/javascript"></script>
        <script src="/public/js/application.js" type="text/javascript"></script>
</head>
<body>
    <script type="text/x-handlebars">
    {{outlet}}
    </script>

    <script type="text/x-handlebars" id="product">
        Product list
        <button {{action "addProduct"}}>Add product</button>

        <ul>
            {{#each product in controller}}
            <li>{{#link-to "edit" product}}{{product.name}}{{/link-to}}</li>
            {{/each}}
        </ul>

    {{outlet}}
    </script>

    <script type="text/x-handlebars" id="edit">
        <p>{{input type="text" value=name}}</p>
        <p>{{input type="text" value=sku}}</p>
        <button {{action "done"}}>Done</button>
        <button {{action "deleteProduct"}}>Delete</button>
    </script>

</body>
</html>



 

Even if this is your first contact with Ember.js, you will no doubt find the code above to be very self-explanatory

 

  • Create /public/js/application.js with the following code:

 

App = Ember.Application.create();

// Define which routes our application knows about
App.Router.map(function() {
  this.route("product", {path: "/"});
  this.route("edit", {path: "edit/:product_id"});
});

// This is the Product model object in our application.
// It is not necessary to specify the properties here, but making them
// explicit makes the code easier to read and reference
App.Product = Ember.Object.extend({
  id: null,
  name: null,
  sku: null
});

// This application stores data in memory only (no server and no database)
// Here is where we keep track of everything
App.appdata = Ember.Object.create({
  lastId: 0,
  products: []
});

App.ProductRoute = Ember.Route.extend({
  model: function() {
  return App.get("appdata.products");
  }
});

App.ProductController = Ember.ArrayController.extend({
  actions: {
      addProduct: function() {
            var nextId = App.appdata.incrementProperty("lastId");

            var newProduct = App.Product.create({
                id: nextId,
                name: "Product "+nextId,
                sku: "1111"+nextId
            });

            App.appdata.products.pushObject(newProduct);
            this.transitionToRoute("edit", newProduct);
    }
  }
});

App.EditRoute = Ember.Route.extend({
  model: function(params) {
      return App.appdata.products.findBy("id", parseInt(params.product_id,10));
  }
});


App.EditController = Ember.ObjectController.extend({
  actions: {
      done: function() {
            this.transitionToRoute("product");
      },
      deleteProduct: function() {
            App.appdata.set("products", App.appdata.products.without(this.get("model")));
            this.transitionToRoute("product");
      }
  }
});



 

Again, even if you are not yet familiar with how Ember.js works, this code is very simple.

 

That's it. Now run your application and you will see that, for the small amount of code we have written, we get a very featureful and very structured application that is easy to extend with new routes and views. Note in particular the complete absence of any code to update HTML elements, everything is taken care of automatically. Note also that there is no "save" button, and the "done" action in EditController is merely a redirect: changes in the view are also being applied automatically to the model. You can see these changes in real time by updating the "edit" template as follows:

 

    <script type="text/x-handlebars" id="edit">
        <p>{{input type="text" value=name}}</p>
        <p>{{input type="text" value=sku}}</p>
        <button {{action "done"}}>Done</button>
        <button {{action "deleteProduct"}}>Delete</button>
        <p>{{name}}</p>
        <p>{{sku}}</p>
    </script>



 

If you run the application now, you will see that as soon as you change the contents of the input field, the values below are updated in real time.

 

The model in this application, however, is not being saved to persistent storage, which means that the next time you open the application, the data will be gone. In a real application, you will want to store your data either in a server (possibly via AJAX calls) or with Rhom (or even both with Rhom and a server with RhoConnect). Ember.js has an optional library called Ember Data that helps you treat different data sources with a single, common API. If your server provides a JSON REST API, Ember Data's RestAdapter will help you connect your mobile app and if you want to take advantage of on-device storage with Rhom, there is a rhomobile-ember sample where you can see a more advanced version of this application together with a Rhom adapter for Ember Data. See this link to know how to enable it.

 

There is a lot more to Ember.js than what we can cover here and once you have gone through this tutorial, you will want to browse through the official guides, which are an excellent source of information.

 

Speak up!

Did you find this post useful? What else would you like to see covered? Leave a comment below, we read all messages and try to accomodate requests whenever possible.