functions in nested objects are not bound to that object, resulting in inconsistent behavior
Given the following input:
const context = {
nested: {
awesome: function () {
return this.more;
},
more: 'Deeply awesome'
},
more: 'More awesome'
}
the following template:
{{nested.awesome}}
yields 'More awesome', whereas the equivalent Javascript:
context.nested.awesome()
yields 'Deeply awesome'
More importantly,
{{#with nested}}
{{awesome}}
{{/with}}
also yields 'Deeply awesome'.
Here is the js-fiddle demonstrating this
PR submitted with a failing test case: #1861.
This doesn't seem to be a bug but wrong expectations:
JavaScript and Handlebars are not the same and therefore don't work the same. A scope in Handlebars means something different than in JavaScript:
With context.nested.awesome() the function in JavaScript looks up the scope it's in whereas in Handlebars the scope is set to where you call something from.
Calling {{nested.awesome}} still means that you are in @root and call something from there, no matter how deep it is nested. So even {{nested.inside.some.other.object.awesome}} would still call awesome() from within the @root scope.
Therefore the this in your function awesome() is taken from the @root (as it's called from there) and return this.more; means @root/more, so More awesome.
The way to change the scope would be using {{#with ...}}:
{{#with nested}}
{{awesome}}
{{/with}}
This outputs Deeply awesome. Now you call {{awesome}} with the scope of @root/nested.
From within this scope you could call awsome() also like {{@root/nested/awesome}} or {{../nested/awesome}} but you're still passing it the scope @root/nested.
Handlebars doesn't make a difference between a registered helper function or a function from within the context. The context of the function is always bound to where you call it from.
JavaScript and Handlebars are not the same
Sure, but in this case we are talking specifically about a Javascript feature and the Javascript Handlebars library. We are talking specifically about how Javascript implemented contexts with Javascript functions as properties in that context behave. It stands to reason that such functions behave in a way that is consistent with and natural to Javascript.
Much more importantly, it also stands to reason, from the template author's side, without even knowing that the context is implemented in Javascript or even that the awesome property is a function, that:
{{#with nested}}
{{awesome}}
{{/with}}
and
{{nested.awesome}}
yield the exact same thing, just as if awesome were a plain old property.
I would expect this consistency from any other Handlebars library, be it Python, Rust, Golang or any other language implementations.
Perhaps I should update the title of this issue.
@jaylinski, @ilharp, or @nknapp, if you confirm that this is a bug that should be fixed, I could take a shot at a PR. Does the label indicate that?
@PitPik has some valid points in his response, I'm not sure if this is really a bug, but wrong expectations.
This behavior should be defined as part of a specification. (But so far all attempts to write a specification failed: https://github.com/handlebars-lang/spec, https://github.com/handlebars-lang/specification)
@jaylinski Kinda sounds like you're saying "it is what it is". Is it because the various implementations are inconsistent and also too entrenched to impose any consistency?
Which points of @PitPik are valid? Are none of mine? Honest question.
In this case Handlebars mimics the behavior of Mustache. There is no {{#with}} helper in mustache, but {{#nested}}{{awesome}}{{/nested}} would be the same thing.
This fiddle demonstrates it: https://jsfiddle.net/ob5ezgcn/6/
{{nested.awsome}} => More awesome
{{#nested}}{{awesome}}{{/nested}} => Deeply awesome
I am not sure if it is specified in the Mustache spec explicitly.
Personally, I would advise against the use of functions in the context. This feature is a relict due to the compatibility with Mustache. Use a helper instead. Helper-behavior is much more constent.
In my eyes, the primary use case of Handlebars is rendering JSON-like objects.