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.
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.
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.
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.