Beginning Ember.js on Rails: Part 3

Posted by Dan Gebhardt on Jan 31, 2012

This is part of a series of posts about Ember.js. Please read Warming up to Ember.js as well as the Ember.js home page if you’re new to Ember.js.

As promised in Part 2 of this series, this post will show how records are added, updated and removed in our example app. This app uses our ultra-simple Ember REST library to communicate with a REST interface served by Rails. I’ll try to point out when parts of this example use the persistence library (ember-rest.js) instead of core Ember (ember.js).

Because our REST interface was completed in Part 2, let’s move right to the client-side code…

Creating records

We’ll start by extending our Ember model and views to enable creating records. As you’ll soon see, much of this work is applicable to editing records as well.

Extend the model

In order to serialize our model, we need to define its resourceName and resourceProperties. In this way, ember-rest.js can send back only the data required by our REST interface:

app/assets/javascripts/app/models/contacts.js
App.Contact = Ember.Resource.extend({
  resourceUrl:        '/contacts',
  resourceName:       'contact',
  resourceProperties: ['first_name', 'last_name'],

  validate: function() {
    if (this.get('first_name') === undefined || this.get('first_name') === '' ||
        this.get('last_name') === undefined  || this.get('last_name') === '') {
      return 'Contacts require a first and a last name.';
    }
  },

  fullName: Ember.computed(function() {
    return this.get('first_name') + ' ' + this.get('last_name');
  }).property('first_name', 'last_name')
});

The optional validate() method will be called by ember-rest.js when saving records. Returning an error string or object will prevent saving an invalid record. And yes, those validations might look cleaner in CoffeeScript ;)

Extend the listing view

Our listing view needs to allow contacts to be added. Let’s modify its template first:

app/assets/javascripts/app/templates/contacts/list.handlebars
<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>Name</th>
    </tr>
  </thead>
  <tbody>
  {{#each contacts}}
    {{view App.ShowContactView contactBinding="this"}}
  {{/each}}
  {{#if isNewVisible}}
    <tr>
      <td>*</td>
      <td>
        {{view App.NewContactView}}
      </td>
    </tr>
  {{/if}}
  </tbody>
</table>
<div class="commands">
  <a href="#" {{action "showNew"}}>New Contact</a>
</div>

Notice the “New Contact” link at the bottom. We’re using the {{action}} helper to delegate its click event (the default for action) to the showNew() handler on the listing’s view class:

app/assets/javascripts/app/views/contacts/list.js
App.ListContactsView = Ember.View.extend({
  templateName:    'app/templates/contacts/list',
  contactsBinding: 'App.contactsController',

  showNew: function() {
    this.set('isNewVisible', true);
  },

  hideNew: function() {
    this.set('isNewVisible', false);
  }
});

In turn, the view class sets its isNewVisible property to true. By using set(), Ember’s bindings will trigger a change in the above template. {{#if isNewVisible}} will now be true, so the extra row will be displayed in our listing.

Create a view for new contacts

As you can see in the listing’s template, a NewContactView will be created within the new row in our listing:

app/assets/javascripts/app/views/contacts/new.js
App.NewContactView = Ember.View.extend({
  tagName:      'form',
  templateName: 'app/templates/contacts/edit',

  init: function() {
    this._super();
    this.set("contact", App.Contact.create());
  },

  didInsertElement: function() {
    this._super();
    this.$('input:first').focus();
  },

  cancelForm: function() {
    this.get("parentView").hideNew();
  },

  submit: function(event) {
    var self = this;
    var contact = this.get("contact");

    event.preventDefault();

    contact.saveResource()
      .fail( function(e) {
        App.displayError(e);
      })
      .done(function() {
        App.contactsController.pushObject(contact);
        self.get("parentView").hideNew();
      });
  }
});

There’s a lot going on here!

First of all, this view’s tagName will make it a form instead of the default div. Next are a couple methods overridden from the standard Ember.View:

init() - This is called to set up a view, but before it gets added to the DOM. It’s a good place to create a new contact object and associate it with our view. This object will be referenced in templates associated with our view.

didInsertElement() - This is called immediately after our element gets inserted in the DOM. It’s a good place for us to set focus to the initial input element on our form.

There are also a couple custom methods:

cancelForm() - hides this form in our parent view (see hideNew() in the listing view above).

submit() - Ember will automatically set up event listeners for views when you add methods from a standard list (currently here). In this case, we’ll be listening for our form’s submit() event.

Within the submit() handler, we’ll attempt to create our contact with the saveResource() method defined in ember-rest.js. The jQuery deferred methods fail() and done() will handle failure and success. Failure could come from either a client-side validation problem in our model, a problem communicating with the server, or an error returned from our Rails controller. On success, the new contact will be pushed into our controller’s collection. There’s no need to refresh our views - Ember will just add a new entry to our listing!

Let’s check out the template for this view:

app/assets/javascripts/app/templates/contacts/list.handlebars
{{#with contact}}
  {{view Ember.TextField valueBinding="first_name" placeholder="First name"}}
  {{view Ember.TextField valueBinding="last_name"  placeholder="Last name"}}
  <button type="submit">
    {{#if id}}Update{{else}}Create{{/if}}
  </button>
{{/with}}
<a href="#" {{action "cancelForm"}}>Cancel</a>

Perhaps you noticed that we aren’t applying any attributes from our form to our model before calling saveResource()? We can skip this messiness because we’ve bound Ember.TextField views to our model’s properties. Each view tracks focus and keyboard events to ensure that the model’s property stays in sync with its corresponding input field.

You might also notice that, to determine the submit button’s text, we’re checking the model’s id to see if this is a new record to create or an existing one to update. In this way, we can reuse this template to handle edits.

Editing and deleting records

Editing records is very similar to adding them. The major difference is that, in order to enable changes to be saved or cancelled, we’re going to edit a copy of our object instead of the object itself.

Because the changes needed to edit and delete records require changes in the same views, I’m going to explain them together.

Extending the show view

Let’s extend the template we’re using to display each record in our listing:

app/assets/javascripts/app/templates/contacts/show.handlebars
<td>{{contact.id}}</td>
<td class="data">
  {{#if isEditing}}
    {{view App.EditContactView}}
  {{else}}
    {{contact.fullName}}
  {{/if}}
  <div class="commands">
    {{#unless isEditing}}
      <a href="#" {{action "showEdit"}}>Edit</a>
      <a href="#" {{action "destroyRecord"}}>Remove</a>
    {{/unless}}
  </div>
</td>

You can see that we’re following the same pattern from our listing template to either show an EditContactView when isEditing is true, or contact.fullName when it’s not. Also, when we’re not editing this contact, we display links to edit / remove it.

Let’s also extend the corresponding view class:

app/assets/javascripts/app/views/contacts/show.js
App.ShowContactView = Ember.View.extend({
  templateName: 'app/templates/contacts/show',
  classNames:   ['show-contact'],
  tagName:      'tr',

  doubleClick: function() {
    this.showEdit();
  },

  showEdit: function() {
    this.set('isEditing', true);
  },

  hideEdit: function() {
    this.set('isEditing', false);
  },

  destroyRecord: function() {
    var contact = this.get("contact");

    contact.destroyResource()
      .fail( function(e) {
        App.displayError(e);
      })
      .done(function() {
        App.contactsController.removeObject(contact);
      });
  }
});

The showEdit() and hideEdit() handlers toggle isEditing, which controls whether EditContactView will be shown within the template (as seen above).

The destroyRecord() handler calls our model’s destroyResource() method, defined in ember-rest.js. It has fail() and done() deferreds, just like saveResource(). On success, the record is removed from the controller’s collection. As you could guess, Ember ensures that this change is reflected in the DOM immediately.

Extending the edit view

EditContactView is similar to NewContactView, with a few exceptions:

app/assets/javascripts/app/views/contacts/edit.js
App.EditContactView = Ember.View.extend({
  tagName:      'form',
  templateName: 'app/templates/contacts/edit',

  init: function() {
    // create a new contact that's a duplicate of the contact in the parentView;
    // changes made to the duplicate won't be applied to the original unless
    // everything goes well in submit()
    var editableContact = App.Contact.create(this.get('parentView').get('contact'));
    this.set("contact", editableContact);
    this._super();
  },

  didInsertElement: function() {
    this._super();
    this.$('input:first').focus();
  },

  cancelForm: function() {
    this.get("parentView").hideEdit();
  },

  submit: function(event) {
    var self = this;
    var contact = this.get("contact");

    event.preventDefault();

    contact.saveResource()
      .fail( function(e) {
        App.displayError(e);
      })
      .done( function() {
        var parentView = self.get("parentView");
        parentView.get("contact").duplicateProperties(contact);
        parentView.hideEdit();
      });
  }
});

Within init(), we create a copy of the parent view’s contact. This allows us to make changes that can be discarded if the form is cancelled.

In submit(), the contact’s updated properties are copied back to the original after it’s been saved successfully. This is done using the duplicateProperties() helper from ember-rest.js.

Of course, if you’d prefer that edits be made directly to the original record, you could skip both of these steps.


Whew, we made it! I hope that this series piqued your interest enough to try Ember yourself. If you’ve just been reading along, try running and modifying the example app yourself.

Although this is the final part of “Beginning Ember.js on Rails”, I plan to blog more about Ember. There’s quite a bit more to this framework than I’ve covered here, so please let me know if you have suggestions for future posts. If you’d like to stay tuned, you can follow our RSS, Twitter and Github accounts in the lower right. Thanks! - Dan

Beginning Ember.js on Rails: Part 2

Posted by Dan Gebhardt on Jan 26, 2012

This is part of a series of posts on Ember.js. Please read Warming up to Ember.js as well as the Ember.js home page if you’re new to Ember.js.

As promised in Part 1 of this series, this post will show how the example app uses our ultra-simple Ember REST library to query a resource’s REST interface to display a list of records.

Server-side Rails

Our Rails code is close to the minimum needed to serve up a RESTful JSON interface. It was originally generated with rails g scaffold and then simplified further.

Model

The Rails model for contacts is quite basic:

app/models/contact.rb
class Contact < ActiveRecord::Base
  validates :first_name, :presence => true
  validates :last_name, :presence => true
end

Controller

The controller is a simplified version of the scaffolding:

app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
  # GET /contacts
  # GET /contacts.json
  def index
    @contacts = Contact.all

    respond_to do |format|
      format.html # index.html.erb
      format.json { render json: @contacts }
    end
  end

  # GET /contacts/1.json
  def show
    @contact = Contact.find(params[:id])

    respond_to do |format|
      format.json { render json: @contact }
    end
  end

  # POST /contacts.json
  def create
    @contact = Contact.new(params[:contact])

    respond_to do |format|
      if @contact.save
        format.json { render json: @contact, status: :created, location: @contact }
      else
        format.json { render json: @contact.errors, status: :unprocessable_entity }
      end
    end
  end

  # PUT /contacts/1.json
  def update
    @contact = Contact.find(params[:id])

    respond_to do |format|
      if @contact.update_attributes(params[:contact])
        format.json { render json: nil, status: :ok }
      else
        format.json { render json: @contact.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /contacts/1.json
  def destroy
    @contact = Contact.find(params[:id])
    @contact.destroy

    respond_to do |format|
      format.json { render json: nil, status: :ok }
    end
  end
end

There are a couple interesting aspects to this controller code. First of all, the only action that responds to an html request is index. I’ve trimmed the others to respond only with json.

Also note that the update and destroy actions respond to success with:

render json: nil, status: :ok

… instead of the scaffolded default (head :ok), which jQuery.ajax() interprets as an error.

Routes

As you can see, we’re routing root requests to our contacts controller:

config/routes.rb
EmberRestExample::Application.routes.draw do
  root :to => 'contacts#index'
  resources :contacts
end

Let’s move to the client-side before we get back to our server-side views…

Client-side Ember

We’re now going to take a quick look at the client-side Ember model, view and controller code needed to display a list of contacts. This code is all organized within app/assets/javascripts/app/, as discussed in part 1 of this series.

If you’re following along in the example code, note that I’m leaving out a lot of the code having to do with editing records for the sake of simplicity.

Application

As reviewed in the first part of this series, the application object can be quite simple:

app/assets/javascripts/app/app.js
App = Ember.Application.create();

This object will contain all of the models, controllers and views for the application.

Model

Let’s move along to the model:

app/assets/javascripts/app/models/contact.js
App.Contact  = Ember.Resource.extend({
  resourceUrl: '/contacts',

  fullName: Ember.computed(function() {
    return this.get('first_name') + ' ' + this.get('last_name');
  }).property('first_name', 'last_name')
});

This is an extension of Ember.Resource, which is defined in ember-rest.js as an extension of Ember.Object. The resourceUrl is the base url of the resource.

Also of interest in the model is the computed property fullName, which returns a concatenation of first_name and last_name. It defines these properties as dependencies with property('first_name', 'last_name'). If either of those dependencies is updated, then fullName will be updated too. Any bindings to fullName in views will also be updated.

Controller

Our Ember-based contacts controller is very simple:

app/assets/javascripts/app/controllers/contacts.js
App.contactsController = Ember.ResourceController.create({
  resourceType: App.Contact
});

Instead of defining a class like App.Contact, we’re creating App.contactsController as an object. It’s an instance of Ember.ResourceController, which is defined in ember-rest.js as a thin extension to an Ember.ArrayController. Its only configured property is resourceType, which links it to our model. The controller knows enough to use the resourceUrl from the model to query resources (although the resourceUrl can be overridden at the controller level if needed).

Views

Let’s now check out our Ember views.

ListContactsView renders the listing:

app/assets/javascripts/app/views/contacts/list.js
App.ListContactsView = Ember.View.extend({
  templateName:    'app/templates/contacts/list',
  contactsBinding: 'App.contactsController',

  refreshListing: function() {
    App.contactsController.findAll();
  }
});

While ShowContactView renders each row within the listing:

app/assets/javascripts/app/views/contacts/show.js
App.ShowContactView = Ember.View.extend({
  templateName: 'app/templates/contacts/show',
  classNames:   ['show-contact'],
  tagName:      'tr'
});

Each view is associated with a template via templateName. The rendered template is contained within an html element that is mapped to its corresponding view. By default this is a div, although views can override this with the tagName property. This element can be customized further with classNames.

Also note the contactsBinding property on App.ListContactsView, which binds the controller’s data to this view as the property contacts.

Templates

Let’s see how each of these views is templated in Handlebars. First up is our listing:

app/assets/javascripts/app/templates/contacts/list.handlebars
<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>Name</th>
    </tr>
  </thead>
  <tbody>
  {{#each contacts}}
    {{view App.ShowContactView contactBinding="this"}}
  {{/each}}
  </tbody>
</table>

This template loops through contacts, which are bound to App.contactsController, using the each helper and renders each one using ShowContactView. Because ShowContactView has a tagName of tr, a new row will be inserted for each record. It will also render the show template:

app/assets/javascripts/app/templates/contacts/show.handlebars
<td>{{contact.id}}</td>
<td class="data">
  {{contact.fullName}}
</td>

Notice how the contactBinding in list.handlebars is passed through to the show view, which can reference the object as contact in its template.

All together now

Let’s pull this all together in a single server-rendered ERB view:

app/views/contacts/index.html.erb
<h1>Contacts</h1>

<script type="text/x-handlebars">
  {{ view App.ListContactsView }}
</script>

<script type="text/javascript">
  $(function() {
    App.contactsController.loadAll(<%= @contacts.to_json.html_safe %>);
  });
</script>

The first script block is written in Handlebars, as indicated by its type. It uses the Ember view helper to render App.ListContactsView.

The javascript block is a bit of an optimization. It loads contacts, fetched in our controller’s index action, into our Ember-based App.contactsController. The reason for this is simple: we can improve performance by loading contacts in the same request as the page. A simpler alternative to calling loadAll() would be to find all contacts after jQuery has loaded, like so:

<script type="text/javascript">
  $(function() {
    App.contactsController.findAll();
  });
</script>

Let’s recap what we’ve done here. We first configured a REST interface to our data in Rails. Next we configured our Ember controller and model to load data from that interface via ember-rest.js. Finally, we hooked up our Ember views and templates to our controller’s data.

Let’s also recap what we haven’t done here. Thanks to ember-rest.js, we haven’t needed to write any ajax code to query our REST interface. And thanks to Ember, we haven’t needed to write code to update our views when contacts are loaded into the controller’s array.

Ember will really begin to shine when we see that our views are kept in sync even as contacts are added, updated, and removed. Speaking of which, my next post will cover just that…


Please continue on to Beginning Ember.js on Rails: Part 3, the final part in this series.

Beginning Ember.js on Rails: Part 1

Posted by Dan Gebhardt on Jan 24, 2012

This is part of a series of posts on Ember.js. Please read Warming up to Ember.js as well as the Ember.js home page if you’re new to Ember.js.

Ember.js is not dependent upon any particular server stack. In fact, it can be used on its own to control single page web or mobile PhoneGap apps that don’t have a backend at all. Check out the canonical Ember Todos app for a simple online example.

I’ve chosen to write a demonstration app that I wish I could have referenced when I started learning Ember. It’s a simple Rails 3.1 app that demonstrates CRUD much like the standard Rails scaffolding. It has a front-end written entirely in Ember that communicates with its backing resources through their REST interfaces.

Just lovely, isn’t it?

ember_rest_example

DIY Demo

Let’s first get the example app running on your own system. This example will be used throughout this series, so you’ll want to refer to it often.

Just pull it down from Github and fire it up in Ruby 1.9.2+:

Terminal
git clone https://github.com/dgeb/ember_rest_example
cd ember_rest_example
bundle install
bundle exec rake db:migrate
rails s

Point your browser to localhost:3000 to give this CRUD-dy app a try.

Dependencies

This example has just a few dependencies beyond the basic Rails 3.1 stack.

The straightforward ember-rails gem pre-compiles handlebars templates in the asset pipeline. It also simplifies including ember.js, but my preference is to build or download a particular version for stricter control.

For persistence, this example uses Ember REST, a very simple library that I created (and open sourced) for managing RESTful resources. I tried to keep this library as lean and understandable as possible. Other alternatives for persistence include Ember Data, which is in heavy development by the Ember team and “definitely alpha-quality” (in their words), and Ember Resource.

Architecture

Ember does not require that your files be organized in a particular way. However, I find it helpful to organize Ember applications with the following structure within app/assets/javascripts:

  • app/ - the container for all js and templates related to this Ember app
  • app/app.js - declaration of a global App object that extends Ember.Application
  • app/models/ - model classes that represent resources
  • app/controllers/ - controllers that manage resources and are bound to views
  • app/views/ - view classes, organized in subfolders
  • app/templates/ - handlebars templates, organized in subfolders to match views
  • app/helpers/ - handlebars helpers
  • vendor/ - vendored libraries like ember.js and ember-rest.js
  • lib/ - general customizations to Ember

Note that I’m including vendor and lib in the app/assets/javascripts directory. This allows for a clear distinction between vendor/ember.js and ember.js, which would come from the ember-rails gem. However, feel free to follow the Rails convention of using vendor/assets/javascripts and lib/assets/javascripts instead.

The ember_rest_example app follows this pattern:

ember_rest_project

Adding Ember to your own app

Let’s now go through the steps of adding Ember to any new or existing Rails 3.1+ app. Start by modifying your Gemfile to include:

Gemfile
gem 'ember-rails'

From your app’s root:

Terminal
bundle

Next, duplicate the directory structure described above. From your app’s root:

Terminal
cd app/assets/javascripts
mkdir app app/controllers app/helpers app/models app/templates app/views lib vendor

Pull down the latest stable or development version of ember.js and add it to your app/assets/javascripts/vendor directory. I recommend this approach to ensure complete control over your js.

Because the Asset Pipeline doesn’t include dependency management concepts, everything must be included in the proper order within the js file you include on your pages. For instance, our example’s application.js contains:

app/assets/javascripts/application.js
//= require jquery
//= require jquery_ujs
//= require ./vendor/ember
//= require ./vendor/ember-rest
//= require_tree ./lib
//= require app/app
//= require_tree ./app/models
//= require_tree ./app/controllers
//= require_tree ./app/views
//= require_tree ./app/helpers
//= require_tree ./app/templates

You can use this as a starting point, but remove the ember-rest line if you’re using a different persistence library.

You’re now ready to create a global Ember App object. It’s not important that this object be named App. You might prefer to use your app’s name to personalize this object and namespace your whole app. Either way, creating your Ember app can be as simple as:

app/assets/javascripts/app/app.js
App = Ember.Application.create();

Hopefully that was easy. Your Rails app is now configured to work with Ember!


Please continue on to Beginning Ember.js on Rails: Part 2, which demonstrates querying a resource’s REST interface to display a list of records in an Ember view.