有gin-gonic网络应用程序.
有3个文件:
1)base.html - 基本布局文件
<!DOCTYPE html>
<html lang="en">
<body>
header...
{{template "content" .}}
footer...
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
2)page1.html,用于/ page1
{{define "content"}}
<div>
<h1>Page1</h1>
</div>
{{end}}
{{template "base.html"}}
Run Code Online (Sandbox Code Playgroud)
3)page2.html,用于/ page2
{{define "content"}}
<div>
<h1>Page2</h1>
</div>
{{end}}
{{template "base.html"}}
Run Code Online (Sandbox Code Playgroud)
问题是/ page1和/ page2使用一个模板 - page2.html.我想,我有这样的结构的误区:{{define "content"}},{{template "base.html"}}.
请问,您能举例说明如何在golang中使用基本布局吗?
小智 24
Go 1.16引入了embed包,它将非.go文件打包成二进制文件,极大地方便了Go程序的部署。该ParseFS函数也被添加到标准库 html/template 中,它将 embed.FS 中包含的所有模板文件编译成模板树。
// templates.go
package templates
import (
"embed"
"html/template"
)
//go:embed views/*.html
var tmplFS embed.FS
type Template struct {
templates *template.Template
}
func New() *Template {
funcMap := template.FuncMap{
"inc": inc,
}
templates := template.Must(template.New("").Funcs(funcMap).ParseFS(tmplFS, "views/*.html"))
return &Template{
templates: templates,
}
}
// main.go
t := templates.New()
Run Code Online (Sandbox Code Playgroud)
t.templates是一个全局模板,包含所有匹配的views/*.html模板,所有模板都是相关的,可以互相引用,模板的名称就是文件的名称,例如article.html.
此外,我们Render为该*Template类型定义了一个方法,该方法实现了RendererEcho Web 框架的接口。
// templates.go
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
Run Code Online (Sandbox Code Playgroud)
然后,您可以指定 Echo 的渲染器,以便于在每个处理程序中生成 HTML 响应,只需将模板的名称传递给函数即可c.Render。
// main.go
func main() {
t := templates.New()
e := echo.New()
e.Renderer = t
}
// handler.go
func (h *Handler) articlePage(c echo.Context) error {
id := c.Param("id")
article, err := h.service.GetArticle(c.Request().Context(), id)
...
return c.Render(http.StatusOK, "article.html", article)
}
Run Code Online (Sandbox Code Playgroud)
由于t.templates模板包含了所有已解析的模板,因此每个模板名称都可以直接使用。
为了组装 HTML,我们需要使用模板继承。例如,为基本HTML框架和<head>元素定义一个layout.html,并设置{{block "title"}}和{{block "content"}},其他模板继承layout.html,并用自己定义的块填充或覆盖布局模板的同名块。
以下是layout.html模板的内容。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{block "title" .}}{{end}}</title>
<script src="/static/main.js"></script>
</head>
<body>
<div class="main">{{block "content" .}}{{end}}</div>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)
对于其他模板,您可以参考(继承)layout.html,并在layout.html模板中定义块。
例如,login.html 的内容如下。
{{template "layout.html" .}}
{{define "title"}}Login{{end}}
{{define "content"}}
<form class="account-form" method="post" action="/account/login" data-controller="login">
<div div="account-form-title">Login</div>
<input type="phone" name="phone" maxlength="13" class="account-form-input" placeholder="Phone" tabindex="1">
<div class="account-form-field-submit ">
<button type="submit" class="btn btn-phone">Login</button>
</div>
</form>
{{end}}
Run Code Online (Sandbox Code Playgroud)
Article.html 还引用了layout.html:
{{template "layout.html" .}}
{{define "title"}}<h1>{{.Title}}</h1>{{end}}
{{define "content"}}
<p>{{.URL}}</p>
<article>{{.Content}}</article>
{{end}}
Run Code Online (Sandbox Code Playgroud)
我们希望在渲染和渲染article.html 模板时,login.html 模板中定义的块会覆盖layout.html 中的块。但事实并非如此,这取决于 Go 文本/模板的实现。在我们的实现中ParseFS(tmplFS, "views/*.html"),假设先解析article.html,并将其content块解析为模板名称,那么当稍后解析login.html模板并content在其中发现块时,text/template将覆盖该模板的模板与后面解析的内容同名,所以当所有模板都解析完毕后,content我们的模板树中实际上只有一个命名的模板,它是content在最后解析的模板文件中定义的。
因此,当我们执行article.html模板时,有可能该content模板不是这个模板中定义的内容,而是content其他模板中定义的内容。
社区针对这个问题提出了一些解决方案。例如,不使用全局模板,而是每次渲染时都会创建一个新模板,仅包含layout.html和子模板的内容。但这确实很乏味。事实上,当 Go 1.6 引入了用于文本/模板的指令 [1] 时,我们只需对上面的代码进行一些更改,就block可以使用该方法执行我们想要的操作。Clone
// templates.go
package templates
import (
"embed"
"html/template"
"io"
"github.com/labstack/echo/v4"
)
//go:embed views/*.html
var tmplFS embed.FS
type Template struct {
templates *template.Template
}
func New() *Template {
funcMap := template.FuncMap{
"inc": inc,
}
templates := template.Must(template.New("").Funcs(funcMap).ParseFS(tmplFS, "views/*.html"))
return &Template{
templates: templates,
}
}
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
tmpl := template.Must(t.templates.Clone())
tmpl = template.Must(tmpl.ParseFS(tmplFS, "views/"+name))
return tmpl.ExecuteTemplate(w, name, data)
}
Run Code Online (Sandbox Code Playgroud)
可以看到Render这里只修改了函数。我们不执行全局模板,而是将其克隆到一个新模板中,而content这个新模板中的块可能不是我们想要的,所以这里我们解析一个子模板的内容,我们最终将在这个子模板上渲染全局模板,这样content新添加的子模板的 会覆盖之前的,可能不正确的content. 我们的目标子模板引用了全局模板中的layout.html,这并不冲突,而且由于全局模板永远不会被执行(我们Render每次执行时都会在函数中克隆一个新的全局模板),因此它也是干净的。当一个模板最终执行时,我们得到了一个干净的layout.html,里面有content我们想要的内容,相当于每次执行都会生成一个新的模板,里面只包含我们需要的布局模板和子模板。想法是一样的,但是执行模板时不是手动生成新模板,而是在函数中自动完成Render。
当然,你也可以{{ template }}在子模板中引用其他布局模板,只要这些布局模板不互相覆盖即可,执行时只需要指定目标子模板的名称,以及模板引擎会自动使用{{ template }}其中定义的标签为我们找到布局模板,这些模板都在克隆的全局模板中。
[1] https://github.com/golang/go/commit/12dfc3bee482f16263ce4673a0cce399127e2a0d
Anz*_*zel 13
只要您将模板与"内容"一起解析,就可以使用base.html,如下所示:
base.html文件
{{define "base"}}
<!DOCTYPE html>
<html lang="en">
<body>
header...
{{template "content" .}}
footer...
</body>
</html>
{{end}}
Run Code Online (Sandbox Code Playgroud)
page1.html
{{define "content"}}
I'm page 1
{{end}}
Run Code Online (Sandbox Code Playgroud)
page2.html
{{define "content"}}
I'm page 2
{{end}}
Run Code Online (Sandbox Code Playgroud)
然后ParseFiles与("your-page.html","base.html")和ExecuteTemplate与您的上下文.
tmpl, err := template.New("").ParseFiles("page1.html", "base.html")
// check your err
err = tmpl.ExecuteTemplate(w, "base", yourContext)
Run Code Online (Sandbox Code Playgroud)