60

I've got a handlebar template that loads a partial for a sub-element.

I would need to access a variable from the parent context in the calling template, from within the partial. .. doesn't seem to resolve to anything inside the partial.

Simplified code goes like this:

the template

    {{#each items}} 
        {{> item-template}}
    {{/each}}

the partial

    value is {{value}}

(obviously the real code is more complicated but it's the same principle, within the partial .. appears to be undefined.)


To show it's undefined, I've used a very simple helper whatis like this:

Handlebars.registerHelper('whatis', function(param) {
    console.log(param);
});

and updated the above code to this:

updated template

    {{#each items}} 
        {{whatis ..}}  <-- Console shows the correct parent context
        {{> item-template}}
    {{/each}}

updated partial

    {{whatis ..}}  <-- Console shows "undefined"
    value is {{value}}

Is there a way to go around that issue? Am I missing something?

EDIT: There's an open issue relating to this question on handlebars' github project

Ben
  • 20,737
  • 12
  • 71
  • 115

8 Answers8

40

Just in case anyone stumbles across this question. This functionality exists now in Handlebars.

Do this:

{{#each items}} 
    {{! Will pass the current item in items to your partial }}
    {{> item-template this}} 
{{/each}}
James Andres
  • 1,522
  • 14
  • 20
  • Thanks @james-andres. Good to know that's finally in. I'll give it a spin to confirm.. – Ben Feb 04 '13 at 11:06
  • 17
    Just tested this. Doenst work for me unfortunately. The parent context does not get passed. "this" only refers to the items[:index] so the parent context is left out. And if you pass "..", which would include the parent context, you cant access the current item in the partial. – Björn Jun 28 '13 at 15:12
  • 3
    You need at least [version 2.0.0 alpha 1](http://cdnjs.com/libraries/handlebars.js/) for this to work. Also check [these comments](https://github.com/wycats/handlebars.js/pull/182#issuecomment-32659931), as it might work a bit different. – doekman Jul 03 '14 at 12:44
  • 3
    @Hans, you can pass `{{> item-template parent=..}}` and then call `{{parent.stuff}}` in the item-template to access something from outside the each. – Guillaume Gendre Apr 11 '17 at 08:11
  • How to access properties of context passed to child by ```this```. – Muhammad Saquib Shaikh Jun 07 '20 at 23:45
22

Working fiddle (inspired by handlebars pull request #385 by AndrewHenderson) http://jsfiddle.net/QV9em/4/

Handlebars.registerHelper('include', function(options) {
    var context = {},
        mergeContext = function(obj) {
            for(var k in obj)context[k]=obj[k];
        };
    mergeContext(this);
    mergeContext(options.hash);
    return options.fn(context);
});

Here's how you'd setup the parent template:

{{#each items}} 
    {{#include parent=..}}
        {{> item-template}}
    {{/include}}
{{/each}}

And the partial:

value is {{parent}}
pward123
  • 734
  • 6
  • 9
18

As of 2.0.0 partials now supports passing in values.

{{#each items}}
    {{> item-template some_parent_var=../some_parent_var}}
{{/each}}

Took me awhile to find this, hope it's useful for someone else too!

SeanWM
  • 16,789
  • 7
  • 51
  • 83
  • You have a typo: it should be: '{{> item-template some_var=../some_parent_var}}' but thanks for the info, it helped a lot, I've posted a fiddle with several examples using your and @StefanHayden answer, I've up voted both. [Jsfiddle](https://jsfiddle.net/v7pLxa19/) – mike Apr 05 '20 at 21:27
4

The easiest way to pass the parent context to the partial is to do the loop inside the partial. This way the parent context is passed by default and when you do the loop inside the partial the {{../variable}} convention can access the parent context.

example fiddle here.

The Data

{
  color: "#000"
  items: [
    { title: "title one" },
    { title: "title two" },
  ]
}

The Template

<div class="mainTemplate">
  Parent Color: {{color}}
  {{> partial}}
</div>

The Partial

<div>
  {{#each items}}
    <div style="color:{{../color}}">
      {{title}}
    </div>
  {{/each}}
</div>
StefanHayden
  • 3,569
  • 1
  • 31
  • 38
3

You can use some of the proposed solutions on the comments from the link to github:

https://github.com/wycats/handlebars.js/issues/182#issuecomment-4206666
https://github.com/wycats/handlebars.js/issues/182#issuecomment-4445747

They create helpers to pass the info to the partial.

Ricardo Souza
  • 16,030
  • 6
  • 37
  • 69
  • Yeah! Sometimes we miss the obvious looking for other solutions. At least 'til they release a milestone or a new version. – Ricardo Souza Jun 12 '12 at 12:44
  • That's just an incredibly inelegant solution – Marc Aug 02 '12 at 14:32
  • Think off a temporary solution if you don't want to browse the source code and fix it yourself. I hate this kind of "workaroud" (even this word seems wrong to me) but it works and can save the day in a tight deadline. – Ricardo Souza Aug 02 '12 at 15:10
2

I created an each Helper function that includes the parent key/values within the subcontext under the key parentContext.

http://jsfiddle.net/AndrewHenderson/kQZpu/1/

Note: Underscore is a dependency.

Handlebars.registerHelper('eachIncludeParent', function ( context, options ) {
var fn = options.fn,
    inverse = options.inverse,
    ret = "",
    _context = [];
    $.each(context, function (index, object) {
        var _object = $.extend({}, object);
        _context.push(_object);
    });
if ( _context && _context.length > 0 ) {
    for ( var i = 0, j = _context.length; i < j; i++ ) {
        _context[i]["parentContext"] = options.hash.parent;
        ret = ret + fn(_context[i]);
    }
} else {
    ret = inverse(this);
}
return ret;

});

To be used as follows:

{{#eachIncludeParent context parent=this}}
    {{> yourPartial}}
{{/eachIncludeParent}}

Access parent context values in your partial using {{parentContext.value}}

AndrewHenderson
  • 4,686
  • 3
  • 26
  • 33
  • How does this fix the problem? also, there's a `var option=` that doesn't seem to be used subsequently.. – Ben Dec 04 '12 at 02:38
  • If you use this helper, you will have access to one object in the partial template which contains all of the values, both the parent and the child object. It's basically a merged context. – AndrewHenderson Dec 06 '12 at 00:58
  • Note: The child and parent values will be on the same level at that point. – AndrewHenderson Dec 06 '12 at 01:00
  • May help as a workaround, but should be noted it pollutes the original objects passed to the template by adding parent context variables to them.. this might not be ideal for everything.. also `var option = context[i].value || context[i]` should be taken out, it doesn't do anything... – Ben Dec 06 '12 at 02:54
  • 1
    I concur that this is definitely a workaround while the functionality is missing from Handlebars. _.defaults only adds to the object, as opposed to _.extend which will override matching key values. The object is only being polluted in the local scope of the helper. The helper uses the object to return a string and that's the end of it. I don't think there is a need to be concerned about object pollution in this case. – AndrewHenderson Dec 06 '12 at 19:24
  • The object pollution carries on past the scope of the helper. If you run this through an array of object and log your objects *after* the handlebar code runs, you'll see each object retain the extra properties. Could become a huge problem with models for instance.. – Ben Dec 06 '12 at 21:15
  • You were right. I was actually working on solving this problem since I last wrote. I've modified the original code above. See what you think. – AndrewHenderson Dec 06 '12 at 21:29
1

I needed dynamic form attributes for something like this...

    {{#each model.questions}}
      <h3>{{text}}</h3>

          {{#each answers}}
                {{formbuilder ../type id ../id text}}
            {{/each}}

    {{/each}}

and a helper like so...

    Handlebars.registerHelper('formbuilder', function(type, id, qnum, text, options)
    {
        var q_type = options.contexts[0][type],
            a_id = options.contexts[1].id,
            q_number = options.contexts[0][qnum],
            a_text = options.contexts[1].text;


            return new Handlebars.SafeString(
                    '<input type=' + q_type + ' id=' + a_id + ' name=' + q_number + '>' + a_text + '</input><br/>'
            );
    });

Which produces...

<input type="checkbox" id="1" name="surveyQ0">First question</input>

My model is a big blob of arrays and objects mixed together. What's noteworthy is that using '../' like so '../type', passes in the parent model as the context, and without it, such as with 'id', it passes in the current model as the context.

nullsteph
  • 791
  • 15
  • 28
0

To get specifically the parent of the partial (where you may be several partials deep) then follow the other answers like SeanWM.

If you know that the parent is the main template then you can use @root which resolves to the top-most context no matter how deep you are.

e.g. {{@root.rootObject.rootProperty}}

It is a pity that ../../.. does not go up past a partial.

Etherman
  • 1,777
  • 1
  • 21
  • 34