Golang templates a better approach for role based views - templates

I'm trying to get a better and more efficient way for a role based templates use case.
You have three different roles with different contents, to simplify the example the sidebar is the same for every role.
The code and the template approach is very repetitive, there must be a better way to get it.
I could using some "if" sentences to load different templates, but that is slow.
If I use the new {{block}} action in the templates, it only saves 1 line for every content. I know that something is wrong here, but I can't get it.
Thanks in advance.
package main
import (
"html/template"
"log"
"os"
)
const t = `
{{define "header"}}==Header=={{end}}
{{define "sidebar"}}==Side Bar=={{end}}
{{define "footer"}}==Footer=={{end}}
{{define "index-role1"}}Welcome Stuff for Role1{{end}}
{{define "index-role2"}}Welcome Stuff for Role2{{end}}
{{define "index-role3"}}Welcome Stuff for Role3{{end}}
{{define "content1-role1"}}Content 1 for Role1{{end}}
{{define "content1-role2"}}Content 1 for Role2{{end}}
{{define "content1-role3"}}Content 1 for Role3{{end}}
{{define "content2-role1"}}Content 2 for Role1{{end}}
{{define "content2-role2"}}Content 2 for Role2{{end}}
{{define "content2-role3"}}Content 2 for Role3{{end}}
{{define "display-role1-index"}} {{template "header" .}}{{template "sidebar" . }} {{template "index-role1" .}}{{template "footer" .}} {{end}}
{{define "display-role1-content1"}} {{template "header" .}}{{template "sidebar" . }} {{template "content1-role1" .}}{{template "footer" .}}{{end}}
{{define "display-role1-content2"}} {{template "header" .}}{{template "sidebar" . }} {{template "content2-role1" .}}{{template "footer" .}}{{end}}
{{define "display-role2-index"}} {{template "header" .}}{{template "sidebar" . }} {{template "index-role2" .}}{{template "footer" .}}{{end}}
{{define "display-role2-content1"}} {{template "header" .}}{{template "sidebar" . }} {{template "content1-role2" .}}{{template "footer" .}}{{end}}
{{define "display-role2-content2"}} {{template "header" .}}{{template "sidebar" . }} {{template "content2-role2" .}}{{template "footer" .}}{{end}}
{{define "display-role3-index"}} {{template "header" .}}{{template "sidebar" . }} {{template "index-role3" .}}{{template "footer" .}}{{end}}
{{define "display-role3-content1"}} {{template "header" .}}{{template "sidebar" . }} {{template "content1-role3" .}}{{template "footer" .}}{{end}}
{{define "display-role3-content2"}} {{template "header" .}}{{template "sidebar" . }} {{template "content2-role3" .}}{{template "footer" .}}{{end}}
`
var templates map[string]*template.Template
type User struct {
Login string
Role string
}
func init() {
if templates == nil {
templates = make(map[string]*template.Template)
}
//Templates for role1
templates["role1-index"] = template.Must(template.New("display-role1-index").Parse(t))
templates["role1-content1"] = template.Must(template.New("display-role1-content1").Parse(t))
templates["role1-content2"] = template.Must(template.New("display-role1-content2").Parse(t))
//Templates for role2
...
//Templates for role3
...
}
func main() {
loggedUser := User{Login: "Username1", Role: "role1"}
// ONLY FOR INDEX
switch loggedUser.Role {
case "role1":
err := templates["role1-index"].Execute(os.Stdout, nil)
if err != nil {
log.Println(err.Error())
}
case "role2":
err := templates["role2-index"].Execute(os.Stdout, nil)
if err != nil {
log.Println(err.Error())
}
case "role3":
err := templates["role3-index"].Execute(os.Stdout, nil)
if err != nil {
log.Println(err.Error())
}
}
...
//CODE FOR CONTENT1
...
//CODE FOR CONTENT2
...
}
EDIT:
I'm thinking if something like that could help...
const t = `
{{define "header"}}==Header=={{end}}
{{define "sidebar"}}==Side Bar=={{end}}
{{define "footer"}}==Footer=={{end}}
{{define "display-base"}}
{{template "header" .}}
{{template "sidebar" . }}
{{block "content" .}}Welcome {{block "role"}}Role 1{{end}}{{end}}
{{template "footer" .}}
{{end}}`
In my question I'm trying to simplify the things to explain what have in my head, the stuff into the template code like "Content 1 Role1" is only to indicate that there should be some html code only for Role1 role view.
I added more details in the original question code.

Simplifying code
Your code can be simplified considerably:
You only need to parse the templates once. You can use Template.ExecuteTemplate() to execute one from the collection designated by its name.
The template names are easily derivable from the role name (stored in user.Role), so you don't need any switches.
See this simplified solution which besides index also renders content1 and content2 yet is much shorter than yours:
const t = `...` // Your template source here
var templates = template.Must(template.New("all").Parse(t))
type User struct {
Login string
Role string
}
func main() {
u := User{Login: "Username1", Role: "role1"}
for _, name := range []string{"index", "content1", "content2"} {
templName := "display-" + u.Role + "-" + name
if err := templates.ExecuteTemplate(os.Stdout, templName, nil); err != nil {
log.Println(err.Error())
}
}
}
It outputs:
==Header====Side Bar== Welcome Role1==Footer== ==Header====Side Bar==
Content 1 Role1==Footer== ==Header====Side Bar== Content 2 Role1==Footer==
If you change user's role to role2:
==Header====Side Bar== Welcome Role2==Footer== ==Header====Side Bar==
Content 1 Role2==Footer== ==Header====Side Bar== Content 2 Role2==Footer==
You can create a helper renderFor() function:
func renderFor(u User) {
for _, name := range []string{"index", "content1", "content2"} {
templName := "display-" + u.Role + "-" + name
if err := templates.ExecuteTemplate(os.Stdout, templName, nil); err != nil {
log.Println(err.Error())
}
}
}
And calling it for multiple users:
renderFor(User{Login: "Username1", Role: "role1"})
fmt.Println()
renderFor(User{Login: "Username2", Role: "role2"})
Try it on the Go Playground.
Simplifying template
A way to simplify your templates is to not define separate templates for separate roles, but use template actions to render different content and/or pass different data to the execution to be included in the output (to result in different content). You have to pass the role and other required information when you execute your template, so it can be used to differentiate. For example all your templates can be substituted with these:
const t = `
{{define "header"}}==Header=={{end}}
{{define "sidebar"}}==Side Bar=={{end}}
{{define "footer"}}==Footer=={{end}}
{{define "index"}}Welcome {{.Role}}{{end}}
{{define "content1"}}Content 1 {{.Role}}{{end}}
{{define "content2"}}Content 2 {{.Role}}{{end}}
{{define "display-index"}} {{template "header" .}}{{template "sidebar" . }} {{template "index" .}}{{template "footer" .}} {{end}}
{{define "display-content1"}} {{template "header" .}}{{template "sidebar" . }} {{template "content1" .}}{{template "footer" .}}{{end}}
{{define "display-content2"}} {{template "header" .}}{{template "sidebar" . }} {{template "content2" .}}{{template "footer" .}}{{end}}
`
Note that the roleX is gone from the name of the templates, that was the point.
And executing the templates:
func renderFor(u User) {
for _, name := range []string{"index", "content1", "content2"} {
templName := "display-" + name
if err := templates.ExecuteTemplate(os.Stdout, templName, u); err != nil {
log.Println(err.Error())
}
}
}
Note that the user u is passed to ExecuteTemplate(). Try it on the Go Playground.
Your example might be too unrealistic and that's why we could drastically reduce it, but this is the way to go in more complex examples too.
Also note that by design philosophy, templates should not contain complex logic. If something is (or looks) too complex in templates, you should consider calculating the result in Go code and either pass the result as data to the execution, or register a callback function in the templates and have a template action call that function and insert the return value.

Related

Template renders nothing, and no error, but status is 200

I'm playing with Go in a simple HTTP server:
// var tpl = template.Must(template.New("").Funcs(template.FuncMap{"isRegistered": isRegistered}).ParseGlob("templates/*")) // functions will be added later
var tpl = template.Must(template.ParseGlob("templates/*"))
func contact(w http.ResponseWriter, r *http.Request) {
//// defined templates are: "home.html", "layout", "layout.html", "contact.html", "body"
log.Println("in handler: ", tpl.DefinedTemplates())
err := tpl.ExecuteTemplate(w, "contact.html", nil)
if err != nil {
fmt.Println(err) // no error displayed
}
// fmt.Fprintf((w), "write") - This works fine
}
func main() {
log.Println("Serving on 8888 port")
http.HandleFunc("/contact", contact)
http.ListenAndServe(":8888", nil)
}
{{define "layout"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{.Title}}</title>
<meta name="description" content="{{.Description}}">
<link rel="canonical" href="{{.Canonical}}" />
</head>
<body>
{{template "body" .}}
</body>
</html>
{{end}}
{{define "body"}}
<h1>Contact us page</h1>
<p>
Your name is...
</p>
{{end}}
The localhost:8888/contact returns OK 200 and empty body.
I used this example: https://stackoverflow.com/a/36643663/2110953
But I need to add template functions in future also:
var tpl = template.Must(template.New("").Funcs(template.FuncMap{"isRegistered": isRegistered}).ParseGlob("templates/*"))
Your contact.html does not "render" anything. It just defines the body template, but does not include it (execute it).
To execute a template (within a template), you may use the {{template}} action. To define and execute a template, you may use the {{block}} action.
Template Actions:
{{template "name"}}
The template with the specified name is executed with nil data.
{{template "name" pipeline}}
The template with the specified name is executed with dot set
to the value of the pipeline.
{{block "name" pipeline}} T1 {{end}}
A block is shorthand for defining a template
{{define "name"}} T1 {{end}}
and then executing it in place
{{template "name" pipeline}}
The typical use is to define a set of root templates that are
then customized by redefining the block templates within.
If your goal is to have a "fixed" header and footer in all pages, then you have to restructure your templates. Have a header and footer template defined somewhere, and the pages should include them as first and last elements. See How to use a field of struct or variable value as template name?
Update: So I had to just create a header and footer templates:
{{template "header" .}}
<h1>Contact us page</h1>
<p>
Your name is...
</p>
{{template "footer" .}}
{{define "header"}}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{.Title}}</title>
<meta name="description" content="{{.Description}}">
<link rel="canonical" href="{{.Canonical}}" />
</head>
<body>
{{end}}
{{define "footer"}}
</body>
</html>
{{end}}
and it worked fine

Parsing multiple templates with data

How do i pass the data to the right template?
I have the following templates and want to parse them
layout.html:
<!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<header>
...
</header>
<main>
{{template "main"}}
</main>
</body>
</html>
list.html:
{{define "main"}}
{{range $index, $element := . }}
<div>
<a href=#>{{ $element.Data1 }}</a>
<p>{{ $element.Data2 }}</p>
<p>{{ $element.Data3 }}</p>
</div>
{{end}}
{{end}}
When i use this in the handler func only the "main" template is executed and i dont get the layout.
t, err := template.ParseFiles(layoutPath, templatePath)
t.ExecuteTemplate(w, "main", Data)
And with this i dont have the Data in the list template and so cant display the list.
t, err := template.ParseFiles(layoutPath, templatePath)
t.ExecuteTemplate(w, Data)
So how do i execute this properly?
From the docs:
{{template "name" pipeline}} The template with the specified name is
executed with dot set to the value of the pipeline.
This means that to pass data from the layout.html template to the list.html template you need to pass the data as the second argument of the template action.
E.g. {{template "main" .}}

With Golang Templates how can I set a variable in each template?

How can I set a variable in each template that I can use in other templates e.g.
{{ set title "Title" }}
in one template then in my layout
<title> {{ title }} </title>
Then when it's rendered
tmpl, _ := template.ParseFiles("layout.html", "home.html")
it will set the title according to whatever was set in home.html instead of having to make a struct for each view page when it isn't really necessary. I hope I made sense, thanks.
Just for clarification:
layout.html:
<!DOCTYPE html>
<html>
<head>
<title>{{ title }} </title>
</head>
<body>
</body>
</html>
home.html:
{{ set Title "Home" . }}
<h1> {{ Title }} Page </h1>
If you want to use the Value in another template you can pipeline it to the dot:
{{with $title := "SomeTitle"}}
{{$title}} <--prints the value on the page
{{template "body" .}}
{{end}}
body template:
{{define "body"}}
<h1>{{.}}</h1> <--prints "SomeTitle" again
{{end}}
As far as i know it is not possible to go upwards in the chain.
So layout.html gets rendered before home.html, so you cant pass a value back.
In your example it would be the best solution to use a struct and pass it from the layout.html to the home.html using the dot:
main.go
package main
import (
"html/template"
"net/http"
)
type WebData struct {
Title string
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
tmpl, _ := template.ParseFiles("layout.html", "home.html")
wd := WebData{
Title: "Home",
}
tmpl.Execute(w, &wd)
}
func pageHandler(w http.ResponseWriter, r *http.Request) {
tmpl, _ := template.ParseFiles("layout.html", "page.html")
wd := WebData{
Title: "Page",
}
tmpl.Execute(w, &wd)
}
func main() {
http.HandleFunc("/home", homeHandler)
http.HandleFunc("/page", pageHandler)
http.ListenAndServe(":8080", nil)
}
layout.html
<!DOCTYPE html>
<html>
<head>
<title>{{.Title}} </title>
</head>
<body>
{{template "body" .}}
</body>
</html>
home.html
{{define "body"}}
<h1>home.html {{.Title}}</h1>
{{end}}
page.html
{{define "body"}}
<h1>page.html {{.Title}}</h1>
{{end}}
Also go has a nice documentation on how to use templates:
http://golang.org/pkg/text/template/

Golang: What's the pre-requisite to use {{ template "partial.html" . }}

import "os"
import "html/template"
...
t, _ := template.ParseFiles("login.html")
t.Execute(os.Stdout, data)
...
login.html:
{{ template "header.html" . }}
<form ....>...</form>
{{ template "footer.html" . }}
no output, no error.
If I remove those two lines of {{ template "..." . }}, I could see the part being outputed.
What's required to make {{ template "..." . }} work or am I misunderstanding html/template completely?
You need to define a name for the file that will contain the other templates and then execute that.
login.tmpl
{{define "login"}}
<!doctype html>
<html lang="en">
..
{{template "header" .}}
</body>
</html>
{{end}}
header.tmpl
{{define "header"}}
whatever
{{end}}
Then you parse both those files
t := template.Must(template.ParseFiles("login.tmpl", "header.tmpl"))
// and then execute the template with the defined name
t.ExecuteTemplate(os.Stdout, "login", data)

Optional templates in go html/template?

Given a set of templates like:
layout.tpl
<html>
<head>
<title>Some title</title>
{{template extracss}}
</head>
<body>
<h1>Page title</h1>
{{template content .}}
</body>
</html>
home.tpl
{{define "content"}}
<p>page content goes here</p>
{{end}}
edit.tpl
{{define "content"}}
<form>form content goes here</form>
{{end}}
{{define "extracss"}}<style>body{background:pink}</style>{{end}}
using this to render the template:
func Render(w http.ResponseWriter, tmpname string, data interface{}) {
t, err := template.ParseFiles("views/layout.tpl", "views/"+tmpname+".tpl")
// parse error
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
if err := t.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
edit.tpl will render correctly as it has 'extracss' defined, home.tpl will not as the template parser rightly says 'no such template "extracss"'.
So what mechanism would I use to allow 'optional' templates to be used?
Any ideas?
An empty define works: {{define "extracss"}}{{end}}. It's maybe not super elegant, but simple to understand.
Note that you don't need to repeat the empty defines. You can put them into your master template and redefine them in the included templates only if needed.
The answer from #thomas is awesome, but I found what he wrote ambiguous and wasted a lot of time trying to translate it into code.
Here's code that works (and I believe is what he's suggesting):
layout.tpl
<html>
<head>
<title>Some title</title>
{{template extracss}}
{{define "extracss"}}{{end}}
</head>
<body>
<h1>Page title</h1>
{{template content .}}
</body>
</html>
home.tpl
{{define "content"}}
<p>page content goes here</p>
{{end}}
edit.tpl
{{define "content"}}
<form>form content goes here</form>
{{end}}
{{define "extracss"}}<style>body{background:pink}</style>{{end}}
using this to render the template:
func Render(w http.ResponseWriter, tmpname string, data interface{}) {
t, err := template.ParseFiles("views/layout.tpl", "views/"+tmpname+".tpl")
// parse error
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
if err := t.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
Perhaps this wasn't available at the time the question was asked but it seems that you can use block for this purpose:
{{block "name" pipeline}} T1 {{end}}
A block is shorthand for defining a template
{{define "name"}} T1 {{end}}
and then executing it in place
{{template "name" pipeline}}
The typical use is to define a set of root templates that are then customized by redefining the block templates within.
layout.tpl
<html>
<head>
<title>Some title</title>
{{block "extracss" .}}{{end}}
</head>
<body>
<h1>Page title</h1>
{{block "content" .}}{{end}}
</body>
</html>
home.tpl
{{define "content"}}
<p>page content goes here</p>
{{end}}
edit.tpl
{{define "content"}}
<form>form content goes here</form>
{{end}}
{{define "extracss"}}<style>body{background:pink}</style>{{end}}