Introduction
Last year InfoQ published my article "Ember.js – Rich Web Applications done right”; at the time the code was based on version 0.9.4 of Ember.js, and Ember.js itself was a fairly young project.
The framework has come a long way since then and it's time to update the article with the state of the art in Ember.js development practices and technologies, in particular the Ember Router.
The Ember.js API has stabilized a lot with the release of the Ember.js 1.0 Release Candidate 1. This article is based off a fresh build off the master branch (24th of March 2013) both for Ember.js and for Ember Data.
My book "Ember.js in Action" will be published by Manning Publications in the second half of this year. Manning has an early access release available, in which you will be able to get the first four chapters right away, with new chapters coming out moving towards the books release.
So how has the Ember.js landscape improved? Is Ember.js still the framework for developing single-page web applications the right way?
These are a couple of questions that I aim to answer though this article. We have a lot of ground to cover, so lets get started! The source code for the application we are building can be found at GitHub.
What are we building?
We will be building a simple photo-album application in which users will be presented with a row of photographs along the bottom edge of the page. When the user selects one of these photographs, the URL will update to reflect this while the selected photograph will be displayed above the list of thumbnails.
We will also be adding a simple slideshow function that will transition to the next photograph in the list every 4 seconds.
The finished application looks like figure 1 below.
(Click on the image to enlarge it)
Figure 1 – The finished application
We will start out with a clean slate, and build up the features that we require from our application piece by piece. So lets get started with setting up our application.
Project structure and setup
The finished source code shown for this application is available via GitHub. The source code for the application is located inside the site-directory. You need to start the application so that it will be available without a context path. There are many ways in which you can achieve this, a few simple alternatives are:
- Use the asdf-gem to host the current directory. For further instructions see GitHub. Once the gem is installed issue the command “asdf” from the site-directory
- Any other webserver that will host your application as http://localhost/
The example project have the structure as shown below in figure 2.
Figure 2 – The Project Structure
All of the external libraries are located in the site/scripts directory. The required libraries are
- Ember.js, built on the 24th of March 2013
- Ember Data, built on the 16th of February 2013
- Handlebars.js 1.0 RC 3
- JQuery 1.9.1
Inside the img directory there is a total of 10 image files, that we will use to form our photo album. There is a single CSS file called master.css inside the css directory.
All of our application logic will be split into separate files inside the app directory. We will start out with the single app.js file. The article will tell you whenever you need to add an additional file to the application.
Short introduction to Bindings
In Ember.js, Bindings are used to synchronize the values of a variable between objects. Consider code in listing 1 below:
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <title>Ember.js Example Bindings</title> <link rel="stylesheet" href="css/master.css" type="text/css" charset="utf-8"> <script src="scripts/jquery-1.9.1.min.js" type="text/javascript" charset="utf-8"></script> <script src="scripts/handlebars-1.0.0.rc.3.js" type="text/javascript" charset="utf-8"></script> <script src="scripts/ember.prod.20130324.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> BindingsExample = Ember.Application.create(); BindingsExample.person = Ember.Object.create({ name: 'Joachim Haagen Skeie' }); BindingsExample.car = Ember.Object.create({ ownerBinding: 'BindingsExample.person.name' }); </script> <script type="text/x-handlebars"> <div id="mainArea">{{BindingsExample.car.owner}}</div> </script> </head> <body bgcolor="#555154"> </body> </html>
Listing 1 - Simple bindings example
In the code above we are starts out by loading the CSS file before it includes the JavaScript libraries jQuery, Handlebars and Ember.js. The application itself is defined in full from line 10 to 25. It starts out by defining a namespace for the application, BindingsExample in this case. The application have two objects defined, one BindingsExample.person and one BindingsExample.car. Here we are following the Ember.js naming convention, by starting instantiated objects with a lower case letter.
The name property for the BindingsExample.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 in “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.name property changes, that the contents on the website will also be updated. Try typing the following into your browser's JavaScript console and watch the contents of the displayed website change with it:
BindingsExample.person.set('name', 'Some random dude')
The contents of the displayed webpage will now be “Some random dude”. With that in mind, it is time to start bootstrapping our example application.
Bootstrapping your Ember.js 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.
To get started, lets create an empty index.html file, add the directories app, css, img and scripts. Now, create or copy the following files:
- Inside you app directory, create a single file named app.js.
- Inside the css directory, copy in the file.
- Inside the scripts directory, copy in the files from Github.
Lets get started with our index.html file. We will start out with a rather empty file, only referencing our scripts and CSS files as show below in listing 2.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"> <title>Ember.js Example Application</title> <link rel="stylesheet" href="css/master.css" type="text/css" charset="utf-8"> <meta name="author" content="Joachim Haagen Skeie"> <script src="scripts/jquery-1.9.1.min.js" type="text/javascript" charset="utf-8"></script> <script src="scripts/handlebars-1.0.0.rc.3.js" type="text/javascript" charset="utf-8"></script> <script src="scripts/ember.prod.20130324.js" type="text/javascript" charset="utf-8"></script> <script src="scripts/ember-data-20130216.js" type="text/javascript" charset="utf-8"></script> <script src="app/app.js" type="text/javascript" charset="utf-8"></script> <script type="text/x-handlebars"> </script> </head> <body bgcolor="#555154"> </body> </html>
Listing 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:
EME = Ember.Application.create({});
Listing 3 – Ember-Example namespace (EME)
The last part, before we move on to defining the object model for our application, is to add some 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.
#mainArea { border-radius: 25px; -moz-bottomleft: 25px; padding: 15px; margin-top: 5px; background: #fff; text-align: left; position: absolute; top: 5px; left: 5px; right: 5px; bottom: 5px; z-index:1; }
Listing 4 – CSS for the application mainArea
In order to render a div element with the id "mainArea", we will extend the application template, which you will find inside index.html between the two empty script-tags of type text/x-handlebars. Listing 5 below shows the end result.
<script type="text/x-handlebars"> <div id="mainArea"></div> </script>
Listing 5 – Adding a div-element to hold our application
You can now refresh your page. You should see an empty white area with rounded corners covering most of the screen. With that in place, its time to start defining a model for our Photographs.
Defining your object model with Ember Data
At this point we first need to add ember-data to our list of included scripts into your index.html file, if you haven’t done so already.
To start out with Ember.js, we need to initialize the data store. Open up app/app.js and add the lines shown in listing 6 below.
EME.store = DS.Store.create({ adapter: "DS.RESTAdapter", revision: 11 });
Listing 6 – Creating the Datastore
As you can see, we are creating a new DS.Store object, called EME.store. We need to tell the datastore two pieces of information. The first is the Ember Data revision number and the second is which adapter we will use. The revision number is there to ensure that you will be notified whenever breaking changes occur in Ember Data. Because Ember Data has not reached a finalized and stable 1.0 API yet, breaking changes will occur from time to time when the API evolves, moving towards a final release. We will be using the DS.RESTAdapter to interface with the backend throughout this application.
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. The photograph data model is defined by by extending DS.Model. Create the file models/photo_model.js and add the code from listing 7 below.
EME.Photo = DS.Model.extend({ imageTitle: DS.attr('string'), imageUrl: DS.attr('string') });
Listing 7 – The Photograph Model Object
You may note that we haven’t actually defined an "id" property. This is due to the fact that Ember Data will create one for us. In fact it won’t even allow you to specify one manually. In addition, we have defined two properties "imageTitle" and "imageUrl", both of which are defined as strings via DS.attr('string') .
Now, with that out of the way, it is time to think about how we are going to structure our application, as well as how we are going to define our applications URLs.
Structuring our application via the Ember Router
In the previous article, we used the Ember StateManager to structure the application states. Since then the StateManager has been replaced with a more thorough implementation named Ember Router. Ember Router serves the same purpose, but it will generate a lot of the boilerplate code automatically for you at runtime using sane defaults that is easy to override when necessary.
The first thing we need to define in our application is the routes that our application will be built up with. You can think of a route as a state that the user can be in . Each route will have its own clearly defined URL. Ember.js will generate controllers, view and template automatically for you, which is convenient. Whenever you find yourself needing more than the basic pre-defined functionality you simply create you own implementation and Ember.js will automatically substitute your code in place of the generated code.
For the Photo album we require a total of three routes:
- An Index Route that responds to the “/” URL
- A Photos Route that responds to the “/photos” URL
- A Selected Photo Route that responds to the “/photos/:photo_id” route.
As I mentioned, Ember.js will happily generate all of the above for us, but first we need to tell Ember.js which routes we want our application to have. We do this by simply implementing EME.Router.map() as shown below in listing 8. Create a new file called router.js and add the following code to it.
EME.Router.map(function() { this.route("index", {path: "/"}); this.resource("photos", {path: "/photos"}, function() { this.route("selectedPhoto", {path: ":photo_id"}) }); });
Listing 8 – Defining the Application Routes
As you can see there are two different types of constructs that we can use inside the map() function, route() representing final routes and resource() representing a route that can contain sub routes. We are defining two top-level routes, one named index that responds to the “/” URL, as well as a resource called photos that responds to the “/photos” URL. In addition we are creating a sub route called selectedPhoto that will respond to the “/photos/:photo_id” URL. The Ember Router uses URL attributes prepended with a colon to indicate a dynamic part. Ember Router will replace this dynamic part later when it updates the applications URL.
Note also, that in Ember Router's naming conventions, :photo_id refers to the "id" property on the EME.Photo model object.
Because we won’t be having anything to display inside the index route, we will simply redirect from the index route to the photos route in case the user requests the “/” URL directly. We can do this by creating an EME.IndexRoute class as shown below in listing 9. Add this to the router.js file.
EME.IndexRoute = Ember.Route.extend({ redirect: function() { this.transitionTo('photos'); } });
Listing 9 – Redirecting from the Index to the Photos route
In the code above we are using the route’s redirect function in order to redirect to the route named photos, using the transitionTo() function.
Fetching the Photos from the Backend using Ember Data
The final piece of code required for our router in order to be able to show any photos is to tell what data the photos route will be using to draw its data. Extend router.js with the code shown below in listing 10.
EME.PhotosRoute = Ember.Route.extend({ model: function() { return EME.Photo.find(); } });
Listing 9 – Redirecting from the Index to the Photos route
In the code above, we are relying on Ember Data to fetch all of the EME.Photo objects from the server asynchronously. Once the server has responded, EME.PhotosRoute will populate the automatically generated PhotosController with the data returned. Note that, we could have asked for a specific photograph from the server by specifying an id to the find() function, EME.Photo.find(1), but for our purpose, fetching all photos is sufficient.
Because our model class is named EME.Photos, Ember Data will, by default, look for the data for this model via the URL “/photos”, the contents of which is shown below in listing 10.
{ "photos": [ { "id": 1, "image_title": "Bird", "image_url": "img/bird.jpg"}, { "id": "2", "image_title": "Dragonfly", "image_url": "img/dragonfly.jpg"}, { "id": "3", "image_title": "Fly", "image_url": "img/fly.jpg"}, { "id": "4", "image_title": "Frog", "image_url": "img/frog.jpg"}, { "id": "5", "image_title": "Lizard", "image_url": "img/lizard.jpg"}, { "id": "6", "image_title": "Mountain 1", "image_url": "img/mountain.jpg"}, { "id": "7", "image_title": "Mountain 2", "image_url": "img/mountain2.jpg"}, { "id": "8", "image_title": "Panorama", "image_url": "img/panorama.jpg"}, { "id": "9", "image_title": "Sheep", "image_url": "img/sheep.jpg"}, { "id": "10", "image_title": "Waterfall", "image_url": "img/waterfall.jpg"} ]}
Listing 10 – The Sample Photo Data
This is all well and good, but having the photos loaded doesn’t do us any good without any code to actually display them. So lets get started with defining some templates for our application.
Creating the Application Templates
Ember.js uses Handlebars.js as its default templating engine. We will, in total define four templates. We have already seen the application template above in listing 5, but it is missing one crucial item, an outlet into which it can draw the current route’s template. So lets get started by extending the application template with an outlet, as shown below in listing 11.
<script type="text/x-handlebars"> <div id="mainArea"> {{outlet}} </div> </script>
Listing 11 – Extending the Application Template with an Outlet
An outlet is simply defined via the {{outlet}} handlebars expression and it is used to tell Ember.js exactly where that template expects to draw sub templates. The contents of this template will change depending on which route is the current viewed route. For the index route this outlet will contain the index template while for the photos resource this outlet will contain the photos template.
Now that we have a place to draw the list of photo thumbnails, lets define the photos template, shown below in listing 12.
<script type="text/x-handlebars" id="photos"> {{outlet}} <div class="thumbnailViewList"> {{#each photo in controller}} <div class="thumbnailItem"> {{#linkTo photos.selectedPhoto photo}} {{view EME.PhotoThumbnailView srcBinding="photo.imageUrl" contentBinding="photo"}} {{/linkTo}} </div> {{/each}} </div> </script>
Listing 12 – Defining the photos template
There are a couple of new things to note in the example above. The first is the fact that the text/x-handlebars script tag has an "id" attribute. This id attribute tells Ember.js that what this template is named, and this name – photos – tells Ember.js that this template belongs to the PhotosRoute. As long as you keep to the standard naming convention Ember.js is nice enough to take care of the rest.
The first thing we define in our template is an {{outlet}} into which we can draw whichever photograph is the selected photograph. Next, we define a div-element into which we can draw each of our thumbnails. In order to iterate over each of the photos, loaded into the PhotosController we are using the {{#each photo in controller}} handlebars expression. Inside the each-loop we are using the {{#linkTo ..}} expression to link each photograph up with the selectedPhoto route. Note that we are both specifying which route to link to (via the parent-route.sub.route construct), as well as specifying a context that Ember.js will pass into this route. In our case this context is simply the photograph that the user clicks on.
We have created a custom view to render each of the thumbnails. Create the file views/photo_thumbnail_view.js, the contents of which are shown below in listing 13.
EME.PhotoThumbnailView = Ember.View.extend({ tagName: 'img', attributeBindings: ['src'], classNames: ['thumbnailItem'], classNameBindings: 'isSelected', isSelected: function() { return this.get('content.id') === this.get('controller.controllers.photosSelectedPhoto.content.id'); }.property('controller.controllers.photosSelectedPhoto.content', 'content') });
Listing 13 – Creating a Custom View for the Thumbnails
The PhotoThumbnailView starts out by specifying that this view will be rendered as an img tag via the tagName property. It then binds up the src-attribute before specifying the CSS class thumbnailItem for this tag.
We want to make the selected photograph larger than the other photographs to make it clear to the user which photograph is selected. In order to append a custom is-selected CSS class to the selected property we are using the classNameBindings property. This will simply append the CSS class is-selected to the view if the isSelected computed property returns true, which it only does if the views photo id is the same as the selected photos id.
The only template missing from our application at this point is the template that renders the selected photo. Because this route is a sub route of the photos resource, it will have an id of photos/selectedPhoto. Listing 14 below shows the selectedPhoto template.
<script type="text/x-handlebars" id="photos/selectedPhoto"> <div id="selectedPhoto"> <h1>{{imageTitle}}</h1> <div class="selectedPhotoItem"> <img id="selectedImage" {{bindAttr src="imageUrl"}}/> </div> </div> </script>
Listing 14 –The Selected Photo Template
There is nothing new going on in this template, as it simply renders a div-element to place the selected photograph in, into which it renders the title and the photo.
At this point you can reload the application. The end result will look something like figure 3 below.
(Click on the image to enlarge it)
Figure 3 – The Photo album so far
You may notice that the currently selected photograph is not highlighted, nor is it larger than the other thumbails. The reason for this is that we need to specify a controller for both the photos route and for the selectedPhotos route. In addition we need to link these controllers together in order for our PhotoThumbnailView to resolve the isSelected computed property correctly. So lets go ahead and implement these two controllers.
Defining Controllers
At this point we will define customized controllers for the photos resource and for the selectedPhoto route. In addition we will need to link the photos controller up with the selectedPhoto controller. Lets start by creating a new file controllers/photos_controller.js, shown below in listing 15.
EME.PhotosController = Ember.ArrayController.extend({ needs: ['photosSelectedPhoto'], });
Listing 15 –The PhotosController
As you can see, there is not much going on inside this controller at this point in time. Because we have defined the controller to be an Ember.ArrayController, Ember.js takes care of proxying our list of photos out to the template, as well as handling any bindings or other controller-issues. The only thing we need to specify is the fact that this controller needs the photosSelectedPhotoController. Because we have specified an explicit relationship between the controllers, we will also need to create the controller we are referring to. Create a new file controllers/selected_photo_controller.js, the content of which is shown below in listing 16.
EME.PhotosSelectedPhotoController = Ember.ObjectController.extend({});
Listing 16 –The PhotosSelectedPhotoController
Because we aren’t implementing any custom logic for this controller, its enough to declare it, specifying that the controller is an Ember.ObjectController, meaning that it will serve as a proxy for a single photograph.
Refreshing the application, as shown below in figure 4, reveals that the selected photograph is now highlighted as we expected.
(Click on the image to enlarge it)
Figure 4 – The Result After Adding The Controllers
The final piece of the application is a set of control buttons that allow the user to navigate to the next and to the previous photograph, as well as starting and stopping a slideshow.
Adding Control Buttons and Slide Show Functionality
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 by adding a template to hold the control buttons called photoControls, shown below in listing 17.
<script type="text/x-handlebars" id="photoControls"> <div class="controlButtons"> <button {{action playSlideshow}}>Play</button> <button {{action stopSlideshow}}>Stop</button> <button {{action prevPhoto}}>Prev</button> <button {{action nextPhoto}}>Next</button> </div> </script>
Listing 17 –The Selected Photo Template
This template simply creates four buttons. To each of the buttons it attaches an appropriate action, for which we will implement action functions later. First we need to tell the application where to render this photoControls template. We will render this template directly beneath the selected photograph, so it makes sense to specify this just after the {{outlet}} in the photos template, as shown in listing 18 below.
<script type="text/x-handlebars" id="photos"> <div>Disclamer: The photographs used for this example application is under Copyright to Joachim Haagen Skeie. You are allowed to use the photographs while going through this example application. The source code itself is released under the MIT licence. </div> {{outlet}} {{render photoControls}} <div class="thumbnailViewList"> {{#each photo in controller}} <div class="thumbnailItem"> {{#linkTo photos.selectedPhoto photo}} {{view EME.PhotoThumbnailView srcBinding="photo.imageUrl" contentBinding="photo"}} {{/linkTo}} </div> {{/each}} </div> </script>
Listing 18 –Adding the photoControls to the Application
Next, we need to define action functions for our four buttons. Because we are using the {{render}} expression, we are able to create a PhotoControls controller and make Ember.js automatically use this controller as the controller for the photoControls template. Create a new file controllers/photo_controls_controller.js with the contents of listing 19 below.
EME.PhotoControlsController = Ember.Controller.extend({ needs: ['photos', 'photosSelectedPhoto'], playSlideshow: function() { console.log('playSlideshow'); }, stopSlideshow: function() { console.log('stopSlideshow'); }, nextPhoto: function() { console.log('nextPhoto'); }, prevPhoto: function() { console.log('prevPhoto'); } });
Listing 19 –Adding a PhotoControlsController to Handle Events
If you reload the application now, and bring up the browsers console, you should see a new line being logged to the console each time you click one of the four buttons.
Note that we have specified that this controller needs both the photosController and the photosSelectedPhoto. The reason for this is that we don’t want to implement the contents of these action functions inside this controller. Rather, we want to delegate some of these to the other controllers, where this responsibility makes more sense. Both the nextPhoto and the prevPhoto make more sense in the PhotosController, so lets update the code to delegate the actions to this controller, shown below in listing 20.
nextPhoto: function() { console.log('nextPhoto'); this.get('controllers.photos').nextPhoto(); }, prevPhoto: function() {< console.log('prevPhoto'); this.get('controllers.photos').prevPhoto(); }
Listing 19 – Delegating Events to the PhotosController
As you can see, we are simply delegating to functions on the PhotosController. But before we have a closer look at the implementation those functions, lets finish this controller first by implementing the slideshow slideshow functionality, as shown below in listing 20.
playSlideshow: function() { console.log('playSlideshow'); var controller = this; controller.nextPhoto(); this.set('slideshowTimerId', setInterval(function() { Ember.run(function() { controller.nextPhoto(); }); }, 4000)); }, stopSlideshow: function() { console.log('stopSlideshow'); clearInterval(this.get('slideshowTimerId')); this.set('slideshowTimerId', null); }
Listing 20 – Adding Slideshow Functionality
There are a couple of things to note here. First off, when the user clicks on the play button, the playSlideshow() function will immediately trigger the nextPhoto() function in order to select the next photograph. It then starts a timer using the setInterval() function built into JavaScript. This function returns an id which we can use to return clear the interval later on, so we are making sure that we store this id in the PhotoControlsControllers slideshowTimerId property.
Because we don’t control when the timer executes the callback function, and because we want to ensure that we are executing this callback as part of the Ember.js run-loop we need to wrap the contents of the callback into Ember.run().
The final piece missing in our application at this point is the implementation of the nextPhoto and prevPhoto functions. I will only show the implementation of the nextPhoto here, because their implementation is almost identical. Listing 21 shows the nextPhoto implementation.
nextPhoto: function() { var selectedPhoto = null; if (!this.get('controllers.photosSelectedPhoto.content')) { this.transitionToRoute("photos.selectedPhoto", this.get('content.firstObject')); } else { var selectedIndex = this.findSelectedItemIndex(); if (selectedIndex >= (this.get('content.length') - 1)) { selectedIndex = 0; } else { selectedIndex++; } this.transitionToRoute("photos.selectedPhoto", this.get('content').objectAt(selectedIndex)) } }
Listing 21 –The nextPhoto() function
If there is no photograph selected, the function will simply display the first photograph in the list. It accomplishes this by transitioning to the photos.selectedPhoto route using the content.firstObject property.
If a photograph is selected already, we need to find out which index the selected photograph has inside the content array. We do this by calling the findSelectedItemIndex() function, the contents of which is shown below in listing 22.
Once the index of the currently selected photograph is found, we need to increment the selected index. The only exception is in the case where the very last photograph is selected, in which case we want to select the first photograph in the content array.
When the index of the photograph we want to transition to has been determined, we can simply transition to the photos.selectedPhoto using that photograph as the context.
findSelectedItemIndex: function() { var content = this.get('content'); var selectedPhoto = this.get('controllers.photosSelectedPhoto.content'); for (index = 0; index < content.get('length'); index++) { if (this.get('controllers.photosSelectedPhoto.content') === content.objectAt(index)) { return index; } } return 0; }
Listing 22 –The findSelectedItemIndex() function
There should be no surprises in the code for the findSelectedItemIndex(). It simply iterates across the content array until it finds the selected photograph. If it is unable to find a photograph, it simply returns the index of the first photograph in the content array.
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
What Ember.js brings to the table is a clean and consistent application development model. It makes it very easy to create your own 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.
Ember.js’ naming conventions mean that you only have to specify and implement classes when the defaults Ember.js provides aren’t sufficient. What you end up with is a clean codebase that is easy to test and, most importantly, easy to maintain.
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.
All of the concepts and constructs that are used in this article, and more, will be covered in detail in "Ember.js in Action". This book is currently available via Manning Publications Early Access Program, meaning that you can get a head start at the books content right away.
About the Author
Joachim Haagen Skeie is the owner at Haagen Software AS in Oslo, Norway where he works as an independent consultant and course instructor. He has a keen interest in both application profiling and open source software. Through his company he is currently busy bootstrapping the EurekaJ Application Monitoring Tool. He can be contacted via his Twitter account, or via email joachim (at) haagen-software.no. If you are interested in getting together with other Ember.js developers from around Europe, Joachim is leading the organizing team behind Ember Fest, a mini-conference in Munich, Germany in August.