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.
Your code can be simplified considerably:
Template.ExecuteTemplate()
to execute one from the collection designated by its name.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.
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.