handlebars.java icon indicating copy to clipboard operation
handlebars.java copied to clipboard

block params context is incorrect

Open dontgitit opened this issue 6 years ago • 14 comments

Hi,

It seems helpers require block params if any ancestor helper required a block param. For instance, take this template:

{{#each foo as |fooValue fooIndex|}}
  {{#each bar as |barValue barIndex|}}
    {{fooValue}} {{fooIndex}} {{barValue}} {{barIndex}}
    {{../this}} {{../@index}} {{this}} {{@index}}
  {{/each}}
{{/each}}

with the following data:

{ 
  "foo": ["a", "b", "c"], 
  "bar": ["d", "e", "f"]
}

as expected, this outputs:

a 0 d 0
a 0 d 0
a 0 e 1
a 0 e 1
a 0 f 2
a 0 f 2

b 1 d 0
b 1 d 0
b 1 e 1
b 1 e 1
b 1 f 2
b 1 f 2

c 2 d 0
c 2 d 0
c 2 e 1
c 2 e 1
c 2 f 2
c 2 f 2

now, remove JUST the second block param for bar:

{{#each foo as |fooValue fooIndex|}}
  {{#each bar}}
    {{fooValue}} {{fooIndex}} {{barValue}} {{barIndex}}
    {{../this}} {{../@index}} {{this}} {{@index}}
  {{/each}}
{{/each}}

I would expect the same output as above, except I would expect empty value for barValue and barIndex since they're never set. However, this actually completely messes up the entire context!

a 0
a 0 a 0
a 0
a 0 a 0
a 0
a 0 a 0

b 1
b 1 b 1
b 1
b 1 b 1
b 1
b 1 b 1

c 2
c 2 c 2
c 2 
c 2 c 2
c 2
c 2 c 2

The lines generated by {{fooValue}} {{fooIndex}} {{barValue}} {{barIndex}} are as I would expect. However, the lines generated by {{../this}} {{../@index}} {{this}} {{@index}} are all wrong! Specifically, {{this}} and {{@index}} are referring to the parent block context (rather than the real current context)! It seems like since there was no block context in the second each, it used the parent context as its own, rather than creating a new context with no block parameters.

dontgitit avatar Dec 05 '18 05:12 dontgitit

I think this may be suspect? https://github.com/jknack/handlebars.java/blob/master/handlebars/src/main/java/com/github/jknack/handlebars/Options.java#L532-L539

dontgitit avatar Dec 05 '18 05:12 dontgitit

ah, actually I think it's this! https://github.com/jknack/handlebars.java/blob/master/handlebars/src/main/java/com/github/jknack/handlebars/Context.java#L96-L98

when the second EachHelper creates its child context (https://github.com/jknack/handlebars.java/blob/master/handlebars/src/main/java/com/github/jknack/handlebars/helper/EachHelper.java#L64), the child context will be a ParentFirst context (because the current context from https://github.com/jknack/handlebars.java/blob/master/handlebars/src/main/java/com/github/jknack/handlebars/helper/EachHelper.java#L60 is a BlockParam context. Why does BlockParam overwrite newChildContext to create a ParentFirst rather than the usual Context?

dontgitit avatar Dec 05 '18 06:12 dontgitit

don't remember exactly, but think is was bc of handlebars.js

jknack avatar Dec 05 '18 11:12 jknack

hmm... are you saying this is expected/correct behavior, and same in handlebars.js? it seems strange that not using block params completely messes up the whole context...

dontgitit avatar Dec 06 '18 03:12 dontgitit

it works fine in handlebars.js so i think it's a bug here?

fiddle: https://jsfiddle.net/L3emzo2k/5/

dontgitit avatar Dec 06 '18 22:12 dontgitit

there was a reason.. do me a favor change the childcontext to do a normal Context, not a ParentFirst. Then run the tests, you will see there some odd use cases which were all inherited from handlebarjs (if my memory doesn't fail)

jknack avatar Dec 06 '18 22:12 jknack

i see... this test fails: https://github.com/jknack/handlebars.java/blob/master/handlebars/src/test/java/com/github/jknack/handlebars/BlockParamsTest.java#L70-L87

dontgitit avatar Dec 06 '18 22:12 dontgitit

I'm surprise it is only one, sadly I copied that tests from handlebarsjs tests :S

jknack avatar Dec 06 '18 23:12 jknack

Looking at the test, I'm not quite sure why it expects that output....

it registers the helper goodbyes:

$("goodbyes", new Helper<Object>() {
          int value = 1;

          @Override
          public Object apply(final Object context, final Options options)
              throws IOException {
            if (options.blockParams.size() > 0) {
              return options.apply(options.fn, $("value", "bar"),
                  Arrays.<Object> asList(value++, value++));
            }
            return options.fn($("value", "bar"));
          }
        })

this is the template... i added # value = EXPECTED to what I would think the value should be...

# value = foo
{{#goodbyes as |value|}} # value = 1
  {{#goodbyes}} # value = bar
    {{value}} # bar - why are we expecting 1 here? the goodbyes above just set value to bar... why should its parent block params take precedence?
    {{#goodbyes as |value|}}
        {{value}}
    {{/goodbyes}}
  {{/goodbyes}}
{{/goodbyes}}
{{value}}

going to try to run this in handlebars.js and see what happens... this doesn't seem correct....

dontgitit avatar Dec 06 '18 23:12 dontgitit

hmm... you're right.. handlebars.js has this weird behavior.... https://jsfiddle.net/L3emzo2k/6/

dontgitit avatar Dec 06 '18 23:12 dontgitit

:( doesn't make sense, never understand why they did it like this

jknack avatar Dec 06 '18 23:12 jknack

what i find strange though is that the each sample works in handlebars.js but breaks here.... can't figure out why...

dontgitit avatar Dec 06 '18 23:12 dontgitit

opened an issue against hbars... https://github.com/wycats/handlebars.js/issues/1484

dontgitit avatar Dec 07 '18 00:12 dontgitit

sorry to bring this up again, but do you have any other thoughts/ideas? the behavior here is definitely a bit different than handlebars.js. Here's a sample fiddle: https://jsfiddle.net/ybow1gnr/

As expected, it outputs foo, whereas the same template+input generates top level in handebars.java

dontgitit avatar Apr 04 '20 01:04 dontgitit