Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

functions in nested objects are not bound to that object, resulting in inconsistent behavior #1860

Open
vassudanagunta opened this issue May 10, 2022 · 6 comments
Labels
docs-needed item for language spec Used for issues that describe things that should be covered by a spec needs investigation

Comments

@vassudanagunta
Copy link

vassudanagunta commented May 10, 2022

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.

@PitPik
Copy link

PitPik commented Jun 19, 2022

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.

@vassudanagunta
Copy link
Author

vassudanagunta commented Jul 12, 2022

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.

@vassudanagunta vassudanagunta changed the title functions in nested objects are not bound to that object (inconsistent with Javascript) functions in nested objects are not bound to that object, resulting in inconsistent behavior Jul 12, 2022
@vassudanagunta
Copy link
Author

vassudanagunta commented Jul 12, 2022

@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?

@jaylinski jaylinski added needs investigation item for language spec Used for issues that describe things that should be covered by a spec labels Jul 12, 2022
@jaylinski
Copy link
Member

@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)

@vassudanagunta
Copy link
Author

@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.

@nknapp
Copy link
Collaborator

nknapp commented Jul 13, 2022

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.

@jaylinski jaylinski added docs-needed and removed bug labels Aug 31, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs-needed item for language spec Used for issues that describe things that should be covered by a spec needs investigation
Projects
None yet
Development

No branches or pull requests

4 participants