How to create a global variable and change in multiple places in golang html/template?

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

How to create a global variable and change in multiple places in golang html/template?

问题

我正在html/template中创建一个变量,并根据条件更改其值。但是该值的作用范围仅限于if条件内部:

{{if .UserData}}
    {{$currentUserId := .UserData.UserId}}
    [<a href="#ask_question">Inside {{$currentUserId}}</a>]
{{else}}
    {{$currentUserId := 0}}
{{end}}
[<a href="#ask_question">outside {{$currentUserId}}</a>]

if条件内部,我得到了正确的值,但在外部是0。如何在条件外部使用$currentUserId?有人可以帮我解决这个问题吗?

英文:

I am creating a variable in html/template and and changing the value based on a condition. But the scope of the value stays only inside the if condition:

{{if .UserData}}
	{{$currentUserId := .UserData.UserId}}
	[<a href="#ask_question">Inside {{$currentUserId}}</a>]
{{else}}
	{{$currentUserId := 0}}
{{end}}
[<a href="#ask_question">outside {{$currentUserId}}</a>]

Inside the if condition I am getting the correct value but outside it is 0. How can I use the $currentUserId outside the condition? Could someone help me with this?

答案1

得分: 9

Go 1.11增加了对更改模板变量值的支持。要定义一个变量,请使用:=

{{$currentUserId := 0}}

要更改其值,请使用赋值=

{{$currentUserId = .UserData.UserId}}

如果变量是在{{if}}块之外创建的,但在其中更改,则更改将在{{if}}块之后可见。

{{$currentUserId := 0 -}}
Before: {{$currentUserId}}
{{if .UserData -}}
    {{$currentUserId = .UserData.UserId}}
    [<a href="#ask_question">Inside {{$currentUserId}}</a>]
{{else}}
    {{$currentUserId = 0}}
{{end}}
[<a href="#ask_question">outside {{$currentUserId}}</a>]

测试如下:

m := map[string]interface{}{}
t := template.Must(template.New("").Parse(src))

m["UserData"] = UserData{99}
if err := t.Execute(os.Stdout, m); err != nil {
    panic(err)

输出结果如下(在Go Playground上尝试):

Before: 0

    [<a href="#ask_question">Inside 99</a>]

[<a href="#ask_question">outside 99</a>]

原始答案如下。


简短回答是:你不能。

按设计理念,模板不应包含复杂逻辑。在模板中,你只能创建新变量,不能更改现有模板变量的值。当你在{{if}}块内部执行{{$currentUserId := .UserData.UserId}}时,它创建了一个新变量,该变量遮蔽了外部变量,并且其作用域延伸到{{end}}操作。

这在text/template包的Variables section中有描述(html/template包也适用于相同的规则):

> 变量的作用域延伸到控制结构(“if”、“with”或“range”)的“end”操作,其中它被声明,或者延伸到模板的末尾,如果没有这样的控制结构。模板调用不会从其调用点继承变量。

可能的解决方法

在你的情况下,最简单的方法是向模板注册一个自定义函数CurrentUserId(),如果存在UserData,则返回其idUserData.UserId(),如果不存在,则返回0,就像你的情况一样。

以下是一个示例:

type UserData struct {
    UserId int
}

func main() {
    m := map[string]interface{}{}
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "CurrentUserId": func() int {
            if u, ok := m["UserData"]; ok {
                return u.(UserData).UserId
            }
            return 0
        },
    }).Parse(src))

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
    m["UserData"] = UserData{99}
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const src = `Current user id: {{CurrentUserId}}
`

它首先执行没有UserData的模板,然后执行具有UserDataUserId = 99的模板。输出结果如下:

Current user id: 0
Current user id: 99

Go Playground上尝试。

模拟可更改的变量

你也可以模拟可更改的变量,但同样需要注册自定义函数。你可以注册一个名为SetCurrentUserId()的函数,它会更改变量的值,最好将其设置为传递给模板执行的参数,以便在并发使用时保持安全。

以下是一个示例(它使用map作为模板数据,SetCurrentUserId()将当前用户id设置为该映射中的一个值):

func main() {
    m := map[string]interface{}{}
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "SetCurrentUserId": func(id int) string {
            m["CurrentUserId"] = id
            return ""
        },
    }).Parse(src))

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
    m["UserData"] = UserData{99}
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const src = `Before: {{.CurrentUserId}}
{{if .UserData}}
    {{SetCurrentUserId .UserData.UserId}}Inside: {{.CurrentUserId}}
{{else}}
    {{SetCurrentUserId 0}}Inside: {{.CurrentUserId}}
{{end}}
After: {{.CurrentUserId}}
`

它首先执行没有UserData的模板,然后执行具有UserDataUserId = 99的模板。输出结果如下:

Before: 
    Inside: 0
After: 0
Before: 0
    Inside: 99
After: 99

Go Playground上尝试。

英文:

Go 1.11 added support for changing values of template variables. To define a variable, use :=:

{{$currentUserId := 0}}

To change its value, use assignment =:

{{$currentUserId = .UserData.UserId}}

If the variable is created outside of the {{if}} block but changed inside it, changes will be visible after the {{if}} block.

{{$currentUserId := 0 -}}
Before: {{$currentUserId}}
{{if .UserData -}}
    {{$currentUserId = .UserData.UserId}}
    [<a href="#ask_question">Inside {{$currentUserId}}</a>]
{{else}}
    {{$currentUserId = 0}}
{{end}}
[<a href="#ask_question">outside {{$currentUserId}}</a>]

Testing this like:

m := map[string]interface{}{}
t := template.Must(template.New("").Parse(src))

m["UserData"] = UserData{99}
if err := t.Execute(os.Stdout, m); err != nil {
    panic(err)

The output is (try it on the Go Playground):

Before: 0

    [<a href="#ask_question">Inside 99</a>]

[<a href="#ask_question">outside 99</a>]

Original answer follows.


Short answer is: you can't.

By design philosophy, templates should not contain complex logic. In templates you can only create new variables, you cannot change the values of existing template variables. When you do {{$currentUserId := .UserData.UserId}} inside an {{if}} block, that creates a new variable which shadows the outer one, and its scope extends to the {{end}} action.

This is described in the text/template package, Variables section (the same applies to the html/template package too):

> A variable's scope extends to the "end" action of the control structure ("if", "with", or "range") in which it is declared, or to the end of the template if there is no such control structure. A template invocation does not inherit variables from the point of its invocation.

Possible workarounds

Easiest in your case would be to register a custom function CurrentUserId() to your template, which if UserData is present, returns its id UserData.UserId(), and if it is not present, returns 0 as in your case.

This is an example how it can be done:

type UserData struct {
    UserId int
}

func main() {
    m := map[string]interface{}{}
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "CurrentUserId": func() int {
            if u, ok := m["UserData"]; ok {
                return u.(UserData).UserId
            }
            return 0
        },
    }).Parse(src))

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
    m["UserData"] = UserData{99}
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const src = `Current user id: {{CurrentUserId}}
`

It first executes the template without UserData, then it executes the template with UserData having UserId = 99. The output is:

Current user id: 0
Current user id: 99

Try it on the Go Playground.

Simulating changeable variables

You can also simulate changeable variables, but also with registered custom function(s). You could register a function like SetCurrentUserId() which would change the value of a variable, preferably in the params that is passed to the template execution so it remains safe for concurrent use.

This is an example how to do it (it uses a map as template data, and SetCurrentUserId() sets the current user id as a value in this map):

func main() {
    m := map[string]interface{}{}
    t := template.Must(template.New("").Funcs(template.FuncMap{
        "SetCurrentUserId": func(id int) string {
            m["CurrentUserId"] = id
            return ""
        },
    }).Parse(src))

    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
    m["UserData"] = UserData{99}
    if err := t.Execute(os.Stdout, m); err != nil {
        panic(err)
    }
}

const src = `Before: {{.CurrentUserId}}
{{if .UserData}}
    {{SetCurrentUserId .UserData.UserId}}Inside: {{.CurrentUserId}}
{{else}}
    {{SetCurrentUserId 0}}Inside: {{.CurrentUserId}}
{{end}}
After: {{.CurrentUserId}}
`

This again first executes the template without UserData, then it executes the template with UserData having UserId = 99. The output is:

Before: 
    Inside: 0
After: 0
Before: 0
    Inside: 99
After: 99

Try it on the Go Playground.

huangapple
  • 本文由 发表于 2016年4月10日 03:46:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/36521839.html
匿名

发表评论

匿名网友

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

确定