Closed bitver closed 3 years ago
I came here looking to see if anyone else had run into this problem and figured out how to fix it. Apparently nobody has an answer they want to share, so here is the solution that I came up with, although I'm sure you are long past solving this problem for yourself, but I figured I'd post this here so that I can always find it later if I ever need it again.
There are multiple problems with the implementation of contentFor and contentOf.
To be fair, you don't necessarily need the second feature if the third feature doesn't work, but I figured I would fix the first two and hopefully figure out the 3rd later.
I solved the first two problems locally by modifying the contentFor and contentOf methods.
Note: There may be issues with this solution, but so far it is working for me and hopefully it will be useful to someone else.
TL;DR; - Instead of a single value being set for each contentFor variable we store a slice of them. When we render the contentOf we loop through each item in the slice and add the content to the final output. In the event that there isn't a slice, don't throw an error (because we don't want to add contentFor to each and every template), and just return and empty string.
func ContentFor(name string, help hctx.HelperContext) {
fullName := "contentFor:" + name
var content []interface{}
contentForExisting := help.Value(fullName)
switch sl := contentForExisting.(type) {
case []interface{}:
// set content to the existing items
content = sl
default:
fmt.Println("type unknown") // here v has type interface{}
}
content = append(content, func(data hctx.Map) (string, error) {
hctx := help.New()
for k, v := range data {
hctx.Set(k, v)
}
body, err := help.BlockWith(hctx)
if err != nil {
return "", err
}
return body, nil
})
help.Set(fullName, content)
}
func ContentOf(name string, data hctx.Map, help hctx.HelperContext) (template.HTML, error) {
fullName := "contentFor:" + name
contentForExisting := help.Value(fullName)
var returnVal bytes.Buffer
switch content := contentForExisting.(type) {
case []interface{}:
// set content to the existing items
// var returnVal template.HTML
for _, block := range content {
fn, ok := block.(func(data hctx.Map) (string, error))
if !ok {
if !help.HasBlock() {
continue
}
hc := help.New()
for k, v := range data {
hc.Set(k, v)
}
body, err := help.BlockWith(hc)
if err != nil {
continue
}
_, err = returnVal.WriteString(body)
if err != nil {
return template.HTML(""), err
}
continue
}
result, err := fn(data)
if err != nil {
return template.HTML(""), err
}
_, err = returnVal.WriteString(result)
if err != nil {
return template.HTML(""), err
}
}
}
return template.HTML(returnVal.String()), nil
}
Hey @r3ap3r2004 👋, thanks for putting together this proposal. for point number 1:
Not declaring contentFor in every template throws an error on the pages that don't have it
If you establish a default body in your contentOf
definition you don't need to do it in every page that uses that layout.
p.e.
<!--- layout file div --->
<div id="page-content-wrapper" class="<%= contentOf("pageContentClass"){} %>">
...
Will allow you to use this layout without having to add contentFor
declaration in each of the pages that use it.
@paganotoni - Thank you for the response, I'll definitely give that a try.
Closing this one please reopen if necessary. ContentFor allows to specify a default value on the body of the expression.
@paganotoni
I was struggling with the same issue. It would be great if the fact you can add a default could be added to the Documentation page here: https://gobuffalo.io/documentation/frontend-layer/helpers/
When
contentFor
does not exists,contentOf
falls with exception:could not call contentOf function: missing contentOf block
How to check ifcontentFor
block exists? I do not want to writecontentFor
in each template.