138

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?

alex
  • 438,662
  • 188
  • 837
  • 957
Pascal Precht
  • 8,393
  • 7
  • 36
  • 50

8 Answers8

220

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

Karl Horky
  • 3,362
  • 27
  • 33
Yehuda Katz
  • 27,905
  • 12
  • 85
  • 91
  • 5
    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! – serverman Mar 29 '13 at 17:34
  • 12
    @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}`? – Tri Nguyen Jun 14 '13 at 21:52
  • @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. – chovy Sep 18 '13 at 07:30
  • 22
    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. – Xavier_Ex Dec 09 '13 at 20:54
  • 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... – 31415926 Sep 18 '14 at 15:30
  • 2
    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} }}` – bafromca Oct 21 '14 at 00:08
  • 1
    @bafromca 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? – ohcibi Dec 01 '14 at 14:02
  • 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. – smohadjer Sep 09 '18 at 19:33
  • For me this solution doesn't work. I used `{{> person ../this}}` – Oscar López Jul 12 '19 at 14:28
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. :)

Vlad GURDIGA
  • 1,134
  • 1
  • 13
  • 16
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) );
});
alex
  • 438,662
  • 188
  • 837
  • 957
Jesse Houchins
  • 151
  • 1
  • 2
  • 1
    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! – Claudio Bredfeldt Jul 17 '13 at 11:04
  • 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! – Dan Jul 31 '13 at 19:50
  • @Dan any chance you could share that helper? :) – Tom Sep 15 '13 at 17:25
  • 1
    @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]); } }; }` – Dan Sep 16 '13 at 00:00
  • @Dan Probably better to add it as its own answer. – alex May 01 '14 at 01:01
  • @alex FWIW, I ended up creating an npm package https://github.com/dpolivy/hbs-utils to help with things like this! – Dan May 01 '14 at 06:04
15

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

cweston
  • 10,371
  • 17
  • 74
  • 104
9

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>
Andrew C
  • 3,000
  • 1
  • 20
  • 23
8

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.

Chunky Bacon
  • 81
  • 1
  • 1
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).

vasiliy0s
  • 51
  • 2
1

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"}]' 
}}