Harry Love
Harry Love

Reputation: 1910

Meteorjs nested conditional templates

It is possible to render different templates and place them inside a container template by re-rendering when a session variable changes and then directly manipulating the DOM. However, this doesn't feel like the Meteor way. Is there an idiomatic way of handling the DOM update?

Client.js

if (Meteor.isClient) {

    var getContainerContents = function() {
        console.info('get container contents');
        var active = Session.get('active') == 'foo' ? 'foo' : 'bar';
        return Meteor.render(Template[active]());
    };

    Template.foo.contents = function() {
        console.info('foo contents');
        return 'The foos.';
    };

    Template.bar.contents = function() {
        console.info('bar contents');
        return 'The bars.';
    };

    Meteor.startup(function() {
        console.info('startup');

        Meteor.autorun(function() {
            console.info('autorun');
            $('#container').html(getContainerContents());
        });
    });

}

Client.html

<head>
  <title>conditional templates</title>
</head>

<body>
  {{> main }}
</body>

<template name="main">
  <section id="container"></section>
</template>

<template name="foo">
  <h2>Foos</h2>
  {{ contents }}
</template>

<template name="bar">
  <h2>Bars</h2>
  {{ contents }}
</template>

Update: It's possible to use template helpers for this purpose as well. The solution is to return the un-rendered string contents from the template() function and mark them as safe in Handlebars like so:

html

<template name="main">
  {{ contents }}
</template>

js

var getContainerContents = function() {
    console.info('get container contents');
    var active = Session.get('active') == 'foo' ? 'foo' : 'bar';
    return Template[active]();
};

Template.main.contents = function() {
    return new Handlebars.SafeString(getContainerContents());
};

// Remove the startup and autorun code. Not necessary now.

There are two gotchas with this which might make it less than ideal.

  1. Any nested templates rendered inside the main template must also be marked as SafeString. Otherwise the contents will be escaped by Handlebars. This could lead to some bloat as the SafeString code is needed for every nested template.
  2. By marking a string as safe, i.e. unescaped, the onus is now on the developer to remember to escape any potentially unsafe strings before inserting them into the page. The more of these you have, the more likely you'll create a security hole. (See http://handlebarsjs.com/ for documentation)

So, in my mind, the question is still open. But I will defer to an editor.

Upvotes: 4

Views: 4030

Answers (3)

G. Ghez
G. Ghez

Reputation: 3594

You car reference a template using its name stored in an exposed variable (in template helper):

{{> Template.dynamic template=varHandlingTemplateName }}

This is the way meteorjs resolve dynamic template situations in its latest version (as of today).

Regards, Greg

Upvotes: 0

Rouven Hurling
Rouven Hurling

Reputation: 119

this is the setup i'm using

client/client.js

Template.page.display_content = function () {
    var page_index = Handlebars.Utils.escapeExpression(Session.get('page'));

    if (Template[page_index]) {
        return Template[page_index]();
    } else {
        return Template['page_not_found']();
    }
};

client/template.html

<template name="page">
    {{{display_content}}}
</template>

i just have to do Session.set("page", "xyz") to change pages and because of the {{{ it's automatically marked as SafeString.

to 1. Nested templates inside the template "xyz" don't need to be marked as SafeString, they are all rendered before it is escaped

to 2. You don't have to, because if you output some data like {{text}} it is again automatically escaped, it just won't get escaped again

Upvotes: 1

David Wihl
David Wihl

Reputation: 1491

Use Handlebars conditionals:

<template name="main">
  {{#if active}}
    {{> foo}}
  {{else}}
    {{> bar}}
  {{/if}}
 </template>

In your Meteor JavaScript code:

Template.main.active = function () {
  return Session.get("active");
}

If both foo and bar share contents, you could either either use a Helper, or have both foo and bar use the same partial (ie.

{{> contents}}

) depending on your need.

Upvotes: 3

Related Questions