Вызов других шаблонов с динамическим именем

Я не вижу возможности вызывать шаблоны (текстовые или html) с динамическим именем. Пример:

Это работает:

{{template "Blah" .}}

Это ошибки с "неожиданным" $ BlahVar "при вызове шаблона":

{{$BlahVar := "Blah"}}
{{template $BlahVar .}}

Общая проблема, которую я пытаюсь решить, заключается в том, что мне нужно отображать шаблоны условно на основе файла конфигурации, поэтому я не знаю заранее имена шаблонов. Очевидно, я могу поместить функцию в FuncMap, которая просто выполняет отдельный синтаксический анализ и вызов шаблона и возвращает этот результат, но мне было интересно, есть ли лучший способ.


person Brad Peabody    schedule 21.12.2013    source источник
comment
@Boushley gotcha. См. Другой ответ, который я только что опубликовал, который дает решение, которое я в конечном итоге использовал.   -  person Brad Peabody    schedule 17.05.2014
comment
А пока есть ответ?   -  person Kiril    schedule 12.01.2016
comment
Я тоже ищу способ сделать это ...   -  person Creasixtine    schedule 12.11.2016
comment
У меня такая же проблема. Флимзи просто хотел синхронно изменить $ BlahVar.   -  person shinriyo    schedule 24.09.2017


Ответы (4)


В качестве примечания по этому поводу и в качестве продолжения я в конечном итоге получил два основных ответа на этот вопрос: 1) Постарайтесь этого избежать. В некоторых случаях простой оператор if работал нормально. 2) Мне удалось сделать это с помощью функции в FuncMap, которая просто выполняет отдельный рендеринг. Это не самая лучшая вещь в мире, но она действительно работает и решает проблему. Вот полная автономная демонстрация, демонстрирующая идею:

package main

import (
    "bytes"
    "html/template"
    "os"
)

func main() {

    var err error

    // our main template here calls a sub template
    tpl := template.New("main")

    // provide a func in the FuncMap which can access tpl to be able to look up templates
    tpl.Funcs(map[string]interface{}{
        "CallTemplate": func(name string, data interface{}) (ret template.HTML, err error) {
            buf := bytes.NewBuffer([]byte{})
            err = tpl.ExecuteTemplate(buf, name, data)
            ret = template.HTML(buf.String())
            return
        },
    })

    // this is the main template
    _, err = tpl.Parse(`

{{$Name := "examplesubtpl"}}

from main template

{{CallTemplate $Name .}}

`)
    if err != nil {
        panic(err)
    }

    // whatever code to dynamically figure out what templates to load

    // a stub just to demonstrate
    _, err = tpl.New("examplesubtpl").Parse(`

this is from examplesubtpl - see, it worked!

`)
    if err != nil {
        panic(err)
    }

    err = tpl.Execute(os.Stdout, map[string]interface{}{})
    if err != nil {
        panic(err)
    }

}
person Brad Peabody    schedule 16.05.2014

Другой способ, хотя, возможно, и не лучший, - это иметь отдельные файлы шаблонов, которые предоставляют одинаковый именованный шаблон. Например, предположим, что у вас есть общий макет для веб-страницы:

<html>
  ...
  <body>
    {{template "body" .}}
  </body>
</html>

На каждой странице вы делаете это:

{{define "body"}}
  This will be in the body
{{end}}

А затем объедините их в коде:

func compileTemplate(layout, name string) (*template.Template, error) {
    tpl := template.New(name)
    tpl, err := tpl.ParseFiles(
        "views/layouts/"+layout+".htm",
        "views/"+name+".htm",
    )
    if err != nil {
        return nil, err
    }
    return tpl, nil
}
person Caleb    schedule 21.12.2013
comment
Интересно - не особо решает мою проблему, но да, это еще один подход, который может быть полезен в некоторых случаях. Но все равно спасибо. - person Brad Peabody; 22.12.2013

Другой подход, который придумал талантливый разработчик, с которым я работал, заключался в постобработке экземпляра шаблона для поиска любых включенных шаблонов, которые не определены, и поиск в файловой системе подходящего файла и его синтаксического анализа для каждого найденного; а затем рендеринг после.

Это дает вам следующую настройку:

просмотров / index.html:

{{template "/includes/page-wrapper.html" .}}

{{define "body"}}
<div>Page guts go here</div>
{{end}}

{{define "head_section"}}
<title>Title Tag</title>
{{end}}

включает / page-wrapper.html:

<html>
<head>
{{block "head_section" .}}{{end}}
<head>
<body>

{{template "body" .}}

</body>
</html>

И ваш ServeHTTP() метод ищет файлы в каталоге "views", загружает и анализирует их, а затем вызывает TmplIncludeAll() (см. Ниже).

В итоге я адаптировал ту же базовую концепцию, как только несколько функций, которые заключаются в следующем. t - это шаблон после анализа, но до рендеринга. И fs - это каталог, в котором «просмотры» и «включает» живые (упомянутые выше).

func TmplIncludeAll(fs http.FileSystem, t *template.Template) error {

    tlist := t.Templates()
    for _, et := range tlist {
        if et != nil && et.Tree != nil && et.Tree.Root != nil {
            err := TmplIncludeNode(fs, et, et.Tree.Root)
            if err != nil {
                return err
            }
        }
    }

    return nil
}

func TmplIncludeNode(fs http.FileSystem, t *template.Template, node parse.Node) error {

    if node == nil {
        return nil
    }

    switch node := node.(type) {

    case *parse.TemplateNode:
        if node == nil {
            return nil
        }

        // if template is already defined, do nothing
        tlist := t.Templates()
        for _, et := range tlist {
            if node.Name == et.Name() {
                return nil
            }
        }

        t2 := t.New(node.Name)

        f, err := fs.Open(node.Name)
        if err != nil {
            return err
        }
        defer f.Close()

        b, err := ioutil.ReadAll(f)
        if err != nil {
            return err
        }

        _, err = t2.Parse(string(b))
        if err != nil {
            return err
        }

        // start over again, will stop recursing when there are no more templates to include
        return TmplIncludeAll(fs, t)

    case *parse.ListNode:

        if node == nil {
            return nil
        }

        for _, node := range node.Nodes {
            err := TmplIncludeNode(fs, t, node)
            if err != nil {
                return err
            }
        }

    case *parse.IfNode:
        if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
            return err
        }
        if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
            return err
        }

    case *parse.RangeNode:
        if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
            return err
        }
        if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
            return err
        }

    case *parse.WithNode:
        if err := TmplIncludeNode(fs, t, node.BranchNode.List); err != nil {
            return err
        }
        if err := TmplIncludeNode(fs, t, node.BranchNode.ElseList); err != nil {
            return err
        }

    }

    return nil
}

Это мой любимый подход, и я использую его уже некоторое время. Его преимущество заключается в том, что существует только один рендер шаблона, сообщения об ошибках красивы и понятны, а разметка шаблона Go очень удобочитаема и очевидна. Было бы здорово, если бы внутренности html / template.Template упростили реализацию, но в целом это отличное решение IMO.

person Brad Peabody    schedule 23.03.2017

Использование htmltemplate.HTML () для вставки проанализированного шаблона ("электронная почта / тест") в другой шаблон ("электронная почта / основной")

htmlTplEngine := htmltemplate.New("htmlTplEngine")


_, htmlTplEngineErr := htmlTplEngine.ParseGlob("views/email/*.html")
if nil != htmlTplEngineErr {
    log.Panic(htmlTplEngineErr.Error())
}

var contentBuffer bytes.Buffer
if err := htmlTplEngine.ExecuteTemplate(&contentBuffer, "email/test", params); err != nil {
    return "", "", errors.Wrap(err, "execute content html")
}

var templateBuf bytes.Buffer
if err := htmlTplEngine.ExecuteTemplate(
    &templateBuf,
    "email/main",
    map[string]interface{}{
        "Content": htmltemplate.HTML(contentBuffer.String()),
        "Lang":    language,
    },
); err != nil {
    return "", "", errors.Wrap(err, "execute html template")
}

На "электронная почта / главная"

{{define "email/main"}}

My email/test template: {{.Content}}

{{end}}

person Bryce    schedule 04.12.2019