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

Help with nested template #339

Closed
zeroactual opened this Issue Jun 12, 2015 · 17 comments

Comments

Projects
None yet
4 participants
@zeroactual

zeroactual commented Jun 12, 2015

I'm having problems rendering templates. If all the template files are empty except the base template then there is no issue with the way my templates are currently. The problem is that I can't seem to figure out how to render a template with variables across multiple of the included templates such as in the Base layout and inside a navbar or content area.

I tried nested structs but have had no luck figuring out how to do this. Can anyone point me in the right direction?

Base.html

{{define "base"}}
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>{{.title}}</title>
    </head>
    <body>
        {{template "navbar"}}
        {{template "content"}}
    </body>
</html>
{{end}}

Index.html

{{define "index"}}
{{.stuff}}
{{end}}

What I'm trying to do when rendering the template

// Index page template
var index_template = template.Must(template.ParseFiles(
template_dir + "Base.html",
partials_dir + "Navbar.html",
template_dir + "Index.html"))

// Index page handler
r.GET("", func(c *gin.Context) {
    r.SetHTMLTemplate(contact_template)
    c.HTML(200, "base", data)    // How to make data?
})
@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 12, 2015

Contributor

@zeroactual

  1. r.SetHTMLTemplate(contact_template)
    should be called at initialization. never inside a request since it is not thread safe.
  2. this is not a problem related with Gin
  3. you should pass the parameters to the content template:
    {{template "content" . }}
    http://grokbase.com/t/gg/golang-nuts/138ygwm5yf/go-nuts-whats-the-best-way-to-pass-two-variables-to-a-template
Contributor

manucorporat commented Jun 12, 2015

@zeroactual

  1. r.SetHTMLTemplate(contact_template)
    should be called at initialization. never inside a request since it is not thread safe.
  2. this is not a problem related with Gin
  3. you should pass the parameters to the content template:
    {{template "content" . }}
    http://grokbase.com/t/gg/golang-nuts/138ygwm5yf/go-nuts-whats-the-best-way-to-pass-two-variables-to-a-template
@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 12, 2015

Contributor

Btw, to make the data, you can use a map[string]type{ .. } a struct, or use the built-in type gin.H.

c.HTML(200, "base", gin.H{
    "title": "Welcome",
    "stuff": "this is some interesting stuff",
})

gin.H is a map[string]interface{} with some other facilities (it also works with XML) and more convenient since it is much shorter.

Contributor

manucorporat commented Jun 12, 2015

Btw, to make the data, you can use a map[string]type{ .. } a struct, or use the built-in type gin.H.

c.HTML(200, "base", gin.H{
    "title": "Welcome",
    "stuff": "this is some interesting stuff",
})

gin.H is a map[string]interface{} with some other facilities (it also works with XML) and more convenient since it is much shorter.

@zeroactual

This comment has been minimized.

Show comment
Hide comment
@zeroactual

zeroactual Jun 12, 2015

Okay it seems that switching {{template "content"}} to {{template "content" . }} Fixed the issue with the content not rendering. That only leaves the question of my usage of

r.SetHTMLTemplate(contact_template)

Since it's not thread safe then how do I tell the routes which template I want to render if I have multiple pages. Would I have to change {{template "content" .}} to be {{.content}} then render the partial I wanted to be there and pass the output of the partial in with the data?

zeroactual commented Jun 12, 2015

Okay it seems that switching {{template "content"}} to {{template "content" . }} Fixed the issue with the content not rendering. That only leaves the question of my usage of

r.SetHTMLTemplate(contact_template)

Since it's not thread safe then how do I tell the routes which template I want to render if I have multiple pages. Would I have to change {{template "content" .}} to be {{.content}} then render the partial I wanted to be there and pass the output of the partial in with the data?

@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 12, 2015

Contributor

@zeroactual if you have multiple templates, you have three options:

  1. Change your layout design: instead of a base template + content template, you have content-template that imports the header + footer
  2. Since Gin HTML is pluggable you can use custom renders: you should use multitemplate (created for your use case: https://github.com/gin-gonic/contrib/tree/master/renders/multitemplate
  3. Avoid using c.HTML() and call template.ExecuteTemplate(c.Writer,...) directly.

I recommend you, the second choice!

Contributor

manucorporat commented Jun 12, 2015

@zeroactual if you have multiple templates, you have three options:

  1. Change your layout design: instead of a base template + content template, you have content-template that imports the header + footer
  2. Since Gin HTML is pluggable you can use custom renders: you should use multitemplate (created for your use case: https://github.com/gin-gonic/contrib/tree/master/renders/multitemplate
  3. Avoid using c.HTML() and call template.ExecuteTemplate(c.Writer,...) directly.

I recommend you, the second choice!

@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 12, 2015

Contributor

As you can see: c.HTML can execute arbitrary code, it means you can use any setup and or render engine (like pongo2) and continue using the c.HTML() API.

Contributor

manucorporat commented Jun 12, 2015

As you can see: c.HTML can execute arbitrary code, it means you can use any setup and or render engine (like pongo2) and continue using the c.HTML() API.

@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 12, 2015

Contributor

Would I have to change {{template "content" .}} to be {{.content}} then render the partial I wanted to be there and pass the output of the partial in with the data?

That could be a different way to fix it! but you would be calling c.HTML(200, "base", gin.H{"content": "index"}). Always "base". which is not nice.

Contributor

manucorporat commented Jun 12, 2015

Would I have to change {{template "content" .}} to be {{.content}} then render the partial I wanted to be there and pass the output of the partial in with the data?

That could be a different way to fix it! but you would be calling c.HTML(200, "base", gin.H{"content": "index"}). Always "base". which is not nice.

@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 12, 2015

Contributor
templates := multitemplate.New()
templates.AddFromFiles("index",
    template_dir + "Base.html",
    partials_dir + "Navbar.html",
    template_dir + "Index.html")

templates.AddFromFiles("contact",
    template_dir + "Base.html",
    partials_dir + "Navbar.html",
    template_dir + "Contact.html")

router := gin.New()
router.HTMLRender = templates

but I would suggest you to move the HTML templates initialisation to a different method, so the code would be much cleaner:

router := gin.New()
router.HTMLRender = loadTemplates()
Contributor

manucorporat commented Jun 12, 2015

templates := multitemplate.New()
templates.AddFromFiles("index",
    template_dir + "Base.html",
    partials_dir + "Navbar.html",
    template_dir + "Index.html")

templates.AddFromFiles("contact",
    template_dir + "Base.html",
    partials_dir + "Navbar.html",
    template_dir + "Contact.html")

router := gin.New()
router.HTMLRender = templates

but I would suggest you to move the HTML templates initialisation to a different method, so the code would be much cleaner:

router := gin.New()
router.HTMLRender = loadTemplates()
@zeroactual

This comment has been minimized.

Show comment
Hide comment
@zeroactual

zeroactual Jun 12, 2015

Okay this multitemplate looks pretty good but it looks like the version go get pulled down won't compile.

# github.com/gin-gonic/contrib/renders/multitemplate
/Users/wes/go/src/github.com/gin-gonic/contrib/renders/multitemplate/multitemplate.go:13: cannot use Render literal (type *Render) as type render.HTMLRender in assignment
/Users/wes/go/src/github.com/gin-gonic/contrib/renders/multitemplate/multitemplate.go:50: undefined: render.HTML

zeroactual commented Jun 12, 2015

Okay this multitemplate looks pretty good but it looks like the version go get pulled down won't compile.

# github.com/gin-gonic/contrib/renders/multitemplate
/Users/wes/go/src/github.com/gin-gonic/contrib/renders/multitemplate/multitemplate.go:13: cannot use Render literal (type *Render) as type render.HTMLRender in assignment
/Users/wes/go/src/github.com/gin-gonic/contrib/renders/multitemplate/multitemplate.go:50: undefined: render.HTML
@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 12, 2015

Contributor

go get -u github.com/gin-gonic/gin
go get -u github.com/gin-gonic/contrib/renders/multitemplate

?
it works for me

Contributor

manucorporat commented Jun 12, 2015

go get -u github.com/gin-gonic/gin
go get -u github.com/gin-gonic/contrib/renders/multitemplate

?
it works for me

@zeroactual

This comment has been minimized.

Show comment
Hide comment
@zeroactual

zeroactual Jun 12, 2015

Updating the dependencies fixed that problem. Right now it's returning a blank string.

What I have now for is

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/contrib/renders/multitemplate"
)

func main() {
    templates := multitemplate.New()
    templates.AddFromFiles("index",
        "Base.html",
        "Navbar.html",
        "Index.html")

    templates.AddFromFiles("contact",
        "Base.html",
        "Navbar.html",
        "Contact.html")

    router := gin.New()
    router.HTMLRender = templates

    router.GET("", func(c *gin.Context) {
        c.HTML(200, "index", gin.H{
            "title": "Home",
            "stuff": "Interesting home stuff",
        })
    })

    router.GET("/contact", func(c *gin.Context) {
        c.HTML(200, "contact", gin.H{
            "title": "Contact",
            "stuff": "Interesting contact stuff",
        })
    })
    router.Run(":8080")
}

Base.html

{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{.title}}</title>
</head>
<body>
{{template "navbar"}}
{{template "content"}}
</body>
</html>
{{end}}

Index.html

{{define "index"}}
<div>
    Main content
    {{.stuff}}
</div>
{{end}}

Contact.html

{{define "contact"}}
<div>
    Contact content
    {{.stuff}}
</div>
{{end}}

Navbar.html*

{{define "navbar"}}
<div class="nav">This is a navbar</div>
{{end}}

I think that this part is causing me some of the problems

<body>
{{template "navbar"}}
{{template "content"}} <!--- not sure what this should be -->>
</body>

zeroactual commented Jun 12, 2015

Updating the dependencies fixed that problem. Right now it's returning a blank string.

What I have now for is

package main

import (
    "github.com/gin-gonic/gin"
    "github.com/gin-gonic/contrib/renders/multitemplate"
)

func main() {
    templates := multitemplate.New()
    templates.AddFromFiles("index",
        "Base.html",
        "Navbar.html",
        "Index.html")

    templates.AddFromFiles("contact",
        "Base.html",
        "Navbar.html",
        "Contact.html")

    router := gin.New()
    router.HTMLRender = templates

    router.GET("", func(c *gin.Context) {
        c.HTML(200, "index", gin.H{
            "title": "Home",
            "stuff": "Interesting home stuff",
        })
    })

    router.GET("/contact", func(c *gin.Context) {
        c.HTML(200, "contact", gin.H{
            "title": "Contact",
            "stuff": "Interesting contact stuff",
        })
    })
    router.Run(":8080")
}

Base.html

{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{.title}}</title>
</head>
<body>
{{template "navbar"}}
{{template "content"}}
</body>
</html>
{{end}}

Index.html

{{define "index"}}
<div>
    Main content
    {{.stuff}}
</div>
{{end}}

Contact.html

{{define "contact"}}
<div>
    Contact content
    {{.stuff}}
</div>
{{end}}

Navbar.html*

{{define "navbar"}}
<div class="nav">This is a navbar</div>
{{end}}

I think that this part is causing me some of the problems

<body>
{{template "navbar"}}
{{template "content"}} <!--- not sure what this should be -->>
</body>
@techjanitor

This comment has been minimized.

Show comment
Hide comment
@techjanitor

techjanitor Jun 13, 2015

Contributor

I had this same issue with that template design. I basically got rid of the 'base' template and just have a header/footer with all the html boilerplate included with all my other templates. It accomplishes the same thing, and it allows you to use SetHTMLTemplate with ParseGlob for loading templates as well.

For example:

{{define "tag"}}
{{template "header" .}}
Stuff
{{template "footer" .}}
{{end}}

and that can be called with:

c.HTML(200, "tag", data)
Contributor

techjanitor commented Jun 13, 2015

I had this same issue with that template design. I basically got rid of the 'base' template and just have a header/footer with all the html boilerplate included with all my other templates. It accomplishes the same thing, and it allows you to use SetHTMLTemplate with ParseGlob for loading templates as well.

For example:

{{define "tag"}}
{{template "header" .}}
Stuff
{{template "footer" .}}
{{end}}

and that can be called with:

c.HTML(200, "tag", data)
@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 13, 2015

Contributor

@zeroactual in your case, remove the {{define "base"}}. Since it is the base, it can not have a defined name...

Contributor

manucorporat commented Jun 13, 2015

@zeroactual in your case, remove the {{define "base"}}. Since it is the base, it can not have a defined name...

@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 13, 2015

Contributor

When using multitemplate, Gin runs: templates[name].Execute() instead of template.ExecuteTemplate(name)

Contributor

manucorporat commented Jun 13, 2015

When using multitemplate, Gin runs: templates[name].Execute() instead of template.ExecuteTemplate(name)

@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 13, 2015

Contributor

Also since Base.html is importing a template called: "content" why are you doing {{define "index"}}?

Contributor

manucorporat commented Jun 13, 2015

Also since Base.html is importing a template called: "content" why are you doing {{define "index"}}?

@manucorporat

This comment has been minimized.

Show comment
Hide comment
@manucorporat

manucorporat Jun 13, 2015

Contributor

@zeroactual @techjanitor
fix your templates.

Base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{.title}}</title>
</head>
<body>
{{template "navbar"}}
{{template "content" .}}
</body>
</html>

Index.html

{{define "content"}}
<div>
    Main content
    {{.stuff}}
</div>
{{end}}

Contact.html

{{define "content"}}
<div>
    Contact content
    {{.stuff}}
</div>
{{end}}

Navbar.html*

{{define "navbar"}}
<div class="nav">This is a navbar</div>
{{end}}

RTFM :D This kind of issues are well defined in the standard library documentation.

This is what I changed:

  • Removed {{ define "base" }}
  • Replaced {{ define "index }}, {{ define "contact"}}... with {{ define "content" }}
  • Replaced {{template "content"}} with {{template "content" .}}
Contributor

manucorporat commented Jun 13, 2015

@zeroactual @techjanitor
fix your templates.

Base.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>{{.title}}</title>
</head>
<body>
{{template "navbar"}}
{{template "content" .}}
</body>
</html>

Index.html

{{define "content"}}
<div>
    Main content
    {{.stuff}}
</div>
{{end}}

Contact.html

{{define "content"}}
<div>
    Contact content
    {{.stuff}}
</div>
{{end}}

Navbar.html*

{{define "navbar"}}
<div class="nav">This is a navbar</div>
{{end}}

RTFM :D This kind of issues are well defined in the standard library documentation.

This is what I changed:

  • Removed {{ define "base" }}
  • Replaced {{ define "index }}, {{ define "contact"}}... with {{ define "content" }}
  • Replaced {{template "content"}} with {{template "content" .}}
@zeroactual

This comment has been minimized.

Show comment
Hide comment
@zeroactual

zeroactual Jun 13, 2015

Thanks! That fixed it. It seems I got confused with how to do this when I was playing around with only one page and used a glob. The glob only allowed you to have one content block declared so I changed the names.

I was trying to RTM but I'm so used to Java that I think I was looking at this all the wrong way. Thanks again.

zeroactual commented Jun 13, 2015

Thanks! That fixed it. It seems I got confused with how to do this when I was playing around with only one page and used a glob. The glob only allowed you to have one content block declared so I changed the names.

I was trying to RTM but I'm so used to Java that I think I was looking at this all the wrong way. Thanks again.

@zeroactual zeroactual closed this Jun 13, 2015

ikspres added a commit to ikspres/gostack that referenced this issue Mar 5, 2016

- Applied nested templates See..
  gin-gonic/gin#339
  github.com/gin-gonic/contrib/renders/multitemplate
- Changed web theme to lumino
  https://colorlib.com/wp/free-bootstrap-admin-dashboard-templates/
- Seperated openstack api accessing functions to package /opst
@Tom29

This comment has been minimized.

Show comment
Hide comment
@Tom29

Tom29 Mar 12, 2018

@manucorporat by using multitemplate i have to declare all the multi templates outside of the request handle. It breaks the code organization. Using template.ExecuteTemplate(c.Writer,...) and individual cache system might be the better way.

Tom29 commented Mar 12, 2018

@manucorporat by using multitemplate i have to declare all the multi templates outside of the request handle. It breaks the code organization. Using template.ExecuteTemplate(c.Writer,...) and individual cache system might be the better way.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment