Другой подход, который придумал талантливый разработчик, с которым я работал, заключался в постобработке экземпляра шаблона для поиска любых включенных шаблонов, которые не определены, и поиск в файловой системе подходящего файла и его синтаксического анализа для каждого найденного; а затем рендеринг после.
Это дает вам следующую настройку:
просмотров / 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