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

huangapple go评论84阅读模式
英文:

Golang templates a better approach for role based views

问题

我正在尝试找到一种更好、更高效的方法来处理基于角色的模板使用情况。

你有三个不同的角色,每个角色有不同的内容,为了简化示例,侧边栏对于每个角色都是相同的。

代码和模板的方法非常重复,必须有一种更好的方法来解决这个问题。
我可以使用一些 "if" 语句来加载不同的模板,但这样会很慢。
如果我在模板中使用新的 {{block}} 动作,它只能为每个内容节省一行代码。我知道这里有些问题,但我无法解决。

提前感谢。

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	
	...
}

编辑:
我在思考是否类似下面的代码可以帮助解决问题...

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}}`

在我的问题中,我试图简化事情以解释我脑海中的想法,模板代码中的 "Content 1 Role1" 等内容只是为了表示应该有一些仅适用于 Role1 视图的 HTML 代码。
我在原始问题代码中添加了更多细节。

英文:

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.

答案1

得分: 4

简化代码

你的代码可以大大简化:

  • 你只需要解析模板一次。你可以使用Template.ExecuteTemplate()来执行指定名称的模板。
  • 模板名称可以根据角色名称(存储在user.Role中)轻松推导出来,所以你不需要使用任何switch语句。

看看这个简化的解决方案,除了index之外,还渲染了content1content2,但比你的代码要短得多:

const t = `...` // 在这里放入你的模板源代码

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())
        }
    }
}

它的输出是:

==Header====Side Bar== Welcome Role1==Footer==
==Header====Side Bar== Content 1 Role1==Footer==
==Header====Side Bar== Content 2 Role1==Footer==

如果你将用户的角色更改为role2

==Header====Side Bar== Welcome Role2==Footer==
==Header====Side Bar== Content 1 Role2==Footer==
==Header====Side Bar== Content 2 Role2==Footer==

你可以创建一个辅助函数renderFor()

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())
        }
    }
}

然后为多个用户调用它:

renderFor(User{Login: "Username1", Role: "role1"})
fmt.Println()
renderFor(User{Login: "Username2", Role: "role2"})

Go Playground上尝试一下。

简化模板

简化模板的一种方法是不为不同的角色定义单独的模板,而是使用模板操作来渲染不同的内容和/或传递不同的数据以便包含在输出中(以产生不同的内容)。你必须在执行模板时传递角色和其他所需信息,以便进行区分。例如,你可以用以下模板替换所有的模板:

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}}
`

注意,模板名称中的roleX已经消失了,这就是重点。

然后执行这些模板:

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())
        }
    }
}

注意,用户u被传递给了ExecuteTemplate()。在Go Playground上尝试一下。

你的示例可能过于不切实际,这就是我们可以大大简化它的原因,但在更复杂的示例中,这是正确的方法。

还要注意,根据设计哲学,模板不应包含_复杂_的逻辑。如果模板中的某些内容(或看起来)过于复杂,你应该考虑在Go代码中计算结果,并将结果作为数据传递给执行,或者在模板中注册一个回调函数,并使用模板操作调用该函数并插入返回值。

英文:

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.

huangapple
  • 本文由 发表于 2016年3月10日 11:28:07
  • 转载请务必保留本文链接:https://go.coder-hub.com/35907031.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定