为了账号安全,请及时绑定邮箱和手机立即绑定

如何使用 golang html/template 的基本模板文件?

如何使用 golang html/template 的基本模板文件?

Go
达令说 2022-01-04 13:39:04
拥有 gin-gonic 网络应用程序。有3个文件:1) base.html -- 基本布局文件<!DOCTYPE html><html><body>header...{{template "content" .}}footer...</body></html>2) page1.html,为/page1{{define "content"}}<div>    <h1>Page1</h1></div>{{end}}{{template "base.html"}}3) page2.html,用于/page2{{define "content"}}<div>    <h1>Page2</h1></div>{{end}}{{template "base.html"}}问题是 /page1 和 /page2 使用一个模板 - page2.html。我认为我对这样的结构有误解:{{define "content"}}, {{template "base.html"}}.拜托,你能举例说明如何在 golang 中使用基本布局吗?
查看完整描述

3 回答

?
慕桂英546537

TA贡献1848条经验 获得超10个赞

只要您将模板与“内容”一起解析,您就可以使用 base.html,如下所示:


基本文件


{{define "base"}}

<!DOCTYPE html>

<html>

<body>


header...


{{template "content" .}}


footer...


</body>

</html>

{{end}}

页面1.html


{{define "content"}}

I'm page 1

{{end}}

页面2.html


{{define "content"}}

I'm page 2

{{end}}

然后ParseFiles with ("your-page.html", "base.html") 和ExecuteTemplate与你的上下文。


tmpl, err := template.New("").ParseFiles("page1.html", "base.html")

// check your err

err = tmpl.ExecuteTemplate(w, "base", yourContext)


查看完整回答
反对 回复 2022-01-04
?
慕盖茨4494581

TA贡献1850条经验 获得超11个赞

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()

t.templates是一个全局模板,包含所有匹配的views/*.html模板,所有模板都是相关的,可以相互引用,模板的名字就是文件的名字,例如article.html.


此外,我们Render为该*Template类型定义了一个方法,该方法实现Renderer了 Echo 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)

}

然后,您可以为 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)

}

由于t.templates模板包含了所有解析的模板,每个模板名称都可以直接使用。


为了组装 HTML,我们需要使用模板继承。例如,为基本的 HTML 框架和<head>元素定义一个 layout.html ,并设置{{block "title"}}和{{block "content"}},其他模板继承 layout.html,并使用自己定义的块填充或覆盖布局模板的同名块。


以下是layout.html模板的内容。


<!DOCTYPE html>

<html>


<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>

其他模板可以参考(继承)layout.html,在layout.html模板中定义blocks。


例如,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}}

article.html 还引用了 layout.html:


{{template "layout.html" .}}


{{define "title"}}<h1>{{.Title}}</h1>{{end}}


{{define "content"}}

<p>{{.URL}}</p>

<article>{{.Content}}</article>

{{end}}

我们希望 login.html 模板中定义的块在渲染时覆盖 layout.html 中的块,也在渲染 article.html 模板时覆盖。但事实并非如此,这取决于 Go 文本/模板实现。在我们的实现中ParseFS(tmplFS, "views/*.html"),假设先解析article.html并将其content块解析为模板名称,那么当稍后解析login.html模板并content在其中找到一个块时,text/template将覆盖该模板的模板与后面解析的内容同名,所以当所有的模板都解析完后,content我们的模板树中实际上只有一个模板命名,也就是content上次解析的模板文件中定义的。


因此,当我们执行article.html模板时,有可能该content模板不是本模板中定义的内容,而是content其他模板中定义的内容。


社区针对这个问题提出了一些解决方案。例如,不是使用全局模板,而是在每次渲染时创建一个新模板,仅包含 layout.html 和子模板的内容。但这真的很乏味。事实上,当 Go 1.6block为文本/模板引入指令 [1] 时,我们能够使用该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)

}

可以看到Render这里只修改了函数。我们不会执行全局模板,而是将其克隆到一个新模板中,而content这个新模板中的块可能不是我们想要的,所以这里我们解析一个子模板的内容,我们最终将在此之上渲染全局模板,这样content新添加的子模板的 将覆盖以前的,可能不正确的content。我们的目标子模板引用了全局模板中的layout.html,这并不冲突,而且由于全局模板从来没有被执行过(Render每次执行我们在函数中克隆一个新的全局模板),它也是干净的。当一个模板最终被执行时,我们有一个干净的 layout.htmlcontent我们想要的内容,相当于每次执行都会生成一个新的模板,里面只包含我们需要的布局模板和子模板。思路是一样的,只不过不是在执行模板时手动生成新模板,而是在Render函数中自动完成。


当然,也可以使用{{ template }}来引用子模板中的其他布局模板,只要这些布局模板不相互覆盖,执行时只需要指定目标子模板的名称,模板引擎会自动使用其中{{ template }}定义的标签为我们寻找布局模板,这些模板都在克隆的全局模板中。


[1] https://github.com/golang/go/commit/12dfc3bee482f16263ce4673a0cce399127e2a0d


查看完整回答
反对 回复 2022-01-04
?
四季花海

TA贡献1811条经验 获得超5个赞

据我了解,当您使用 时ParseGlob(),Gin 会解析所有匹配的文件并从中创建一个模板对象。为了做你想做的事,你需要两个不同的模板(一个用于第 1 页,另一个用于第 2 页)。


Gin 文档说这是一个已知的限制,并指出了克服它的方法:


Gin 默认只允许使用一个 html.Template。检查多模板渲染以使用 go 1.6 等功能block template。


使用多模板库,您可以编写如下内容:


    render := multitemplate.NewRenderer()


    render.AddFromFiles("page1", "templates/base.html", "templates/page1.html")

    render.AddFromFiles("page2", "templates/base.html", "templates/page2.html")


    router := gin.Default()

    router.HTMLRender = render


    // Later

    ginContext.HTML(200, "page1", gin.H{

            "title": "The Wonderful Page One",

        })

这需要比我希望的更多的手动设置,但可以完成工作。


查看完整回答
反对 回复 2022-01-04
  • 3 回答
  • 0 关注
  • 305 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
微信客服

购课补贴
联系客服咨询优惠详情

帮助反馈 APP下载

慕课网APP
您的移动学习伙伴

公众号

扫描二维码
关注慕课网微信公众号