Hello there!
I am trying to use the new "block" features in the html/template
package in go 1.6, but I cannot get it to work inside a gin application.
I filled a thorough description of the bug as well as example code in the repository at https://github.com/esantoro/gin-template-bug
TLDR
layout.html
<!doctype html>
<html>
<head>
<title>{{block "title" .}} DEFAULT TITLE {{end}}</title>
</head>
<body>
<div id="container" style="border: 1px solid black;">
{{ block "content" .}} DEFAULT CONTENT {{ end }}
</div>
<div class="footer" style="border: 1px solid yellow;">
{{ block "footer" .}}
DEFAULT FOOTER
{{ end }}
</div>
</body>
</html>
index.html :
{{define "title"}} CUSTOMIZED TITLE {{end}}
{{define "content"}} CUSTOMIZED CONTENT {{end}}
{{ define "footer" }} CUSTOMIZED FOOTER {{end}}
Expected result: Rendering index.html should fill layout.html with values from index.html
Actual result: index.html is rendered to a blank page.
Comment From: im7mortal
Use layout.html instead index.html
ROUTER.GET("/", func(c *gin.Context) {
c.HTML(http.StatusOK, "layout.html", gin.H{})
})
Or do you expected that it should render different layouts in accordance with the name of block's group(like Jade behavior)? Check that a official example
Another issue is that it doesn't allow use several templates in gin.
In gin you can set only one template
ROUTER.SetHTMLTemplate(customTemplate)
//or
ROUTER.LoadHTMLFiles("layout.html", "index.html")
If you will try to redefine template you will have that warning message
[WARNING] Since SetHTMLTemplate() is NOT thread-safe. It should only be called
at initialization. ie. before any route is registered or the router is listening in a socket:
router := gin.Default()
router.SetHTMLTemplate(template) // << good place
But I understood that for blocks we need use several templates like this way
indexTemplate := template.Must(template.ParseFiles("layouts/default.html"))
//we use indexTemplate.Clone() for every block. Then we inject that block
tm1 = template.Must(template.Must(indexTemplate.Clone()).ParseFiles("body.html"))
tm2 = template.Must(template.Must(indexTemplate.Clone()).ParseFiles("body2.html"))
I was trying some stupid approaches like
if trigger {
Router.SetHTMLTemplate(tm1)
println(tm1.DefinedTemplates())
} else {
Router.SetHTMLTemplate(tm2)
println(tm2.DefinedTemplates())
}
But gin ignore it.
Comment From: im7mortal
I solved it for me this way.
I created a global template's storage which contain *render.HTML
var TemplateStorage map[string]*render.HTML = make(map[string]*render.HTML)
Then I have function for initialization of templates
/**
*
* "base" is a base template(i don't use template.Clone() I always create new)
* "name" is name template in the TemplateStorage
* "paths" is paths to necessary templates
*/
func InitTemplate(base, name string, paths... string) {
// append base tmpl dynamically to slice of paths
paths = append(paths, base)
TemplateStorage[name] = &render.HTML{
Template: template.Must(template.New(name).ParseFiles(paths...)),
Name: base,
}
}
default.html
<!DOCTYPE html>
<html>
<head>
{{block "head" .}} {{end}}
</head>
<body>
{{block "body" .}} {{end}}
</body>
</html>
about.html
{{define "head"}}
<title>About</title>
{{end}}
{{define "body"}}
Hello!!
{{end}}
use
defaultTmpl := "default.html"
InitTemplate(defaultTmpl, "/about/", "about.html")
router.GET("/about/", About)
func About(c *gin.Context) {
render, _ := TemplateStorage[c.Request.URL.Path]
//I use a c.Keys map like a data
render.Data = c.Keys
c.Render(http.StatusOK, render)
}
It works fine
@manucorporat @javierprovecho Who can give me some comments about it?
Comment From: esantoro
Hello there, original poster here.
I think this issued should be solved by adding a new HTMLTemplate
function to the gin Context, like this:
// prototype being:
// func (c *Context) HTMLTemplate(code int, layout string name string, obj interface{})
func AController(ctx *gin.Context) {
ctx.HTMLTemplate(http.StatusOK, "template.html", "page.html", gin.H{ ... })
}
In this way we keep compatibility with older code base and still have the default HTML
method, but we gain a new feature.
I would really like to implement this. Is it okay?
Can I start working on this feature?
Comment From: im7mortal
@esantoro Hi! New function for gin.Context is not good solution. There are interfaces. Which allow use gin.Context.HTML without modifications. I created new method for gin.Engine AddHTMLTemplate which allow inject a custom HTMLRender to the engine. Check the commit for details.
Also I noticed you wanted to use template.ParseFiles() every time. It's not good too.
Comment From: im7mortal
@esantoro I found a solution. There are a multitemplate render for gin I am going add link on it to the documentation.
Comment From: esantoro
Hi @im7mortal, I will be testing this as soon as possible (midterms time, I'm sorry) and I will be giving you some feedback as soon as possible.
Thanks!
Comment From: appleboy
new repo link: https://github.com/gin-contrib/multitemplate also update readme https://github.com/gin-gonic/gin/pull/786
Comment From: Ianmuhia
new repo link: https://github.com/gin-contrib/multitemplate also update readme #786 using the go "block " template feature with returns an empty white page