Routeless Ember.js Controllers

The leaves are falling from the trees around the Island. Fall, or as I still like to call it, Autumn, is here. The temperature is getting a little cooler now, and the nights are drawing in, so pull up close for this tale of the Ghostly Routeless Controller.

The beginning view of Controllers in Ember.js is that they are instantiated and setup by the corresponding Route. And while this is somewhat true, it’s not the only way to use Controllers.

Let’s look at how we can use a Controller without a Route, and some of the techniques using the Container described in the previous article to model a Davey Jones’ Locker and display a list of Lost Souls in our App.

We’re going to need four things to implement this feature:

The Controller

Let’s look at the Controller first.

App.DaveyJonesLockerController = Ember.ArrayController.extend({
  actions: {
    addSoul: function () {
      var person = {
        name: "A sad individual",
      };
      // Insert the 'model' at the head of the Array
      this.insertAt(0, person);
    },
    emptyLocker: function () {
      this.clear();
    },
  },
});

We’re declaring an ArrayController because our DaveyJonesLockerController is going to hold a collection of Lost Souls. The core action is addSoul, which has the following tasks:

Finally, we declare an action called emptyLocker for handling when Davey is tired of these pathetic lost souls whining all the time.

The Initializer

But at this point, Ember won’t know about our DaveyJonesLockerController, and no Route we have defined will set it up. For that, we need an Initializer:

Ember.Application.initializer({
  name: "daveyJonesLocker",
  initialize: function (container, application) {
    application.inject("route", "locker", "controller:davey-jones-locker");
  },
});

Again, it’s very, almost childishly, simple. We use the Initializer and the convenience method inject available on the application variable to inject into any routes in the Container an instance of our DaveyJonesLockerController, and make it available from the locker property. As an aside, we could also have injected into any Controller like so:

application.inject('controller', 'locker', 'controller:davey-jones-locker');

And Controllers would also subsequently have an locker property they could access.

The Template

Next, we build our Template that will be used with the DaveyJonesLockerController:


In this Template, we’re saying if length isn’t a truthy value (in this case greater than zero, i.e. we have Lost Souls), then don’t display anything. Well, where does length come from? In this case length is found as a property on ArrayController and returns the underlying length of the Array the Controller is wrapping. The Template is said to be ‘proxying’ the property lookup onto the ArrayController.

Next, we simply iterate on the model that the ArrayController represents and display the appropriate HTML for each Lost Soul.

Lastly, in our main Application Template, we need to use the render helper:

This tells Ember to fire up the DaveyJonesLockerController and when rendering the Application Template, render the output of the DaveyJonesLockerController here.

Here’s the complete example:

Ember Starter Kit

Adding souls from elsewhere

Here’s how you might add souls to the locker from, say, an action in the Index template. We know from our Initializer that we injected a locker property that gives us access to our DaveyJonesLockerController onto all Routes. First, in the IndexRoute, we’ll capture the addSoul action from a button in the template:

App.IndexRoute = Ember.Route.extend({
  actions: {
    addSoul: function () {
      this.get("locker").send("addSoul");
    },
  },
});

Next…well, that’s it! You’ve just added a soul from another template into the DaveyJonesLockerController.

Summary

We’ve created a Controller that isn’t attached to any Route, and we’re using it with a simple pseudo-model that isn’t used anywhere else in the Application other than to provide global functionality that our Application needs.

In the example above, we have injected the DaveyJonesLocker Controller into all Routes – so from any Route we could add new souls to the locker by accessing the locker property and calling addSoul on it like so:

this.get('locker').sendAction('addSoul');

Since this is just an example, we just raise the action addSoul from the Template displayed for DaveyJonesLockerController by the call to the render helper, but it could also be a method call.

Any time you have data, that still needs behaviour around it, but that doesn’t fit into the grander Routing structure of your Application, this technique is your friend. You could consider it as “Global” data, or as I like to think of it, more along the lines of a super lightweight service.

Happy coding!