Sam Selikoff
Sam Selikoff

Reputation: 12704

Passing in a Handlebars template as a variable to a Handlebars template

I have a situation where I need to render a Handlebars template created by the user. I've been able to hack together one solution: using a view to compile the user-generated template + context into HTML, then making that resulting string available to the "final" template.

I'd prefer to be able to just expose the template as a variable to the "final" template, and have the final template evaluate it as if it were actual HBS code; but if I use triple braces {{{, the handlebars variables are (of course) escaped.

Is there another way to do this? A helper perhaps, that first compiles the variable with some context, then outputs the string? The only problem here is that Ember.Handlebars is wired up to work with Ember; I just need the final, unbound HTML.

Upvotes: 2

Views: 2481

Answers (2)

melc
melc

Reputation: 11671

One possible approach could be to use a view dedicated to rendering the user defined template. Then by setting its template variable and re-rendering it, the template could change dynamically.

Example,

http://emberjs.jsbin.com/yexizoyi/1/edit

hbs

<script type="text/x-handlebars">
    <h2> Welcome to Ember.js</h2>

    {{outlet}}
  </script>

  <script type="text/x-handlebars" data-template-name="test">
    this is the test,

    {{view view.userTemplate}}

    <button {{action "changeTemplate" 1 target="view"}}>change to Template 1</button>
    <button {{action "changeTemplate" 2 target="view"}}>change to Template 2</button>
  </script>

js

App.Router.map(function() {
  this.route("test");
});

App.IndexRoute = Ember.Route.extend({
  beforeModel: function() {
    this.transitionTo("test");
  }
});

App.UserTemplateView = Ember.View.extend({
  template:Ember.Handlebars.compile("initial default template <b>{{view.parentView.parentVar}}</b>")
});

App.TestView = Ember.View.extend({
  parentVar:"this is a parent variable",
  userTemplate:App.UserTemplateView.create(),
  actions:{
    changeTemplate:function(templateId){
      if(templateId===1){
        this.get("userTemplate").set("template",Ember.Handlebars.compile("this is template 1 <b>{{view.parentView.parentVar}}</b>"));
        this.get("userTemplate").rerender();
      }else{
        this.get("userTemplate").set("template",Ember.Handlebars.compile("this is template 2 <b>{{view.parentView.parentVar}}</b>"));
        this.get("userTemplate").rerender();
      }
    }
  }
});

This could also be implemented by using a ContainerView, http://emberjs.com/guides/views/manually-managing-view-hierarchy/

example, http://emberjs.jsbin.com/luzufixi/1/edit

edit - supplement to comments and good solution of Sam Selikoff using a helper

This is one more rough example of using the previous concept, along with the model of a router and the {{view}} helper which is backed by a generic View object, i.e. PreviewTemplateView .

http://emberjs.jsbin.com/tonapaqi/1/edit

http://emberjs.jsbin.com/tonapaqi/1#/test/1

http://emberjs.jsbin.com/tonapaqi/1#/test/2

hbs - calling {{view}} helper with the desired context that if it contains a template property, the initial default template will change.

{{view App.PreviewTemplateView contextBinding="this"}}

js

App.Router.map(function() {
  this.route("test",{path:"test/:tmpl_id"});
});

App.IndexRoute = Ember.Route.extend({
  beforeModel: function() {
    this.transitionTo("test",1);
  }
});
App.TestRoute = Ember.Route.extend({
  model:function(params){
    if(params.tmpl_id==1){
      return {template:"this is template 1 <b>{{view.parentView.parentVar}},param from context of model:{{someParams.param1}}</b>",someParams:{param1:"p1",param2:"p2"}};
    }else{
      return {template:"this is template 2 <b>{{view.parentView.parentVar}},param from context of model:{{someParams.param2}}</b>",someParams:{param1:"p1",param2:"p2"}};
    }
  }

});

App.PreviewTemplateView = Ember.View.extend({
    template:Ember.Handlebars.compile("initial default template"),
  init:function(){
    this._super();
    this.refreshTemplate();
  },
  refreshTemplate:function(){
    this.set("template",Ember.Handlebars.compile(this.get("context").get("template")));
    this.rerender();
  }.observes("context.template")
});

App.TestView = Ember.View.extend({
  parentVar:"this is a parent variable"
});

Upvotes: 4

Sam Selikoff
Sam Selikoff

Reputation: 12704

I ended up going with a Handlebars helper. Seems to be a bit more flexible.

I'm using EAK and Coffeescript:

helper = Ember.Handlebars.makeBoundHelper (template, context) ->

dummy = Ember.View.extend
  context: context
  template: Ember.Handlebars.compile template
view = dummy.create()
$elem = null
Ember.run ->
  $elem = $('<div>')
  view.appendTo($elem)

return new Handlebars.SafeString $elem.html()

Now in any template I can write

{{preview-template template context}}

where template is a (possibly user-generated) template, and context the data.

Upvotes: 2

Related Questions