Templating With Inheritance

One of the tools I use regularly for both large and small projects is a templating engine. It keeps your code DRY and helps keep things flexible enough to accommodate last minute design changes. Also, with a little practice, it can be just as fast to implement as vanilla HTML.

My go to templating language is Mustache which works for both Node and client-side JavaScript development. It follows the philosophy of a “logic-less” system by prohibiting arbitrary JavaScript code in your templates, only tags. For example, it uses arrays and booleans instead of loops and if/else statements. At first glance, this constraint may not seem like an advantage because it requires you to message the data before passing it to your template. However, in the long run, this is a more maintainable and testable approach. It forces you to think in terms of separation of concerns, making your templates cleaner and more re-usable, like they should be.

While the Mustache spec is very powerful, I often find myself in situations where I wish I had layouts in addition to partials for my templates. This can be achieved using lambdas and injecting template processing functions into the context of each template but that stinks in my opinion. Also, some clinet-side MVC like frameworks give you this functionality however I like to keep my prototypes lean whenever possible and a framework is often overkill.

One Mustache implementation that does include inheritance today is hogan.js. This is an undocumented feature that you won’t find reading the documentation or using one of the published builds. It is only discussed in the mustache/spec repository under the Proposal: Template inheritance issue. While the Hogan.js tests show the implementation, the builds do not include this version so for convenience, I created a custom build of version 3.0.

To demonstrate the template inheritance implementation I have a slightly contrived example of a menu that when selected, spawns a modal window. Each template uses the modal-layout that provides the HTML that will wrap the content. What’s distinct about this layout is the {{ $body }} and {{ $footer }} tags. They are custom tags with default content that will be overridden with content if specified in the foo-modal and bar-modal templates. Both templates are very similar to each other. The only real difference is the foo-modal only overrides default content for the {{ $body }} tag and not the {{ $footer }} tag in the layout.

Note: JavaScript doesn't have a [here document] syntax so string literal newlines are manually escaped.
!function () {

  var TEMPLATES = window.TEMPLATES = window.TEMPLATES || {};
  
  //  modal-layout
  TEMPLATES["modal-layout"] = Hogan.compile(' \
  <section class="modal {{ class-names }}"> \
    <div class="modal-chrome"> \
      <header> \
        <h3>{{ title }}</h3> \
        {{# has-close-button }} \
        <a href="#" class="close" data-js-action="remove" data-js-target="body > .modal">X</a> \
        {{/ has-close-button}} \
      </header> \
      <div class="modal-content"> \
        {{$ body }} \
          Default content! \
        {{/ body }} \
      </div> \
      {{$ footer }}{{/ footer }} \
    </div> \
  </section> \
  ');

  //  foo-modal
  TEMPLATES["foo-modal"] = Hogan.compile(' \
  {{< modal-layout }} \
    {{$ body }} \
      <p>This is the <b>foo</b> modal!</p> \
    {{/ body }} \
  {{/ modal-layout }} \
  ');

  //  bar-modal
  TEMPLATES["bar-modal"] = Hogan.compile(' \
  {{< modal-layout }} \
    {{$ body }} \
      <p>This is the <b>bar</b> modal!</p> \
    {{/ body }} \
    {{$ footer }} \
      <footer> \
        <a href="#" data-js-action="remove" data-js-target="body > .modal">Cancel</a> \
        <a href="#" data-js-action="remove" data-js-target="body > .modal">Okay</a> \
      </footer> \
    {{/ footer }} \
  {{/ modal-layout }} \
  ');

}();
Note: The `fiddle.css` Gist is purposefully not including however it can be [viewed here][fiddle.css].

The index.html is very bare bones. The only HTML markup is a menu that will spawn the modal windows. It has an id attribute that will be used to set up a delegated event listener and anchor links with custom declarative data- attributes using the HTML5 Data API. This is a great technique I often use because with it, I no-longer need to add and remove listeners manually on individual DOM elements.

The first listens for click events on anchors within the modal menu. When detected, the name of the modal that needs to open is retrieved from the custom data-modal-name attribute. Since I’m using convention over configuration, the name is used as a key to look up the template and corresponding data. The template is then rendered to the DOM with data and any partials and layouts it might need.

The second listener is delegated to the document and controls the removal of the modal. It listens for a click event on an anchor with the data-js-action=remove attribute and removes the element that matches the selector in the data-js-target attribute from the DOM.

<nav id="modal-menu" class="menu">
  <dl>
    <dt>Modal Menu</dt>
    <dd><a href="#" data-modal-name="foo-modal">Show foo!</a></dd>
    <dd><a href="#" data-modal-name="bar-modal">Show bar!</a></dd>
  </dl>
</nav>

<script>

  // template data
  var data = {
    "foo-modal": {
      "title": "Foo Modal",
      "has-close-button": true
    },
    "bar-modal": {
      "title": "Bar Modal",
      "class-names": "bar-modal"
    }
  };

  // create modal
  $("#modal-menu").on("click", "a", function (event) {
    event.preventDefault();
    var modalName = $(this).data("modal-name");
    var output = TEMPLATES[modalName].render(data[modalName], TEMPLATES);
    $("body").append(output);
  });

  // remove modal
  $(document).on("click", "a[data-js-action='remove']", function (event) {
    event.preventDefault();
    var target = $(this).data("js-target");
    $(target).remove();
  });

</script>

Next Steps

Templating with inheritance is a powerful tool to have in your arsenal. Once you get comfortable with the concept and implementation you speed up your prototyping and improve the portability of your code across projects. It’s a technique I use a lot and I hope this has helped inspire you to use it on your next project.

Published 20 May 2013 in Programming with tags: JavaScript.

Urban Faubion

Urban is a designer and developer with a love for creating digital products and services. He has a broad range of professional expertise in design, design research, interaction and user experience design, user interface development, software engineering and prototyping. He also enjoys playing soccer, bike touring, rock climbing, teaching mountaineering and traveling as much as possible.