Go – html/template, template.ParseGlob() and code re-use

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

Go - html/template, template.ParseGlob() and code re-use

问题

我正在尝试理解如何在Go中使用html/template嵌入模板。我非常喜欢无逻辑的模板设计,并且对其能够安全地转义内容的能力有信心(有时其他模板库会出错)。

然而,我在尝试根据“最终”模板名称在我的HTTP处理程序中实现一个小助手来呈现我的模板时遇到了一些问题。我的base.tmpl在所有页面上基本上都是“标准”的,在它不是的情况下,我可以在base.tmpl中设置{{ template checkoutJS }},并通过设置{{ define checkoutJS }}https://path.to/extra.js {{ end }}来添加一些特定页面的JS。

我希望在我的HTTP处理程序中能够这样说:renderTemplate(w, "template_name.tmpl", data),其中data是一个包含字符串或结构体的map[string]interface{},用于填充模板。

以下是目前的代码:

base.tmpl

{{ define "base" }}
<!DOCTYPE html>
<html lang="en">
<head>
<title>{{ template "title" . }}</title>
</head>
<body>
<div id="sidebar">
...
</div>
 
{{ template "content" }}
 
<div id="footer">
...
</div>
</body>
</html>

create_listing.tmpl

{{ define "title" }}Create a New Listing{{ end }}
 
{{ define "content" }}
 
<form>
  ...
</form>
 
{{ end }}

login_form.tmpl

{{ define "title" }}Login{{ end }}
 
{{ define "content" }}
 
<form>
  ...
</form>
 
{{ end }}

main.go

package main
 
import (
  "fmt"
	"github.com/gorilla/mux"
	"html/template"
	"log"
	"net/http"
)
 
// Template handling shortcuts
var t = template.New("base")
 
func renderTemplate(w http.ResponseWriter, tmpl string, data map[string]interface{}) {
 
	err := t.ExecuteTemplate(w, tmpl, data)
 
  // Things will be more elegant than this: just a placeholder for now!
	if err != nil {
		http.Error(w, "error 500: "+err.Error(), http.StatusInternalServerError)
	}
}
 
func monitorLoginForm(w http.ResponseWriter, r *http.Request) {
 
	// Capture forms, etc.
 
	renderTemplate(w, "login_form.tmpl", nil)
}
 
func createListingForm(w http.ResponseWriter, r *http.Request) {
 
	// Grab session, re-populate form if need be, generate CSRF token, etc
 
	renderTemplate(w, "create_listing.tmpl", nil)
}
 
func main() {
 
	r := mux.NewRouter()
 
	r.HandleFunc("/monitor/login", monitorLoginForm)
 
	http.Handle("/", r)
	log.Fatal(http.ListenAndServe(":8000", nil))
 
}
 
func init() {
 
	fmt.Println("Starting up.")
	_, err := t.ParseGlob("templates/*.tmpl")
	if err != nil {
		log.Fatal("Error loading templates:" + err.Error())
	}
}

这段代码可以编译通过,但是我从处理程序中得到了一个空响应。请注意,我没有为第二个处理程序设置路由:这段代码只是为了展示我如何从处理程序中调用renderTemplate()

英文:

I'm trying to get my head around embedding templates using html/template in Go. I very much like the logic-less template design and I have confidence in its ability to safely escape things as expected (sometimes other template libs get this wrong).

I am, however, having a bit of a problem trying to implement a little helper to render my templates in my HTTP handlers based on the "final" template name. My base.tmpl is effectively "standard" across all of my pages, and in cases where it isn't I can set {{ template checkoutJS }} in base.tmpl and add some per-page JS by setting {{ define checkoutJS }}https://path.to/extra.js {{ end }}.

I want to be able to say renderTemplate(w, &quot;template_name.tmpl&quot;, data) in my HTTP handlers, where data is a map[string]interface{} containing strings or structs with whatever I wish to fill in.

Here's the code so far:

base.tmpl

{{ define &quot;base&quot; }}
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
&lt;title&gt;{{ template &quot;title&quot; . }}&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;div id=&quot;sidebar&quot;&gt;
...
&lt;/div&gt;
 
{{ template &quot;content&quot; }}
 
&lt;div id=&quot;footer&quot;&gt;
...
&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;

create_listing.tmpl

{{ define &quot;title&quot; }}Create a New Listing{{ end }}
 
{{ define &quot;content&quot; }}
 
&lt;form&gt;
  ...
&lt;/form&gt;
 
{{ end }}

login_form.tmpl

{{ define &quot;title&quot; }}Login{{ end }}
 
{{ define &quot;content&quot; }}
 
&lt;form&gt;
  ...
&lt;/form&gt;
 
{{ end }}

main.go

package main
 
import (
  &quot;fmt&quot;
	&quot;github.com/gorilla/mux&quot;
	&quot;html/template&quot;
	&quot;log&quot;
	&quot;net/http&quot;
)
 
// Template handling shortcuts
var t = template.New(&quot;base&quot;)
 
func renderTemplate(w http.ResponseWriter, tmpl string, data map[string]interface{}) {
 
	err := t.ExecuteTemplate(w, tmpl, data)
 
  // Things will be more elegant than this: just a placeholder for now!
	if err != nil {
		http.Error(w, &quot;error 500:&quot;+&quot; &quot;+err.Error(), http.StatusInternalServerError)
	}
}
 
func monitorLoginForm(w http.ResponseWriter, r *http.Request) {
 
	// Capture forms, etc.
 
	renderTemplate(w, &quot;login_form.tmpl&quot;, nil)
}
 
func createListingForm(w http.ResponseWriter, r *http.Request) {
 
	// Grab session, re-populate form if need be, generate CSRF token, etc
 
	renderTemplate(w, &quot;create_listing.tmpl&quot;, nil)
}
 
func main() {
 
	r := mux.NewRouter()
 
	r.HandleFunc(&quot;/monitor/login&quot;, monitorLoginForm)
 
	http.Handle(&quot;/&quot;, r)
	log.Fatal(http.ListenAndServe(&quot;:8000&quot;, nil))
 
}
 
func init() {
 
	fmt.Println(&quot;Starting up.&quot;)
	_, err := t.ParseGlob(&quot;templates/*.tmpl&quot;)
	if err != nil {
		log.Fatal(&quot;Error loading templates:&quot; + err.Error())
	}
}

This compiles, but I get back an empty response from my handler. Note that I don't have a route for the second handler: that code is there just to show how I want to call renderTemplate() from handlers.

答案1

得分: 2

你不能用当前的go模板包来实现你想要的功能。模板没有继承,因此没有你在模板中所拥有的命名块。与其定义一个基础模板,更常见的做法是定义头部和尾部模板。然后,在你的页面模板中,明确地包含那些你想要放置的模板。

我认为另一个解决方案是采用两阶段模板处理。第一阶段是编译基础模板的所有块的模板。这些模板被添加到映射中,然后发送到基础模板中进行包含。

英文:

You cannot do what you want with the current go template package. Templates have no inheritance, and thus no named blocks as you have in your templates. Instead of defining a base template, it is more common to define header and footer templates. Then, in your page templates, explicitly include those where you want them to go.

Another solution, I believe, would be to have a 2-stage template phase. The first would be to compile templates for all of the blocks of the base template. These get added to the map, and then sent off to the base template for inclusion.

答案2

得分: 1

这是相当不明显的,我不知道为什么他们这样做。

ParseGlob返回一个你正在丢弃的值,但你需要保留它;它是你需要调用的模板对象,所以你的代码应该像这样:

func init() {
  fmt.Println("Starting up.")
  t, err := template.ParseGlob("templates/*.tmpl")
  if err != nil {
    log.Fatal("Error loading templates:" + err.Error())
  }
}

关于该方法的文档(与上面使用的库函数相对)有点不清楚,它说它将模板与调用该方法的模板对象关联起来,但它还返回一个指向模板对象的指针,如果它按照广告所说的那样工作,它就不需要这样做。有趣!

英文:

<p>This is pretty non-obvious, and I don't know why they do it like this</p>
<p>ParseGlob returns a value that you're throwing away, but you need to keep it; it's the template object that you need to call, so your code should look like:</p>
func init() {
fmt.Println("Starting up.")
t, err := template.ParseGlob("templates/*.tmpl")
if err != nil {
log.Fatal("Error loading templates:" + err.Error())
}
}
<p>the documentation for the method (as opposed to the library function used above) is a bit unclear, as it says that it associates the templates with the template object that the method is called on, but it also returns a pointer to a template object, which it wouldn't need to do if it worked as advertised. Fun!</p>

huangapple
  • 本文由 发表于 2013年10月15日 12:59:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/19373586.html
匿名

发表评论

匿名网友

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

确定