Skip to main content

Ember.js Core Features

Posted by manning_pubs on March 18, 2013 at 8:30 AM PDT



Ember.js Core Features

by Joachim Haagen Skeie, author of Ember.js in Action

Ember.js makes it possible to write large web applications without having to constantly consider how our data will get from A to B, how your web elements will be updated in a clean and efficient manner, and being able to easily integrate with any third-party JavaScript framework of your choice. This article, based on chapter 2 of Ember.js in Action, discusses the core features of Ember.js.

The Ember.js framework is based on a couple of related features that hold the whole framework together and that form the basis of all of the other features offered in Ember.js. Knowing how these core features—bindings, computed properties, and observers—work is essential for any developer using Ember.js.

Bindings

One of the tasks that you are most likely to encounter in a repetitive manner when writing web applications is the task of requesting data from a backend resource, parsing the response in order to update your controllers, and then making sure that your views are up to date whenever your data changes.

Then, as the user works with the data, you need to make sure that you update both your backend and your view in a way that ensures that what is persisted at the backend actually matches what the user sees on their screen.

You have probably written code to support the above use case hundreds, if not thousands of times, yet most web applications are lacking a site-wide infrastructure to handle these interactions in a clean and consistent manner, leaving it up to the developer to reinvent the proverbial wheel each and every time, for each of the layers in the application. A common implementation might look like figure 1.

Figure 1 A Common Data synchronization implementation

The above model assumes that you have though about how you want to structure you application, and that you have implemented an MVC-like structure in you application. The Ember.js MVC model is slightly different from the MVC models you have become accustomed to when writing web applications, but, fear not, the Ember.js MVC model is thoroughly explained in detail in Ember.js in Action. The problem with the model shown in figure 1 is that it leaves it up to the developer to implement a sane structure that ensures that the data persisted to the server is, in fact, the data presented to the user. In addition to having to implement custom code for each of the 6 steps listed above (steps 3 through 8), there also are many edge cases to consider, including:

  • What if the server is unable to persist your data?
  • What if the server has had updates between when you loaded the application data (in step 2) and when you persisted it (in step 5)?
  • What if the user has created new data and the server needs to generate a unique identifier for that data (step 3 and 5)?
  • If the server changes any of the data persisted, how would the client be notified in order to reflect these?
  • What happens if the user changes the data that has been sent to the server, but a response hasn't arrived yet?
  • What happens if the model is updated without notifying the controller of the change?

These are just a few select examples of the decisions that you would need to make for each and every part of your application. You might have done your homework and implemented a common solution across your application. In that case, good for you; now you know the difficulties involved in synchronizing your application data between views and controllers, between models, and between the client and the server. This is a major area where Ember.js will help you out of the box with its complete and robust binding implementation. Ember.js also offers a complete persistence layer called Ember Data.

In their simplest form, bindings are ways to tell your application, "Whenever variable A changes, make sure to keep variable B up to date." Bindings in Ember.js can be either one way or two way. Two-way bindings works just the same as one-way bindings, but it keeps two variables in sync regardless of which of the variables change. The most common binding you will use throughout Ember.js will most likely be two-way bindings. There are two reasons for this: first of all, two-way bindings are the default binding structure in Ember.js, and, second, it's also the type of binding you are most likely to need when writing a client application.

There are two ways to declare a binding. You can use the Ember.Binding.twoWay or Ember.Binding.oneWay function calls to create one explicitly, and you need to do this in order to create one-way bindings, but most likely you will use the Binding suffix-keyword in your objects property declarations. We use bindings in order to synchronize data between the applications controllers. Consider the code shown in listing 1.

Listing 1 Synchronizing two variables using Bindings

Notes.NotesController = Ember.ArrayController.extend({
    content: [],

    selectedNote: null
});

Notes.SelectedNoteController = Ember.ObjectController.extend({
    contentBinding: 'notesController.selectedNote',

    notesController: null

});

In addition to bindings and automatic synchronizing between the controllers, there are a couple of good design decisions happening in the code snippet above that Ember.js implicitly makes for you due to its binding mechanisms. To start things off, we have effectively split our application's data into two controllers that both have a clean and precise concern. The NotesController is responsible for keeping track of each of the Notes available to our application, while the SelectedNoteController is responsible for the bookkeeping required for a single note—the one that the user has selected at any given time.

This whole process is made possible with the code annotated at #B and #C above. Notice that the property declaration of the content property at annotation #B ends with the suffix Binding. This tells Ember.js to create a two-way binding between the SelectedNoteController.content property and to the property that 'notesController.selectedNote' represents in this context. We could have simplified this somewhat and specified an exact path to bind to (Notes.notesController.selectedNote), but this would tightly couple our controllers together making them harder to test and harder to refactor later. Adding the notesController property at #C means that we will be able to inject a controller into this property at runtime, meaning that we will be able to change the controllers connections for when we want to mock out parts of the application during testing.

Even though we have only written a handful of statements in listing 1, Ember.js have helped us create the following features:

  • Created a two-way binding between two controllers keeping the variables in sync when changes occur
  • A clean separation of concern between the controllers
  • Enabling a high degree of testability and application flexibility through a loose coupling between controllers
  • Ensuring that there is only one definition of which of the notes is the selected one, throughout the entire application. Knowing that SelectedNoteController.content will always represent this information enables you to be create simple views that can be automatically updated whenever any changes to the selected note occurs.

Automatic updating templates

Lets add some lines of code in order to understand how to bind the data all the way out to the view via automatic updating templates.

Listing 2 Adding code for the view

Notes.NotesController = Ember.ArrayController.extend({
    content: [],
    selectedNote: null
});

Notes.SelectedNoteController = Ember.ObjectController.extend({
    contentBinding: 'notesController.selectedNote',
    notesController: null
});

Notes.SelectedNoteView = Ember.View.extend({
    elementId: 'selectedNote',

    templateName: 'selectedNoteTemplate'

});

Ember.TEMPLATES['selectedNoteTemplate'] = Ember.Handlebars.compile('' +
    '{{#if controller.content}}' +

        '<h1>{{name}}</h1>' +

        '{{view Ember.TextArea valueBinding="value"}}' +

    '{{/if}}'
);

We've added a couple of new features to the web application in order to display our selected note to the user. First, we have added a SelectedNoteView defining the DOM element's id attribute, as well as which template the view will use to render its content to the DOM tree.

The actual implementation of the view happens within the Handlebars Template and consists of a total of three statements. As we only want to show the selected note to the user if there is one selected, we use the Handlebars if-helper. The statement {{#if controller.content}} simply ensures that the code inside that if-helper is only executed if the content attribute of the controller is not null or undefined. Inside the if-helper block we are binding the view to the selected notes name and value property. With a handful of code lines Ember.js allows us to implement quite a bit of features:

  • Displaying the selected note only when a note is actually selected.
  • Keeping your DOM tree clean if no note is selected it keeps the element completely out of the DOM, no display:hidden in sight.
  • Ensuring that the user always sees the updated information about the selected note.
  • When the user changes the selected note's value (through the text area) Ember.js will ensure that the underlying Note data is updated.

At this point, it should be fairly easy to envision how your application would behave if you implemented synchronization of notes between the Notes application and a backend server application. Updating the number of Notes available in the system, changing which note is selected, or even changing the selected notes data, initiated via a push request from the server-side. We have only written a handful of code statement, and yet we have built in a rather large list of features into the application, all while keeping the application structure sane.

Ember.js' default template engine, Handlebars, have many features that are integrated into Ember.js.

You might be wondering how you are going to deal with the situations where the data available doesn't exactly match up with the data that you want to display to the user? Or if the data you are either displaying or dependent on in an if-helper of forEach-helper is complex. This is where computed properties come into action.

Computed properties

A computed property is a function that returns a value that is derived from other variables or expressions (even other computed properties) in your code. The difference between a computed property and a normal JavaScript function is the fact that Ember.js will treat your function as if it was a real Ember.js property. This means that you will be able to call get() and set() on your computed properties, as well as bind to them or observer them. Normally, you will find your computed properties on your model object, but, every once in a while, you need to use them in your controllers or in your views.

The Notes application has a single computed property, a function called isSelected inside the Notes.NoteListItemView class. This computed property is responsible for highlighting the correct note, in order to visualize to the user which note is selected. The code is shown below in listing 3.

Listing 3 The isSelected computed property

Notes.NoteListItemView = Ember.View.extend({
    template: Ember.Handlebars.compile('' +
        '{{name}}' +
        '{{#if view.isSelected}}' +

            '<button {{action doDeleteNote}} class="btn btn-mini floatRight btn-danger smallMarginBottom">Delete</button>' +
        '{{/if}}'),

    classNames: ['pointer', 'noteListItem'],

    classNameBindings: "isSelected",


    isSelected: function() {

        return this.get('controller.selectedNote.name') === this.get('content.name');
    }.property('controller.selectedNote.name'),


    click: function() {

        this.get('controller').set('selectedNote', this.get('content'));
    }
});

Ember.js provides quite a bit of functionality in this view by utilizing the computed property isSelected. First of all, it binds to this property internally inside its template in order to show a delete button on the selected note. In addition, it uses the classNameBindings property to bind a CSS class attribute to the rendered DOM element if this is the selected note.

As you can see, the isSelected computed property is a normal JavaScript function. In this case, it simply returns true or false depending on if this views content.name has the same string value as its controllers selectedNote.name property. Then, right after the function declaration, a call to property('controller.selectedNote.name') is made. This call is what tells Ember.js to treat this function as a computed property. In the background, it sets up an observer on the controller.selectedNote.name property and it makes sure that whenever that observed value changes, that any variable currently bound to the value of this function will be updated. Ember.js is smart enough to automatically cache the return value of this computed property so that it doesn't have to recalculate the value until the property that it observes changes. This means that you will save many computations within your code compared to using simple JavaScript functions.

An additional nice feature of using computed properties is that there is no polling involved. Each and every property that is bound to the computed property will only be updated exactly once and at the same time—when the computed property's observed value changes. Another bonus is that all of these properties will be updated at the same time, avoiding views displaying contradicting information to your users.

The final piece of code in listing 3 uses the click-observer to tell its controller when a new note is clicked.

I mentioned that you could use your computed properties as setters as well. But how do you set a value that is derived from a combination of other properties? In listing 4, we have an object Notes.Duration that has a single property called durationSeconds. While it might make sense for a backend service to store the duration as seconds, it's rather inconvenient for the user to get the duration presented to them in seconds. Instead, we want to convert the seconds into a string with hours, minutes, and seconds separated by a colon.

Listing 4 Using computed properties as setters

Notes.Duration = Ember.Object.extend({
    durationSeconds: 0,

    durationString: function(key, value) {

        if (arguments.length === 2 && value) {

            var valueParts = value.split(":");
            if (valueParts.length == 3) {

                var duration = (valueParts[0] * 60 * 60) +
                    (valueParts[1] * 60) + (valueParts[2] * 1);
                this.set('durationSeconds', duration);

            }
        }
        var duration = this.get('durationSeconds');

        var hours   = Math.floor(duration / 3600);
        var minutes = Math.floor((duration - (hours * 3600)) / 60);
        var seconds = Math.floor(duration - (minutes * 60) -
            (hours * 3600));

        return ("0" + hours).slice(-2) + ":" +

            ("0" + minutes).slice(-2) + ":" + ("0" + seconds).slice(-2);
    }.property('durationSeconds').cacheable()
});

The first thing you might notice with the code in listing 4 is the fact that our computed property function now includes two function arguments, a key and a value. We can use these arguments to determine whether or not the function call is a getter or a setter by simply checking that there are exactly two arguments. Depending on your requirements, null might have a logical meaning or not; in this case, we only want to update the durationSeconds property if the input has a valid format by splitting the value into an array of parts.

Once we have verified that the input is valid, we start the conversion of the HH:MM:SS string into seconds before we update the object durationSeconds property with the updated value.

The second part of the computed property function is the getter, which, as you might expect, does exactly the opposite of the setter-part. It starts by fetching the durationSeconds property before it generates the durationString and returns it.

As you have probably guessed, it's fairly trivial to use a computed property in this manner in order to fill out an input-field in the graphical user interface via a simple binding to a HTML text field element. Ember will take care of automatically formatting the seconds to a human readable duration and vice versa, when the user updates the duration in the text field.

I mentioned that a computed property uses an observer in order to compute its value, but we have yet to see an observer in action, which brings us along to see how Ember.js observers work.

Observers

Conceptually, a one-way binding consists of an observer and a setter, while a two-way binding consists of two observers and two setters. Observers go by different names and have a different implementation across programming languages and frameworks. In Ember.js, an Observer is a JavaScript function that will be called whenever a variable that it observes changes. You would use an observer in situations where a binding is either not a sufficient mechanism or when you want to simply perform a task whenever a value changes.

There are two ways to implement an observer, either by using the .addObserver() method or via the inline observes() method.
The code in listing 5 shows one possible use of an observer, where we start and stop a timer based on the number of items in a controller's content array.

Listing 5 Observing the length of a controller to control a timer

contentObserver: function() {
    var content = this.get('content');

    if (content.get('length') > 0 && this.get('chartTimerId') == null) {
            //start timer

            var intervalId = setInterval(function() {

                if (EurekaJ.appValuesController.get('showLiveCharts')) {
                    content.forEach(function (node) {
                        node.get('chart').reload();
                    });
                }
            }, 15000);

            this.set('chartTimerId', intervalId);

        } else if (content.get('length') == 0) {

            //stop timer if started
            if (this.get('chartTimerId') != null) {
                EurekaJ.log('stopping timer');
                clearInterval(this.get('chartTimerId'));
                this.set('chartTimerId', null);
            }
        }
    }.observes('content.length')

As you can see, the contentObserver is just a normal JavaScript method. It starts out by getting the controllers content array. If there are items in the content array and a timer is not already started, create a new timer that will fire every 15000 milliseconds. The timer simply goes through each of the items in the content array and reloads its data using a custom reload() method. If, on the other hand, there are no items in the content array, stop any timer that has already been started.

In order to make this function, an observer appends the inline observes() function with the path to the property we want to observe.

It is possible to construct the observer using the addObserver() method instead. The body of the function would remain the same, but its declaration would look slightly different, as shown in listing 6.

Listing 6 Creating an observer using the .addObserver method

var myCar = App.Car.create({				

    owner: "Joachim"
    make: "Toyota"
});

myCar.addObserver('owner', function() {

    //The content of the observer
});

Although it's possible to create observers this way, personally I find the inline version shown in listing 5 to be cleaner and more readable when looking at the source code of my applications. I also like to add the suffix Observer to my observer-functions but this is not required.

Observing changes to properties within arrays

Sometimes you would like to observe changes to properties within an array. In our Notes application, the Notes.NotesController have a content array of Ember Objects with two properties, name and value. If we wanted to observe changes to each of the name properties, we can use the @each observer key, as shown below in listing 7.

Listing 7 Observing changes withing arrays using @each

Notes.NotesController = Ember.ArrayController.extend({
    content: [],
    nameObserver: function() {

        //The content of the observer
    }.observes('content.@each.name')
   });

Summary

As you can see, with the Ember.js approach, you are leaving a lot more of the boilerplate code up to the Ember.js framework. You are still in full control of how the data flows through your application. The main difference is that you are now explicit about these operations as close to the source as possible, which is where you will tell Ember.js just how your application is wired together.


Here are some other Manning titles you might be interested in:

Third-Party JavaScript

Third-Party JavaScript
Ben Vinegar and Anton Kovalyov

Node.js in Action

Node.js in Action
Mike Cantelon, TJ Holowaychuk and Nathan Rajlich

Ext JS in Action, Second Edition

Ext JS in Action, Second Edition
Jesus Garcia, Jacob K. Andresen, and Grgur Grisogono


AttachmentSize
cueball1.png1.19 KB
cueball2.png1.19 KB
cueball3.png1.19 KB
cueball4.png1.19 KB
cueball5.png1.19 KB
ember001.png18.97 KB
cueball6.png1.19 KB
ember002.jpg10.73 KB
ember003.jpg9.88 KB
ember004.jpg10.34 KB
cueball1_06.png1.19 KB
Related Topics >>