BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Ember.js: Rich Web Applications Done Right

Ember.js: Rich Web Applications Done Right

Lire ce contenu en français

Ember.js sprung out of the SproutCore project. For those who don’t know what SproutCore is, it is a JavaScript Model-View-Controller framework that enables you to write rich internet applications with a desktop-like feel, both in regards to the application being developed as well as to its source code.

The SproutCore core team and community had been working on SproutCore 2.0 for quite some time before SproutCore 2.0 in effect became Ember.js. You can read more about the transition on Yehuda Katz’ blog.

Although Ember.js has the same roots as SproutCore, with a similar object-model and template-model, Ember.js has a very different view of the world than SproutCore has. In many ways SproutCore aims to hide away HTML and CSS code for the developer, while Ember.js has HTML and CSS at the core of the development model. The advantage of the SproutCore model is that it will be easier to support a different target rendering platform in the future (like the HTML5 Canvas element), or even a native application deployment. This is a very nice idea, but just as the JavaServer Faces (JSF) project I have not seen a single application that targets anything but HTML, CSS and JavaScript.

The advantage of the Ember.js model is the fact that the HTML and CSS are developed in plain-sight, very easily visible to the developer while still being very easy to adapt to whichever specific needs your web-application might have. Now this brings me nicely along to the next topic: What is Ember.js ?

Ember.js, what’s that?

As stated on the Ember.js website, Ember.js is “a JavaScript framework for creating ambitious web applications that eliminates boilerplate and provides a standard application architecture”.

In my own opinion, Ember.js is a framework for developing Rich Internet Applications (RIA) the right way. It is rooted in the languages that shape our web experience (HTML, CSS and of course JavaScript), it helps the developer keep the source code in a clean, MVC pattern, while it leverages the strengths from SproutCore: a rich object model and automatically updating templates, powerful and consistent bindings that support rich views that are able to accept events, as well as a bunch of extra addons, some of which I will go through in this article.

You can find the Source code for Ember.js on GitHub and the addons being worked at in the ember-addons repository. This article will take a closer look at three addons, Ember Data, the Ember.js adapted sproutcore-statecharts and the Ember.js adapted Sproutcore-Touch. 

Project Structure and Setup

During this article we will develop a simple photo viewer/carousel Ember.js application that is able to display a horizontal list of images. When these images are clicked, the selected image will be shown. The application will have Play and Pause, as well as Next and Previous buttons to control the flow of the images. Near the end of the article we will enable dynamic loading of images from a JSON file, as well as enable touch gestures for touch-enabled devices. The gestures tap, pinch (zoom), swipe and pan will be implemented.

The source code shown for this application is available from GitHub. I generally use IntelliJ IDEA as my IDE of choice when developing with Ember.js or SproutCore. The reason there is a Maven pom.xml file in the repository is mainly a convenience for me, in order to get the project nicely and easily imported into IDEA. You do not at all need to consider this file, but do keep in mind that all of the source code resides inside the src/main/webapp directory. If you’re not familiar with the Maven nomenclature, think nothing of it and navigate straight into src/main/webapp.

The example application is will have a total of three JavaScript library dependencies in addition to Ember version 0.9.4:

When you clone the Ember-Example repo, these dependencies will be included, all ready to go.

If you would like to follow along, and start from scratch, you can download the JavaScript dependencies directly from GitHub.

Whenever I post code examples in this article, I am posting the contents as an image. In addition to the image, I will also refer to the correct GitHub revision of the file, to make it possible for you to copy and paste the file contents.

Short introduction to Bindings

Bindings are used to synchronize the values of a variable between objects. Consider the following example:

(Click on the image to enlarge it)

Figure 0 - Simple bindings example

Line 15 to 23 above created a new Ember.js application called BindingsExample, which has two objects defined, one BindingsExample.Person and one Bindingsexample.Car. The name property for the Person object is set to the string “Joachim Haagen Skeie”. Looking at the Car object, you will notice that it has one property called “ownerBinding”. Because the property name ends with the string “Binding” Ember.js will automatically create an “owner” property for you, that is bound to the contents of another property, in this case the BindingsExample.Person.name property.

If you load this HTML file in the browser, it will simply print out the value of the BindingsExample.Car.owner property, in this case being “Joachim Haagen Skeie”.

The beauty of this approach is that whenever the BindingsExample.Person.nme property changes, that the contents on the website will also be updated. Try typing the following into you Browsers console and watch that the contents of the displayed website changes with it:

BindingsExample.Person.set('name', 'Some random dude')

The contents of the displayed webpage will now be the string “Some random dude”. With that in mind, it is time to start bootstrapping our example application.

Bootstrapping your Ember application

If you would like to get a feel for what we are building in this example application, you can have a look at the finished application, which is hosted at this URL

To get started, lets create an empty index.html file, add the directories app, css, img and scripts. The end result are depicted below:

(Click on the image to enlarge it)

Figure 1 – Project directory structure

Inside you app directory, create four empty files with the following names:

  • app.js
  • main.js
  • stateManager.js
  • fixtureData.js

Inside the css directory, create a single file named:

  • master.css

We will fill these files with content as we progress through this article.

Lets get started with our index.html file. We will start out with a rather empty file, only referencing our script and CSS files, and a single div-element that will house the main area of our application. The contents of the file should be:

(Click on the image to enlarge it)

Figure 2 – Our Initial index.html file

The first thing we need to do, is to create a namespace for our application, which we will place inside app.js. I have decided to create the namespace EME (EMber-Example). Add the following to your app.js file:

Figure 3 – Ember-Example namespace (EME)

The last part, before we move on to defining the objet model for our application, is to add some minor content to the applications CSS file. I would like the application to have a white background, with rounded borders, and I would like the application’s main area to fill the entire screen. Add the following to your css/master.css file.

Figure 4 – CSS for the application mainArea

Defining your object model with Ember Data

At this point we first need to add ember-data to our list of included scripts in our index.html file. Review GitHub Commit if you are unsure of how this is done.

Inside app/app.js we need to initialize a datastore that we will use to keep our data in. We will call our datastore EME.store.

Figure 5 – Creating the Datastore

For this application we will only require one type of data – a photograph – which will have three properties, an ID, a title and a URL. We define our photograph data model by extending DS.Model. Add the following to your app/main.js file, note that we are using the extend keyword rather than the create keyword we have been using up until now. I tend to think of create as a synonym for new, and extend as a synonym for interface.

Figure 6 – The Photograph Model Object

The primaryKey property defaults to “id”, so strictly speaking its redundant in the above example. I tend to include it anyway as I like to be explicit in my data model definition.

Defining controllers and adding Fixture Data

Our application will have two types of controls, one that controls the flow of displaying clickable thumbnails, and one that will display the currently selected photograph. Inside app/main.js create the two controllers EME.PhotoListController and EME.SelectedPhotoController.

Figure 7 – Controllers Definition

Note that the PhotoListController is using the Em.ArrayProxy object, whereas the SelectedPhotoController is using the Em.Object object. Note also that the contents for the SelectedPhotoController is bound to the PhotoListController.selected property by using the Binding suffix. Whenever you suffix your properties with the keyword Binding Ember.js will ensure that whenever the value that you have bound to changes, that your property will automatically update. Bindings are a key component of Ember.js, and one of its main strengths.

At this point, it would be useful to add some fixture data into the mix, so that we have some data to display when defining our views in the next section.

Inside the projects “img” directory there are a set of 10 photographs. You may use these images as you are working through this tutorial, but they are not licensed for redistribution of any kind.

We will create 10 Photo objects, one for each photo inside the img directory, inside our app/fixtureData.js file.

(Click on the image to enlarge it)

Figure 8 – Fixture Data

Above we are using our EME.store, that we created earlier in app.js to create new instances of EME.Photo objects. Each EME.Photo object is initialized with the three data properties, id, imageTitle and imageUrl.

Defining your view states

Ember.js comes with a pretty nice Statechart framework built in. Ember.js’ statecharts are based on the SproutCore Statechart framework, which in turn is based on the KI framework. Ember.js’ Statechart implementation is a bit simpler that what I am used to from SproutCore, while also coming with a new powerful feature: ViewStates.

If you have never worked with Statechart before, you might want to read about them in the SproutCore Blog. Even though the structure is slightly different in Ember.js, you should be able to follow along nicely.

Our Statechart is defined in app/stateManager.js.

(Click on the image to enlarge it)

Figure 9 – State Manager

I like to place the State Manager code inside a setTimeout(…, 50). This is my personal preference, and it’s strictly not necessary. The reason I tend to do this is because it makes it easy to verify that I have set up bindings correctly, or that any other asynchronous tasks actually do what they are supposed to do. Your opinion or mileage might vary.

Just before defining the EME.stateManager object, I make sure that I load my fixtures on line 2.

Line 4 defines our main State Manger object. Notice the rootElement definition on line 5, which specifies which DOM-element that the state manager is able to alter. On line 6 we set the initialState to showPhotoView, which is also the only state we need for this application.

The code on lines 9-12 is very important. Each state (or ViewState) will always invoke one function upon entering the state – the enter function – and one function upon exiting the state – the exit function. Ember.ViewState will take care of adding and removing our view from the DOM. It does this via the enter and exit functions, and its important that we call this.­super(); on line 10 so that we do not override that functionality. On line 11 we are updating the contents array of the PhotoListController to all of the EME.Photo objects present in our EME.store datastore.

Each ViewState has exactly one view property that defines the contents which the ViewState will add or remove from the DOM tree. In our application we have defined this view to be of type Ember.ContainerView, which means that it’s a view that can hold multiple sub-views. There is currently only one subview, the photoListView of type Ember.View.

The Thumbnail Template and view

We have finally come to the point where we are able to create views that actually display our photos. We will start out with the view displaying our thumbnails. We will start out by using Ember.js’ built-in template engine Handlebars. Insert the following code just before the </head>-tag in index.html:

(Click on the image to enlarge it)

Figure 10 – The Photo List Handlebars Template

There are some new elements in the above code that you might not be familiar with. Handlebars uses Mustache as its underlying Template engine and anything between {{ and }} is part of these templates. Line 23 is Handlerbars’ for each loop structure. {{#each content}} means that the contents (until {{/each}}) will be repeated for each item inside the templates content. In this case a single image. Notice that we have bound the src attribute of the img-tag to the imageUrl property (of content). The last bit of important information here is the data-template-name, which we will refer to in our ViewState.

There are two steps remaining in order to display our photo list correctly. The first is to define our photoListView (app/stateManager.js) to utilize the template photo-view-list, and the next is to define the CSS to position and size the thumbnails.

Add the following to app/stateManager.js:

(Click on the image to enlarge it)

Figure 11 – photoListView contents

There are a couple of things worth noting above. The first is the fact that we are defining the HTML-contents of our photoListView by telling the view to use the template photo-view-list. The second is the way we bind our contents to our EME.PhotoListController.content variable via the contentBinding property. Whenever the contents of our PhotoListController’s content variable changes, Ember.js will make sure that the view is updated automatically. It might feel a bit like “magic” at first but you quickly become accustomed to this quick-and-easy way of binding your views to your controllers. Since we haven’t specified a tagName property the contents of the view will be wapped in a div-tag. We have however, specified that the class-attribute of that div-tag is thumbnailViewList.

Next we need to define some CSS for both the thumbnailViewList and the thumbnailItem classes we have defined above. Add the following to your css/master.css file:

Figure 12 – CSS for the Thumbnail View

The CSS above should be quite straightforward. We are placing the row of thumbnails at the very bottom of the screen, and we are defining each thumbnail to occupy a space of 75x75 pixels with a 10 pixel gap between each thumbnail.

Loading (or refreshing) your index.html file in your browser should now give you the following result:

(Click on the image to enlarge it)

Figure 13 – Results of adding the Thumbnail list

In order to make our thumbnails clickable and accept mouse input, I generally recommend creating a view for the image. So we will replace our div-tag inside our template with a view. Define EME.ThumbnailPhotoView inside app/main.js:

(Click on the image to enlarge it)

Figure 14 – Defining a clickable thumbnail view

The code above should start to get familiar to you. We are defining a view extending from Ember.View upon which we are overriding the click-function. We want to set the selected property of the EME.PhotoListController to whichever thumbnail we clicked on.

Then finally we need to update our template to use this new view. Change your index.html accordingly:

(Click on the image to enlarge it)

Figure 15 – The updated photo-view-list template

Notice that we have changed all three lines from 24-26. We are specifying that we want to use the view EME.ThumbnailPhotoView, and we are binding the content of each view to this, meaning that each photograph will have its own unique content. Finally we have changed the {{bindAttr src=…}} from imageUrl to content.imageUrl. Your thumbnails are now clickable and will successfully update the content property of the EME.SelectedPhotoController.

The Selected Photo Template and view

Once an image is selected we want to display a large version of the image above the thumbnail list. Lets start by defining a new view in our app/stateManager.js file:

(Click on the image to enlarge it)

Figure 16 – Adding the selectedPhotoView

Here we have added a new view to the Em.ContainerView that we had previously defined called selectedPhotoView. The contents of the view is very similar to the view we defined above, and should need no additional explanation. Note, though, that we have added the new view to the childViews array on line 15.

Next, we need to define the selected-photo template in index.html:

(Click on the image to enlarge it)

Figure 17 – The template for the selected photo

This template is very similar to the thumbnail list template. We have exchanged {{#each}} with {{with}} as we only have one selected image at a time, and we have created a new view called EME.SelectedPhotoView (listed below in figure 18) that we are using to place the selected photograph in. Other than that, the code above shouldn’t require any additional explanation. Add the following to your app/main.js file.

Figure 18 – The SelectedPhotoView

The only thing missing is the stylesheet definitions for the style selectedPhotoItem. Add the following to your css/master.css file:

Figure 19 – CSS for the selected photo

If you (re)load your index.html file in the browser, and then click on a thumbnail, the end result should look similar to figure 20 below.

(Click on the image to enlarge it)

Figure 20 – The result after selecting a photograph

In order to better visualize which image is selected we need to add a bit of CSS attention. Ideally the selected photo would be presented slightly larger than the other thumbnails and be presented with a triangle to better distinguish between the selected photo and the selectable photos.

(Click on the image to enlarge it)

Figure 21 – After adding CSS to visualize the selected photo

The changes required to add the extra CSS can be viewed in full in the following GitHub Commit.

Adding control buttons

We are going to add a four buttons below the selected photograph to control which photo is selected. The “Play” button will start a slideshow, and change the selected photo each 4 seconds. The “Stop” button will stop the started slideshow, while the “Next” and “Prev” buttons will select the next photo and previous photo respectively. Lets start with adding a view for our buttons to our app/stateManager.js file.

Figure 22 – Adding a view for our control button view

The code above implies that we need to define a new template control-button-view and a new CSS class controlButtons. Lets go ahead and add this to our index.html and css/master.css files:

(Click on the image to enlarge it)

Figure 23 – The Control Button View template

In the code above we are using the Ember.Button view directly. We are defining a target – the controller we wish to notify when the button is clicked – and an action – the function on the target object we wish to invoke. As you can probably guess, our PhotoListController needs to implement these four functions. We will come back to the implementation of the actions a bit later on. First lets have a look at the CSS for the control buttons:

Figure 24 – The CSS for the control buttons

The CSS above should be straight forward an shouldn’t require any additional explanation. Refreshing index.html in your browser should yield the following result:

(Click on the image to enlarge it)

Figure 25 – The Button Bar Added

Lets start with the more complicated action, nextPhoto. Add the following to your app/main.js file:

(Click on the image to enlarge it)

Figure 26 – The nextPhoto action

The nextPhoto action is implemented as a function in our EME.PhotoListController. When we first load our application in the browser, the selected property is null. We cater for this fact on lines 25-27 by setting the selected property to the very first element of our content array. Ember.js will automatically extend the standard JavaScript arrays into Ember.js Enumerables. You can read more about them in the Ember.js documentation, but its this fact that enables us to call get(‘firstObject’) on our content array.

The code in lines 27-36 should be fairly straight forward. We start out by finding the selected item index from the content array. Lines 30-34 enables the nextPhoto function to loop back to the first photo if the user hits the next button while viewing the very last photo. Then, finally, we set the selected property of the controller on line 36.

The code on line 12-19 will iterate over the content array and figure out which index the selected photo is located at.

The code for the prevPhoto function is listed below. It is very similar to the nextPhoto function, so I wont go into the contents of the function in detail.

(Click on the image to enlarge it)

Figure 27 – The prevPhoto action

This brings us nicely along to our slideshow feature. As you might have guessed, the slideshow feature will utilize the nextPhoto function. In addition we need a timer so that we can call the nextPhoto function every 4 seconds. Add the following to the top of your EME.PhotoListController in the app/main.js file:

(Click on the image to enlarge it)

Figure 28 – The slideshow functionality

When we click on the “start” button, we want to move to the next photo immediately. Then we want to start a timer that will call the nextPhoto function every 4 seconds. I am simply using the standard JavaScript function setInterval to implement the timer. I need to store the timer identifier in my controller so that the “stop” button is able to stop the timer via the clearInterval function.

Its now time to reload your application and test our your new control buttons.

Adding touch gestures with Ember Touch

Ember Touch is derived from the SproutCore Touch project. Originally this package is available from the emberjs-addons repository. However, since SproutCore 2.0 became Ember.js, the emberjs-addons repository hasn’t really been updated all that much. To solve this problem, we are going to use ppcano’s fork of SproutCore Touch. This project also builds an ember-touch.js file as well as containing additional touch gestures.

There are two types of touch gestures available, instant gestures like a tap and non-instant gestures like swipe, pinch and zoom. The difference between these two gesture types is that while instant gestures only have an event, non-instant gestures also have a start, and end as well as an event that happens while the gesture is in process. This is important to be able to add animation to you swipe, pinch and pan gestures.

The very first thing we need to do, however is to disable your touch devices default gesture event handler. Otherwise your HTML elements wont be able to accept any touch gestures. As we want our whole application to be applicable for touch gestures we will prevent the default gesture event handler on our body-tag using the ontouchmove attribute.

We will add our touch gestures to our EME.SelectedPhotoView. Lets start with adding the functionality for left and right swipe to move to the next and previous photo respectively. Add the following to you app/main.js file:

(Click on the image to enlarge it)

Figure 29 – Adding Swipe gesture

We start by defining our swipe options on line 87. We have defined swipe to require two fingers, and that we want to accept swipes in the left and right directions. As we do not want to animate our swipe operation we are simply overriding the swipeEnd function. If we wanted to perform animation during our swipe we would also override the swipeChange function, and/or the swipeStart function.

For a left swipe we want to display the previous photograph, and for a right swipe we want to display the next photograph. Here we simply reuse out prevPhoto and nextPhoto functions that we added for our previous and next buttons above.

Now, lets add some zooming capabilities to our selected photo view. We want to animate the zooming to make the effect more visual and accurate. Therefore we need to override the function pinchChange. Add the following to you app/main.js file:

(Click on the image to enlarge it)

Figure 30 – Adding pinch gesture

Here we are using the jquery.transit library to perform the actual scaling of our photo. The code should be fairly straight forward. While our pinch is changing, the pinchChange function is notified with a recognizer object. We get the scale attribute from the recognizer and use this to zoom in and out of the photo.

Notice that we are not zooming on the view itself, but rather on the element with ID “selectedImage”. The next step is to add this to our selected-photo template. Add the ID attribute to line 34 of your index.html file:

(Click on the image to enlarge it)

Figure 31 – Adding the selectedImage ID

Now that we are able to zoom in on the selected photo we also need a way to be able to pan. Otherwise we would not be able to view the edges of the photo while zoomed in. Like the pinch gesture we want to animate the pan gesture. Add the following to you app/main.js file:

(Click on the image to enlarge it)

Figure 32 – Adding pan gesture

As you can see above we define panning to be an operation performed with one finger/touch. As with the pinch gesture we are also using jquery.transit to perform the actual pan operation on the image. The strange looking code on line 119 and 120 is my way of formatting a string that either starts with “+=” or “-=”. This is required if you not want the image to reset its position for each time the panChange function is invoked.

Connecting to a backend data source through Ember Data

Since we wont have a real server to fetch our results from, we will fake this by creating a file called “photos” inside our webapp root folder. This file will contain JSON content that simulates the response from the url “/photos”. The content of this file is listed below:

(Click on the image to enlarge it)

Figure 33 – The photos file

You can probably recognize the contents of this file from our app/fixtureData.js file. This file is no longer required, and it can be deleted. Remember to remove the call to the EME.generateImages function from the app/stateManager.js file:

(Click on the image to enlarge it)

Figure 34 – Removing the generateImages call

The reason I am showing you’re the code for the stateManager again here is that you need to take an extra look at line 10. Here we call EME.store.findAll(EME.Photo)). Per default this function will return any objects that are already loaded into the data store. Since we are no longer generating fixture data, we need to also add some code to be able to fetch the contents of the photos file. Lets open up app/app.js and add some Ember Data magic:

(Click on the image to enlarge it)

Figure 35 – The Store Adapter

We have added some code to our EME.store object to be able to use a custom Adapter. Ember Data uses this adapter to talk to your server and to load any retrieved objects into the store. For our application we only need to override the findAll function. But in a real application you would also override find, findMany, createRecord, updateRecord, deleteRecord, commit and so on. For a rather complete and thorough walk though of the Ember Data features, please have a look at the Ember Data Documentation.

On line 7 we are defining our EME.Adapter class, which has a single function, the findAll function. This function takes two arguments, a store and a type – which represents the object type we are searching for. This type object defines the URL we are retrieving. Since we are not querying for a specific ID or any other parameters we can simply pass this URL on to jQuery.getJSON. On line 15 we load the retrieved items into the store using the loadMany function.

Now, you might wonder how on earth our Photo class knows that it will request the photos file URL. Lets navigate to our app/main.js file and add a couple of lines:

Figure 36 – reopenClass

You should now be able to refresh your browser and see that the images are loaded properly from your photos file.

Normally I would not add my Adapter to my app.js file, but rather to its own .js file. This makes it easier to find the Adapter code later on when you want to change your implementation. 

Conclusion

To me Ember.js represents the way that I personally would like to develop web application clients. Ember.js is tightly coupled with the technologies that make up the web today, but I think it’s very nice that it doesn’t attempt to abstract that away. When the time comes to migrate from HTML to Canvas, or any other technology, I am confident that the Ember.js framework will evolve along with the current trends in web frontend technology.

What Ember.js brings to the table is a clean and consistent application development model. It makes it very easy to create your own “component”, “template views” that are easy to understand, create and update. Coupled with its consistent way of managing bindings and computed properties, Ember.js does indeed offer much of the boilerplate code that a web framework needs, and because it is so obvious which technology is in use and how the DOM tree gets updated its also very easy to add on other third party add-ons and plugins.

Hopefully this article has given you a better understanding of the Ember.js development model and you are left with a clear view on how you can leverage Ember.js for your next project. And as the title of the article reads, I honestly believe that Ember.js is Rich Internet Applications done right!

About the Author

Joachim Haagen Skeie is a co-owner at Kantega AS in Oslo, Norway. He works as a Senior Consultant within Java and Web technologies, with a keen interest in both application profiling and open source software. He can be contacted via his Twitter account, or via his Blog.

 

 

 

Rate this Article

Adoption
Style

BT