How to render templates to multiple layouts in Go? - go

I need to render templates into different kinds of layout. Here's my directory structure.
myapp
|
│ main.go
│
├───static
│ script.js
│ style.css
│
└───templates
│ page1.tmpl
│ page2.tmpl
│ page3.tmpl
│ page4.tmpl
│ page5.tmpl
│
└───layouts
base1.tmpl
base2.tmpl
base3.tmpl
I have done rendering templates to a single layout template but, I can't make it work on multiple layouts. Here's what I got so far:
package main
import (
"html/template"
"net/http"
"fmt"
"github.com/urfave/negroni"
"github.com/oxtoacart/bpool"
"path/filepath"
"log"
)
var (
templates map[string]*template.Template
bufpool *bpool.BufferPool
)
func main() {
loadTemplates()
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, "page1.tmpl",nil)
})
n := negroni.New()
n.Use(negroni.NewLogger())
n.UseHandler(mux)
http.ListenAndServe(":8080", n)
}
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
tmpl, ok := templates[name]
if !ok {
return fmt.Errorf("The template %s does not exist.", name)
}
buf := bufpool.Get()
defer bufpool.Put(buf)
err := tmpl.ExecuteTemplate(buf, "base1.tmpl", data)
if err != nil {
return err
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
buf.WriteTo(w)
return nil
}
func loadTemplates() {
if templates == nil {
templates = make(map[string]*template.Template)
}
tmplDir := "templates/"
layouts, err := filepath.Glob(tmplDir + "layouts/*.tmpl")
if err != nil {
log.Fatal(err)
}
includes, err := filepath.Glob(tmplDir + "*.tmpl")
if err != nil {
log.Fatal(err)
}
for _, include := range includes {
files := append(layouts, include)
templates[filepath.Base(include)] = template.Must(template.ParseFiles(files...))
}
fmt.Print(includes)
bufpool = bpool.NewBufferPool(64)
}
Here's how base1.tmpl looks like:
{{define "base1"}}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{block "title" .}}{{end}}</title>
</head>
<body>
{{template "content" .}}
</body>
</html>
{{end}}
And here's how page1.tmpl looks like:
{{define "title"}}Page 1{{end}}
{{define "content"}}
<p>Page 1 contents</p>
{{end}}

I normally take the approach of rendering twice, once for content, once for layout, this lets you use any content in any layout and defer that decision till runtime. Would be interested in other approaches if other people do it differently, but this is working for me at present.
So using the code you have posted, something like this in handler:
data := map[string]interface{}{
"title": "hello world",
}
renderTemplate(w, "base1.tmpl", "page1.tmpl", data)
...
in renderTemplate pass in a layout as well as a template and:
// Render the template 'name' with data
buf := bufpool.Get()
err := tmpl.ExecuteTemplate(buf, name, data)
if err != nil {
return err
}
// Set the content as a key on data (set as html as it is rendered)
data["content"] = template.HTML(buf.Bytes())
bufpool.Put(buf)
// Render the layout 'layout' with data, using template as content key
buf = bufpool.Get()
defer bufpool.Put(buf)
err = tmpl.ExecuteTemplate(buf, layout, data)
if err != nil {
return err
}
Layout:
<html>
<body>
<h1>Base 1</h1>
{{.content}}
</body>
</html>
Page:
<h2>{{.title}}</h2>
<h3>Page 1</h3>
Here is a link to full code:
https://play.golang.org/p/R2vr4keZec

Related

Go: Templates Embedded in Binary Return Blank Page

Trying to move my golang html templates from files to using embed
Works fine:
func loadTemplates() multitemplate.Render {
r := multitemplate.New()
layouts, err := filepath.Glob("templates/layouts/*.tmpl")
if err != nil {
panic(err.Error())
}
includes, err := filepath.Glob("templates/includes/*.tmpl")
if err != nil {
panic(err.Error())
}
// Generate our templates map from our layouts/ and includes/ directories
for _, layout := range layouts {
files := append(includes, layout)
r.Add(filepath.Base(layout), template.Must(template.ParseFiles(files...)))
log.Println(filepath.Base(layout) + ": " + files[0])
}
return r
}
Very similar code returns blank page, no errors:
//go:embed templates/*
var f embed.FS
func loadTemplates() multitemplate.Render {
r := multitemplate.New()
// Generate our templates map from our layouts/ and includes/ directories
layouts, err := embed.FS.ReadDir(f, "templates/layouts")
if err != nil {
panic(err.Error())
}
for _, layout := range layouts {
embeddedTemplate, err := template.ParseFS(f, "templates/layouts/"+layout.Name(), "templates/includes/base.tmpl")
if err != nil {
log.Println(err)
}
r.Add(layout.Name(), embeddedTemplate)
log.Println(layout.Name() + " loaded")
}
return r
}
I confirmed in the debugger that all templates contain no errors and their respective content. Other embedded files such as static assets work fine and get served ok. Even other templates loaded from a database work fine. Just those from embed end up blank.
Any hints what's happening here?
Thanks!
Edit: Full example:
main.go
package main
import (
"embed"
"html/template"
"log"
"path/filepath"
"github.com/gin-contrib/multitemplate"
"github.com/gin-gonic/gin"
)
//go:embed templates/*
var f embed.FS
func main() {
router := gin.Default()
router.HTMLRender = loadTemplates()
router.GET("/embed", HomeHandlerEmbed(router))
router.GET("/file", HomeHandlerFile(router))
router.Run(":8080")
}
func loadTemplates() multitemplate.Render {
r := multitemplate.New()
//load same template from embed FS
embeddedTemplate, err := template.ParseFS(f, "templates/layouts/home.tmpl", "templates/includes/base.tmpl")
if err != nil {
log.Println(err)
}
r.Add("homeEmbed.tmpl", embeddedTemplate)
log.Println("homeEmbed.tmpl" + " loaded from embed FS")
// load same template from real file system
layoutsFile, err := filepath.Glob("templates/layouts/*.tmpl")
if err != nil {
panic(err.Error())
}
includes, err := filepath.Glob("templates/includes/*.tmpl")
if err != nil {
panic(err.Error())
}
for _, layout := range layoutsFile {
files := append(includes, layout)
r.Add(filepath.Base(layout), template.Must(template.ParseFiles(files...)))
log.Println(filepath.Base(layout) + ": " + files[0])
}
return r
}
func HomeHandlerEmbed(r *gin.Engine) gin.HandlerFunc {
return gin.HandlerFunc(func(c *gin.Context) {
c.HTML(200, "homeEmbed.tmpl", nil)
})
}
func HomeHandlerFile(r *gin.Engine) gin.HandlerFunc {
return gin.HandlerFunc(func(c *gin.Context) {
c.HTML(200, "home.tmpl", nil)
})
}
templates/includes/base.tmpl
<!DOCTYPE html>
<html>
<head>
{{template "head" .}}
</head>
<body>
{{template "body" .}}
</body>
</html>
templates/layouts/home.tmpl
{{define "head"}}<title>Test</title>{{end}}
{{define "body"}}
Body
{{end}}
/file works fine, /embed comes up blank
In function loadTemplates() just fix this line:
embeddedTemplate, err := template.ParseFS(f, "templates/includes/base.tmpl", "templates/layouts/home.tmpl")
In your example patterns will be presented in this sequence:
first: "templates/layouts/home.tmpl"
second: "templates/includes/base.tmpl"
But if I understood correctly, the sequence of patterns is important for the function template.ParseFS, because base.tmpl will be included in all you templates.
The function template.ParseFS reads the templates in the process and tries to generate them.

Template variable not resolving everywhere

I am building a website using Golang templates and needed to display some text in the footer template. It's a variable that resolves in header.html and index.html.
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
type Data struct {
Title string
Field1 string
Field2 template.HTML
FooterField string
}
var tmpl *template.Template
func main() {
router := mux.NewRouter()
port := ":8085"
data := Data{}
data.Title = "Title"
data.FooterField = "This text does not appear in the footer template"
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
err := tmpl.ExecuteTemplate(w, "index", data)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
})
var err error
tmpl, err = template.ParseGlob("views/*")
if err != nil {
panic(err.Error())
}
router.PathPrefix("/").HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
http.FileServer(http.Dir("./static/")).ServeHTTP(res, req)
})
fmt.Println("Server running on localhost" + port)
err = http.ListenAndServe(port, handlers.CompressHandler(router))
if err != nil {
log.Fatal(err)
}
}
And in the ./views I have header.html
{{define "header"}}<!doctype html><html lang="en"><head><meta charset="utf-8"><title>{{.Title}}</title></head><body><h1>Header template</h1><div>{{.FooterField}}</div>{{end}}
index.html
{{define "index"}}{{template "header" . }}
<h1>Index template</h1>
<div>{{.FooterField}}</div>
{{template "footer"}}{{end}}
footer.html
{{define "footer"}}<h1>Footer template</h1>
Missing FooterField->{{.FooterField}}</body></html>{{end}}
And finally the output in the browser on http://localhost:8085/
Header template
This text does not appear in the footer template
Index template
This text does not appear in the footer template
Footer template
Missing FooterField->
This code should be able to be reproduced simply by copying and pasting.
Any clue to what my issue is?
you are not passing anything to the footer template. But you pass . to the header template, so you see the value of .FooterField only there.
In index.html change it to: {{template "footer" . }}

Template is not parsing correctly with embed FS

I've the below code:
package main
import (
"fmt"
"html/template"
"log"
"net/http"
"onsen/resources"
)
var view *template.Template
var err error
func init() {
fmt.Println("Starting up.")
view = template.Must(template.ParseFS(resources.Views, "templates/layouts/*.html", "templates/views/*.html", "templates/partials/*.html"))
if err != nil {
log.Fatal("Error loading templates:" + err.Error())
}
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8070",
}
http.Handle("/webUI/", http.StripPrefix("/webUI/", http.FileServer(http.FS(resources.WebUI))))
http.HandleFunc("/process", process)
http.HandleFunc("/home", home)
http.HandleFunc("/test", test)
server.ListenAndServe()
}
Where onsen/resources is:
package resources
import (
"embed"
)
// Views is our static web server layouts, views with dynamic content and partials content that is a static view.
//go:embed templates/layouts templates/views templates/partials
var Views embed.FS
And the routes functions are:
package main
import (
"log"
"net/http"
)
func home(w http.ResponseWriter, r *http.Request) {
err = view.ExecuteTemplate(w, "home.html", nil)
if err != nil {
log.Fatalln(err)
}
}
func test(w http.ResponseWriter, r *http.Request) {
err = view.ExecuteTemplate(w, "test.html", nil)
if err != nil {
log.Fatalln(err)
}
}
func process(w http.ResponseWriter, r *http.Request) {
err = view.ExecuteTemplate(w, "process.html", nil)
if err != nil {
log.Fatalln(err)
}
}
My templates are:
base.html
<!-- Content of base.html: -->
{{define "base"}}
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta charset="utf-8">
</head>
<body>
{{template "content" .}}
</body>
</html>
{{end}}
And the views are:
<!-- Content of home.html: -->
{{template "base" .}}
{{define "content"}}
This is home
{{end}}
And;
<!-- Content of test.html: -->
{{template "base" .}}
{{define "content"}}
test file is here
{{end}}
And
<!-- Content of process.html: -->
{{template "base" .}}
{{define "content"}}
process goes here
{{end}}
But ALL the routes are showing the same result, which is same as template test.html
I referred to this for my template structure, but looks something related to embed!
Thanks to the comment by Cerise
The application parses all of the template files to one template set.
The "content" template in test.html is the last "content" template to
be added to the set. That's the one you see for each page. Parse the
template for each page to a separate set.
So, here the correct working code
package main
import (
"html/template"
"log"
"net/http"
"onsen/resources"
)
func process(w http.ResponseWriter, r *http.Request) {
view := template.Must(template.ParseFS(resources.Views, "templates/layouts/base.html", "templates/views/other.html", "templates/partials/*.html"))
type approval struct {
Status bool
}
workflow := approval{Status: false}
err = view.ExecuteTemplate(w, "process.html", workflow)
if err != nil {
log.Fatalln(err)
}
}
And template process.html is:
<!-- Content of other.html: -->
{{template "base" .}}
{{define "content"}}
process goes here
{{ .Status }}
{{if .Status}}
{{template "approved"}}
{{else}}
{{template "rejected"}}
{{end}}
{{end}}

Problem at rendering layout page inside other templates

I have problem with rendering my layout page in other html pages.
It gives me this error:
home.gohtml:1:11: executing "home.gohtml" at <{{template "base" .}}>: template "base" not defined
But when I do it without using my layout page and just a simple html rendering it works fine.
also when I print my pages in the code it's getting all of my html pages and rendering them.
I have this directory:
.
templates
├── home.gohtml
└── partials
└── base.layout.gohtml
And here is my base.layout.gohtml:
{{define "base"}}
<html>
<head>
{{block "css" .}}{{end}}
</head>
<body>
{{block "content" .}}{{end}}
{{block "js" .}}{{end}}
</body>
</html>
{{end}}
And it's my home.gohtml:
{{template "base" .}}
{{define "content"}}
<p>hey</p>
{{end}}
And it is template.go:
var functions = template.FuncMap{}
func CreateTemplateCache() (map[string]*template.Template, error) {
templateCache := map[string]*template.Template{}
var pages []string
err := filepath.Walk("./templates", func(path string, info fs.FileInfo, err error) error {
if !info.IsDir() {
pages = append(pages, path)
}
return err
})
if err != nil {
return templateCache, err
}
for _, page := range pages {
name := filepath.Base(page)
templateSet, err := template.New(name).Funcs(functions).ParseFiles(page)
if err != nil {
return templateCache, err
}
templateCache[name] = templateSet
}
return templateCache, nil
}
func RenderTemplate(w http.ResponseWriter, templateName string) {
templateCache, err := CreateTemplateCache()
if err != nil {
log.Fatal(err)
}
template, ok := templateCache[templateName]
if !ok {
log.Fatal("Something went wrong!")
}
buf := new(bytes.Buffer)
err = template.Execute(buf, nil)
if err != nil {
log.Fatal(err)
}
_, err = buf.WriteTo(w)
if err != nil {
log.Fatal(err)
}
}
Please help me if you can I'm new to go and stuck at this problem.

The Template Handler data is not showing in the web

Guys I am making a create your own adventure type book in which the user has options to select events. I am taking the story data from a json file. I have created two files named story.go and main.go where story.go handles all the functions and main.go has only the server. Look at the following codes
story.go
package gyg
import (
"encoding/json"
"io"
"net/http"
"text/template"
)
var defaultHandlerTemplate = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Give Yourself Goosebumps</title>
</head>
<body>
<h1>{{.Title}}</h1>
{{range .Paragraphs}}
<p>{{.}}</p>
{{end}}
<ul>
{{range .Options}}
<li> {{.Text}} </li>
{{end}}
</ul>
</body>
</html>
`
var tpl *template.Template
type Story map[string]Chapter
type Chapter struct {
Title string `json:"title"`
Paragraphs []string `json:"story"`
Options []Option `json:"options"`
}
type Option struct {
Text string `json:"text"`
Arc string `json:"arc"`
}
type handler struct {
s Story
}
func init() {
tpl = template.Must(template.New("").Parse("defaultHandlerTemplate"))
}
func JsonStory(r io.Reader) (Story, error) {
d := json.NewDecoder(r)
var story Story
if err := d.Decode(&story); err != nil {
return nil, err
}
return story, nil
}
func NewHandler(s Story) http.Handler {
return handler{s}
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
err := tpl.Execute(w, h.s["intro"])
if err != nil {
panic(err)
}
}
main.go
package main
import (
"flag"
"fmt"
"go-projects/gyg"
"log"
"net/http"
"os"
)
func main() {
filename := flag.String("file", "gopher.json", "the file with GYG story.")
flag.Parse()
f, err := os.Open(*filename)
if err != nil {
panic(err)
}
story, err := gyg.JsonStory(f)
if err != nil {
panic(err)
}
server := http.Server{
Addr: "127.0.0.1:3000",
}
h := gyg.NewHandler(story)
fmt.Printf("Startin the server on port %s\n", server.Addr)
log.Fatal(http.ListenAndServe(server.Addr, h))
}
On running main.go the server is listening on port 3000 but when opening port 3000 on browser, it's displaying
defaultHandlerTemplate

Resources