56

I'm new to Mustache.

Many templating languages (e.g., Django / Jinja) will let you extend a "parent" template like so...

base.html

<html><head></head>
    <body>
    {% block content %}{% endblock %}
    </body>
</html>

frontpage.html

{% extends "base.html" %}
{% block content %}<h1>Foobar!</h1>{% endblock %}

rendered frontpage.html

<html><head></head>
    <body>
    <h1>Foobar!</h1>
    </body>
</html>

I'm aware of Mustache's partials (e.g., {{>content}}), but those seem to be just includes.

Does template extension exist for Mustache? Or, failing that, is there at least some design pattern that effectively turns includes into template extension equivalents.

Paul D. Waite
  • 89,393
  • 53
  • 186
  • 261
Chris W.
  • 32,518
  • 30
  • 89
  • 121

9 Answers9

63

I recently found myself in the same boat, except I came from a mako background.

Mustache does not allow for template extension/inheritance but there are a few options available to you that I know of.

  1. You could use partials:

    {{>header}}
        Hello {{name}}
    {{>footer}}
    
  2. You could inject template pre-processing functions into the context for each template that needs to inherit from some other page:

    {{#extendBase}}      
        Hello {{name}}
    {{/extendBase}} 
    

    Hash:

    {
       "name": "Walden",
       "extendBase": function() {
           return function(text) {
               return "<html><head></head>" + render(text) + "</body></html>"
           }
       }
    }
    
  3. Prepend and append the desired HTML to the relevant pages in your controller.

  4. Have a layout template ala:

    {{>header}}
        {{{body}}}
    {{>footer}}
    

    And render the body in your controller, passing that to the layout template as a variable named body.

  5. Implement template inheritance, pre-mustache, in your code that loads templates.

I wouldn't, however, use the triple mustache because I don't want unescaped HTML to be appearing anywhere, it's just too risky in my opinion.

If someone else has a better solution to this problem I'd love to hear it as well, since I haven't yet taken the plunge in any one of these directions.

Walden
  • 668
  • 5
  • 9
  • 1
    Should #4 be "triple mustache?"--`{{{body}}}` – Chris W. Nov 04 '11 at 04:02
  • Yes, unfortunately you are correct which rules out #4 for me. I've updated the answer. – Walden Nov 04 '11 at 16:07
  • 1
    Ultimately, creating reusable sub-templates and implementing inheritance in the template loading code (i.e. #5) is a lot more flexible, and safer, in general. The problem with template inheritance is that when writing the outer template, you can never have a good sense of what is required (or not required) in a child template. This often results in including JS/CSS which may be necessary for a particular child page (but unnecessary for another) in the outer template. The only way around this problem when using template inheritance is to pass the JS/CSS requirements as a variable. – Walden Nov 04 '11 at 16:29
  • 8
    [Twitter's version](http://twitter.github.com/hogan.js/) of mustache supports [template inheritance](https://gist.github.com/1854699). – Walden Apr 04 '12 at 20:23
  • (@Walden Perhaps you should add a no. 0 or no. 6 to your list and mention Hoogian? Thanks for adding the comment anyway :-) ) – KajMagnus Apr 18 '12 at 19:14
  • Is it then a partial similar to a way to use includes in PHP? – Alvaro Oct 22 '14 at 11:52
12

I've proposed this to the specification for Mustache here:

https://github.com/mustache/spec/issues/38

Currently mustache.java, hogan.js and phly_mustache support template inheritance.

Sam Pullara
  • 610
  • 4
  • 10
  • 2
    Although, regarding Hogan.js, you wouldn’t know it from the documentation: https://github.com/twitter/hogan.js/issues/70 – Paul D. Waite May 05 '14 at 00:40
3

In mustache php, template inheritance is supported since version 2.7.0.

https://github.com/bobthecow/mustache.php/wiki/BLOCKS-pragma

You can figure out your current version from the file Mustache/Engine.php and search for the line containing:

class Mustache_Engine
{
    const VERSION        = '2.8.0';
    ...
Basil Musa
  • 6,637
  • 6
  • 52
  • 58
3

You could use variables containing HTML. A "triple mustache" like {{{variable}}} will return unescaped HTML. It's not exactly the same as template extensions, but you could render frontpage-content.html and then put its output in a content variable that gets passed to base.html.

(I added -content to the frontpage.html filename with the expectation that such a naming pattern will help keep the filenames manageable.)

Kurt McKee
  • 1,368
  • 12
  • 16
3

Mustache doesn't do template extension.

If you really want template extension then you may want to use a library purpose built with this functionality for you language/framework of choice.


FYI, I'm using Node.js/Express, so I will probably end up using https://github.com/fat/stache

Chris W.
  • 32,518
  • 30
  • 89
  • 121
  • W Stash isn't maintained, and hogan.js, the suggest replacement, doesn't seem to implement extends. – mikemaccana Mar 13 '12 at 19:24
  • 1
    Twitter's Hoogian does seem to support inheritance, now. See this recent commit: [Hogan 3. Add template inheritance, ...](https://github.com/twitter/hogan.js/commit/73ec68b72df84553cccb55bc6dd69c198c13723a) – KajMagnus Apr 18 '12 at 19:10
1

I'm playing around with this right now in Python (note I'm the creator of Mako), adding in a dynamic context that captures sections seems to be doing the right thing, though I'd need to test this a lot more.

Basically we are using lambdas, where a "<" prefix indicates "inherit from this template" (similar to the syntax discussed at https://github.com/mustache/spec/issues/38) and a "$" prefix indicates "this is an inherited section".

import pystache

class NameSpace(object):
    def __init__(self, renderer, vars_={}):
        self.renderer = renderer
        self._content = {}
        self.vars = vars_

    def add_content(self, name, value):
        self._content[name] = value

    def __getattr__(self, key):
        if key in self.vars:
            # regular symbol in the vars dictionary
            return self.vars[key]
        elif key.startswith("<"):
            # an "inherit from this template" directive
            name = key[1:]
            return inheritor(self, name)
        elif key.startswith("$"):
            # a "here's a replaceable section" directive
            name = key[1:]
            if name in self._content:
                # if we have this section collected, return the rendered
                # version
                return sub_renderer(self, name)
            else:
                # else render it here and collect it
                return collector(self, name)
        else:
            # unknown key.
            raise AttributeError(key)

def sub_renderer(namespace, key):
    def go():
        def render(nested):
            return namespace._content[key]
        return render
    return go


def collector(namespace, key):
    def go():
        def render(nested):
            content = namespace.renderer.render(nested, namespace)
            namespace.add_content(key, content)
            return content
        return render
    return go


def inheritor(namespace, name):
    def go():
        def render(nested):
            namespace.renderer.render(nested, namespace)
            return namespace.renderer.render_name(name, namespace)
        return render
    return go

So here's some templates. base.mustache:

<html>

{{#$header}}
    default header
{{/$header}}

{{#$body}}
    default body
{{/$body}}

{{#$footer}}
    default footer, using {{local key}}
{{/$footer}}


</html>

hello.mustache:

{{#<base}}

{{#$header}}
    new header
{{/$header}}

{{#$body}}
    new body, with {{local key}}
{{/$body}}

{{/<base}}

and then to play with three levels deep, subhello.mustache:

{{#<hello}}

{{#$footer}}
    im some new footer
{{/$footer}}

{{/<hello}}

Rendering hello.mustache like this:

renderer = pystache.Renderer(search_dirs=["./templates/"])

print renderer.render_name("hello",
                    NameSpace(renderer, {"local key": "some local key"}))

output:

<html>

    new header

    new body, with some local key

    default footer, using some local key


</html>

Rendering subhello.mustache:

print renderer.render_name("subhello",
                    NameSpace(renderer, {"local key": "some local key"}))

output:

<html>

    new header

    new body, with some local key

    im some new footer


</html>

I just wrote this in twenty minutes, and I've only used handlebars.js a little bit in the past and pystache for the first time just now so the whole "mustache" idea is not deep for me yet. But this seems to work ?

zzzeek
  • 61,905
  • 18
  • 174
  • 175
0

In node.js you can use express-handlebars or hogan-express to have layouts inna mustache templates, but the way they do things is different, in none of them you set the layout at the template itself, layouts are registered in your app code.

Alfgaar
  • 156
  • 1
  • 7
0

I wrote a short code to enable extend and create layouts. .

If you don't want to copy/paste the header/footer, or maybe an menu bar that is repeated in all admin sub sections, each time you render a page/view, you just need to use the function: build of the followed class, that you can use as utils module

class MustacheLayout {
  constructor() {
    if (!MustacheLayout.instance) {
      MustacheLayout.instance = this;
    }
    return MustacheLayout.instance;
  }

  async build(...layers) {
    let previousLayer = '';
    let combinedLayout = '';
    for (const layer of layers) {
      const { name: layerName } = layer;
      let { data: layerData } = layer;
      if (!layerData) layerData = {};
      layerData.child = previousLayer;
      combinedLayout = await this.renderHtml(layerName, layerData);
      previousLayer = combinedLayout;
    }
    return combinedLayout;
  }

  renderHtml(viewName, data) {
    return new Promise((resolve, reject) => {
      this.app.render(viewName, data, (error, html) => {
        if (error) reject(error);
        resolve(html);
      });
    });
  }

  setExpressApp(expressApp) {
    this.app = expressApp;
  }
}

module.exports = new MustacheLayout();
  • In your server.js/index file of express app, add the app instance to our class.
//import the class const mustacheLayout = require('') //
const app = express();;
mustacheLayout.setExpressApp(app);

Controller

Put all the layer in the build function as parameters,

warning: - you need to respect order, from the smallest layout to the biggest (base.html) - data properties should be named as in the view

const layout = require('mustache-layout');

router.get('/edit', async (_, res) => {
    const user = 'admin'
    const html = await layout.build(
        { name: 'admin/edition/editor.view.html', data: { user } },
        { name:'layout/base.view.html' }
    )
    return res.send(html);
});

View

warning You need to use the keyword child in layout view pages. don't forget triple braces {{{ }}}

Use your passed data here

<!-- ditor.view.html -->
<div> {{ user }} </div>
<!-- base.view.html -->
<!DOCTYPE html>
<html lang="en">
<body>
    {{ > layout/header.view }}
    <div class="main">
        <div class="layout-content">
            {{{ child }}}
        </div>
    </div>
    {{ > layout/footer.view }}
</body>
</html>

If you need the package version of this code check mustache-layout-s

0

If you're happy with a server-side only code, Nun is a Mustache-like templating system with extends functionality via its 'template overrides' feature - modelled on django. While it works, however, it is no longer maintained by its author.

mikemaccana
  • 81,787
  • 73
  • 317
  • 396