如何在Go中将模板渲染到多个布局?

huangapple go评论180阅读模式
英文:

How to render templates to multiple layouts in Go?

问题

我需要将模板渲染到不同种类的布局中。这是我的目录结构。

myapp
|
│   main.go
│
├───static
│       script.js
│       style.css
│
└───templates
    │   page1.tmpl
    │   page2.tmpl
    │   page3.tmpl
    │   page4.tmpl
    │   page5.tmpl
    │
    └───layouts
            base1.tmpl
            base2.tmpl
            base3.tmpl

我已经成功将模板渲染到单个布局模板中,但是无法在多个布局上工作。以下是我目前的代码:

package main

import (
	"html/template"
	"net/http"
	"fmt"
	"github.com/urfave/negroni"
	"github.com/oxtoacart/bpool"
	"path/filepath"
	"log"
)

var (
	templates        map[string]*template.Template
	bufpool          *bpool.BufferPool
)

func main() {
	loadTemplates()
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		renderTemplate(w, "page1.tmpl",nil)
	})
	n := negroni.New()
	n.Use(negroni.NewLogger())
	n.UseHandler(mux)
	http.ListenAndServe(":8080", n)
}

func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
	tmpl, ok := templates[name]
	if !ok {
		return fmt.Errorf("The template %s does not exist.", name)
	}

	buf := bufpool.Get()
	defer bufpool.Put(buf)

	err := tmpl.ExecuteTemplate(buf, "base1.tmpl", data)
	if err != nil {
		return err
	}

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	buf.WriteTo(w)
	return nil
}

func loadTemplates() {
	if templates == nil {
		templates = make(map[string]*template.Template)
	}

	tmplDir := "templates/"

	layouts, err := filepath.Glob(tmplDir + "layouts/*.tmpl")
	if err != nil {
		log.Fatal(err)
	}

	includes, err := filepath.Glob(tmplDir + "*.tmpl")
	if err != nil {
		log.Fatal(err)
	}

	for _, include := range includes {
		files := append(layouts, include)
		templates[filepath.Base(include)] = template.Must(template.ParseFiles(files...))
	}
	fmt.Print(includes)
	bufpool = bpool.NewBufferPool(64)
}

base1.tmpl 的内容如下:

{{define "base1"}}
<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">
    <title>{{block "title" .}}{{end}}</title>
</head>
<body>
{{template "content" .}}
</body>
</html>
{{end}}

page1.tmpl 的内容如下:

{{define "title"}}Page 1{{end}}

{{define "content"}}
<p>Page 1 contents</p>
{{end}}
英文:

I need to render templates into different kinds of layout. Here's my directory structure.

myapp
|
│   main.go
│
├───static
│       script.js
│       style.css
│
└───templates
    │   page1.tmpl
    │   page2.tmpl
    │   page3.tmpl
    │   page4.tmpl
    │   page5.tmpl
    │
    └───layouts
            base1.tmpl
            base2.tmpl
            base3.tmpl

I have done rendering templates to a single layout template but, I can't make it work on multiple layouts. Here's what I got so far:

package main
import (
&quot;html/template&quot;
&quot;net/http&quot;
&quot;fmt&quot;
&quot;github.com/urfave/negroni&quot;
&quot;github.com/oxtoacart/bpool&quot;
&quot;path/filepath&quot;
&quot;log&quot;
)
var (
templates        map[string]*template.Template
bufpool          *bpool.BufferPool
)
func main() {
loadTemplates()
mux := http.NewServeMux()
mux.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
renderTemplate(w, &quot;page1.tmpl&quot;,nil)
})
n := negroni.New()
n.Use(negroni.NewLogger())
n.UseHandler(mux)
http.ListenAndServe(&quot;:8080&quot;, n)
}
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
tmpl, ok := templates[name]
if !ok {
return fmt.Errorf(&quot;The template %s does not exist.&quot;, name)
}
buf := bufpool.Get()
defer bufpool.Put(buf)
err := tmpl.ExecuteTemplate(buf, &quot;base1.tmpl&quot;, data)
if err != nil {
return err
}
w.Header().Set(&quot;Content-Type&quot;, &quot;text/html; charset=utf-8&quot;)
buf.WriteTo(w)
return nil
}
func loadTemplates() {
if templates == nil {
templates = make(map[string]*template.Template)
}
tmplDir := &quot;templates/&quot;
layouts, err := filepath.Glob(tmplDir + &quot;layouts/*.tmpl&quot;)
if err != nil {
log.Fatal(err)
}
includes, err := filepath.Glob(tmplDir + &quot;*.tmpl&quot;)
if err != nil {
log.Fatal(err)
}
for _, include := range includes {
files := append(layouts, include)
templates[filepath.Base(include)] = template.Must(template.ParseFiles(files...))
}
fmt.Print(includes)
bufpool = bpool.NewBufferPool(64)
}

Here's how base1.tmpl looks like:

{{define &quot;base1&quot;}}
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;meta http-equiv=&quot;X-UA-Compatible&quot; content=&quot;IE=edge&quot;&gt;
&lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot;&gt;
&lt;title&gt;{{block &quot;title&quot; .}}{{end}}&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
{{template &quot;content&quot; .}}
&lt;/body&gt;
&lt;/html&gt;
{{end}}

And here's how page1.tmpl looks like:

{{define &quot;title&quot;}}Page 1{{end}}
{{define &quot;content&quot;}}
&lt;p&gt;Page 1 contents&lt;/p&gt;
{{end}}

答案1

得分: 0

我通常采用两次渲染的方法,一次用于内容,一次用于布局。这样可以在运行时使用任何内容在任何布局中,并将决策推迟到运行时。如果其他人有不同的方法,我会对其他方法感兴趣,但目前这种方法对我来说是有效的。

根据你发布的代码,可以在处理程序中这样使用:

data := map[string]interface{}{
    "title": "hello world",
}
renderTemplate(w, "base1.tmpl", "page1.tmpl", data)

renderTemplate函数中,除了传入模板之外,还要传入一个布局:

// 使用数据渲染模板'name'
buf := bufpool.Get()
err := tmpl.ExecuteTemplate(buf, name, data)
if err != nil {
    return err
}

// 将内容作为键设置在数据中(设置为HTML,因为它将被渲染)
data["content"] = template.HTML(buf.Bytes())
bufpool.Put(buf)

// 使用模板作为内容键,使用数据渲染布局'layout'
buf = bufpool.Get()
defer bufpool.Put(buf)
err = tmpl.ExecuteTemplate(buf, layout, data)
if err != nil {
    return err
}

布局如下:

<html>
<body>
    <h1>Base 1</h1>
    {{.content}}
</body>
</html>

页面如下:

<h2>{{.title}}</h2>
<h3>Page 1</h3>

这里是完整代码的链接:

https://play.golang.org/p/R2vr4keZec

英文:

I normally take the approach of rendering twice, once for content, once for layout, this lets you use any content in any layout and defer that decision till runtime. Would be interested in other approaches if other people do it differently, but this is working for me at present.

So using the code you have posted, something like this in handler:

data := map[string]interface{}{
&quot;title&quot;: &quot;hello world&quot;,
}
renderTemplate(w, &quot;base1.tmpl&quot;, &quot;page1.tmpl&quot;, data)

...

in renderTemplate pass in a layout as well as a template and:

// Render the template &#39;name&#39; with data
buf := bufpool.Get()
err := tmpl.ExecuteTemplate(buf, name, data)
if err != nil {
return err
}
// Set the content as a key on data (set as html as it is rendered)
data[&quot;content&quot;] = template.HTML(buf.Bytes())
bufpool.Put(buf)
// Render the layout &#39;layout&#39; with data, using template as content key
buf = bufpool.Get()
defer bufpool.Put(buf)	
err = tmpl.ExecuteTemplate(buf, layout, data)
if err != nil {
return err
}

Layout:

&lt;html&gt;
&lt;body&gt;
&lt;h1&gt;Base 1&lt;/h1&gt;
{{.content}}
&lt;/body&gt;
&lt;/html&gt;

Page:

&lt;h2&gt;{{.title}}&lt;/h2&gt;
&lt;h3&gt;Page 1&lt;/h3&gt;

Here is a link to full code:

https://play.golang.org/p/R2vr4keZec

huangapple
  • 本文由 发表于 2017年3月12日 20:08:23
  • 转载请务必保留本文链接:https://go.coder-hub.com/42747183.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定