How to Render multiple templates

28,738

Solution 1

If you define all your templates in a template-folder, you can easily parse the whole directory with:

template.Must(template.ParseGlob("YOURDIRECTORY/*"))

For example:

head.html

{{define "header"}}
     <head>
         <title>Index</title>
     </head>
{{end}}

index.html

{{define "indexPage"}}
    <html>
    {{template "header"}}
    <body>
        <h1>Index</h1>
    </body>
    </html>
{{end}}

main.go

package main

import(
    "html/template"
)

// compile all templates and cache them
var templates = template.Must(template.ParseGlob("YOURTEMPLATEDIR/*"))

func main(){
    ...
}

func IndexHandler(w http.ResponseWriter, r *http.Request) {

    // you access the cached templates with the defined name, not the filename
    err := templates.ExecuteTemplate(w, "indexPage", nil)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}

You execute your indexPage-Template with templates.ExecuteTemplate(w, "indexPage", nil)

Solution 2

You can easily add more .html files by just adding them as arguments:

var tmpl = template.Must(template.ParseFiles(
    "templates/base.html",
    "templates/first.html",
    "templates/second.html",
))

This works fine as long as first and second don't define the same template.
However, the template package does not allow dynamic invocation of templates, using a pipeline value for the template name. So, if you are trying to do something similar to my example below, then it will not work.

Some workarounds exists, and there is a discussion about it on Go-nuts. But it seems the template package is designed that you should have one *Template object per page.

Broken example of attempted dynamic template invocation:
Error: unexpected ".Content" in template invocation

package main

import (
    "log"
    "os"
    "text/template"
)

const base= `<!DOCTYPE html>
<html>
    <head><title>Title</title></head>
<body>
    {{template .Content}}
</body>
</html>`

const first = `{{define "first"}}This is the first page{{end}}`
const second = `{{define "second"}}And here is the second{{end}}`

type View struct {
    Content  string
}

func main() {
    var view = &View{ "first" } // Here we try to set which page to view as content
    t := template.Must(template.New("base").Parse(base))
    t = template.Must(t.Parse(first))
    t = template.Must(t.Parse(second))
    err := t.Execute(os.Stdout, view)
    if err != nil {
        log.Println("executing template:", err)
    }
}

Code on play.golang.org

Solution 3

If you want to parse multiple directories a long with specific files, here's a code snippet for it:

//parse a pattern in a specific directory  
allTemplates := template.Must(template.ParseGlob("Directory/*"))

//add another directory for parsing 
allTemplates = template.Must(allTemplates.ParseGlob("Another_Directory/*.tmpl"))

//add specific file name 
allTemplates = template.Must(allTemplates.ParseFiles("path/to/file.tmpl"))


func main() {
 ...
}

func FileHandler(w http.ResponseWriter, r *http.Request) {

    // access cached template by file name (in case you didn't define its name) 
    err := allTemplates.ExecuteTemplate(w, "file.tmpl", nil)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}

func NameHandler(w http.ResponseWriter, r *http.Request) {

    // access cached template by handler name 
    err := allTemplates.ExecuteTemplate(w, "handlerName", nil)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
}

a quick shortcut:

allTemplates := template.Must(
        template.Must(
        template.Must(
            template.ParseGlob("directory/*.tmpl")).
            ParseGlob("another_directory/*.tmpl")).
            ParseFiles("path/to/file.tmpl")),
                )

file.tmpl can be like the following:

<html>
This is a template file 
</html>

name.tmpl should be looking like this

{{define "handlerName" }}
<p>this is a handler</p>
{{end}}

Solution 4

part 3 of this tutorial is useful:

http://golangtutorials.blogspot.co.nz/2011/11/go-templates-part-3-template-sets.html

Sample from the tutorial:

Full template file - t1.tmpl

{{define "t_ab"}}a b{{template "t_cd"}}e f {{end}}

The file above will be parsed in as a template named "t_ab". It has, within it, "a b /missing/ e f", but is missing a couple of letters in the alphabet. For that it intends to include another template called "t_cd" (which should be in the same set).

Full template file - t2.tmpl

{{define "t_cd"}} c d {{end}}

The file above will be parsed in as a template called "t_cd".

Full program

package main

import (
    "text/template"
    "os"
    "fmt"
    )

func main() {
    fmt.Println("Load a set of templates with {{define}} clauses and execute:")
    s1, _ := template.ParseFiles("t1.tmpl", "t2.tmpl") //create a set of templates from many files.
    //Note that t1.tmpl is the file with contents "{{define "t_ab"}}a b{{template "t_cd"}}e f {{end}}"
    //Note that t2.tmpl is the file with contents "{{define "t_cd"}} c d {{end}}"


    s1.ExecuteTemplate(os.Stdout, "t_cd", nil) //just printing of c d
    fmt.Println()
    s1.ExecuteTemplate(os.Stdout, "t_ab", nil) //execute t_ab which will include t_cd
    fmt.Println()
    s1.Execute(os.Stdout, nil) //since templates in this data structure are named, there is no default template and so it prints nothing
}
Share:
28,738
fmt.Fprint
Author by

fmt.Fprint

Updated on December 06, 2020

Comments

  • fmt.Fprint
    fmt.Fprint over 3 years

    One base template is created. With that rendered first.html one more template.

    eg. :
        var tmpl = template.Must(template.ParseFiles(
        "templates/base.html",
        "templates/first.html",
        ))
    

    But I also want to add more .html files to render. Any reference?

  • fmt.Fprint
    fmt.Fprint almost 11 years
    ANisus : If I click event trigger on button of first page then it should render the other page mentioned ... There are Redirect and renderTemplate functions but they are also not working. For base.html and first.html I am able to render the page. But for third html page it is not working
  • ANisus
    ANisus almost 11 years
    @fmt.Fprint I think what you need is to get some structure for your templates and handlers. Kyle Finley has a good suggestion for this: (stackoverflow.com/a/9587616/694331)
  • fmt.Fprint
    fmt.Fprint almost 11 years
    :main.go : var templates template.Template templates = template.Must(template.ParseGlob("templates/.tmpl")) func init(){http.HandleFunc("/", handler)} func handler(w http.ResponseWriter, r *http.Request){templates.ExecuteTemplate(w, "indexPage", nil)} This example is giving Error "expected declaration, found 'IDENT' templates"
  • fmt.Fprint
    fmt.Fprint almost 11 years
    This link is already tried. base.html and first.html is fine. But problem with third and fourth ones
  • ioboi
    ioboi almost 11 years
    Your declaration is false, it should be: var templates := template.Must(template.ParseGlob("templates/.tmpl/.*")) I assume that all your templates are in the templates/.tmpl/.* directory
  • eduncan911
    eduncan911 almost 10 years
    I updated this answer to correct the typeos and compile errors. +1 as this help me myself to correctly define multiple templates. It isnt clear at first as most of us coming from .net and other languages with layout and templates that you do NOT create one single master and a single content area but instead define shared header and footers and sidebars. This is actually nice because you can control rather to show the sidebar or not on a per template process.
  • swill
    swill about 9 years
    This should be the accepted answer. This answer is MUCH clearer than the documentation. It shows how to use the global templates variable so you load all your templates on server start instead of in your actual handler (which would be loaded on each page load). It also shows how to define a template by name and then call it with templates.ExecuteTemplate, which is super helpful. By far the best documentation I have seen on this topic...
  • wuliwong
    wuliwong over 8 years
    I'm quite new to golang but it seems the issue boils down to the fact that you cannot pass a variable of type string to the template method but only a string literal? I'm also not a CS guy, is there an obvious reason for the Go developers to make this choice? I guess this is actually in the documentation here: {{template "name"}} The template with the specified name is executed with nil data. I just didn't pick up that they were writing "name" to imply a string literal instead of writing name string.
  • FisNaN
    FisNaN about 5 years
    Have you test it? syntax error: non-declaration statement outside function body...
  • Muhammad Soliman
    Muhammad Soliman about 5 years
    @FisNan have you tried to use var allTemplates = ... instead. if it doesn't help, you still can put all the code inside main() func , the answer gives you idea how to do it in general .. hope my comment would help you though, thanks
  • FisNaN
    FisNaN about 5 years
    I worked out with a global null pointer var allTemplates *template.Template and then put all template.Must(...) inside main function. Thanks for the response though!