gobuffalo / plush

The powerful template system that Go needs
MIT License
901 stars 56 forks source link

Check if contentFor block exists. #108

Closed bitver closed 3 years ago

bitver commented 4 years ago

When contentFor does not exists, contentOf falls with exception: could not call contentOf function: missing contentOf block How to check if contentFor block exists? I do not want to write contentFor in each template.

r3ap3r2004 commented 4 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.

contentFor - original

https://github.com/gobuffalo/helpers/blob/022c40f7950f388a23822b44560d7676b30463d7/content/for.go#L18-L30

contentFor - modified

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

contentOf - original

https://github.com/gobuffalo/helpers/blob/022c40f7950f388a23822b44560d7676b30463d7/content/of.go#L16-L35

contentOf - modified

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
}
paganotoni commented 4 years ago

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.

r3ap3r2004 commented 4 years ago

@paganotoni - Thank you for the response, I'll definitely give that a try.

paganotoni commented 3 years ago

Closing this one please reopen if necessary. ContentFor allows to specify a default value on the body of the expression.

silverark commented 1 year ago

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