Mustache.js: Accessing parent scope

Created on 11 Dec 2014  ·  18Comments  ·  Source: janl/mustache.js

Accessing parent scope

Considering :

node = {
  id: 1,
  children : [
      { id : 2 },
      { id : 3 }
  ]
}

And following template :

{{ id }} {# will output node.id #}
{{#children}}
    {{children.id}}  {# will output node.children[i].id #}
    {{id}}  {# will also output node.children[i].id #}
{{/children}}

As it is, you sometimes have to access parent property (in a nested node model for exemple).
It can be easily implemented like using "../" to get parent scope

Ex :

{{ id }} {# will output node.id #}
{{#children}}
    {{children.id}}  {# will output node.children[i].id #}
    {{id}}  {# will also output node.children[i].id #}
    {{ ../id }}  {# will output node.id #}
{{/children}}

To achieve that :

  Context.prototype.lookup = function (name) {
    var cache = this.cache;

    var value;
    if (name in cache) {
      console.log(name + ' found');
      value = cache[name];
    } else {
      var context = this, names, index;

      while (context) {
        if (name.indexOf('.') > 0) {
          value = context.view;
          names = name.split('.');
          index = 0;

          while (value != null && index < names.length)
            value = value[names[index++]];
        } else if(name.match(/^\.\.\//)) {
          name = name.replace(/^\.\.\//, '');
        } else {
          value = context.view[name];
        }

        if (value != null)
          break;

        context = context.parent;
      }

      cache[name] = value;
    }

    if (isFunction(value))
      value = value.call(this.view);

    return value;
  };
Future Plugin

Most helpful comment

Right. I completely forgot about handlebars.

Lets lose users to handlebars.
That's an acceptable design decision.

All 18 comments

This is not in mustache's specs, is it? There's no workaround for this? I'm surprised no one bumped into this limitation before.

I agree there's often need to access things in the parent scope. My pragmatic approach has always been to avoid ambiguous property names. That has worked for a long time for me, although it often results in strange objects.

On the other hand, speaking of experience with handlebars; having this ability might tempt people creating crazy tangled parent scope resolution: {{../../../id}} is alot harder to grasp than {{movieId}}.

It's true that using "../" could lead to unreadability. But in the other way using caml to resolve parent can not be achieve as you can have caml in local scope. Morever, defining a keyword to access parent would not respect Mustache philosophy I think.

speaking frankly I could not come to a simpler and better idea than using the the directory representation.

Yeah, avoiding ambiguous property names also leads to more readable/verbose templates. But I understand why some people would like this feature.

What about writing a separate package that modifies the inner workings of mustache.js to add the desired feature? Kind of like a plugin.

To be honest, I don't really see this happening unless via a plugin, or a pragma. And a plugin API doesn't seem to be the priority right now.

I've been thinking a bit more about this...

I believe Mustache's philosophy is not to pass in data _as-is_ to the renderer, but to 'prepare' it into a view beforehand. You'd then have a parentId property in your nodes.

It's also easier to read and maintain templates that have more verbose variables:

{{ id }}
{{#children}}
    {{children.id}}
    {{id}}
{{/children}}

Before vs After

{{ nodeId }}
{{#children}}
    {{ nodeId }}
    {{ parentId }}
{{/children}}

Relevant: http://stackoverflow.com/questions/4067093/mustache-read-variables-from-parent-section-in-child-section

(sorry to summon you @bobthecow, but I always appreciate your mustache wisdom :smile:)

you can already go up, so all ones needs to do to fix the problem shown with the first template is to wrap the data with a temp object {node: ... }, wrap the template with {{#node}}...{{/node}} and then {{node.id}} part can work. you don't need to (and won't) mutate the existing data that way, and you can add both of those "JIT" as you to_html() the template...

feels much like a patchy work-around. It also works only with simplistic 2-levels model where you can solve by an envelope wrap for the outer layer. But what if the model is deeper? that feels juggling.

Often I need to bind a model I get from lower data layers, and my job is to present it - in whatever form I got it from the infra. The proposition here is that I have to convert the model recursively all way deep to a presentable state - what would be considered here redable property names.
This does not meet reality conveniently...
Yes, true, it couples the template with the model. But can you show me a template that is not coupled with a concrete model rules? All templates are by definition made to render a defined model.
Another layer moves the layer of definition one step back, and requires a translation layer - which is dedious and not always necessary

IMHO, I think the tool should leave the choice to the user, rather than imposing opinionated rules

For readers, example of what @rndme is suggesting:

const Mustache = require('mustache')

var view = {
  node: {
    id: 5,
    children: [ { id: 6 }, { id: 7 } ]
  }
}

const template = `
{{#node}}
  children:
  {{#children}}

    id: {{ id }}
    parentId: {{ node.id }}
  {{/children}}
{{/node}}
`

const output = Mustache.render(template, view)
console.log(output)

  children:

    id: 6
    parentId: 5

    id: 7
    parentId: 5

Using the following template doesn't work as of latest, but _it should work_, imo.

const template = `
  children:
  {{#node.children}}

    id: {{ id }}
    parentId: {{ node.id }}
  {{/node.children}}
`

Yes, true, it couples the template with the model. But can you show me a template that is not coupled with a concrete model rules? All templates are by definition made to render a defined model.
Another layer moves the layer of definition one step back and requires a translation layer - which is tedious and not always necessary

Afaik, Mustache philosophy has always been that you generate a view that is passed to the template – you don't pass the model directly.

@osher Can you show us an example that you cannot easily work out with the tip/trick mentioned above?

I'll try to provide snippets later, but basically - with 3 levels nesting it won't work because you cannot wrap the middle-level - you have to transmute the source to a processed view.
You will be able to access the wrapped top-level, but you have no solution for the middle level.

Take for example a swagger document, where you have root level, path level, verb level (and there's more but lets stop here). Each level can specify a custom directive - x-uses, which is a DI directive for the implementation layer.

Suppose you want to generate HTML docs from this swagger document.
You need a flat table specifying for each operation handler (the verb level) the DI it accepts, and from what layer it inherits it.
While all the info is inherent in the swagger doc - you now have a problem.

Next.
Try to use mustache to generate the code that implements the API described in the document that returns mock responses based on the operation default response.
Try generate doclets that describe what the implementing developer that comes to replace the mock response with real logic should expect in their DI context and be specific about what level they get it.
Same...

Not classical HTML generation - yea. but who said that mustache is only for HTML? it's a templating engine, and code generation is commonly implemented using such template engines ;)

you don't pass the model directly.

that should be a user's choice, not a limitation / restriction

I'll give you one more example, and I'll try to do it without betraying secret sauce.

Assume a tree data-structure describing assets owned by a player in a strategy game.
The tree may go about 5 levels, for example:
Aliance -> Empire -> City -> Army -> Troops

Each level may provide modifier bonus - for example - or attack bonus, defense bonus, health bonus, etc.
Modifiers that address the same stats are described by the same name in all levels, (mainly because they are calculated recursively).
You need to use the template engine to present a battle-simulator that should help players chose what army is the ideal army for a given challange by displaying the battle-stats of troops in the army - which reside on the lowermost levels, but collect battle modifiers that are named by the same attribute name across the tree.
This is _very_ simplified, but based on a true story where other tools solved the problem with great ease, without requiring a middle translation layer.

I'll add difficulty: Sometimes armies are allocated in Alliance-level task force.
Alliance -> Rally -> Troops
The tool should be generic enough (plain recursive) and not depend on concrete levels.

I solved it with what mustache will call partials, only that I did not use mustache...

@osher said:

that should be a user's choice, not a limitation / restriction

There's no doubt mustache has opinions. It's "logic less templates" philosophy puts lots of restriction on the templates, that fact often requires data/model preparation before giving it to the template for rendering. If this doesn't suite your needs, there are alternatives which may be better, such as handlebars or even lodash.template just to name a few

Right. I completely forgot about handlebars.

Lets lose users to handlebars.
That's an acceptable design decision.

I'm pretty sure @osher was being sarcastic when he said "That's an acceptable design decision.", but this topic has been abandoned since 2016. What's going on? It seems like this question is being avoided in this JavaScript repository as well as in the main repository:
https://github.com/mustache/mustache.github.com/issues/103

I for one think that being able to reference the parent scope is logic-less and shouldn't interfere with the mustache ideals.

I like the idea of always being able to access the root scope with a symbol, like a leading "../":

Mustache.render('{{a1}}{{#a}}{{b.c}}{{../a1}}{{/a}}',{"a":{"b":{"c":"x1"}},"a1":"x2"})
"x2x1"

I would want this to render "x2x1x2" but it omits the last one because that's not how it works.
I thought I would recommend using something like JSONPath: https://goessner.net/articles/JsonPath/index.html#e2 however unlike XPath for XML, it doesn't recommend/implement the parent operator, which is what I was hoping for.

Perhaps Mustache could just try to stay compatible with handlebars and use the ../ syntax for parent context?

AFAIK, handlebars is JS only, while mustache uses the same syntax across many environs, PHP for example. If you want to change mustache syntax, you would need to convince all other non-js implementations to do the same; a tall order. Furthermore, in modifying the JS code, I found it problematic to go up "one level", though I added a way to go back to root in my fork, which was pretty simple to implement...

Anything on this?

There also needs to be documentation and style recommendation for situations where dot notation is optional.

view = { wrap: { txt: "test" } };
{{#wrap}}
  {{wrap.txt}} {{! Should I use this?}}
  {{txt}} {{! Or this?}}
{{/wrap}}

More details here: https://stackoverflow.com/q/62166467/5637701

Was this page helpful?
0 / 5 - 0 ratings

Related issues

funston picture funston  ·  7Comments

mbrodala picture mbrodala  ·  16Comments

ForbesLindesay picture ForbesLindesay  ·  14Comments

zekth picture zekth  ·  18Comments

chlab picture chlab  ·  11Comments