Ember.js
By Aaron Probus
Aug 21, 2012
With Ember.js v1.0 coming out soon, I thought it would be the perfect time to build up some interest in those of you who haven't had the chance to try it out yet. I have been using Ember for the past month to create my first web app, Colext, so my time spent learning Ember is your gain.
I'm going to start off with a short description of what Ember is and then go straight into examples. I will only be able to go into some of the basics, which should provide a preliminary foundation for working with Ember.
Description
Ember.js is a javascript client-side MVC framework. While many developers have used MVC frameworks on their servers, not as many have used them on the client. By moving MVC to the client, you can converge your datasources into a single REST API and use AJAX calls to update your data. So, instead of full page refreshes you'll be able to make smaller AJAX calls, encouraging a quick and responsive app.
Ember.js has two main features: bindings and auto-updating templates. Bindings keep data in sync between objects/views, which in turn allows templates to update automatically. Thus, once bindings are set up, all you have to do is manipulate the data, and the display will reflect your changes.
Examples
What would an introduction be without some examples? I'm going to start simple and keep building up to cover all the basics. Currently, Ember best supports handlebar templates, so that's what I'll use for the examples.
**NOTE: In order to get syntax highlighting to work, I had to replace "{{" with "{ {", so be sure to remove the extra space when running the examples**
Example 1: Bindings + Templates
For the first example, we are going to keep the values of two textboxes in sync.
Code
HTML
<!DOCTYPE html>
<html>
<body>
<script type="text/x-handlebars">
{ {view Ember.TextField valueBinding="MyApp.nameController.name"}} <br/>
{ {view Ember.TextField valueBinding="MyApp.nameController.name"}}
</script>
<script type="text/javascript" src="jquery-1.7.2.min.js"></script>
<script type="text/javascript" src="handlebars-1.0.0.beta.6.js"></script>
<script type="text/javascript" src="ember-1.0.pre.js"></script>
<script type="text/javascript" src="example1.js"></script>
</body>
</html>
Javascript
window.MyApp = Ember.Application.create();
MyApp.nameController = Ember.Object.create({
name: 'Aaron'
});
Explanation
window.MyApp = Ember.Application.create();
Notice the first line of the javascript. An "Application" object is required for every Ember application, as it sets up a global namespace as well as event listeners. It is important to note that "MyApp" is capitalized because this determines where Ember will look for the data. Variables that start with a capital letter are assumed to be global variables, and variables that start with lowercase letters local.
MyApp.nameController = Ember.Object.create({
name: 'Aaron'
});
This statement, in our javascript code, creates a data source to which our views can bind.
{ {view Ember.TextField valueBinding="MyApp.nameController.name"}}
Ember considers any variable ending with "Binding" to signify a binding. In this case, we are binding the value of the textbox with "MyApp.nameController.name", our data source. Since we are binding two textboxes to the same data source, the textboxes will have the same data.
Example 2: Arrays + Computed Properties + Actions
For the second example, we'll make an app that rolls a variable number of dice and computes the sum.
Code
HTML
<script type="text/x-handlebars">
<input type="button" value="Add" { {action "add" on="click" target="MyApp.diceController"}} />
<input type="button" value="Reroll" { {action "reroll" on="click" target="MyApp.diceController"}} />
<table>
{ {#each MyApp.diceController}}
<tr>
<td>
{ {value}}
</td>
</tr>
{ {/each}}
</table>
Sum: { {MyApp.diceController.sum}}
</script>
Javascript
window.MyApp = Ember.Application.create();
MyApp.Dice = Ember.Object.extend({
value: 1,
reroll: function () {
this.set('value', Math.floor(Math.random() * 6) + 1);
}
});
MyApp.diceController = Ember.ArrayController.create({
content: [],
add: function () {
this.get('content').pushObject(MyApp.Dice.create({}));
},
reroll: function () {
var content = this.get('content');
for (var i = 0; i < content.length; i++) {
content[i].reroll();
}
},
sum: function () {
var content = this.get('content');
var val = 0;
for (var i = 0; i < content.length; i++) {
val += content[i].get('value');
}
return val;
}.property('content.@each.value')
});
Explanation
The first thing you should notice is:
<input type="button" value="Add" { {action "add" on="click" target="MyApp.diceController"}} />
This line serves two functions: 1) it creates an input button that we will click, and 2) it designates how to handle the click event. The action tag can be read as "On click, call the add function of MyApp.diceController". Although the "add" function doesn't take any parameters, Ember passes in two objects.
The next noticable line is:
{ {#each MyApp.diceController}}
This line tells Ember to iterate over every item in "MyApp.diceController". Inside of the loop, all lowercase bindings will be evaluated in the context of the current item. This is what allows "{ {value}}" to bind to the correct value for each die. Other Ember objects could still be referenced though the global "MyApp" object.
The last noteworthy HTML line is:
Sum: { {MyApp.diceController.sum}}
This line sets up the view binding to the computed property "sum"; the display will be updated every time "sum" changes, exactly how it would work with a regular property.
There are only two lines of javascript I want to point out. First:
this.get('content').pushObject(MyApp.Dice.create({}));
In order for the bindings to know when to update, we have to use the special "pushObject" method instead of the "push" method one might typically use. Whenever you manually set a value on an Ember object, you need to use the designated getter/setter for that type of object.
The second important javascript line is:
}.property('content.@each.value')
This line has a twofold purpose. First, it tells Ember to treat the function as a property so that it can be bound to. Secondly, it specifies that its value depends on each die. From this, Ember knows to recompute "sum" every time a new die is added/removed ("@each"), and every time the value of a die changes ("value").
Relevant Links
Summary
Through my time spent working with Ember, I have found it to be an amazing tool for creating web apps. For the sake of full disclosure, I should mention that I have had difficulties as well, particularly with bindings. From my struggles, I can't stress enough the importance of mapping out object hierarchies before implementing them in Ember. Otherwise, you can end up with spaghetti bindings that are very difficult to debug.
At this point, I hope I have piqued your interest enough to give Ember a try. To learn more about what Ember has to offer, check out the official Ember site.