Welcome to my Ember Tutorial!
My intent with this guide is to provide enough instruction on how to use Ember that you could start writing your own app when you’re done. I have aimed for my writing to be simple, friendly and concise. I’m very interested in getting feedback and improving this tutorial so please don’t hesitate to send me feedback if you have any suggestions.
This tutorial was written primarily in May/June of 2014. A lot has changed in Ember-land since then. The biggest change is that Ember CLI is now considered the “proper” platform for building your Ember app. Ember CLI has its own learning curve, but it is considered the way forward.
The Ember team is still committed to providing a standalone ember.js distribution that you can use without Ember CLI. So all you really need to run ember is ember.js, jquery, handlebars, and your own ember javascripts. You can throw them all together, point Ember to a back-end, and you’re up and running. That being said, if you’re starting from scratch, you should probably use Ember CLI, which works well with Rails via Ember-CLI-Rails.
The concepts and ideas in this tutorial are still valid, but it really should be ported to Ember-CLI. I’m pretty busy with other projects right now, so if you or someone you know would be interested in doing the port, please have them contact me. This tutorial is also on GitHub, so technically anyone can make a pull request.
As far as I know this tutorial is still the most complete tutorial that covers both the concepts and implementation of Ember, so almost everything you learn here will serve you well even if you are using Ember CLI. So if you’re down to learn Ember, you can sit back and enjoy the ride.
Ember is a front-end Javascript framework. You can use it to write complex, front-end heavy web apps. It gives you an organized structure, conventions, and built-in ways to do some of the hard things.
Like Angular, Backbone and Knockout, Ember arrived recently to help developers build great front-end applications while maintaining a clean code base.
I can’t speak for every framework out there, but I can tell you what’s great about Ember.
Logical Code Organization. If you come from a Rails background you will appreciate Ember’s naming conventions. For example a Users Controller looks for a Users View and a Users Template.
Easy Persistence. Once you have your back-end communicating with Ember, saving a
record as easy as calling user.save()
. Want to delete this user? Call
user.destroyRecord()
.
Auto-Updating Templates. Create a property and display it with {{ myProperty }}
in your Handlebars template. Any updates will appear instantly. Handlebars might not be beautiful, but your templates can look pretty darn good if you use Emblem, a templating language for Handlebars that’s like Slim.
Helpful Object APIs. Ember implements its own set of objects, each of which comes with
a really friendly API. For example Ember has an Array object with methods like contains
,
filterBy
, and sortBy
. These come in handy all the time.
I think Ember is absolutely worth learning if you find yourself needing to build complex front-end applications. Ember may seem big, but it’s really not that complicated. Once you learn how the various Ember objects interact and get a handle on the basics of the API you’ll be coding glorious front-end apps in no time.
My takeaway from working in Ember is that it’s really fun. It opens up new potential for developers to write the crazy front-end apps they’ve always dreamed of while still maintaining a clean, readable codebase. Also, the Ember core team does a great job of consistently releasing bug fixes and improvements, while keeping a stable codebase.
At the end of the day, I reckon that if you’re going to choose a front-end framework to learn, you can’t go wrong if you pick Ember.
This tutorial is designed for developers with basic knowledge of Javascript and Ruby on Rails. You should have a Rails development environment setup on your computer if you want to follow along.
I originally wrote this tutorial in CoffeeScript because I use CoffeeScript when I write Ember. I think it results in cleaner code. If you’re going to do full-time development with Ember then I feel like you are better off with CoffeeScript.
However, I know that there are many people who don’t know or like CoffeeScript, so I went ahead and made a Javascript version too. Just use the toggle in the upper right of the page to switch languages.
As a heads up for CoffeeScript learners, JS2Coffee.org can convert any CoffeeScript code to Javascript and vice versa.
While I typically use TDD and don’t ship code without tests, I’m not covering testing in this tutorial for a couple of reasons.
First, there’s a lot to learn here as is, and throwing testing into the mix would make this tutorial just that much more complicated.
Second, you can write integration tests for Ember with Cucumber (or Rspec Integration), and this can be better than using Ember’s built in testing helpers because you can test persistence. However, I think you still should write unit tests for your Ember code. I recommend Ember Qunit for that. There’s also an Ember guide on testing that you can read after this tutorial.
If there’s a lot of demand for help with testing then I can write another section on how to do it, just let me know.
If you have a question or find a mistake then please email me at vic@vicramon.com or submit a pull request.
Let’s build a Hello World app first. We can ensure that we have our development environment setup and get a high level view of how Ember works.
The app we’re going to build in this tutorial is a CRM. We can reuse this hello world code when we start the app, so let’s name our Rails app ember-crm
.
First create new rvm gemset to sandbox our gems:
rvm gemset create ember-crm rvm gemset use ember-crm gem install rails
Now generate the Rails app:
rails new ember-crm -d postgresql
cd ember-crm
bundle
rake db:create
If you run rails s
and visit localhost:3000 then you should see the Rails Welcome Aboard page.
You’ll need to remove Turbolinks because it conflicts with Ember. Make sure to remove it from all of the following places:
I am going to use Ember Rails for this tutorial. It’s stable and works great. I think Ember CLI may replace Ember Rails as the default Rails/Ember integration gem, but it’s pre 1.0 right now so I’m going with Ember Rails for this tutorial.
Add following gems to your Gemfile:
gem 'ember-rails' gem 'ember-source', '~> 1.8.1' gem 'emblem-rails'
And bundle:
bundle
Ember Rails provides a generator that will create a skeleton for our Ember app. The -n flag tells it to name the Ember app App
, which is the typical convention.
For Javascript:
rails g ember:bootstrap -n App --javascript-engine js
If you want the generated files to use CoffeeScript then add a flag:
rails g ember:bootstrap -n App --javascript-engine coffee
Ember Rails comes with default Ember versions, but let’s explicitly install Ember 1.5.0 and Ember Data 1.0.0 beta 7 so that your version is the same as mine. They will be installed to vendor/assets/ember
.
rails g ember:install --tag=v1.5.0 --ember rails g ember:install --tag=v1.0.0-beta.7 --ember-data
Add the following lines to your environment files. These tell Ember Rails which version of ember.js to use in each environment. The production version is minified and has no logging, while the development version is not minified and allows for logging.
# config/environments/test.rb config.ember.variant = :development # config/environments/development.rb config.ember.variant = :development # config/environments/production.rb config.ember.variant = :production
Ember Rails generates an application.js.coffee
for us, so lets use that. Delete application.js
.
The generated application.js.coffee
requires jquery
, but not jquery_ujs
, so make sure to require it right below jquery
:
# app/assets/javascripts/application.js.coffee
#= require jquery_ujs
We’ll need a basic Rails controller and view so that we can output something from Ember. I’m going to make a controller named HomeController
with an index view and make it the root path.
Add the route:
# config/routes.rb root to: 'home#index'
Create the controller:
# app/controllers/home_controller.rb class HomeController < ApplicationController end
Create an index view. It’s going to be blank:
<!-- app/views/home/index.html.erb -->
Last but not least, we need to create a template for Ember to render. Ember looks for an application template by default, so all we need to do is create it:
// app/assets/javascripts/templates/application.js.emblem
h1 Hello World
outlet
Restart your server then visit http://localhost:3000. You should see ‘Hello World’ printed on the screen. If you see it then congratulations! You’re one step closer to being an Embereño. Yes, Embereño is a thing, though I kind of like Emberista.
If you don’t see Hello World
, you should clone my hello world repo and see what you’ve done differently.
If you open your console you should see output that looks like this:
DEBUG: ------------------------------- ember.js?body=1:3522 DEBUG: Ember : 1.5.0 ember.js?body=1:3522 DEBUG: Handlebars : 1.3.0 ember.js?body=1:3522 DEBUG: jQuery : 1.11.0 ember.js?body=1:3522 DEBUG: -------------------------------
If you don’t see this output then your Ember javascripts are not being loaded properly.
There’s a whole page on debugging Ember in the guides. I suggest that you check it out if you ever get stuck.
The first thing I usually do if things aren’t working is place a debugger
in the code and open Chrome dev tools. If that doesn’t help then the next thing I’ll do is log my route transitions to get more insight:
# app/assets/javascripts/application.js.coffee window.App = Ember.Application.create LOG_TRANSITIONS: true LOG_TRANSITIONS_INTERNAL: true LOG_VIEW_LOOKUPS: true
// app/assets/javascripts/application.js window.App = Ember.Application.create({ LOG_TRANSITIONS: true, LOG_TRANSITIONS_INTERNAL: true, LOG_VIEW_LOOKUPS: true })
Beyond that I suggest reading in the guides for more detailed tips on debugging, and of course using the Ember Inspector…
The Ember Inspector is an invaluable tool for debugging Ember. It’s a Chrome Extension that helps you see what’s going on in your app. You can get it here.
Once it’s installed refresh your browser and open Chrome dev tools. You should see a tab titled Ember. Inside are all sorts of helpful tools. View Tree will show you exactly what’s being rendered and where it came from. Routes show you all the routes in your app, and what other objects each one looks for. The route you are currently on will be bold. Data will show you all the active records in your app. You can click on a record to view all of its attributes.
I can’t say enough good things about the Ember Inspector. It makes the inner workings of your app very visible.
As you’ve seen it doesn’t take much time to get our Ember App up and running. Most of the work is in preparing the Rails app.
If you’ve still got issues getting this working then please post your issue in the comments below. This hello world app is also on GitHub if you want to look at it.
We could start coding right away, but I think that if you haven’t seen Ember-flavored javascript before then the code might not make much sense. So before we dive into building out the app I’m going to cover some core Ember concepts and a few of the Ember objects that you’ll be seeing.
If you are dying to code you can skip directly the Our App chapter, but if you want to know what’s going on then I recommend that you stick around.
The following few chapters will be a whirlwind tour of Ember Objects, Routing, Routes, Controllers, Views, and Templates. I’m only going to cover the basics so we can start coding as soon as possible. You can do a deep dive through the Ember Guides once you’ve completed this tutorial.
Ember implements its own object system. The base object is Ember.Object. All of the other objects in Ember extend Ember.Object.
Most of the time you will be using an object that extends Ember.Object like Ember.Controller or Ember.View, but you can also use Ember.Object itself. One common use for Ember.Object is to create service objects that handle some specific logic.
If you open your browser’s console in your Hello World app you’ll be able to follow along with these commands, though you’ll need to convert the CoffeeScript to Javascript (or just click the toggle at the top of the page).
You can instantiate a basic object like this:
user = Ember.Object.create()
var user = Ember.Object.create();
Initialize it with properties by just passing them to create:
user = Ember.Object.create({ firstName: 'Sam', lastName: 'Smith' })
var user = Ember.Object.create({ firstName: 'Sam', lastName: 'Smith' });
You can get a property from the object by calling .get
on it and passing the string name of the property:
user = Ember.Object.create({ firstName: 'Sam', lastName: 'Smith' }) user.get('firstName') is 'Sam' #=> true user.get('lastName') is 'Smith' #=> true
var user = Ember.Object.create({ firstName: 'Sam', lastName: 'Smith' }); user.get('firstName') == 'Sam' //=> true user.get('lastName') == 'Smith' //=> true
Inquire about the object with .toString()
. In this case we see that it’s just Ember.Object.
user = Ember.Object.create(); user.toString() #=> <Ember.Object:ember{objectId}>
var user = Ember.Object.create(); user.toString() //=> <Ember.Object:ember{objectId}>
So far we’ve just been using Ember.Object. You can create a “subclass” of Ember.Object by using extend
. Say we want to make a user Object “class”:
App.User = Ember.Object.extend()
App.User = Ember.Object.extend();
Now you can instantiate a user with create
:
user = App.User.create()
var user = App.User.create();
Note that I’m putting this User object inside App
. Ember needs to place all of the app data inside a variable, and Ember devs typically use App
. So when you define some kind of Object in ember you always want to have it on App
.
Now what we’ve done is great and all, but we probably want to add things to our User object.
Objects can have three types of things inside them: properties, functions, and observers. I’ll cover each of these.
Here’s how you could define a property:
App.User = Ember.Object.extend isHuman: true temperature: 98.6 favoriteDirector: 'Tarantino'
App.User = Ember.Object.extend({ isHuman: true, temperature: 98.6, favoriteDirector: 'Tarantino' })
isHuman
, temperature
, and favoriteDirector
would now be accessible with .get
.
user = App.User.create() user.get('isHuman') #=> true user.get('favoriteDirector') #=> "Tarantino" user.get('temperature') #=> 98.6
var user = App.User.create(); user.get('isHuman'); //=> true user.get('favoriteDirector'); //=> "Tarantino" user.get('temperature'); //=> 98.6
These are the basic versions of Ember properties. We can also create computed properties that actually do some work and call other properites:
App.User = Ember.Object.extend fullName: ( -> @get('firstName') + ' ' + @get('lastName') ).property('firstName', 'lastName')
App.User = Ember.Object.extend({ fullName: function() { return this.get('firstName') + ' ' + this.get('lastName') }.property('firstName', 'lastName') })
Let’s dissect this.
First, we specify the property name with fullName:
.
Second, we pass it a function that will return the value we want. We can get at other properties in our object by doing this.get('propertyName')
, or in CoffeeScript, @get('propertyName')
.
Third, we call .property()
on our function to tell Ember that it’s a computed property.
Finally, we have to tell property()
which other properties this property depends on. It expects a list of the property names as strings. In this case, fullName
should change any time firstName
or lastName
changes, so it needs to watch both of them.
Functions are quite simple:
App.User = Ember.Object.extend showMessage: (message) -> alert(message) showName: -> @showMessage(@get('fullName'))
App.User = Ember.Object.extend({ showMessage: function(message) { alert(message) }, showName: function() { this.showMessage(this.get('fullName')) } })
Our showMessage
function takes one argument: the message we want to alert. The showName
function calls the showMessage
function with the fullName
of our user (assuming we’ve implemented the fullName
property).
Here’s how you actually call a function:
user = App.User.create() user.showMessage('it works')
var user = App.User.create() user.showMessage('it works')
Observers are functions that fire whenever any of the things they observe change. They look like properties but they end with observes()
instead of property()
App.User = Ember.Object.extend weightChanged: ( -> alert('yikes') if @get('weight') > 400 ).observes('weight')
App.User = Ember.Object.extend({ weightChanged: function() { if (this.get('weight') > 400) alert('yikes') }.observes('weight') })
The above code would fire an alert saying “yikes” whenever the weight property on this user changes and is greater than 400. You can observe as many things as you’d like:
App.User = Ember.Object.extend bodyObserver: ( -> alert("You've changed. I feel like I don't even know you anymore.") ).observes('weight', 'height')
App.User = Ember.Object.extend({ bodyObserver: function() { alert("You've changed. I feel like I don't even know you anymore."); }.observes('weight', 'height') })
This would fire every time weight
or height
changed.
The Ember Object system is really great. It makes it easy to write modular, reusable code.
You can extend any existing object in your app like this:
App.Animal = Ember.Object.extend likesFood: true App.Human = App.Animal.extend() human = App.Human.create() human.get('likesFood') #=> true
App.Animal = Ember.Object.extend({ likesFood: true }) App.Human = App.Animal.extend() var human = App.Human.create() human.get('likesFood') //=> true
Properties, functions, and observers in the child object will override those in the parent:
App.Animal = Ember.Object.extend likesFood: true App.Bird = App.Animal.extend likesFood: false bird = App.Bird.create() bird.get('likesFood') #=> false
App.Animal = Ember.Object.extend({ likesFood: true }) App.Bird = App.Animal.extend({ likesFood: false }) var bird = App.Bird.create() bird.get('likesFood') //=> false
Extending objects is a pattern you will use all the time while developing in Ember. You can use it to extract out common functionality or pull in functionality from some other object.
All ember objects call an init
function when they are first initialized. You can use this to do setup work.
App.Human = Ember.Object.extend init: -> alert("I think, therefore I am")
App.Human = Ember.Object.extend({ init: function() { alert("I think, therefore I am"); } })
This is fine with basic Ember Objects, but if you are using other, more specific Ember Objects like Route or Controller, then you should try to avoid using init and instead opt for other Ember conventions. If you must use it in one of these objects make sure that you call @_super()
in the init
function, otherwise you may break things.
This is fine with basic Ember Objects, but if you are using other, more specific Ember Objects like Route or Controller, then you should try to avoid using init and instead opt for other Ember conventions. If you must use it in one of these objects make sure that you call this._super()
in the init
function, otherwise you may break things.
You can go back and add more properties, functions, and observers by calling reopen
on the object:
App.Human = Ember.Object.extend() App.Human.reopen name: 'Señor Bacon' francis = App.Human.create() francis.get('name') #=> 'Señor Bacon'
App.Human = Ember.Object.extend(); App.Human.reopen({ name: 'Señor Bacon' }); var francis = App.Human.create(); francis.get('name') //=> 'Señor Bacon'
As you’ve seen, base objects can function like classes do in other languages. You can even define a sort of class method on objects by calling reopenClass
:
App.Human = Ember.Object.extend() App.Human.reopenClass sayUncle: -> alert("uncle")
App.Human = Ember.Object.extend(); App.Human.reopenClass({ sayUncle: function() { alert("uncle"); } })
Then call the class method on the object definition itself:
App.Human.sayUncle()
App.Human.sayUncle();
Ember Objects provide us with the ability to write object-oriented javascript. This is one of Ember’s best features.
Everything in Ember starts with routes. If you’re familiar with routing systems in other frameworks then I don’t think Ember’s will cause you much trouble.
Note: do not add the following code to your app – the code in the next few chapters is here just to show you how things work.
First, let’s cover the mechanics of routing in Ember. By default Ember uses the hashchange
event in the browser to know when you’ve changed routes. It implements its own HashLocation object to handle this.
With HashLocation, an Ember route will be visible after the #
in your url. For example, your routes might look like:
http://myemberapp.com/#/
http://myemberapp.com/#/about
http://myemberapp.com/#/users/1
http://myemberapp.com/#/users/1/edit
You may not want to serve your Ember app directly from your root url. In this case, just tell Ember what the root url should be:
# app/assets/javascripts/router.js.coffee App.Router.reopen rootURL: '/some/path/'
// app/assets/javascripts/router.js App.Router.reopen({ rootURL: '/some/path/' })
Now your users index route would look like this:
http://myemberapp.com/some/path/#/users
Some of you may think that hashes look ugly. There’s a solution to that! Ember also implements a HistoryLocation class which will handle routes by using your browser’s history API.
Here’s how to use HistoryLocation instead of HashLocation:
# app/assets/javascripts/router.js.coffee App.Router.reopen location: 'history'
// app/assets/javascripts/router.js App.Router.reopen({ location: 'history' })
Boom, it’s that simple.
Not all browsers implement the history API. Luckily Ember comes to the rescue again with AutoLocation, which will use HistoryLocation if the user’s browser supports it, otherwise it will use HashLocation.
# app/assets/javascripts/router.js.coffee App.Router.reopen location: 'auto'
// app/assets/javascripts/router.js App.Router.reopen({ location: 'auto' })
Both HashLocation and HistoryLocation implement Ember’s Location API. You could write your own Location class if you wanted to use something other than hashes or history, it would just need to respond properly to the API.
A set of CRUD routes might look like this:
# app/assets/javascripts/router.js.coffee App.Router.map -> @resource 'users' @route 'user.new', path: '/users/new' @resource 'user', path: '/users/:id', -> @route 'edit'
// app/assets/javascripts/router.js App.Router.map(function() { this.resource('users'); this.route('user.new', { path: '/users/new' }); this.resource('user', { path: '/users/:id' }, function () { this.route('edit'); }) })
This would generate the following routes:
/users
(index)
/users/new
(new)
/users/:id
(show)
/users/:id/edit
(edit)
Delete and create would be handled by custom actions so they don’t need to be routes.
There are two functions in use here: resource
and route
. The difference between them is important.
You can use resource
to take in a param in order to get a specific record. You can nest things under resource
.
You use route
to specify some new UI that doesn’t need a specific record. route
is a dead end – you cannot nest things under it. It can not take in params.
The .
that you see is simply an alternative to using camel case. user.new
could just as well be userNew
. Both of these will look for a series of objects who’s names start with UserNew.
In Ember, the UI for any active route will be visible by default. Take for example the following routes:
# app/assets/javascripts/router.js.coffee App.Router.map -> @resource 'posts', path: '/posts', -> @route 'new', path: '/new'
// app/assets/javascripts/router.js App.Router.map(function() { this.resource('posts', { path: '/posts' }, function() { this.route('new', { path: '/new' }); }) })
When you visit http://localhost:3000/posts/new
, you will see both posts
template and the posts/new
template. The posts
template will need an outlet
tag inside itself to specify where posts/new
will appear.
This is very different from server-side development where every route can have totally different UI. If you see a route in Ember in the url bar, that means that it is active and its UI should be visible. This is a feature. It allows you to compartmentalize UI that builds on top of other UI, so this pattern makes a lot of sense for the front-end.
I recommend playing around with the router in your hello world app. It’s located in app/assets/javascripts/router.js
. Once you’ve added routes you can refresh your page and look at the Ember inspector to see what routes Ember has generated. The Ember Inspector will also show you which Route, Controller, View, and Template that route will look for. This is extremely useful for making sure your object names match up.
If you’re interested in learning more about routes I recommend the Ember docs section on routing. There are also two tables in the Defining your Routes guide that really illuminate how you can use route
and resource
to create you your ideal routes.
In the next chapter we’ll go over what the route actually does when you hit it.
(Again, you don’t need to add the following code to your app. You can add it just to play with it, but then delete it.)
Let’s say we have a route called about
.
# app/assets/javascripts/router.js.coffee App.Router.map -> @route 'about'
// app/assets/javascripts/router.js App.Router.map(function() { this.route('about'); })
What actually happens when you go to it? It will instantiate a series of objects with the same name as the route… kind of like Rails!
When a route is activated Ember flows downwards from Route, to Controller, to View, to Template. In this case our about route will look for the following in this order: AboutRoute
, AboutController
, AboutView
, and a template named about.js.emblem
(or about.hbs
if you’re using Handlebars).
When it finds each object it will call specific functions, or hooks on that object. I’m going to cover those hooks later, so for now I’m just going to place a console log in init
so you can see that these objects are instantiated.
When you go to the about route, which would be http://localhost:3000/#/about
, all of these console logs will get called, in this order:
# app/assets/javascripts/routes/about.js.coffee App.AboutRoute = Ember.Route.extend init: -> @_super() console.log 'route called' # app/assets/javascripts/controllers/about.js.coffee App.AboutController = Ember.Controller.extend init: -> @_super() console.log 'controller called' # app/assets/javascripts/views/about.js.coffee App.AboutView = Ember.View.extend init: -> @_super() console.log "view called" # app/assets/javascripts/templates/about.js.emblem h1 Template rendered!
// app/assets/javascripts/routes/about.js App.AboutRoute = Ember.Route.extend({ init: function() { this._super(); console.log('route called'); } }) // app/assets/javascripts/controllers/about.js App.AboutController = Ember.Controller.extend({ init: function() { this._super(); console.log('controller called'); } }) // app/assets/javascripts/views/about.js App.AboutView = Ember.View.extend({ init: function() { this._super(); console.log('view called'); } }) // app/assets/javascripts/templates/about.js.emblem h1 Template rendered!
Ember actually won’t complain if it can’t find any of these objects. Instead it will just create them for you in memory. So if you don’t need to do anything in the AboutController
, AboutView
, or AboutRoute
then just don’t create them.
This is what I call the Ember Object Flow. When a route is activated it flows downwards to its associated objects.
Now that you understand the flow of objects in the Ember system I’m going to provide brief overview of each one.
The first object we’ll be covering is the Ember Route object. This is different from the Router. The Router creates named url routes. A Route object is a specific type of Ember object that helps you setup and manage what happens when you visit that url route.
Let’s say we want to show a list of users. First we would add a line to the router:
App.Router.map -> @resource 'users'
App.Router.map(function() { this.resource('users'); })
Now when you visit /users
, Ember will look for a UsersRoute
object. Here’s how that could look:
App.UsersRoute = Ember.Route.extend model: -> @store.find 'user'
App.UsersRoute = Ember.Route.extend({ model: function() { this.store.find('user') } })
model
is a function hook that’s called upon entering a route. The result of the model function is then accessible by other objects.
The store
is an Ember data construct that you go through when dealing with persisted records. find
fetches records of the type you pass it. It returns a promise, which in this case will return a DS.RecordArray
object once you call then
on it. DS.RecordArray
is essentially an array of models.
Route objects are really all about using hooks to prepare data and perform any setup actions required for the controller.
model
is just one of a series of hooks that are called when you enter a route. Here are a few more that you’ll get to know, listed in order of when they are called.
App.UsersRoute = Ember.Route.extend beforeModel: (transition) -> model: (params, transition) -> afterModel: (model, transition) -> activate: -> setupController: (controller, model) -> controller.set 'model', model #or @_super(arguments...) deactivate: ->
App.UsersRoute = Ember.Route.extend({ beforeModel: function(transition) { }, model: function(params, transition) { }, afterModel: function(model, transition) { }, activate: function() { }, setupController: function(controller, model) { controller.set('model', model) // or this._super(arguments...) }, deactivate: function() { } })
beforeModel
is called immediately before model
is called.
model
we just went over.
afterModel
is called after the model is resolved (i.e. either pulled down from the server or pulled out of the store).
activate
is called after all of the model hooks have completed, meaning that the route is now active.
setupController
is where you would do any controller setup. You get access to the controller itself as an argument. Note that if you implement setupController
you will need to set the model
property of the controller to the model
argument, because this hook overrides the parent. If you don’t do this then the controller will not have its model
property set. You could also call @_super(arguments...)
to accomplish the same thing.
deactivate
is called when you exit the route. It will not get called if you just change the model but stay on the same route. For example deactivate
would not get called if you changed from /users/1
to /users/2
The transition
argument being passed into the route model hooks can be used to abort the transition. For example, say we have a HouseRoute and you don’t like red houses:
App.HouseRoute = Ember.Route.extend afterModel: (model, transition) -> transition.abort() if model.get('color') is 'red'
App.HouseRoute = Ember.Route.extend({ afterModel: function(model, transition) { if (model.get('color') == 'red') { transition.abort() } } })
Here I’m using afterModel
, because the model is resolved and I can ask for its color property. If you abort, Ember will just go back to whatever route you came from. This can be handy for error checking, confirmation dialogs, and locking certain parts of your app depending on state.
Routes are the one place where you can reach across your app. Usually you do this to give the controller the information it needs.
this.modelFor('routeName')
will return the current model for that route.
this.controllerFor('controllerName')
will get that controller object. Sometimes you may want to get the model for that controller, in which case you would do this.controllerFor('controllerName').get('model')
.
Route objects are your friend. You’ll find that all of their hooks are extremely handy when you start trying to do fancy things in your app.
Controllers handle non-persisted logic related to a specific piece of UI. They typically wrap a model or an array of models. You can put functions, properties, and observers on controllers. They function much like normal Ember Objects with a couple of exceptions.
First we’ll look at the three different types of controllers.
Ember provides you with three types of controllers: ObjectController
, ArrayController
, and Controller
. You use ObjectController
whenever that controller’s route fetches a single model. Use ArrayController
when the route fetches an array of models. And finally use Controller
when the route isn’t fetching any models at all.
Controllers don’t have any specific hooks that are called upon entering them. You can use init
if you really need to, but you shouldn’t. All setup work for a controller should be done in the route, usually in the route’s setupController
hook.
So what’s left? Properties, observers, and functions. These are your bread and butter.
# app/assets/javascripts/controllers/user.js.coffee App.UserController = Ember.ObjectController.extend someFunction: -> alert('so functional') someProperty: ( -> if @get('model.firstName') is "Gregory" "Hey Gregory" else "Hey, you're not Gregory" ).property('model.firstName') someObserver: ( -> alert "You changed your name? I don't really see you as a #{@get('model.firstName')}." ).observes('model.firstName')
// app/assets/javascripts/controllers/user.js App.UserController = Ember.ObjectController.extend({ someFunction: function() { alert('so functional') }, someProperty: function() { if (this.get('model.firstName') == "Gregory") { return "Hey Gregory" } else { return "Hey, you're not Gregory" } }.property('model.firstName'), someObserver: function() { alert("You changed your name? I don't really see you as a " + this.get('model.firstName')); }.observes('model.firstName') })
You can put as many of these as you like in your controller. Any controller properties will be available to the template and view.
If you find yourself duplicating a lot of logic you can extract your code to an Ember.Mixin
and have your controller extend it like so:
# app/assets/javascripts/mixins/excited.js.coffee App.Excited = Ember.Mixin.create levelOfExcitement: "I'm so freaking excited right now!!!" # app/assets/javascripts/controllers/user.js.coffee App.UserController = Ember.ObjectController.extend App.Excited, # your controller code
// app/assets/javascripts/mixins/excited.js App.Excited = Ember.Mixin.create({ levelOfExcitement: "I'm so freaking excited right now!!!" }) // app/assets/javascripts/controllers/user.js App.UserController = Ember.ObjectController.extend(App.Excited, { // your controller code })
Now UserController
would have the levelOfExcitement
property. Also notice that mixins are created with .create
rather than .extend
.
Another major use for controllers is handling actions from templates. For example, a template may have a submit button or a delete link. These actions would be handled like so:
# app/assets/javascripts/controllers/user.js.coffee App.UserController = Ember.ObjectController.extend actions: deleteUser: -> @get('model').destroyRecord() saveChanges: -> @get('model').save()
// app/assets/javascripts/controllers/user.js App.UserController = Ember.ObjectController.extend({ actions: { deleteUser: function() { this.get('model').destroyRecord() }, saveChanges: function() { this.get('model').save() } } })
All action handlers must go inside an actions
object in the controller. This is an Ember convention to help keep your code organized.
The View is one of the most powerful objects in Ember. You can think of a view as a wrapper for a template. It contains all the javascript you might want to execute on the template and manages the logic around attributes and class names.
The view can get the controller through this.get('controller')
. The view does not have the current model by default, so to get the model you’ll have to do this.get('controller.model')
. To get a property on the controller you would do this.get('controller.myProperty')
.
Like Routes, views have a series of hooks that you can use. Here are three of the more common ones you’ll use, in order of when they are called.
App.UserView = Ember.View.extend willInsertElement: -> didInsertElement: -> willDestroyElement: ->
App.UserView = Ember.View.extend({ willInsertElement: function() { }, didInsertElement: function() { }, willDestroyElement: function() { } })
willInsertElement
is called before the view is inserted into the DOM.
didInsertElement
is called immediately after the view is inserted into the DOM. This is what you’ll use most often for running any template-specific javascript. It’s also helpful for debugging – you can place a debugger
or console.log
in didInsertElement to get more insight if things aren’t working.
willDestroyElement
is called when view is about to be removed from the DOM. You can use this for any teardown you need to do.
This isn’t related specifically to views, but I’m about to use a computed alias so I need to explain what they are. Computed aliases are essentially shorthand for grabbing properties from other objects.
You just pass Em.computed.alias
the string name of the property you want to look up. (Em.
is an alias for Ember.
). This will look up the property and watch it at the same time. For example:
App.MyController = Ember.Controller.extend name: 'Zelda' App.MyView = Ember.View.extend # this is kind of lame name: ( -> @get('controller.name') ).property('controller.name') # instead, use a computed alias name: Em.computed.alias 'controller.name'
App.MyController = Ember.Controller.extend({ name: 'Zelda' }) App.MyView = Ember.View.extend({ // this is kind of lame name: function() { this.get('controller.name') }.property('controller.name'), // instead, use a computed alias name: Em.computed.alias('controller.name') })
Ember provides a whole variety of computed functions that let you create these kind of shorthand properties. For example, Em.computed.not
returns the inverse of a boolean value. You can view the full list in the Ember API.
When I said views wrap templates, I meant it quite literally. A view will by default wrap the template in a div
with a generated ember id, like ember45
.
We can customize this wrapping element very easily.
App.UserView = Ember.View.extend tagName: 'article' classNames: ['myClass', 'anotherClass']
App.UserView = Ember.View.extend({ tagName: 'article', classNames: ['myClass', 'anotherClass'] })
tagName
specifies the html element that the view uses.
classNames
lets you add any css class names you want on the element.
If you really need to set an id on an element, you can use elementId: 'myId'
, but I recommend avoiding that if you can. Views are designed to be reused, so you may have more than one instance of the same view present on the page, and id’s are meant for single elements.
Now comes the fancy stuff. What if you want to dynamically assign class names? Ember has got your back.
App.AnimalView = Ember.View.extend classNameBindings: ['active'] active: Em.computed.alias 'controller.model.isActive'
App.AnimalView = Ember.View.extend({ classNameBindings: ['active'], active: Em.computed.alias('controller.model.isActive') })
classNameBindings
will look for the property you named and execute it. In this case a class name of active
will appear if the active
property returns true, otherwise it won’t appear.
But what if you need more control than that? Try this:
App.AnimalView = Ember.View.extend classNameBindings: ['soundClass'] soundClass: ( -> if @get('model.kind') is "cat" "meow" else "woof" ).property('model.kind')
App.AnimalView = Ember.View.extend({ classNameBindings: ['soundClass'] soundClass: function() { if (this.get('model.kind') == "cat") { return "meow" } else { return "woof" } }.property('model.kind')
In this case, since the return value is not a boolean, the return value will be used as the class name. If the return value is undefined then it won’t do anything. Here, a class of meow
will be applied if the model is a cat, and woof
if it’s a dog.
There’s actually an abbreviated version of this that you can use:
App.AnimalView = Ember.View.extend classNameBindings: ['isCat:meow:woof'] isCat: Em.computed.equal 'model.kind', 'cat'
App.AnimalView = Ember.View.extend({ classNameBindings: ['isCat:meow:woof'], isCat: Em.computed.equal('model.kind', 'cat') })
The first value after the colon will be applied if the property returns true, otherwise the second value will be applied. Note that you can omit the second value if only want a class to be applied in the first case.
Attribute bindings work similarly, though with some differences. Let’s say we’re trying to show an image, and assume that the model has an attribute called src
:
App.ImageView = Ember.View.extend tagName: 'img' attributeBindings: ['src'] src: Em.computed.alias 'controller.model.src'
App.ImageView = Ember.View.extend({ tagName: 'img', attributeBindings: ['src'], src: Em.computed.alias('controller.model.src') })
This is the simplest case of attribute bindings. Ember will create an attribute with the name of the property (src
), and the value of the attribute will be the return value of the property.
There are often cases where you want the property to have a different name than the attribute. In this case just do propertyName:attributeName
like so:
App.ImageView = Ember.View.extend tagName: 'img' attributeBindings: ['srcProperty:src'] srcProperty: Em.computed.alias 'controller.model.src'
App.ImageView = Ember.View.extend({ tagName: 'img', attributeBindings: ['srcProperty:src'], srcProperty: Em.computed.alias('controller.model.src') })
Now there will be a src
attribute that’s set to the result of srcProperty
.
A UserView
will look for a user
template, but if you want to use a different one you can specify templateName
:
App.UserView = Ember.View.extend templateName: 'someOtherTemplate'
App.UserView = Ember.View.extend({ templateName: 'someOtherTemplate' })
Often times you’ll need the current element. It’s available to you as element
, like this:
App.UserView = Ember.View.extend didInsertElement: -> console.log @get('element')
App.UserView = Ember.View.extend({ didInsertElement: function() { console.log this.get('element') } })
That would log the plain element.
Need to get the element and run some jQuery on it? You could do $(this.get('element'))
, but that’s pretty long. Ember gives you a shortcut: this.$()
.
App.UserView = Ember.View.extend didInsertElement: -> @$('.someClass').fadeOut()
App.UserView = Ember.View.extend({ didInsertElement: function() { this.$('.someClass').fadeOut() } })
This would look for .someClass
inside your current view and fade it out.
UI events like click, doubleClick, and mouseEnter are accessible by the view. Just define a function with the event name and it will get called when the event occurs:
App.UserView = Ember.View.extend click: -> console.log 'clicked' mouseEnter: -> console.log 'mouse entered' mouseLeave: -> console.log 'mouse left'
App.UserView = Ember.View.extend({ click: function() { console.log('clicked') }, mouseEnter: function() { console.log('mouse entered') }, mouseLeave: function() { console.log('mouse left') } })
The event listeners will be applied to the entire view, so clicking anywhere inside it would trigger the click function.
A full list of view events can be found in the Ember docs.
That’s it for views. Next I’ll cover templates. It’s the last chapter before we start actually building stuff, so hang in there!
Ember templates are simply Handlebars files. I’m going to cover some of the basics of Handlebars and some of the unique Handlebars helpers that Ember provides.
In our app we’re going to use Emblem, which compiles to Handlebars. To help aid your learning I will show these examples in both Handlebars and Emblem.
Templates have direct access to controller properties. Just throw ‘em in:
Handlebars:
Name: {{name}}
Emblem:
| Name: name
Emblem expects the first word on each line to be either a controller property, a Handlebars helper, or an html tag, so if you want to immediately output plain text then use a pipe: |
. Emblem parses everything after a pipe as a string.
The template also has access to view properties, but you must prefix calls to them with view
. For example:
Handlebars:
Name: {{view.someViewProperty}}
Emblem:
| Name: view.someViewProperty
Handlebars gives you if
, else
and unless
. They only accept a single argument:
Handlebars:
{{#if isBirthday}} <div class="celebrate">Happy Birthday!</div> {{else}} <div class="too_bad">Nope.</div> {{/if}}
Emblem:
if isBirthday .celebrate Happy Birthday! else .too_bad Nope.
There are no and
s or or
s allowed in Handlebars. It is designed to contain zero application logic. If you need some kind of combined boolean then you need to do it in the controller.
You can use nested if
statements, but that gets ugly quickly.
The above example really shows why I prefer Emblem. That’s 112 characters for the Handlebars version and 57 for Emblem. That’s almost one half the code! Less code, as long as it’s readable, is always a win in my book.
Handlebars offers two ways to loop through things. The first gives you access to the current object as this
:
Handlebars:
{{#each users}} {{this.name}} {{/each}}
In Emblem the call to this
is implicit. However, you still have access to this
if you need it.
each users name
The second way to do loops is to name the object:
Handlebars:
{{#each user in users}} {{user.name}} {{/each}}
Emblem:
each user in users user.name
If you are iterating over records from an ArrayController
, which is extremely common, you just pass it controller
:
Emblem:
each user in controller user.name
I think by now you may be getting the idea of Handlebars, so I’m going to switch to just showing Emblem.
Templates come with helpers that allow you to render another controller, view or template. This helps you reuse and compartmentalize logic.
The render
helper calls a controller:
render 'user'
This will look for UserController
and instantiate it. The controller will then look for UserView
, and a user
template, as per the usual Ember Object Flow.
You can optionally pass the render method a model object:
render 'user' model
The view
helper calls a view:
view 'user'
This would look for UserView
, which would then look for a template named user
. Using the view
helpers means that Ember will not instantiate a controller, it will skip it.
The partial
helper only calls a template:
partial 'user'
This would render the user
template inside the current template. It would not use the controller or view. Unlike Rails, Ember does not expect your template to be prefixed with an underscore.
As you can see, the render
, view
, and partial
helpers enable you to compartmentalize code as much as you need. If you only need to show additional markup, just use partial
. If you need markup with some javascript attached, use view
. If you need markup that has access to its own set of properties and actions, then you’ll need to use render
.
The Ember docs provide a great comparison table that helps explain how these helpers differ.
You can specify what are called actions on any element in a template. Actions will call a function in the controller of the same name:
h1 click="tickle" Tickle Me
This would call a tickle
method in the controller when a user clicks on the h1
. The method must be defined inside an actions
object:
App.MyController = Ember.Controller.extend actions: tickle: -> alert('hahaha')
App.MyController = Ember.Controller.extend({ actions: { tickle: function() { alert('hahaha') } } })
Ember provides a link-to
helper that will transition you to a different route. You pass it the name of the route and any models that you need to send along.
Say you had an array controller that gave you a list of users, and you wanted to provide a link to each one. Here’s how you’d do that:
each userRecord in controller link-to 'user' userRecord userRecord.name
Ok, now that we’ve covered Routes, Controllers, Views and Templates, we can actually build something!
Now we’re actually going to code.
We are going to build a CRM. This is a good project for Ember because we’ll be searching, listing, showing, creating, and editing records. If you can build this app and understand how it works then you will be well on your way to developing your own complex front-end apps.
You can look at a completed version of the app to see what you’ll be building. The code is also on GitHub in case you need a reference: CoffeeScript Code, Javascript Code.
The main object in this system is the lead. If you don’t know the term, a lead is the same thing as a potential customer.
You can use the Hello World app we built earlier as the base for this app. I’m going to provide you with styles so that you can just focus on the Ember stuff:
Download this stylesheet and save it to app/assets/stylesheets/application.css
, replacing your existing application.css
.
If you use Vim, there’s a very useful plugin called vim-projectionist that provides some help in navigating files. Vim projections are great for quickly opening and creating Ember files.
Here’s are Gists with the projections that I use: Coffeescript Projections or Javascript Projections. Once you’ve got vim-projectionist installed, just copy that file into config/projections.json
and restart Vim.
These projections use Ej
as a prefix for all things Ember, so you have Ejmodel
, Ejcontroller
, Ejview
, etc. Ejini
will open your Ember router, which is consistent with Eini
opening your Rails router.
If you don’t use Vim, consider learning it. It will probably improve your programming experience. You can open a terminal and type vimtutor
to get an instant tutorial. You might also check out VimGenius, a little app I built to help you remember commands. Lastly, I wrote a Vim Tutorial that will help you get to an intermediate level.
Let’s use the AutoLocation API to get rid of hashes in our urls. We also need to set the rootURL to '/'
so Ember knows where to start parsing the url from.
Open your router and add the following to the top:
# app/assets/javascripts/router.js.coffee App.Router.reopen location: 'auto' rootURL: '/'
// app/assets/javascripts/router.js App.Router.reopen({ location: 'auto', rootURL: '/' })
We also need to create a catch-all Rails route to handle whatever arbitrary routes we create in Ember, otherwise we’ll get a 404 when we try to reload the page on an Ember subroute:
# config/routes.rb # add this to the bottom of your Rails routes get '*path', to: 'home#index'
We need a Rails API for Ember to communicate with in order to store and retrieve data.
When we used the Ember Rails generate command to setup Ember it added what’s called the Active Model Adapter. You’ll see this if you open the store file:
# app/assets/javascripts/store.js.coffee App.Store = DS.Store.extend() App.ApplicationAdapter = DS.ActiveModelAdapter.extend()
// app/assets/javascripts/store.js App.Store = DS.Store.extend({}); App.ApplicationAdapter = DS.ActiveModelAdapter.extend({});
If you don’t see this code then replace whatever is in your store with this.
The Active Model Adapter enables Ember to communicate with your Rails backend through Active Model Serializers. Normally you would need to include the active_model_serializers
gem, but Ember Rails already has it a as a dependency.
We need to tell Ember to prepend all API requests with api/v1/
, as we’ll be versioning our API. Add these two lines to the top of your store file:
# app/assets/javascripts/store.js.coffee DS.RESTAdapter.reopen namespace: 'api/v1'
// app/assets/javascripts/store.js DS.RESTAdapter.reopen({ namespace: 'api/v1' })
First let’s create our leads:
rails g migration create_leads first_name:string last_name:string email:string phone:string status:string notes:text
Open up the migration and make sure to add timestamps:
class CreateLeads < ActiveRecord::Migration def change create_table :leads do |t| t.string :first_name t.string :last_name t.string :email t.string :phone t.string :status t.text :notes t.timestamps end end end
Run the migration:
rake db:migrate
Now create the Rails model:
# app/models/lead.rb class Lead < ActiveRecord::Base end
Add the serializer. You need to list out all the attributes you want to serialize into JSON and send to Ember:
# app/serializers/lead_serializer.rb class LeadSerializer < ActiveModel::Serializer attributes :id, :first_name, :last_name, :email, :phone, :status, :notes end
Now that we have our model and serializer, we can create the API controller.
First we need routes for the API controller. Add them to the top of your Rails router:
# config/routes.rb namespace :api do namespace :v1 do resources :leads end end
Now create the controller. The actions here are fairly standard:
# app/controllers/api/v1/leads_controller.rb class Api::V1::LeadsController < ApplicationController respond_to :json def index respond_with Lead.all end def show respond_with lead end def create respond_with :api, :v1, Lead.create(lead_params) end def update respond_with lead.update(lead_params) end def destroy respond_with lead.destroy end private def lead Lead.find(params[:id]) end def lead_params params.require(:lead).permit(:first_name, :last_name, :email, :phone, :status, :notes) end end
Let’s create some records and see our API actually work.
First add the ffaker gem:
# Gemfile gem 'ffaker'
Then create a populate task:
# lib/tasks/populate.rake namespace :db do task populate: :environment do Lead.destroy_all def random_status ['new', 'in progress', 'closed', 'bad'].sample end 20.times do Lead.create( first_name: Faker::Name.first_name, last_name: Faker::Name.last_name, email: Faker::Internet.email, phone: Faker::PhoneNumber.phone_number, status: random_status, notes: Faker::HipsterIpsum.words(10).join(' ') ) end end end
I’m using Hipster Ipsum for the notes to spice things up.
Run the populate task:
rake db:populate
Restart the server, and now you should be able to visit http://localhost:3000/api/v1/leads.json and see the JSON output for all leads. http://localhost:3000/api/v1/leads/1.json should show you the first lead.
While we’re fiddling with Rails let’s switch out Webrick for Puma. It’s much faster and it’s multithreaded. All you have to do is add Puma to your Gemfile:
# Gemfile gem 'puma'
Bundle and restart your server.
That’s it for the Rails side! Now we can get to the fun stuff.
We need to create our layout so we can build stuff inside it. Let’s do that now.
Your Rails layout should look like this to start:
<!-- app/views/layouts/application.html.erb --> <!DOCTYPE html> <html> <head> <title>Ember CRM</title> <%= stylesheet_link_tag 'application' %> <%= javascript_include_tag 'application' %> <%= csrf_meta_tags %> </head> <body> <%= yield %> </body> </html>
Ember will insert itself at the end of your body tag by default. However, you can tell Ember to render into a specific element. Let’s do that:
First, create a div with the id ember-app
.
<body> <%= yield %> <div id="ember-app"></div> </body>
Now, open up application.js.coffee
and tell Ember that the rootElement
is ember-app
:
Now, open up application.js
and tell Ember that the rootElement
is ember-app
:
window.App = Ember.Application.create(rootElement: '#ember-app')
App = Ember.Application.create({rootElement: '#ember-app'})
Now Ember will render everything inside the ember-app
div.
The Rails view you created for Hello World should still exist but remain empty.
Now we face an interesting question. You will face questions like this often as you build with Ember inside Rails. Should we build the layout in Ember or in Rails? This really depends.
If your Ember app is just on a single page, then it might make sense to build the layout in Rails so that it can be used on other non-Ember pages. But if your Ember app truly has multiple pages, and the layout links to these other Ember pages, then it makes sense to do it in Ember.
In our app we only have a single page with Ember, so we could build our layout in Rails and be just fine. However, this tutorial is all about learning Ember, so let’s do it in Ember anyway.
Ember always renders the application
template, so we’ll use that for the layout:
// app/assets/javascripts/templates/application.js.emblem header article .logo h1 a href="#" Ember CRM section#main = outlet footer
Now if you refresh you should see the orange Ember CRM banner across the top. Next we’ll be dealing with getting and showing data.
To handle data we’ll be using Ember Data. We already installed it in Hello World so we don’t have do anything to start using it. Although there are a few different data adapters for Ember, Ember Data is the standard.
We have a Lead model in Rails but Ember needs to know about leads too. To do that we’ll create a lead model in Ember. Ember Data gives us the DS.Model
object which we’ll extend:
# app/assets/javascripts/models/lead.js.coffee App.Lead = DS.Model.extend firstName: DS.attr('string') lastName: DS.attr('string') email: DS.attr('string') phone: DS.attr('string') status: DS.attr('string', defaultValue: 'new') notes: DS.attr('string')
// app/assets/javascripts/models/lead.js App.Lead = DS.Model.extend({ firstName: DS.attr('string'), lastName: DS.attr('string'), email: DS.attr('string'), phone: DS.attr('string'), status: DS.attr('string', { defaultValue: 'new' }), notes: DS.attr('string'), })
Ember will automatically read in the json api’s first_name
to firstName
, and etc. across the rest of our attributes. We’re only using the string
data type here. The other ones available to you are number
, boolean
, and date
.
DS.Model gives you a variety of useful methods and properties. Here are a few methods that you will use often:
model.save() # save changes to the database model.rollback() # wipe clean any unsaved changes model.destroyRecord() # delete a record from the database
model.save() // save changes to the database model.rollback() // wipe clean any unsaved changes model.destroyRecord() // delete a record from the database
Note that DS.Model extends Ember.Object, so all of the things you learned about Ember Objects will still work with DS.Model. For example, you can set an arbitrary property on a DS.Model instance like so:
model.set('myProperty', 'hello!')
model.set('myProperty', 'hello!');
The main difference between DS.Model instances and regular Ember.Object instances is that you cannot create them the same way. To create a new DS.Model instance you have to go through the store
:
@store.createRecord('modelName', firstName: 'John', lastName: 'Snow')
this.store.createRecord('modelName', { firstName: 'John', lastName: 'Snow' });
This is because the store
encapsulates your app’s knowledge of all the active DS.Model instances, and the store has to do additional work when you create a model instance.
The first thing we’ll do on the Ember side is list out all of our leads.
Everything always starts with routes. Open up your Ember Router and create a leads resource with the path set to root:
# app/assets/javascripts/router.js.coffee App.Router.map -> @resource 'leads', path: '/'
// app/assets/javascripts/router.js App.Router.map(function() { this.resource('leads', { path: '/' }) })
Next we need to fetch all lead records. Let’s create a LeadsRoute
:
# app/assets/javascripts/routes/leads.js.coffee App.LeadsRoute = Ember.Route.extend model: -> @store.find 'lead'
// app/assets/javascripts/routes/leads.js App.LeadsRoute = Ember.Route.extend({ model: function() { return this.store.find('lead') } })
Remember that model
is a hook that’s called whenever the route is entered. The result of the model function is then available to the controller, view, and template.
To be sure this is working properly, simply visit your root route and look at the “Data” tab in the Ember Inspector. You should see all of your leads.
Now that we have our leads we need to show them. Let’s create a template:
// app/assets/javascripts/templates/leads.js.emblem article#leads h1 Leads ul each lead in controller li= lead.firstName
Now refresh the page and you should see your leads' listed on the left. Cool, right!?
Let’s create a property to display the leads' full names. Open the lead model and add it:
# app/assets/javascripts/models/lead.js.coffee fullName: ( -> @get('firstName') + ' ' + @get('lastName') ).property('firstName', 'lastName')
// app/assets/javascripts/models/lead.js fullName: function() { return this.get('firstName') + ' ' + this.get('lastName') }.property('firstName', 'lastName')
Then modify the template:
li= lead.fullName
Our leads are not in any particular order. This is chaos! Let’s sort them by name.
You can sort arrays of models by specifying sortProperties
in the controller. We don’t have a controller yet, so let’s make one:
# app/assets/javascripts/controllers/leads.js.coffee App.LeadsController = Ember.ArrayController.extend sortProperties: ['firstName', 'lastName']
// app/assets/javascripts/controllers/leads.js App.LeadsController = Ember.ArrayController.extend({ sortProperties: ['firstName', 'lastName'] })
sortProperties
takes an array of strings. These strings are the properties you want to sort by with the highest priority first.
Note that I’ve made this controller an ArrayController
. If you remember from the Controllers chapter, this is because the controller wraps an array of leads. Ember expects you to do this because ArrayController
defines certain things like sortProperties
that are not available on regular Controller
instances.
Refresh the page and marvel at the beauty of your sorted leads.
Next we’ll show each lead’s data when when we click on it.
When we click on a lead we should see it appear on the right. Let’s do it.
First we’ll need to add a resource to show a specific lead. Since we want the list of leads to still remain present on the page when we show a lead, this resource should be nested under the leads resource.
# app/assets/javascripts/router.js.coffee App.Router.map -> @resource 'leads', path: '/', -> @resource 'lead', path: '/leads/:id'
// app/assets/javascripts/router.js App.Router.map(function() { this.resource('leads', { path: '/' }, function() { this.resource('lead', { path: '/leads/:id' }); }) })
Make sure to add , ->
after the leads resource.
We need a Route Object to pull down our specific lead.
# app/assets/javascripts/routes/lead.js.coffee App.LeadRoute = Ember.Route.extend model: (params) -> @store.find 'lead', params.id
// app/assets/javascripts/routes/lead.js App.LeadRoute = Ember.Route.extend({ model: function(params) { return this.store.find('lead', params.id) } })
We have access to id
through the params
argument.
The this.store.find
function gets a record by its id. It actually returns a promise which Ember will attempt to resolve.
Our template will show the information about our lead.
// app/assets/javascripts/templates/lead.js.emblem article#lead h1= model.fullName p ' Name: model.fullName p ' Email: model.email p ' Phone: model.phone
The single quote '
leaves a trailing whitespace after the line.
Open your leads template so you can create a link to each lead:
// app/assets/javascripts/templates/leads.js.emblem article#leads h1 Leads ul each lead in controller link-to 'lead' lead tagName="li" lead.fullName outlet
Two things are happening here.
First, our li
became a link-to
. We passed it tagName="li"
so that the html element will be an li
. You can set any property on a view this same way, propertyName="value"
, when you are using the view helper.
Second, we’ve placed an outlet
tag at the end of the template. Since the lead route is nested under leads, its content will appear in this outlet. In this case the markup is on the same level in the DOM so this doesn’t present a problem. If you had a situation where you needed to output the content elsewhere on the page you could use the renderTemplate method in the route to specify an outlet somewhere else.
One nice thing about the link-to
helper is that it automatically adds a class of active
to the element when you are on the route it’s linking to. Our stylesheet takes advantage of this to highlight the currently selected lead.
Now refresh the page and click on a lead. You should see that lead’s information show up on the right, and it should be snappy.
Next we’ll create form elements and save data.
There are two places to edit a lead in the app. We have status and notes which are immediately visible on lead show, and we can also click an “edit” link that will let us edit the lead’s basic info like name, email, and phone.
Let’s start with the status and notes. These will be additions to our existing lead template.
First we need to tell the app about the different statuses a lead can have. The Lead
model should know about this, but it’s more class knowledge than instance knowledge, so lets use reopenClass
to add it to the class.
Add this code after your Lead
model in the same file:
# app/assets/javascripts/models/lead.js.coffee App.Lead.reopenClass STATUSES: ['new', 'in progress', 'closed', 'bad']
// app/assets/javascripts/models/lead.js App.Lead.reopenClass({ STATUSES: ['new', 'in progress', 'closed', 'bad'] });
You will now be able to get to this array through App.Lead.STATUSES
.
Open the lead template and add the following to the bottom, in line with the other p
tags:
// app/assets/javascripts/templates/lead.js.emblem p label Status: ' view Ember.Select content=App.Lead.STATUSES value=model.status
Ember provides a built in select view which we can use. We point content
to our status array, and we point value
to our lead’s status. Now this select will be automatically bound to the lead model’s status.
Add this code after the status select:
// app/assets/javascripts/templates/lead.js.emblem p label Notes: br view Ember.TextArea value=model.notes
Here we use Ember’s text area view and bind it to model.notes
.
Add a submit button below the notes field:
// app/assets/javascripts/templates/lead.js.emblem p input type='submit' value='Save Changes' click='saveChanges'
The click
attribute specifies a function to call when you click this button. Ember expects the controller to define this function.
Now everything in our form is wired up except the submit button.
Since we’re in the middle of the lead object flow, Create a LeadController
to handle the saveChanges
click action:
# app/assets/javascripts/controllers/lead.js.coffee App.LeadController = Ember.ObjectController.extend actions: saveChanges: -> @get('model').save()
// app/assets/javascripts/controllers/lead.js App.LeadController = Ember.ObjectController.extend({ actions: { saveChanges: function() { this.get('model').save(); } } });
Note that this controller is an ObjectController
because it wraps a single lead model.
Our submit button will call the saveChanges
function. This function must be within actions
since it is being called from a template action. This is an Ember convention to help keep your code organized.
save()
will actually send an ajax put request to our Rails API, and everything should just work. Try it out in your browser. Edit a record, click “Save Changes”, and look at the Network tab in the Chrome console. You should see a put request to api/v1/leads/(id of record)
. If you refresh the page the saved record should show your changes.
Note: If you get a 422 error stating “Invalid Authenticity Token” then check to make sure that you’ve required jquery_ujs
in your application javascript.
Saving is that easy! Just bind and call save()
. It’s fun and it’s healthy.
Ember will always send an API request on save, even if the record isn’t dirty. You can prevent this behavior with a simple if:
saveChanges: -> @get('model').save() if @get('model.isDirty')
saveChanges: function() { if (this.get('model.isDirty')) this.get('model').save(); }
Now we’re going to get fancy and give the user some nice feedback around saving. When there are unsaved changes we will show a message saying “unsaved changes”, and when the record is actually saving we’ll show “saving…”.
Add these messages immediately below the submit button:
// app/assets/javascripts/templates/lead.js.emblem p input type='submit' value='Save Changes' click="saveChanges" if isDirty .unsaved unsaved changes if isSaving .saving saving...
Ember will look in the controller to find isDirty
and isSaving
. If the controller doesn’t find them it will look in the model. Both isSaving
and isDirty
come built-in with DS.Model
, so we are good.
Try it out it in the browser. You should see “unsaved changes” appear in red next to the save button if you add anything to notes or change the status of a lead. Click “save changes” to see “saving…” in green. It will probably appear only briefly – since you are in development the server is going to be quick.
We have a little problem here. “unsaved changes” is still visible while you are saving a record. You can see this more easily if you add sleep 1
to the top of your update
action in the leads API controller. This is because the record remains dirty until saving is completed, as you would expect. Let’s do some work to hide this text while saving.
We’ll make a property called showUnsavedMessage
. Replace isDirty
with showUnsavedMessage
in the template:
// app/assets/javascripts/templates/lead.js.emblem if showUnsavedMessage .unsaved unsaved changes
Open the controller and add the property:
# app/assets/javascripts/controllers/lead.js.coffee App.LeadController = Ember.ObjectController.extend showUnsavedMessage: ( -> @get('isDirty') and !@get('isSaving') ).property('isDirty', 'isSaving')
// app/assets/javascripts/controllers/lead.js App.LeadController = Ember.ObjectController.extend({ showUnsavedMessage: function() { return this.get('isDirty') && !this.get('isSaving') }.property('isDirty', 'isSaving'), // actions, etc... })
Our new logic will return false when the record is saving, giving us the result we want.
That’s it for editing part one. Onwards and upwards!
Now let’s finish the second half of editing. In this scenario a user clicks on the “edit” link and sees fields to edit the lead’s name, phone, and email.
This UI will need to replace the UI that shows the lead, so we’ll have to do some special work to handle that.
As always, add a route first. We will place an edit
route under our existing lead
resource. The lead
resource handles fetching the lead. We shouldn’t repeat that work if we don’t need to.
# app/assets/javascripts/router.js.coffee @resource 'lead', path: 'leads/:id', -> @route 'edit'
// app/assets/javascripts/router.js this.resource('lead', { path: 'leads/:id' }, function() { this.route('edit') })
Make sure to add , ->
after the lead resource.
This route is going to look for a LeadEdit
controller, view, and template.
I’m going to add the template first because it will inform us about what actions we need to handle in the controller.
Since this route
is nested inside a resource
, Ember expects the template to be inside a subdirectory with the name of the resource. So this template will be app/assets/javascripts/templates/lead/edit.js.emblem
.
If you’re not sure where to place a template just look in the Ember Inspector’s “Routes” tab.
// app/assets/javascripts/templates/lead/edit.js.emblem article#lead h1 model.fullName form fieldset dl dt: label First Name: dd: view Ember.TextField value=model.firstName dl dt: label Last Name: dd: view Ember.TextField value=model.lastName dl dt: label Email: dd: view Ember.TextField value=model.email dl dt: label Phone: dd: view Ember.TextField value=model.phone fieldset.actions input type='submit' value='Save Changes' click="saveChanges" a.cancel href="#" click="cancel" cancel
You can use a colon :
in Emblem to nest elements on the same line.
Ember gives you the Ember.TextField
view which renders a text input. Just assign value
to the property you want to bind to.
The form
tag doesn’t actually do anything, it’s just there for markup.
This template has two actions: saveChanges
and cancel
. Let’s implement them now.
# app/assets/javascripts/controllers/lead_edit.js.coffee App.LeadEditController = Ember.ObjectController.extend actions: saveChanges: -> @get('model').save().then => @transitionToRoute 'lead' cancel: -> @get('model').rollback() @transitionToRoute 'lead'
// app/assets/javascripts/controllers/lead_edit.js App.LeadEditController = Ember.ObjectController.extend({ actions: { saveChanges: function() { var self = this; this.get('model').save().then(function() { self.transitionToRoute('lead'); }) }, cancel: function() { this.get('model').rollback(); this.transitionToRoute('lead'); } } })
This part is fairly simple, though you might not be familiar with .then
. save()
returns a Promise
object, which we can call .then
on to execute code when the promise is resolved. Basically what this means is that transitionToRoute
won’t be called until the server has confirmed that the model was saved.
We need a way to get to our new route. Add a link inside the h1
tag in the lead
template. Don’t worry, the stylesheet will make it look pretty.
// app/assets/javascripts/templates/lead.js.emblem h1 model.fullName link-to 'edit' 'lead.edit' model classNames='edit'
The first argument, 'edit'
, is the link text. The second, 'lead.edit'
, is the route name.
Open the browser and try clicking the edit link. You should see the URL change, but nothing else should happen. Can you figure out why?
Outlets, don’t forget about them. If a template doesn’t appear, always ask yourself: did I add an outlet?! If you’re like me, you’ll forget one at some point and be super annoyed that your template isn’t showing up.
Our edit
route is nested under lead
so Ember is trying to render the template into an outlet
tag inside the lead
template. Since it can’t find it, nothing happens.
Add an outlet to the top of the lead
template:
// app/assets/javascripts/templates/lead.js.emblem outlet
Now try it. It should work, but now we have a new problem: the show UI for a lead is still present. That’s because nested routes means nested UI. Since the lead
resource is still active, the UI is still active.
There’s a simple fix to this – we’ll just hide the show UI when we’re editing.
Now we’ll set an isEditing
property on the lead
controller to hide the UI we don’t want to see when it’s true.
First add unless isEditing
to the template and indent all the show UI under it:
// app/assets/javascripts/templates/lead.js.emblem outlet unless isEditing article#lead h1 fullName link-to 'edit' 'lead.edit' model classNames='edit' // etc...
Now whenever we visit the edit route we need to set isEditing
to true. We can do that inside the LeadEdit
route. We haven’t made one yet, so do it now:
# app/assets/javascripts/routes/lead_edit.js.coffee App.LeadEditRoute = Ember.Route.extend activate: -> @controllerFor('lead').set 'isEditing', true deactivate: -> @controllerFor('lead').set 'isEditing', false
// app/assets/javascripts/routes/lead_edit.js App.LeadEditRoute = Ember.Route.extend({ activate: function() { this.controllerFor('lead').set('isEditing', true) }, deactivate: function() { this.controllerFor('lead').set('isEditing', false) } })
Now we see those route hooks coming in handy! On activate
we get the LeadController
and set isEditing
to true. On deactivate
we do the opposite. And boom, we’re done.
We could do one last thing for clarity – add isEditing
to the LeadController
and default it to false.
# app/assets/javascripts/controllers/lead.js.coffee App.LeadController = Ember.ObjectController.extend isEditing: false #etc...
// app/assets/javascripts/controllers/lead.js App.LeadController = Ember.ObjectController.extend({ isEditing: false, // etc... })
This way future programmers (or our future selves) will know that we have an isEditing
property on this controller and it should be false
by default. We don’t have to do this but I think it’s good style.
Now that we can edit everything about leads I’ll show you how to delete them.
This may be the shortest chapter. Deleting in Ember is typically handled by an action.
Open up the lead
template and add a delete link immediately above the submit button:
// app/assets/javascripts/templates/lead.js.emblem p a.delete href='#' click="delete" delete input type='submit' value='Save Changes' click="saveChanges" # etc ...
Template actions are handled in the controller, so open up the LeadController
and add a delete
action:
# app/assets/javascripts/controllers/lead.js.coffee actions: delete: -> @get('model').destroyRecord().then => @transitionToRoute 'leads'
// app/assets/javascripts/controllers/lead.js actions: { delete: function() { var self = this; this.get('model').destroyRecord().then(function() { self.transitionToRoute('leads'); }); } }
We call destroyRecord()
on the model, which sends a DELETE
request to the server. After we delete the record we need to transition back to the leads
route.
And that’s it!
Next I’ll cover adding a new lead.
By now you know the drill…
# app/assets/javascripts/router.js.coffee @resource 'leads', path: '/', -> @route 'new'
// app/assets/javascripts/router.js this.resource('leads', { path: '/' }, function() { this.route('new'); });
New lead will be a route
and not a resource
because it does not need to load an existing model in our system.
We’re going to create a fields
property that will be a plain javascript object. We’ll use this to hold the attributes for the new lead until we’re ready to actually create it.
We set fields
to an empty object in the route so that it’s reset every time we visit the new lead route.
# app/assets/javascripts/routes/leads_new.js.coffee App.LeadsNewRoute = Ember.Route.extend setupController: (controller) -> controller.set 'fields', {}
// app/assets/javascripts/routes/leads_new.js App.LeadsNewRoute = Ember.Route.extend({ setupController: function(controller) { controller.set('fields', {}) } });
And here’s our new lead template. Note that it goes in templates/leads/
because it’s a route
nested under a resource named leads
.
// app/assets/javascripts/templates/leads/new.js.emblem article#lead h1 New Lead form fieldset dl dt: label First Name: dd: view Ember.TextField value=fields.firstName dl dt: label Last Name: dd: view Ember.TextField value=fields.lastName dl dt: label Email: dd: view Ember.TextField value=fields.email dl dt: label Phone: dd: view Ember.TextField value=fields.phone fieldset.actions input type='submit' value='Create Lead' click="createLead"
As you can see, I’ve bound all of the inputs to the fields
property.
The submit has a click action called createLead
, which we’ll deal with now.
Create a controller to handle the createLead
action:
# app/assets/javascripts/controllers/leads_new.js.coffee App.LeadsNewController = Ember.Controller.extend actions: createLead: -> lead = @store.createRecord 'lead', @get('fields') lead.save().then => @transitionToRoute 'lead', lead
// app/assets/javascripts/controllers/leads_new.js App.LeadsNewController = Ember.Controller.extend({ actions: { createLead: function() { var self = this; var lead = this.store.createRecord('lead', this.get('fields')); lead.save().then(function() { self.transitionToRoute('lead', lead); }); } } });
This action first calls createRecord
, which we pass the string name of the model and an object with the attributes we want to give the new model. Since we bound fields
to all the attributes we can just use it as is. If you logged fields
you would see something like { firstName: 'Sam', lastName: 'Smith', email: 'sam@example.com', phone: '123-456-7890' }
.
Once the record is created we save it, then transition to the show lead route.
There’s another common pattern to create new records that I didn’t use. I could have created a new record in the route and bound the inputs to the record. The reason I didn’t do this is because I didn’t want the record to show up in our list of leads on the left until the user pressed “Create Lead”. By keeping the attributes in fields
and waiting until createLead
is called to create a record, the record won’t appear in the list until the user has chosen to create it.
Add a link to our new lead route in the leads
template:
// app/assets/javascripts/templates/leads.js.emblem article#leads h1 | Leads link-to 'leads.new' | New Lead
Now refresh and try it. Everything should work, but it’s not perfect. You can create leads where every attribute is null. We should perform a validation here to prevent this.
Validation libraries for Ember exist and if you want to use one I recommend Dockyard’s Ember Validations library. However, given Ember’s robust object system and the large amount of control it gives you over the client, you don’t necessarily need a library.
I’m going to do these validations by hand. There are any number of different ways to do validations, this is just one.
First let’s define a valid method on the Lead class. Let’s say we just care that a first and last name are present:
# app/assets/javascripts/models/lead.js.coffee App.Lead.reopenClass valid: (fields) -> fields.firstName and fields.lastName
// app/assets/javascripts/models/lead.js App.Lead.reopenClass({ valid: function(fields) { return fields.firstName && fields.lastName } });
We pass this method an object with the attributes we want to assign to a lead, and it tells us if this collection of attributes is valid or not.
Now we need to modify our createLead
method in the new lead controller:
# app/assets/javascripts/controllers/leads_new.js.coffee createLead: -> fields = @get('fields') if App.Lead.valid(fields) lead = @store.createRecord 'lead', fields lead.save().then (lead) => @transitionToRoute 'lead', lead else @set 'showError', true
// app/assets/javascripts/controllers/leads_new.js createLead: function() { var self = this; var fields = this.get('fields') if (App.Lead.valid(fields)) { var lead = this.store.createRecord('lead', fields) lead.save().then(function(lead) { self.transitionToRoute('lead', lead) }); } else { this.set('showError', true) } }
We check to see if these fields are valid. If they are, create the record. If they aren’t, set the showError
property to true. Now we can use the showError
property to display a message to the user:
// app/assets/javascripts/templates/leads/new.js.emblem article#lead h1 New Lead if showError .error Leads must have a first and last name.
One last thing: controller instances remain active, so if you created this error, went to another route, then came back, the showError
property would still be true. We don’t want that, so we need to default it to false in the route on setupController
:
# app/assets/javascripts/routes/leads_new.js.coffee setupController: (controller) -> # etc... controller.set 'showError', false
// app/assets/javascripts/routes/leads_new.js setupController: function(controller) { // etc... controller.set('showError', false) }
Now if you create the error, leave, then come back, the form should be fully reset.
That’s it for adding leads. This chapter feels a bit dry, but creating new records is probably something you’ll do a lot so it’s good to know.
The next chapter is more exciting: we’re going to instantly search leads.
Let’s create a search box that will instantly search leads by name as we type.
The neat thing about this is that it can be accomplished with a surprisingly small amount of code in a very clean manner. That, my friend, is the beauty of Ember.
Add a text field view at the top of the list of leads, right under the h1
:
// app/assets/javascripts/templates/leads.js.emblem article#leads h1 | Leads link-to 'leads.new' | New Lead view Ember.TextField value=search placeholder="search" classNames="search" ul # etc ...
I’m binding the text field’s value to a property named search
.
Right now we’re doing each lead in controller
. We need to manually modify the list of leads, so change it to each lead in leads
, and we’ll create a leads
property on the controller.
// app/assets/javascripts/templates/leads.js.emblem ul each lead in leads # etc ...
Open up LeadsController
and add the following two properties:
# app/assets/javascripts/controllers/leads.js.coffee leads: ( -> if @get('search') then @get('searchedLeads') else @ ).property('search', 'searchedLeads') searchedLeads: ( -> search = @get('search').toLowerCase() @filter (lead) => lead.get('fullName').toLowerCase().indexOf(search) != -1 ).property('search', '@each.fullName')
// app/assets/javascripts/controllers/leads.js leads: function() { return this.get('search') ? this.get('searchedLeads') : this }.property('search', 'searchedLeads'), searchedLeads: function() { var search = this.get('search').toLowerCase() return this.filter(function(lead) { return lead.get('fullName').toLowerCase().indexOf(search) != -1 }) }.property('search', 'this.@each.fullName')
The leads
property looks to see if there is a search string. If there is, it returns searchedLeads
. If there isn’t, it returns this
. this
in an ArrayController
references the array of models that it is wrapping.
searchedLeads
gets the search string and lower cases it. It then runs filter
on this
, which is the list of leads, and returns the leads where the full name includes the search string.
searchedLeads
needs to depend on `this.@each.fullName‘, which means that the property will be updated whenever the full name of any lead changes.
It should work right now. There are two cool things to notice here.
First, as you search the list of names they stay sorted by first and last name. That’s sortProperties
in action.
Second, try clicking on a lead, then entering a search string that doesn’t match. Then, edit the lead’s name to something that matches and watch it appear in the search list. That’s pretty cool, and it’s a good example of how everything is properly bound together.
The world should see our masterpiece. If you use Git and Heroku then you can deploy your app very quickly.
First add the rails_12factor
gem that Heroku likes you to have. Bundle and commit. Then run these commands:
heroku create <whatever-name-you-want> git push heroku master heroku run rake db:migrate db:populate heroku open
And that’s it!
I hope that this tutorial provided a good introduction to the Ember framework and was maybe even fun along the way. Please don’t hesitate to let me know if anything is unclear or needs further explanation.
If you want to keep learning I recommend reading through the Ember Guides and the Ember API Docs. They are very detailed and they do an excellent job explaining all the nuances of Ember.
Beyond that, I think building your own applications is the best way to learn.
I’m considering adding more to the tutorial to cover more Ember topics. If you’d like this then let me know. Also, if there’s anything in particular that you think would be cool to add then please tell me.
This tutorial is on GitHub, so you could even add a chapter yourself if you want to!
Thanks for giving my tutorial a read, and best of luck on your Ember journey!