I have three templates like this:
base.html:
<h1>Base.html rendered here</h1>
{{template "content" .}}
view.html:
{{define "content"}}
...
{{end}}
edit.html:
{{define "content"}}
...
{{end}}
I store them in folder "templates".
What i want is to dynamically change template which will be rendered in {{template "content" .}} place, without parsing every time. So what i DO NOT want is this :
func main() {
http.HandleFunc("/edit", handlerEdit)
http.HandleFunc("/view", handlerView)
http.ListenAndServe(":8080", nil)
}
func handlerView(w http.ResponseWriter, req *http.Request) {
renderTemplate(w, req, "view")
}
func handlerEdit(w http.ResponseWriter, req *http.Request) {
renderTemplate(w, req, "edit")
}
func renderTemplate(w http.ResponseWriter, req *http.Request, tmpl string) {
templates, err := template.ParseFiles("templates/base.html", "templates/"+tmpl+".html")
if err != nil {
fmt.Println("Something goes wrong ", err)
return
}
someData := &Page{Title: "QWE", Body: []byte("sample body")}
templates.Execute(w, someData)
}
I was looking at the template.ParseGlobe(), in order to do something like this
var templates = template.Must(template.ParseGlob("templates/*.html"))
... //and then somthing like this:
err := templates.ExecuteTemplate(w, tmpl+".html", p)
But ExecuteTamplate() recieves only one string as template's name. How in this case i can render two and more templates?
Instead of writing directly to the http.ResponseWriter on your call to ExecuteTemplate, write to a byte buffer and send that through the call to the next template by prepping it with a template.HTML call.
var b bytes.Buffer
var templates = template.Must(template.ParseGlob("templates/*.html"))
err := templates.ExecuteTemplate(b, templ_1, p)
if err != nil { //handle err }
err := templates.ExecuteTemplate(w, templ_2, template.HTML(b.String()))
if err != nil { //handle err }
If you're going to use an unknown number of templates, you can capture the intermediate step with a string:
var strtmp string
err := templates.ExecuteTemplate(b, templ_1, p)
if err != nil { //handle err }
strtemp = b.String() //store the output
b.Reset() //prep buffer for next template's output
err := templates.ExecuteTemplate(b, templ_2, template.HTML(strtmp))
if err != nil { //handle err }
//... until all templates are applied
b.WriteTo(w) //Send the final output to the ResponseWriter
EDIT: As #Zhuharev pointed out, if the composition of the view and edit templates are fixed, they can both reference base rather than base trying to provide a reference to either view or edit:
{{define "viewContent"}}
{{template "templates/base.html" .}}
...Current view.html template...
{{end}}
{{define "editContent"}}
{{template "templates/base.html" .}}
...Current edit.html template...
{{end}}
Related
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.
Im trying to create a handler which then will compile 2 templates:
template.html which serves layout purposes and the actual page: config.html.
this code builds the page, but no data is passed:
func config(w http.ResponseWriter, r *http.Request) {
fpTemplate := filepath.Join("static", "template.html")
fpPage := filepath.Join("static", "config.html")
tmpl, err := template.ParseFiles(fpPage, fpTemplate)
if err != nil {
log.Println("webserver.config: " + err.Error())
}
vd := ViewData{&Settings}
err = tmpl.ExecuteTemplate(w, "template.html", vd)
if err != nil {
log.Println("webserver.config: " + err.Error())
}
}
and config.html like this:
{{define "title"}}
Config
{{end}}
{{define "body"}}
<p class="text-break">
{{ .}}
</p>
{{end}}
, when I run this code:
func config(w http.ResponseWriter, r *http.Request) {
fpTemplate := filepath.Join("static", "template.html")
fpPage := filepath.Join("static", "config.html")
//tmpl, err := template.ParseFiles(fpPage, fpTemplate)
tmpl, err := template.New("config.html").ParseFiles(fpPage, fpTemplate)
if err != nil {
log.Println("webserver.config: " + err.Error())
}
vd := ViewData{&Settings}
err = tmpl.ExecuteTemplate(w, tmpl.Name(), vd)
fmt.Println(err)
//err = tmpl.ExecuteTemplate(w, "template.html", vd)
if err != nil {
log.Println("webserver.config: " + err.Error())
}
}
I get error: template: no template "config.html" associated with template "config.html" and blank black page.
What im I missing here ?
Appreciated any help!
When you pass "vd" to ExecuteTemplate in first code, the data pass to main template and you must pass the data into "body" template when you called it on "template.html" like as:
{{ template "body" . }}
I have created a simple scraper that takes the top 10 news from a website and returns a JSON with the title and the score. I want to pass the title and the score as HTML template so I can generate a webpage. I'm not familiar with the templating Go language and I don't know how to pass the values for each of the links. Here is the HTML code that I should use and my implementation for now:
<!DOCTYPE html>
<html>
<head><linkrel="stylesheet" href="https://unpkg.com/mvp.css"
/>
</head>
<body>
<h1>{{.PageTitle}}</h1>
<ul>
{{range .Links}}
<li>{{.Title}}: {{.Score}}</li>
{{end}}
</ul>
</body>
</html>
My code:
package main
import (
"encoding/json"
"html/template"
"log"
"net/http"
"strconv"
)
type TopStories struct {
Title string `json:"title"`
Score int `json:"score"`
}
type TopStoriesPayload struct {
TopStories []TopStories
}
type NewsScraper struct {
url string
Data []TopStories
}
type templateData struct {
PageTitle string
Data []TopStories
}
func NewNewsScraper(url string) *NewsScraper {
return &NewsScraper{url: url}
}
func Top10Stories() []string {
req, err := http.NewRequest("GET", "https://hacker-news.firebaseio.com/v0/topstories.json", nil)
if err != nil {
log.Fatal(err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
var IDs []int
json.NewDecoder(resp.Body).Decode(&IDs)
IDs = IDs[:10]
var IDsString []string
for _, id := range IDs {
IDsString = append(IDsString, strconv.Itoa(id))
}
return IDsString
}
func (n *NewsScraper) GetTopStories() {
req, err := http.NewRequest("GET", n.url, nil)
if err != nil {
log.Fatal(err)
}
for _, id := range Top10Stories() {
req.URL.Path = "/v0/item/" + id + ".json"
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal(err)
}
var topStory TopStories
json.NewDecoder(resp.Body).Decode(&topStory)
n.Data = append(n.Data, topStory)
}
}
//create html template handler for top stories
func HTMLHandler(w http.ResponseWriter, r *http.Request) {
scraper := NewNewsScraper("https://hacker-news.firebaseio.com")
scraper.GetTopStories()
tmpl:= template.Must(template.ParseFiles("template.html"))
data := templateData{
PageTitle: "Top Stories",
Data :[]TopStories{
//what should I put here?
},
}
tmpl.Execute(w, data)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/top", HTMLHandler)
http.ListenAndServe(":8080", mux)
}
I see three issues with your code:
a) The template.html file should have space between link & rel
<linkrel="stylesheet" href="https://unpkg.com/mvp.css"/>
to
<link rel="stylesheet" href="https://unpkg.com/mvp.css"/>
b) The template.html file should contain .Data instead of .Links.
c) The go code should be replaced from the below
Data :[]TopStories{
//what should I put here?
},
to
Data : scraper.Data,
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" . }}
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.