Passing variables through handlebars partial
Asked Answered
M

8

153

I'm currently dealing with handlebars.js in an express.js application. To keep things modular, I split all my templates in partials.

My problem: I couldn't find a way to pass variables through an partial invocation. Let's say I have a partial which looks like this:

<div id=myPartial>
    <h1>Headline<h1>
    <p>Lorem ipsum</p>
</div>

Let's assume I registered this partial with the name 'myPartial'. In another template I can then say something like:

<section>
    {{> myPartial}}
</section>

This works fine, the partial will be rendered as expected and I'm a happy developer. But what I now need, is a way to pass different variables throught this invocation, to check within a partial for example, if a headline is given or not. Something like:

<div id=myPartial>
    {{#if headline}}
    <h1>{{headline}}</h1>
    {{/if}}
    <p>Lorem Ipsum</p>
</div>

And the invokation should look something like this:

<section>
    {{> myPartial|'headline':'Headline'}}
</section>

or so.

I know, that I'm able to define all the data I need, before I render a template. But I need a way to do it like just explained. Is there a possible way?

Mencius answered 17/7, 2012 at 13:15 Comment(0)
H
239

Handlebars partials take a second parameter which becomes the context for the partial:

{{> person this}}

In versions v2.0.0 alpha and later, you can also pass a hash of named parameters:

{{> person headline='Headline'}}

You can see the tests for these scenarios: https://github.com/wycats/handlebars.js/blob/ce74c36118ffed1779889d97e6a2a1028ae61510/spec/qunit_spec.js#L456-L462 https://github.com/wycats/handlebars.js/blob/e290ec24f131f89ddf2c6aeb707a4884d41c3c6d/spec/partials.js#L26-L32

Hildebrand answered 23/7, 2012 at 15:13 Comment(9)
It is not immediately clear how this would apply to your scenario? Could you write down the solution - applying it in your case, please? Thanks!Osteogenesis
@Yehuda Katz instead of passing in this, could you pass in your own context. For example, define extra data to pass in, such as {new_variable: some_data}?Cleanlimbed
@TriNguyen passing 'this' will give you context from the caller. So if "some_data" is available in your caller, it will be available in the partial as well. I just tried passing a list to the caller data object and could use it in my partial.Interpreter
Although having the ability to pass in "this" is nice, it is not always enough. Often you want to reuse a certain piece of html potentially on the same page, but you're doomed if the partial has IDs... the same ID will show up more than once and becomes invalid. It'd be extremely useful if you can pass in arguments to partials when invoking it, to further customize its content.Yiddish
And how to get access to the content that was passed through 'this'? I need parent id but writing this.id gives me local id...Papillote
Which version of Handlebars supports this? I'm using 1.3.0 and it has a compile error when trying to pass json via {{> partialName {new_variable: some_data} }}Slesvig
@Slesvig thats the exact problem you cannot pass arbitrary data but only one single object. So you either pass this or you create a new property which returns your json-data in the controller or the view. I second that it should be possible to pass arbitrary data to partial in the form of key=value. Is there any issue covering this in github?Harl
The answer says you can pass a hash of named properties, but this is not always true. If your named property has a value of array or object type, it won't be accepted.Covet
For me this solution doesn't work. I used {{> person ../this}}Convector
S
19

Just in case, here is what I did to get partial arguments, kind of. I’ve created a little helper that takes a partial name and a hash of parameters that will be passed to the partial:

Handlebars.registerHelper('render', function(partialId, options) {
  var selector = 'script[type="text/x-handlebars-template"]#' + partialId,
      source = $(selector).html(),
      html = Handlebars.compile(source)(options.hash);

  return new Handlebars.SafeString(html);
});

The key thing here is that Handlebars helpers accept a Ruby-like hash of arguments. In the helper code they come as part of the function’s last argument—options— in its hash member. This way you can receive the first argument—the partial name—and get the data after that.

Then, you probably want to return a Handlebars.SafeString from the helper or use “triple‑stash”—{{{— to prevent it from double escaping.

Here is a more or less complete usage scenario:

<script id="text-field" type="text/x-handlebars-template">
  <label for="{{id}}">{{label}}</label>
  <input type="text" id="{{id}}"/>
</script>

<script id="checkbox-field" type="text/x-handlebars-template">
  <label for="{{id}}">{{label}}</label>
  <input type="checkbox" id="{{id}}"/>
</script>

<script id="form-template" type="text/x-handlebars-template">
  <form>
    <h1>{{title}}</h1>
    {{ render 'text-field' label="First name" id="author-first-name" }}
    {{ render 'text-field' label="Last name" id="author-last-name" }}
    {{ render 'text-field' label="Email" id="author-email" }}
    {{ render 'checkbox-field' label="Private?" id="private-question" }}
  </form>
</script>

Hope this helps …someone. :)

Selfless answered 21/9, 2013 at 16:23 Comment(0)
H
17

This can also be done in later versions of handlebars using the key=value notation:

 {{> mypartial foo='bar' }}

Allowing you to pass specific values to your partial context.

Reference: Context different for partial #182

Hispidulous answered 8/10, 2014 at 15:28 Comment(1)
This is available starting in version v2.0.0 alphaNormative
H
15

This is very possible if you write your own helper. We are using a custom $ helper to accomplish this type of interaction (and more):

/*///////////////////////

Adds support for passing arguments to partials. Arguments are merged with 
the context for rendering only (non destructive). Use `:token` syntax to 
replace parts of the template path. Tokens are replace in order.

USAGE: {{$ 'path.to.partial' context=newContext foo='bar' }}
USAGE: {{$ 'path.:1.:2' replaceOne replaceTwo foo='bar' }}

///////////////////////////////*/

Handlebars.registerHelper('$', function(partial) {
    var values, opts, done, value, context;
    if (!partial) {
        console.error('No partial name given.');
    }
    values = Array.prototype.slice.call(arguments, 1);
    opts = values.pop();
    while (!done) {
        value = values.pop();
        if (value) {
            partial = partial.replace(/:[^\.]+/, value);
        }
        else {
            done = true;
        }
    }
    partial = Handlebars.partials[partial];
    if (!partial) {
        return '';
    }
    context = _.extend({}, opts.context||this, _.omit(opts, 'context', 'fn', 'inverse'));
    return new Handlebars.SafeString( partial(context) );
});
Huntsville answered 31/1, 2013 at 3:38 Comment(6)
To have access to the passed arguments, you need to look for them into the 'hash' object: {{hash.foo}}. (I'm new with handlebars and this took me a while to figure out) - Thanks, great helper!Riches
Note, this requires you to have your partials pre-compiled before using the helper. I'm using Handlebars in node.js, and found that this wasn't always the case (the partials were compiled on demand). I had to add a simple helper to pre-compile partials after they were loaded, then this worked great!Cannell
@Cannell any chance you could share that helper? :)Undersell
@Tom, Here it is (can't figure out how to format it nicely, sorry): hbs.registerPartials(path.join(__dirname, '/views/partials'), function() { utils.precompileHandlebarsPartials(hbs); }); // Pre compile the partials precompileHandlebarsPartials : function(hbs) { var partials = hbs.handlebars.partials; for (var partial in partials) { if (typeof partials[partial] === 'string') { partials[partial] = hbs.handlebars.compile(partials[partial]); } }; }Cannell
@Cannell Probably better to add it as its own answer.Transience
@Transience FWIW, I ended up creating an npm package github.com/dpolivy/hbs-utils to help with things like this!Cannell
H
11

Sounds like you want to do something like this:

{{> person {another: 'attribute'} }}

Yehuda already gave you a way of doing that:

{{> person this}}

But to clarify:

To give your partial its own data, just give it its own model inside the existing model, like so:

{{> person this.childContext}}

In other words, if this is the model you're giving to your template:

var model = {
    some : 'attribute'
}

Then add a new object to be given to the partial:

var model = {
    some : 'attribute',
    childContext : {
        'another' : 'attribute' // this goes to the child partial
    }
}

childContext becomes the context of the partial like Yehuda said -- in that, it only sees the field another, but it doesn't see (or care about the field some). If you had id in the top level model, and repeat id again in the childContext, that'll work just fine as the partial only sees what's inside childContext.

Hydrastine answered 4/11, 2014 at 19:56 Comment(0)
H
10

The accepted answer works great if you just want to use a different context in your partial. However, it doesn't let you reference any of the parent context. To pass in multiple arguments, you need to write your own helper. Here's a working helper for Handlebars 2.0.0 (the other answer works for versions <2.0.0):

Handlebars.registerHelper('renderPartial', function(partialName, options) {
    if (!partialName) {
        console.error('No partial name given.');
        return '';
    }
    var partial = Handlebars.partials[partialName];
    if (!partial) {
        console.error('Couldnt find the compiled partial: ' + partialName);
        return '';
    }
    return new Handlebars.SafeString( partial(options.hash) );
});

Then in your template, you can do something like:

{{renderPartial 'myPartialName' foo=this bar=../bar}}

And in your partial, you'll be able to access those values as context like:

<div id={{bar.id}}>{{foo}}</div>
Habsburg answered 21/1, 2015 at 20:6 Comment(2)
I've tried this version with Handlebars 1.0.0 and it worked flawlessly.Homologous
where does this 'search' for a partial named '...' ?Americanize
T
3

Yes, I was late, but I can add for Assemble users: you can use buil-in "parseJSON" helper http://assemble.io/helpers/helpers-data.html. (Discovered in https://github.com/assemble/assemble/issues/416).

Thomasinathomasine answered 23/10, 2014 at 21:11 Comment(0)
C
2

Not sure if this is helpful but here's an example of Handlebars template with dynamic parameters passed to an inline RadioButtons partial and the client(browser) rendering the radio buttons in the container.

For my use it's rendered with Handlebars on the server and lets the client finish it up. With it a forms tool can provide inline data within Handlebars without helpers.

Note : This example requires jQuery

{{#*inline "RadioButtons"}}
{{name}} Buttons<hr>
<div id="key-{{{name}}}"></div>
<script>
  {{{buttons}}}.map((o)=>{
    $("#key-{{name}}").append($(''
      +'<button class="checkbox">'
      +'<input name="{{{name}}}" type="radio" value="'+o.value+'" />'+o.text
      +'</button>'
    ));
  });
  // A little test script
  $("#key-{{{name}}} .checkbox").on("click",function(){
      alert($("input",this).val());
  });
</script>
{{/inline}}
{{>RadioButtons name="Radio" buttons='[
 {value:1,text:"One"},
 {value:2,text:"Two"}, 
 {value:3,text:"Three"}]' 
}}
Courcy answered 24/8, 2019 at 2:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.