An equivalent to Rails’s `content_for`?


#1

Let’s say I wish to display various resources with a common layout consisting of a toolbar and a list. In Rails, I might do something like the following:

application.html.erb

…
<div class="toolbar">{{yield :toolbar}}</div>
<main>{{yield}}</main>
…

users.erb

…
<%= content_for :toolbar do %>
  <%= render partial: "toolbar" %>
<% end %>
…

posts.erb

…
<%= content_for :toolbar do %>
  <%= render partial: "toolbar" %>
<% end %>
…

users/_toolbar.html.erb

<button>New User</button>

posts/_toolbar.html.erb

<button>New User</button>

In Ember:

router.js

App.Router.map(function() {
  this.resource('users');
  this.resource('posts');
});

application.hbs

…
<div class="toolbar">{{outlet 'toolbar'}}</div>
<main>{{outlet}}</main>
…

routes/resource.js

App.ResourceRoute = Em.Route.extend({
  renderTemplate: function() {
    this.render();
    this.renderContent('toolbar');
  },
  
  renderContent: function(name, options) {
    options = Em.$.extend(options, { outlet: name });
    try {
      this.render(this.get('routeName') + '/' + name, options);
    } catch (e) {}
  }
});

routes/users.js

App.UsersRoute = App.ResourceRoute.extend();

routes/posts.js

App.PostsRoute = App.ResourceRoute.extend();   

templates/users/toolbar.hbs

<button>New User</button>

templates/posts/toolbar.hbs

<button>New Post</button>

Each resource route extends App.ResourceRoute which renders RESOURCE_NAME/toolbar into the toolbar outlet.

(see: http://emberjs.jsbin.com/cujaq/1/edit)

Is this a sensible approach? Could this pattern be improved?


#2

The toolbar shouldn’t be an outlet helper. Use a Component instead. That’s why you have to make that extra resource route with the render content function, which essentially kind of duplicates what Ember would give you for free.

I’ve got some time right now, so if you still need help, I can extend that jsbin for you to implement the toolbar component.


#3

Thanks. Are you suggesting that every template have a toolbar component?


#4

Like this? http://emberjs.jsbin.com/cujaq/2/edit


#5

Yes, that’s how to do it. Notice how that simplified the logic and templates.It’s also reusable across this, and any other Ember App. Ember guides you toward a more composable, component architecture, but especially recently when Web Components got mainstream.


#7

Yes, it appears more concise for that example for sure. The downside is that it requires the component to be included in each template, whereas in the previous version the markup only exists in the application template (or “layout”), and is filled in as required.

I guess what I’m trying achieve is templates that are free from as much required common “layout” markup as possible.

In this example the toolbar is present regardless of whether there are buttons or not, and can be filled in by creating a toolbar template. Consider this toolbar required layout markup.

With the components method, a developer would need to include an empty toolbar component to achieve the same effect.

It’s only a subtle difference, but there is something that feels incorrect about duplicating layout markup.

I hope that makes sense!?


#8

I don’t know much about Rails anymore so I’m kinda lost right now. I think it’s a totally valid question, so I’ll defer to someone that knows more, but I’ll pop back in if I can think of something soon.


#9

Thanks!

I’m not sure if I’ve articulated the problem particularly well. I’m currently working on the ember inspector (itself an ember app). application.hbs includes something like this:

{{#custom-view}}
  <main>
{{/custom-view}}

and the desired layout is something like:

<header>

{{#custom-view}}
  <main>
{{/custom-view}}

<footer>

I could change the application template so that it just contains the main {{outlet}}, and include the header, custom view, and footer templates within each “leaf” template, but that seems to be rather a lot of repetition.

Thanks again


#10

I’ve just extracted something from our (in progress) app which might help.

It lets you render to an outlet as normal but handles all the edge cases where you need to re-render etc…

Eg consider the layout:

{{outlet "header"}}
{{outlet}}
  • / has a default header
  • /foo has a custom header
  • /foo/bar/ doesn’t change the header
  • /foo/bar/baz sets a new header

When going from /foo/bar/baz -> /foo/bar we want to re-render the header from /foo but normally exiting /foo/bar/baz will just clear the outlet. This addon handles all that stuff.

If you’re using ember-cli it’s just a npm install --save-dev ember-render-stack away, it’s on Github here along with the usage instructions.

Hopefully it’s of some use.


#11

That looks really great. We currently don’t have the need to re-render the header from a parent route, but it certainly looks useful.

Out of interest, how are you standardising outlet rendering across routes? Are you subclassing e.g.:

…
App.BaseRoute = Em.Route.extend(StackRouteMixin, {
  renderStack: function() {
    this.renderToStack(this.get('routeName') + '/nav, {
      outlet: 'navigation'
    });
  }
});

App.ResourceRoute = App.BaseRoute.extend();
App.AnotherResourceRoute = App.BaseRoute.extend();
…

#12

We currently handle it on a route by route basis as there are many resources which don’t change the outlet and just use their parent’s ones. We have two or three different outlets which we update on a case by case basis - contextual navigation, header content etc…

If every resource has its own template for the outlet then something like your example would work well.