Creating Reusable Page Layouts in Ember

Creating components is a great way to remove redundancy in Ember.js apps. For example, you might have a custom button that is used over and over in many different views but is defined only once. This is great, but what if you want to reuse an entire nested page layout instead? It’s easy to do with yields and some Ember magic.

The first step is to create a new component for the page.

Your .js file should look something like the code below, and you should have a property for each dynamic section.


import Ember from 'ember';

export default Ember.Component.extend({
  title: {isTitle: true},
  formGroups: {isFormGroups: true},
  error: {isError: true},
  footer: {isFooter: true},
  pageFooter: {isPageFooter: true}
});

Your .hbs file should look something like this:


{{title-bar}}

<div class="page-wrapper">
  <div class="content">
    <div class="custom-header horizontal-box">
      <div class="large-title">
        {{yield title}}
      </div>
    </div>
    <form>
      <div class="vertical-box horizontal-box">
        <div class="custom-body">
          <div class="custom-form">
            {{yield formGroups}}
            {{yield error}}
          </div>
        </div>
      </div>
      <div class="form-footer">
        {{yield footer}}
      </div>
    </form>
  </div>
  {{yield pageFooter}}
</div>

Using this page layout is easy. Inside your view’s .hbs template file, just add the following code:


{{#page-layout as |section|}}
  {{#if section.isTitle}}
    "Add New Comment"
  {{else if section.isFormGroups}}
    <div class="form-group">
      <label for="comment-name">Name</label>
      {{input value=model.name id="comment-name" class="form-control" autofocus="autofocus"}}
    </div>
    <div class="form-group">
      <label for="comment-body">Comment</label>
      {{textarea value=model.body id="comment-name" class="form-control" autofocus="autofocus"}}
    </div>
  {{else if section.isError}}
    {{#if hasSwearWords}}
      {{validation-error error="Comments must not contain swear words."}}
    {{/if}}
  {{else if section.isFooter}}
    <button {{action "submit"}}>Submit</button>
  {{/if}}
{{/paged-dialog}}

You can use this page layout in many different view templates. The benefit here is that the page layout guarantees the exact same HTML structure, and any tweaks will be applied to all views.

The page layout is intentionally pretty dumb–it only cares about the nested structure of the elements. It leaves the view templates in charge of all business logic and data management.

Hope this approach is helpful for you.

Conversation
  • Ivan says:

    Nice! I was thinking about how to do “multiple named yields” in the layout components for some time now. This is kinda hacky (with the {{#if}}s) but it solves the problem. Thanks for this, I’m gonna try it right away :)

  • Steven Glanzer says:

    I’d recommend taking a look at the ember-block-slots addon, which solves this issue with less boilerplate, a clean syntax, and support for yielding back different values per targeted block https://github.com/ciena-blueplanet/ember-block-slots

    • Jeff Burt Jeff Burt says:

      Hey Steven,

      That’s pretty sweet – thanks for linking to it.

      Jeff

  • Ivan says:

    BTW, you can use the `hash` helper, so you don’t have to implement any `title: {isTitle: true}` stuff in your `component.js`.
    http://emberjs.com/api/classes/Ember.Templates.helpers.html#method_hash

    Example:

    “`js

    {{yield (hash isTitle=true)}}

    “`

    • Jeff Burt Jeff Burt says:

      Hey Ivan,

      Thanks for checking out my post. Also, thanks for the suggestion – that’s pretty sweet!

      Jeff

  • Minor thing you could also yield `contextual` components or “sub-components” to remove the need for the `if`s.

  • Comments are closed.