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

Helper within partial doesn't have access to the partial context #15

Open
kabukky opened this issue Apr 19, 2017 · 3 comments · May be fixed by #19
Open

Helper within partial doesn't have access to the partial context #15

kabukky opened this issue Apr 19, 2017 · 3 comments · May be fixed by #19

Comments

@kabukky
Copy link

kabukky commented Apr 19, 2017

Hi,

thanks for this great library!

Here is the code to reproduce the issue mentioned in the title:

package main

import (
	"fmt"

	"github.com/aymerick/raymond"
)

func main() {
	//template
	templateString := `
<div class="post">
	{{> userMessage tagName="h1" }}
	<h1>Comments</h1>
	{{#each comments}}
		{{> userMessage tagName="h2" }}
	{{/each}}
</div>
`
	tpl := raymond.MustParse(templateString)

	// partial
	partialString := `
<{{tagName}}>
	By {{author.firstName}} {{author.lastName}}
</{{tagName}}>
<div class="body">
	{{content}}
	{{body}}
</div>
`
	tpl.RegisterPartial("userMessage", partialString)

	// content helper
	raymond.RegisterHelper("content", func(options *raymond.Options) string {
		fmt.Println("body: ", options.ValueStr("body"))
		return ""
	})

	// context
	ctx := map[string]interface{}{
		"author": map[string]string{
			"firstName": "Alan",
			"lastName":  "Johnson",
		},
		"body": "I Love Handlebars",
		"comments": []map[string]interface{}{
			map[string]interface{}{
				"author": map[string]string{
					"firstName": "Yehuda",
					"lastName":  "Katz",
				},
				"body": "Me too!",
			},
		},
	}
	result := tpl.MustExec(ctx)
	fmt.Println(result)
}

And here's the output:

body:
body:

<div class="post">

	<h1>
		By Alan Johnson
	</h1>
	<div class="body">

		I Love Handlebars
	</div>
	<h1>Comments</h1>

		<h2>
			By Yehuda Katz
		</h2>
		<div class="body">

			Me too!
		</div>

</div>

As you can see, options.ValueStr("body") inside the content helper is an empty string, while {{body}} in the partial renders just fine.
I would expect the content helper to have access to the context it is used under.
Is this intended behavior?

Best,
Kai

@kabukky
Copy link
Author

kabukky commented Apr 20, 2017

Update: If I remove the hash parameters for the partial it works just fine:

<div class="post">
	{{> userMessage }}
	<h1>Comments</h1>
	{{#each comments}}
		{{> userMessage tagName="h2" }}
	{{/each}}
</div>

Results in:

body:  I Love Handlebars
body:

So the helper in {{> userMessage }} gets the parent context, the helper in {{> userMessage tagName="h2" }} does not.

@kabukky
Copy link
Author

kabukky commented Apr 20, 2017

I think I got to the root of it:

  • evalPartial pushes the hash parameters of the partial on the context stack.
  • Value on the Options usescurCtx(), which returns the context at the top of the stack. Inside a partial with hash parameters, this means that Value only has access to those hash parameters.
  • Evaluating {{body}} works because evalPathExpression walks through all contexts using evalDepthPath

How could we fix this?

  • Value could have access to all parent contexts. It seems reasonable at first glance since expressions within partials have access to all parent contexts too. Is that how handlebars.js does it?
  • partialContext, when it detects hash parameters, could create a new context that also includes the parent context like so:
	if node.Hash != nil {
		hash, _ := node.Hash.Accept(v).(map[string]interface{})
		curCtx, _ := v.curCtx().Interface().(map[string]interface{})
		newCtx := make(map[string]interface{})
		for k, v := range curCtx {
			newCtx[k] = v
		}
		for k, v := range hash {
			newCtx[k] = v
		}
		return reflect.ValueOf(newCtx)
	}

The second option seems preferable to me. That way the separation of contexts still holds when using Value inside a helper.
What do you think?
Just to repeat: I'm not sure if this is intended behavior - so please excuse me if this is in line with handlebars.js

@kabukky
Copy link
Author

kabukky commented Apr 26, 2017

I went another way in my fork and added a options.ValueFromAllCtx function (see #19). That way, there is no tinkering with existing functions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
1 participant