I'm just getting started with Ember.js and one thing that seems promising is the idea of multiple outlets combining multiple templates mixed together to produce a complex but modular layout.
I can't get it to work, though. It seems like there were many questions, answers, and examples on this a few months ago (mid 2012) but in the march to 1.0 they very recently (December 2012/January 2013) rewrote the router to a "v2" API. The docs are good at what they do describe but omit a lot of big picture context, and I have yet to find a single end-to-end example.
Here's what I've read:
- everything under the Routing guide (up to date, but not exhaustive)
- "outlet" template helper api reference (this may be out of date? Every attempt I've made to call
controller.connectOutlet()
fails withUncaught TypeError: Object <(generated animals.cats controller):ember170> has no method 'connectOutlet'
. - announcement of Ember.js Router API v2. Specifically the bottom couple comments (question and answer on multiple outlets). Yes, this gist is marked "Warning; outdated; for up-to-date information see the routing guide". But the current routing guide doesn't seem to completely describe the behavior. The Rendering a template section of the guide shows how to render to different outlets that already exist (and I can get this to work), but I can't figure out how to connect additional outlets or instantiate additional templates.
What does work for me:
- Setting up nested routes (well, nested resources; you can't nest routes; but you can customize routes for the nested resources), and nesting templates and outlets that are automatically instantiated according to the routes.
What I have not been able to figure out how to accomplish:
- Manually instantiate templates and connect them to outlets. This seems necessary if you want to use multiple outlets, or if you want to have a structure your outlet/template relationships differently than your routes. (There will be an example of this below. Essentially what I'm trying to do is use a template as a mixin that I can embed wherever else I want.)
The thing that seems promising but fails for me is
- Override a route's controller (extend the route using
App.WhateverRoute = Ember.Route.extend()
, supply my ownsetupController
method) and callcontroller.connectOutlet
here. This fails as described above; the controller object passed into this method does not have aconnectOutlet
method.
Example (here as a jsFiddle, or below as a self-contained html document which embeds the CSS and scripts and loads Ember and dependencies from https links, so you should be able to just save to a local file and open in a browser if you want to try it):
<!DOCTYPE html>
<html lang="en">
<head>
<title>Ember.js Router Example</title>
<style>
.outlet {
border: 1px solid black;
padding: 5px;
}
</style>
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js"></script>
<script src="https://raw.github.com/wycats/handlebars.js/1.0.rc.2/dist/handlebars.js"></script>
<script src="https://raw.github.com/emberjs/ember.js/release-builds/ember-1.0.0-pre.4.js"></script>
<script type="text/x-handlebars" data-template-name="index">
<p>Root index template. You should not see this because we redirect App.IndexRoute elsewhere.</p>
</script>
<script type="text/x-handlebars" data-template-name="about">
<p>About this demo.</p>
</script>
<script type="text/x-handlebars" data-template-name="guide">
<p>Guide to this demo.</p>
</script>
<script type="text/x-handlebars" data-template-name="animals">
<p>Animals. You have selected:</p>
<div class='outlet'>{{ outlet }}</div>
</script>
<script type="text/x-handlebars" data-template-name="animals/index">
<!-- you will not see this unless you disable App.AnimalsIndexRoute redirect. -->
<p>No animal selected.</p>
</script>
<script type="text/x-handlebars" data-template-name="animals/cats">
<p>Cat. I can meow. Like all animals, I
<span class='outlet'>{{ outlet }}</span>
</p>
</script>
<script type="text/x-handlebars" data-template-name="animals/dogs">
<p>Dog. I can bark. Like all animals, I
<span class='outlet'>{{ outlet }}</span>
</p>
</script>
<script type="text/x-handlebars" data-template-name="animal_mixin">
<p>am alive.</p>
</script>
<script type="text/x-handlebars" data-template-name="application">
<div class="container">
<p>
Select contents for my outlet:
{{#linkTo "index"}}/ (root){{/linkTo}}
{{#linkTo "about"}}/about{{/linkTo}}
{{#linkTo "guide"}}/guide{{/linkTo}}
{{#linkTo "animals"}}/animals{{/linkTo}}
{{#linkTo "animals.cats"}}/animals/cats{{/linkTo}}
{{#linkTo "animals.dogs"}}/animals/dogs{{/linkTo}}
</p>
<div class='outlet'>
{{ outlet }}
</div>
</div>
</script>
<script>
App = Ember.Application.create();
App.Router.map(function() {
this.resource("about");
this.resource("guide");
this.resource("animals", function() {
this.route("cats");
this.route("dogs");
})
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('about');
}
});
App.AnimalsIndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('animals.cats');
}
});
App.AnimalsCatsRoute = Ember.Route.extend({
setupController: function(controller, model) {
// BUG: this controller object has no connectOutlet method
// (uncomment to see this yourself)
// controller.connectOutlet('animal_mixin');
}
});
App.initialize();
</script>
</html>
Essentially animal_mixin is a chunk of boilerplate that I want to use repeatedly as a mixin, dropping it wherever I want by putting an outlet there and connecting it to this template. I realize this example is contrived, because I could do it with "inheritance" provided by the nesting structure: the contents of animal_mixin could go directly in the "animals" template, and I wouldn't need to mention it in animals/cats and animals/dogs. That would be fine if I wanted it in all animals, but let's say I had another subroute of /animals that I don't want to include this snippet. Again, the example is contrived but I hope the question and the intent are clear.