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