José Mota — Web craftsman

Your Ember app is a click away from home

13 December 2012

I've been having some time to explore Ember.js lately and I wanted to learn by doing instead of just reading about it. So bear with me as this might probably require some updating in the future. This is a result of trial and error and I thought of making the Ember learning curve a little easier and share this tip.

So I've been trying to build a simple application that manages pictures. I started the good ol' fashioned way of manually calling all the scripts and declaring all the templates from the index page, for argument's sake. I would much rather have something like Iridium to help with my development process but I had faced some trouble with it before.

My first step: list of pictures

The first thing that came to my mind was to have a list of pictures and allow the user to add a new one by clicking on a link that would transition to something like /pictures/new. In the pictures list template I would have something like:

<h2>Images</h2>
{{#each image in controller}}
  <li>{{image.title}}</li>
{{/each}}
<p>
  <a {{action newImage href="true"}}>New image</a>
</p>

The link in the paragraph goes to the route transition in "root". Let me give you the router so you can better understand what I'm saying:

Exhibit.Router = Ember.Router.extend({
  root: Ember.Route.extend({
  , index: Ember.Route.extend({
      route: "/"
    , connectOutlets: function(router, context) {
        router.get("applicationController").connectOutlet("images", Exhibit.Image.all());
      }
    , newImage: Ember.Route.transitionTo("newImage")
    })
    , newImage: Ember.Route.extend({
        // ...
      })
  })
});

Inside the index route, the newImage path points to the newImage route which will print a form.

The challenge: Going back home

At this point in time, I was curious as to the possibility of aborting the process of creating a new image and simply going back home and start over. As I was learning, I realized that actions in the templates point to routes in the router as a way to acknowledge an application state change. I faced the challenge of wanting to leave the form and go back and not being able to do it.

I recalled that actions should be defined in the state (the route) the application is in. Clicking on the newImage link transitions my app to the root.newImage state. So I thought of doing something like this in the router:

Exhibit.Router = Ember.Router.extend({
  root: Ember.Route.extend({
  , index: Ember.Route.extend({
      // ...
    })
    , newImage: Ember.Route.extend({
        route: "/new"
      , connectOutlets: function(router, context) {
          // connect the outlet
        })
===> , goHome: Ember.Route.transitionTo("root.index")
    })
  })
});

This looks rather trivial and probably won't make the point alone. What I thought at the time when I wrote this was should I really need to write this transition in every single route? After all, going back home should be possible for every action, every state in the app.

The solution: Use the state machine

After some trial and error, I ended up with this:

Exhibit.Router = Ember.Router.extend({
  root: Ember.Route.extend({
=>  goHome   : Ember.Route.transitionTo("root.index")
  , index    : Ember.Route.extend({ /* ... */  })
  , newImage : Ember.Route.extend({ /* ... */ })
  })
});

Since the router is a state machine, when defining the root route, I'm able to add state changes there that will be inherited for every sibling state in that tree. Both index and newImage and all the routes that are defined in root will have this state change available.

This was a finding for me as I have never grasped the concept of state machines like this.

Creative Commons License

comments powered by Disqus