Change template content at runtime

I am wondering how to solve this problem:

I have a template which contains some text with some template helpers inside:

<template>Hello {{who}}, the wheather is {{weather}}</template>

Now I need to change the content of the template dynamically at runtime, while maintaining the helper functionality. For example I would need it like this:

<template>Oh, the {{weather}}. Good evening {{who}}</template>

The text changes and the helpers are needed at different positions. Think of an application where users can create custom forms with placeholders for certain variables like the name of the user who fills out the form. Basically, the content of the template is stored in a mongo document and needs to be turned into a template at runtime, or an existing template needs to be changed.

How to approach this?

One way is to just use a helper to load the whole thing into your page. If you use 3 brackets around your {{{helper}}} statement it renders as HTML, so if your forms are stored as HTML strings it’ll be almost automatic. Your helper function will have to fill in the variables before returning, but that shouldn’t be difficult.

Assuming you’ve got a collection Forms for the templates with documents that look like this:

{_id: "asdfasdf", userId: "qwerqwer", htmlString: "<div>Hello whoplace, the weather is <u>weatherplace</u></div>."}
{_id: "fdsafdsa", userId: "rewqrewq", htmlString: "<div>Oh, the <u>weatherplace</u>. Good evening whoplace.</div>"}

and a collection Details for the data like this:

{_id: "uiopuiop", useId: "qwerqwer", who: "John", weather: "sun"}
{_id: "poiupoiu", useId: "rewqrewq", who: "Sally", weather: "rain"}

And assuming the correct template and data are decided by the current user

The template would be pretty simple

<template name="weather">
    {{{weatherStatement}}}
</template>

And the fancy stuff would be done in js

Templates.weather.helpers({
    weatherStatement: function() {
        var myform = Forms.findOne({userId: Meteor.userId()});
        var mydetails = Details.findOne({userId: Meteor.userId()});
        myform.htmlString.replace("whoplace", mydetails.who);
        myform.htmlString.replace("weatherplace", mydetails.weather);
        return myform.htmlString;
    }
});

What about:

<template name="message">
  {{#with who=who weather=weather}}
    {{message}}
  {{/with}}
</template>
Template.message.helpers({
  message: function () {
    return "Hello " + this.who + ", the weather is " + weather + ".";
  }
});

This will not work because the text and the position of the helpers is only known at runtime.

You can return any string you want from the message helper, so you can get the pattern from the database:

Template.message.helpers({
  message: function () {
    // can get this from DB
    var messagePattern = "Hello {{who}}, the weather is {{weather}}.";
    return messagePattern.replace("{{who}}", this.who)
      .replace("{{weather}}", this.weather);
  }
});

You could write a more generalized version of that function if you need to do this all over the place.

Another approach would be getting template source code out of the database and compiling it at runtime, but that seems like more trouble than it’s worth.

Yes, that’s true. But I want to use helpers for who and wheater, not just context data. who and wheater are helpers of Template.message. Can I call a helper from another helper? That could work.

In my previous snippet, {{#with who=who weather=weather}} puts the data from the helpers into the context.

Why? Will something not work if you load that information into the form before it’s sent to the template? Since you’re loading the entire form from the database, I don’t see any advantages this could create for you.

Ok, I see. But isn’t there a way to access the other helpers directly? Otherwise when you create a context specifically things get messy quite quickly.

I don’t understand what you mean by “the other helpers.”

I think it would help if you showed us how your forms and data are stored and how they are selected. That would be what really drives the structure of the app.

I think it now boils down to this: in a helper of Template.foo I would like to call the other helpers of Template.foo so that I can reuse the logic in there. Is that possible?

Define a helper function which can be used from all templates with http://docs.meteor.com/#/full/template_registerhelper

I think right now it’s pretty hard to call helpers from inside other helpers.

You can define common template’s logic in a TemplateInstance

Template.some.created = function() {
  this.fn1 = function(data) {
    return data.count + 60;
  };
  this.fn2 = function(data) {
    return data.count + 120;
  };
}

Template.some.heplers({
  helper1: function() {
    return Template.instance().fn1(this);
  },
  helper1: function() {
    return Template.instance().fn1(this) + Template.instance().fn2(this);
  }
});

Ok, I used the common template logic in the end. Thanks.

you could use a global trick to awoid of Template.instance() each time, i.e:

window.__defineGetter__('_this', function() {return Template.instance()})

and then

Template.some.heplers({
  helper1: function() {
    return _this.fn1(this);
  },
  helper1: function() {
    return _this.fn1(this);.fn1(this) + _this.fn2(this);
  }
});
1 Like

Just to add another option, you could use dynamic templates:

<template name="message1">Hello {{who}}, the wheather is {{weather}}</template>
<template name="message2">Oh, the {{weather}}. Good evening {{who}}</template>

<template name="main">
    {{> Template.dynamic template=templateName }}
</template>

Templates.main.helpers({
    templateName: function() {
        // decide here which to use
        var templateName = "message1";
        return templateName;
    }
    who: ...
    weather: ...
}

Hello.
We can do this

<template name="A"><span class="test">test A</a></template> <template name="B"><span class="test">test B</a></template>

Set events for master template A

Template.A.events({ 'click .test':function(e){ alert("Hello Test"); } })

For share same events with template B, overwrite with

Template.B.__eventMaps=Template.A.__eventMaps

Enjoy !

JP