Как создать глобальную переменную и изменить в нескольких местах в 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вне условия? Может ли кто-нибудь помочь мне с этим?

1 ответ

Решение

Короткий ответ: вы не можете.

Согласно философии дизайна, шаблоны не должны содержать сложной логики. В шаблонах вы можете только создавать новые переменные, вы не можете изменять значения существующих переменных шаблона. Когда вы делаете {{$currentUserId := .UserData.UserId}} внутри {{if}} блок, который создает новую переменную, которая затеняет внешнюю, и ее область действия распространяется на {{end}} действие.

Это описано в text/template пакет, раздел переменных (то же самое относится и к html/template пакет тоже)

Область действия переменной распространяется на действие "end" структуры управления ("if", "with" или "range"), в которой она объявлена, или до конца шаблона, если такой структуры управления нет. Вызов шаблона не наследует переменные с момента его вызова.

Возможные обходные пути

В вашем случае проще всего зарегистрировать пользовательскую функцию CurrentUserId() на ваш шаблон, который, если UserData присутствует, возвращает его идентификатор UserData.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 затем он выполняет шаблон с UserData имеющий UserId = 99, Выход:

Current user id: 0
Current user id: 99

Попробуйте это на игровой площадке Go.

Имитация изменяемых переменных

Вы также можете моделировать изменяемые переменные, но также и с зарегистрированными пользовательскими функциями. Вы можете зарегистрировать функцию как SetCurrentUserId() который изменит значение переменной, предпочтительно в параметрах, которые передаются в выполнение шаблона, так что он остается безопасным для одновременного использования.

Это пример того, как это сделать (он использует map в качестве данных шаблона и SetCurrentUserId() устанавливает текущий идентификатор пользователя в качестве значения на этой карте):

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 затем он выполняет шаблон с UserData имеющий UserId = 99, Выход:

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

Попробуйте это на игровой площадке Go.

Другие вопросы по тегам