1

For the actual call, I need something like this:

<script id="messagesTemplate" type="text/x-handlebars-template"> 

{{#each messages.messages}}
    {{#each to}}
        {{#ifCond username messages.sessionUserName}}
          <h1>{{username}} is equal to {{messages.sessionUserName}}</h1>
        {{else}}
          <h1>{{username}} is not equal to {{messages.sessionUserName}}</h1>
        {{/ifCond}}
    {{/each}}
{{/each}}

Where, in the db, 'to' is an array of docs that each have a 'username'..that thus need === the messages.sessionUserName to then template/render HTML for certain values (e.g. {{#if read.marked}} )

"to" : [
    {
        "user" : ObjectId("53aada6f8b10eb0000ec8a90"),
        "username" : "username1",
        "updated" : ISODate("2014-07-01T19:39:45Z"),
        "_id" : ObjectId("53b30e81b0eff5cb1e2ecb21"),
        "read" : {
            "marked" : true
        }
    }
]

Worth noting, both usernameTest & sessionUserName are values appended to the end of the res.json() via express, so they are accessible by messages.usernameTest & messages.sessionUserName, but they are not present in each document..these values are only available in the global parent doc.

res.json({
    messages : messages,
    sessionUserName: req.session.username,
    usernameTest: usernameTest
});

This factor may be responsible for why each of these only render is equal to, but doesn't really make sense for the third (The ../ path segment references the parent template scope):

{{#each messages.messages}}
    <h1>{{usernameTest}} is equal to {{sessionUserName}}</h1>
    <h1>{{../usernameTest}} is equal to {{../sessionUserName}}</h1>
    <h1>{{../messages.usernameTest}} is equal to {{../messages.sessionUserName}}</h1>

Drawing from https://stackoverflow.com/a/9405113/3095287 for the custom comparison helper, the template that follows {{#ifCond v1 v2}} doesn't seem to render upper-level scoped elements..

Handlebars.registerHelper('ifCond', function(v1, v2, options) {
  if(v1 === v2) {
    return options.fn(this);
  }
  return options.inverse(this);
});

The ifCond comparison does work outside of an {{#each}} block:

<script id="messagesTemplate" type="text/x-handlebars-template"> 

{{#ifCond messages.usernameTest messages.sessionUserName}}
  <h1>{{messages.usernameTest}} is equal to {{messages.sessionUserName}}</h1>
{{else}}
  <h1>{{messages.usernameTest}} is not equal to {{messages.sessionUserName}}</h1>
{{/ifCond}}

{{#each messages.messages}}
..

..as that renders:

username1 is equal to username1

However, it does not work inside of an {{#each}} block:

{{#each messages.messages}}
    {{#ifCond messages.usernameTest messages.sessionUserName}}
      <h1>{{messages.usernameTest}} is equal to {{messages.sessionUserName}}</h1>
    {{else}}
      <h1>{{messages.usernameTest}} is not equal to {{messages.sessionUserName}}</h1>
    {{/ifCond}}
    ...

..as it only renders:

is equal to

Even with {{../element}}

{{#each messages.messages}}
    {{#ifCond messages.usernameTest messages.sessionUserName}}
      <h1>{{../messages.usernameTest}} is equal to {{../messages.sessionUserName}}</h1>
    {{else}}
      <h1>{{../messages.usernameTest}} is not equal to {{../messages.sessionUserName}}</h1>
    {{/ifCond}}
    ...

..the rendering is:

is equal to

Community
  • 1
  • 1
StackThis
  • 883
  • 3
  • 15
  • 45

1 Answers1

2

Ok so the main thing is you want to be able to gain access to your top level scope deeper down. Ive done this using a customer helper that adds a little extra to the normal each block.

so here is the normal handle bars each

    Handlebars.registerHelper('each', function(context, options) {
      var ret = "";

      for(var i=0, j=context.length; i<j; i++) {
        ret = ret + options.fn(context[i]);
      }

      return ret;
    });

all i do is set 'this' to a property called root and pass it back with the result. To overcome nested loops i check for the existence of my 'root' property and if it exists I pass it along other wise root = this.

  Handlebars.registerHelper("myEach", function(context, options) {
    var ret = "";

    for (var i = 0, j = context.length; i < j; i++) {
        if (this.root) {
            root = this.root;
        } else {
            root = this;
        }
        ret = ret + options.fn(_.extend({}, context[i], {
            root: root
        }));
    }


    return ret;
});

Now no matter how deep i am in my loops if i want to use something from the root i just use root.property.

A working codepen can be found here with a simplified version of your example.

EDIT: Ok so 5 minutes later after posting this i read about paths in another templating language and then realise handlebars also has paths. so you don't need to do the above you can just use the relative nested path in your template like below. I'm going to continue using the helper though as I think it is tidier to go root.property rather than adding n many "../" for how nested your are.

here is a working example using the paths

<script type="text/x-handlebars-template" id="messages-template">
  Logged in user {{userSession}}
    {{#each messages}}
  <ul>
    <li> Title: {{title}}</li>
    <li> Conetent: {{content}}</li>
    <li> TO:
      <ul>
        {{#each to}}

        <li>{{user}} {{#ifvalue user ../../userSession}}
        thats me
        {{else}}
        thats not me
        {{/ifvalue}}</li>
      {{/each}}
      </ul>
    </li>
    </ul>

    {{/each}}



</script>
Quince
  • 14,790
  • 6
  • 60
  • 69
  • Thanks for this.. One question though: for the myEach helper, `ret = ret + options.fn(_.extend({}, context[i], {` causes a reference error for the `_` .. any suggestions? – StackThis Jul 07 '14 at 20:59
  • oh yeah it's underscore, i normally use backbone so am always used to having it around. You could use jquery's extend if you use jQuery in your project: ret = ret + options.fn($.extend(true, context[i], { root: root })) – Quince Jul 07 '14 at 21:44
  • Perfect, many thanks.. This really sets us up for the next phase of features... Very grateful – StackThis Jul 08 '14 at 00:29