Building an App with Ember App Kit - Part 2

Oct 30, 2013

In this installment, we'll get started with the Ember app.

Demo | Ember Code | Rails Code

In part 1, we introduced the app itself and got started with the Rails API side of things. In this part, we’ll focus on setting up the Ember app.

Part 2 - Ember Setup

First we need to start a new Ember App Kit project. Currently, the easiest way to do this is to simply clone the EAK repo. This process will likely improve in the future, but for now it’s easy enough:

git clone https://github.com/stefanpenner/ember-app-kit.git contacts-client
cd contacts-client
rm -rf .git

Notice that we are removing the existing .git directory as it contains the full history of the EAK project. You can then go ahead and git init for a fresh start.

We are going to need the Grunt command line tool, as well as Bower, to get up and running. Make sure you have Node installed, then:

npm install -g grunt-cli
npm install -g bower

Once those are both installed, we need to install the project’s dependencies:

npm install
bower install

Before moving on, let’s make sure everything is working correctly. Start the server:

grunt server

Visit localhost:8000 and you should see something like this:

EAK Welcome

Nice! That wasn’t so bad.

Now open up app/router.js. We are going to use the history location here. Go ahead and get rid of the existing routes and just add a contacts resource. It should look like this:

1 var Router = Em.Router.extend({
2   location: 'history'
3 });
4 
5 Router.map(function(){
6   this.resource('contacts');
7 });
8 
9 export default Router;

Next, open up app/routes/index.js and change it to:

1 var IndexRoute = Ember.Route.extend({
2   redirect: function() {
3     this.transitionTo('contacts');
4   }
5 });
6 
7 export default IndexRoute;

When the app is entered, we just want it to redirect to contacts right away.

Let’s setup the basic HTML/CSS for the app. We aren’t going to spend much time worrying about style, or responsiveness, or anything like that here. We want to focus on building the app. However, I want to at least point out how you can use SCSS in your project:

npm install --save-dev grunt-sass

And then just rename app.css to app.scss. That’s it. Moving on.

I’m not going to show any CSS here as it will just clutter things up. You’ll wanna copy it over from the github if you are following along. Let’s start with application.hbs:

1 <div id="header">
2   <a href="#" class="left" id="logo">Contacts</a>
3   <a href="#" class="right">New Contact</a>
4 </div>
5 <div id="main">
6   {{outlet}}
7 </div>

Simple enough. The links are just stubbed out for now. We’ll fill them in as we go along. Go ahead and create templates/contacts.hbs now. We are going to do a basic master-detail UI so let’s try to get our list of contacts showing first. Here is what the contacts template should look like:

 1 <div id="master">
 2   <ul>
 3   {{#each}}
 4     <li class="contact">
 5       <a href="#">{{name}}</a>
 6     </li>
 7   {{/each}}
 8   </ul>
 9 </div>
10 <div id="detail">
11   {{outlet}}
12 </div>

Again, the link is stubbed out for now but we’ll come back to that.

Things start to get a little more interesting now as we finally need to have our Ember app talk to our Rails app. The first step is creating our Ember Data model:

1 var Contact = DS.Model.extend({
2   name: DS.attr('string'),
3   twitter: DS.attr('string'),
4   notes: DS.attr('string')
5 });
6 
7 export default Contact;

Next, create app/routes/contacts.js in order to fetch the data:

1 var ContactsRoute = Ember.Route.extend({
2   model: function() {
3     return this.store.find('contact');
4   }
5 });
6 
7 export default ContactsRoute;

This may look a little foreign to you if you haven’t worked with Ember Data since the recent reboot. This model hook will simply fetch all contacts from our API’s /contacts endpoint. The new syntax here is a big win, especially for module based apps like ours.

Note that we don’t need to bother creating a contacts controller in this case. Ember will happily create an ArrayController for us automatically.

The next step is to create the adapter and serializer. Go ahead and replace the code in adapters/applications.js with:

1 var ApplicationAdapter = DS.RESTAdapter.extend({
2   host: 'http://localhost:5000'
3 });
4 
5 export default ApplicationAdapter;

Note that you do have to specify the host here and make sure you have the correct port.

The serializer is even simpler:

1 var ApplicationSerializer = DS.ActiveModelSerializer.extend();
2 
3 export default ApplicationSerializer;

Note that we use the ActiveModelSerializer as this obviously works great with our server stack. Among other things, the ActiveModelSerializer will camelize our api keys. We could actually get away with not using this since our contact model is so simple but if you are building something even a little more complicated with Rails API and AMS, you’ll likely want this.

Alright, let’s go ahead and test everything out in the browser. Make sure both the rails and grunt servers are running and try to visit localhost:8000/contacts.

Whoops! That didnt work. If you open up the console, you should see:

XMLHttpRequest cannot load http://localhost:5000/contacts. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access.

This was to be expected. We are going to need our server to play along with these CORS requests. The answer is rack-cors. Add this to your gemfile:

1 source 'https://rubygems.org'
2 
3 gem 'rails', '4.0.0'
4 gem 'rails-api'
5 gem 'pg'
6 gem 'thin'
7 gem 'active_model_serializers'
8 gem 'rack-cors', :require => 'rack/cors'
bundle install

For our purposes, we are just going to set some simple config options, but youll likely want to be more specific when running in production.

Youre config/application.rb should look something like this:

 1 require File.expand_path('../boot', __FILE__)
 2 
 3 # Pick the frameworks you want:
 4 require "active_record/railtie"
 5 require "action_controller/railtie"
 6 require "action_mailer/railtie"
 7 # require "sprockets/railtie"
 8 require "rails/test_unit/railtie"
 9 
10 # Require the gems listed in Gemfile, including any gems
11 # you've limited to :test, :development, or :production.
12 Bundler.require(:default, Rails.env)
13 
14 module ContactsServer
15   class Application < Rails::Application
16     config.assets.enabled = false
17 
18     config.middleware.use Rack::Cors do
19       allow do
20         origins '*'
21         resource '*', headers: :any, methods: [:get, :post, :put, :delete, :options]
22       end
23     end
24   end
25 end

Restart the rails server and ty visiting localhost:8000/contacts again.

Working Master

Nice! Our Ember app is succesfully talking to our rails app!

Before we get started on the CRUD actions, let’s setup index.hbs under app/templates/contacts:

1 <div class="none-selected">
2   Please select a contact.
3 </div>

Now at least something will show up when there is no contact selected. On to the CRUD. First, let’s get the show route working. Modify the router:

 1 var Router = Em.Router.extend({
 2   location: 'history'
 3 });
 4 
 5 Router.map(function(){
 6   this.resource('contacts', function() {
 7     this.route('show', { path: '/:id' });
 8   });
 9 });
10 
11 export default Router;
12 });

Note that this need to be a nested route for our master-detail UI to work correctly.

We will now create our first explicit Ember controller. We want to show the twitter handle with a leading ‘@’ and so our controller will need to hold the necessary computed property. app/controllers/contacts/show.js should look like this:

1 var ContactsShowController = Ember.ObjectController.extend({
2   twitterDisplay: function() {
3     var twitter = this.get('twitter');
4     return twitter && '@' + twitter;
5   }.property('twitter')
6 });
7 
8 export default ContactsShowController;

Next up is the route app/routes/contacts/show.js:

1 var ContactsShowRoute = Ember.Route.extend({
2   model: function(params) {
3     return this.store.find('contact', params.id);
4   }
5 });
6 
7 export default ContactsShowRoute;

The model hook here is actually the ember default, so it could technically be left out. However, we are going to need this route later to handle some actions anyway so it doesn’t hurt to be explicit.

Now that that is hooked up, we can go fix our stubbed link in contacts.hbs:

 1 <div id="master">
 2   <ul>
 3     {{#each}}
 4       <li class="contact">
 5         {{#link-to 'contacts.show' this}}
 6           {{name}}
 7         {{/link-to}}
 8       </li>
 9     {{/each}}
10   </ul>
11 </div>
12 <div id="detail">
13   {{outlet}}
14 </div>

And then add our show template:

 1 <div class="contact">
 2   <h1>{{name}}</h1>
 3   <div class="clear"></div>
 4   <h3>{{twitterDisplay}}</h3>
 5   <div class="notes">
 6     {{notes}}
 7   </div>
 8   <div class="controls">
 9     <a href="#" class="btn">Edit</a>
10     <button type="button" class="btn red">Delete</button>
11   </div>
12 </div>

Note that the Edit link and Delete button don’t actually do anything yet. We’ll fix that later.

Open up your browser and visit localhost:8000/contacts again. Click on the first contact and you should see this:

Working Detail

It looks like everything is working. But wait, we haven’t even implemented the server show action. How does that work? The reason is that our contacts route loads all the contacts from the server index action before entering the show route. So the model backing the show route is pulled from the Ember Data cache without having to hit the server. Anyway, let’s go ahead and implement the server show action now:

 1 class ContactsController < ApplicationController
 2   def index
 3     render json: Contact.all
 4   end
 5 
 6   def show
 7     contact = Contact.find(params[:id])
 8     render json: contact
 9   end
10 end

Cool. Weve covered a lot of ground in this post. I think it’s time for a break. In part 3, we’ll finish implementing the rest of the CRUD actions.