Golang模板是基于角色的视图的更好方法

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.