Goテンプレートでは、適切なデータを適切なテンプレートに渡す方法が不便な場合があります。パイプラインパラメータを使用してテンプレートを呼び出すと、パラメータを1つだけ使用して関数を呼び出すように見えます。
Gopherに関するGopherのサイトがあるとします。ホームページのメインテンプレートと、Gopherのリストを印刷するユーティリティテンプレートがあります。
http://play.golang.org/p/Jivy_WPh16
出力:
*The great GopherBook* (logged in as Dewey)
[Most popular]
>> Huey
>> Dewey
>> Louie
[Most active]
>> Huey
>> Louie
[Most recent]
>> Louie
次に、サブテンプレートに少しコンテキストを追加します。「Dewey」という名前は、現在ログインしているユーザーの名前なので、リスト内で別の形式に設定します。しかし、 1つだけ 可能な「ドット」引数パイプラインがあるため、名前を直接渡すことはできません!私に何ができる?
複数の値をテンプレート呼び出しに渡すために使用できる「dict」関数をテンプレートに登録できます。呼び出し自体は次のようになります。
{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}
テンプレートfuncとしての登録を含む、小さな「dict」ヘルパーのコードは次のとおりです。
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
"dict": func(values ...interface{}) (map[string]interface{}, error) {
if len(values)%2 != 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{}, len(values)/2)
for i := 0; i < len(values); i+=2 {
key, ok := values[i].(string)
if !ok {
return nil, errors.New("dict keys must be strings")
}
dict[key] = values[i+1]
}
return dict, nil
},
}).ParseGlob("templates/*.html")
テンプレートで関数を定義し、これらの関数を次のようにデータで定義されたクロージャにすることができます。
template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User);},}
次に、テンプレートでこの関数を呼び出すだけです。
{{define "sub"}}
{{range $y := .}}>> {{if isUser $y}}!!{{$y}}!!{{else}}{{$y}}{{end}}
{{end}}
{{end}}
この更新されたバージョン プレイグラウンドの出力はかなり!!
現在のユーザーの周り:
*The great GopherBook* (logged in as Dewey)
[Most popular]
>> Huey
>> !!Dewey!!
>> Louie
[Most active]
>> Huey
>> Louie
[Most recent]
>> Louie
[〜#〜]編集[〜#〜]
Funcs
を呼び出すときに関数をオーバーライドできるため、テンプレートをコンパイルするときにテンプレート関数を実際に事前入力し、次のように実際のクロージャーで更新できます。
var defaultfuncs = map[string]interface{} {
"isUser": func(g Gopher) bool { return false;},
}
func init() {
// Default value returns `false` (only need the correct type)
t = template.New("home").Funcs(defaultfuncs)
t, _ = t.Parse(subtmpl)
t, _ = t.Parse(hometmpl)
}
func main() {
// When actually serving, we update the closure:
data := &HomeData{
User: "Dewey",
Popular: []Gopher{"Huey", "Dewey", "Louie"},
Active: []Gopher{"Huey", "Louie"},
Recent: []Gopher{"Louie"},
}
t.Funcs(template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User); },})
t.ExecuteTemplate(os.Stdout, "home", data)
}
いくつかのgoroutineが同じテンプレートにアクセスしようとしたときにそれがどのように機能するかはわかりませんが...
最も単純な方法(最もエレガントではありませんが)-特に比較的新しい人が行く場合-は、「オンザフライ」でanon構造体を使用します。これは、Andrew Gerrandの優れた2012年のプレゼンテーション「行くことについておそらく知らない10のこと」まで遡って文書化/提案されました。
https://talks.golang.org/2012/10things.slide#1
以下の簡単な例:
// define the template
const someTemplate = `insert into {{.Schema}}.{{.Table}} (field1, field2)
values
{{ range .Rows }}
({{.Field1}}, {{.Field2}}),
{{end}};`
// wrap your values and execute the template
data := struct {
Schema string
Table string
Rows []MyCustomType
}{
schema,
table,
someListOfMyCustomType,
}
t, err := template.New("new_tmpl").Parse(someTemplate)
if err != nil {
panic(err)
}
// working buffer
buf := &bytes.Buffer{}
err = t.Execute(buf, data)
テンプレートは多少のクリーンアップを必要とするため(つまり、範囲ループの最後の行にあるコンマを削除するため)、これは技術的にはそのままでは実行されないことに注意してください。テンプレートのパラメーターを匿名の構造体でラップするのは面倒で冗長に見えるかもしれませんが、テンプレートの実行時に何が使用されるかを正確に明示するという追加の利点があります。記述する新しいテンプレートごとに名前付き構造体を定義する必要があるよりも、退屈な作業はまったくありません。
@ Tux21bに基づく
インデックスを指定しなくても使用できるように関数を改善しました(変数をテンプレートにアタッチする方法を維持するためだけに)
だから今あなたはこのようにそれを行うことができます:
{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}
または
{{template "userlist" dict .MostPopular .CurrentUser}}
または
{{template "userlist" dict .MostPopular "Current" .CurrentUser}}
ただし、パラメーター(.CurrentUser.name)が配列でない場合は、この値をテンプレートに渡すためにインデックスを配置する必要があります
{{template "userlist" dict .MostPopular "Name" .CurrentUser.name}}
私のコードを参照してください:
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
"dict": func(values ...interface{}) (map[string]interface{}, error) {
if len(values) == 0 {
return nil, errors.New("invalid dict call")
}
dict := make(map[string]interface{})
for i := 0; i < len(values); i ++ {
key, isset := values[i].(string)
if !isset {
if reflect.TypeOf(values[i]).Kind() == reflect.Map {
m := values[i].(map[string]interface{})
for i, v := range m {
dict[i] = v
}
}else{
return nil, errors.New("dict values must be maps")
}
}else{
i++
if i == len(values) {
return nil, errors.New("specify the key for non array values")
}
dict[key] = values[i]
}
}
return dict, nil
},
}).ParseGlob("templates/*.html")
他のいくつかの回答で述べたように、マップはこのような状況に対する迅速で簡単な解決策になる場合があります。 Gopherを頻繁に使用しているため(そして、他の質問に基づいて、現在のGopherが管理者であるかどうかを気にしているため)、独自の構造体に値すると思います。
type Gopher struct {
Name string
IsCurrent bool
IsAdmin bool
}
Playgroundコードの更新を以下に示します。 http://play.golang.org/p/NAyZMn9Pep
明らかに、追加のレベルの深さで例の構造体を少し面倒な手作業でコーディングしますが、実際にはそれらは機械で生成されるため、IsCurrent
とIsAdmin
を必要に応じてマークするのは簡単です。
私がこれまでに見つけた最高の(そして私は本当にそれが好きではない)は、「汎用」ペアコンテナーを使用してパラメーターを多重化および逆多重化することです。
http://play.golang.org/p/ri3wMAubPX
type PipelineDecorator struct {
// The actual pipeline
Data interface{}
// Some helper data passed as "second pipeline"
Deco interface{}
}
func decorate(data interface{}, deco interface{}) *PipelineDecorator {
return &PipelineDecorator{
Data: data,
Deco: deco,
}
}
私は自分のウェブサイトを構築するためにこのトリックを頻繁に使用しており、同じことを達成するためのいくつかの慣用的な方法があるかどうか疑問に思っています。
私がこれに取り組む方法は、一般的なパイプラインを装飾することです:
type HomeData struct {
User Gopher
Popular []Gopher
Active []Gopher
Recent []Gopher
}
コンテキスト固有のパイプラインを作成する:
type HomeDataContext struct {
*HomeData
I interface{}
}
コンテキスト固有のパイプラインの割り当ては非常に安価です。ポインタをコピーすることにより、潜在的に大きいHomeData
にアクセスできます。
t.ExecuteTemplate(os.Stdout, "home", &HomeDataContext{
HomeData: data,
})
HomeData
はHomeDataContext
に埋め込まれているため、テンプレートから直接アクセスできます(例:.Popular
ではなく.HomeData.Popular
)。さらに、自由形式のフィールド(.I
)。
最後に、Using
のHomeDataContext
関数をこのように作成します。
func (ctx *HomeDataContext) Using(data interface{}) *HomeDataContext {
c := *ctx // make a copy, so we don't actually alter the original
c.I = data
return &c
}
これにより、状態(HomeData
)を維持しながら、任意の値をサブテンプレートに渡すことができます。
http://play.golang.org/p/8tJz2qYHbZ を参照してください。
広告 "...パラメータを1つだけ使用して関数を呼び出すように見えます。":
ある意味で、すべての関数はoneパラメータをとります-複数値の呼び出しレコード。テンプレートの場合も同じで、その「呼び出し」レコードはプリミティブ値または複数値の{map、struct、array、slice}にすることができます。テンプレートは、どの{key、field、index}を使用するかを、任意の場所の「単一」パイプラインパラメータから選択できます。
IOW、この場合はoneで十分です。
この問題のために、パイプのような引数の受け渡しをサポートするライブラリを実装しました。
デモ
{{define "foo"}}
{{if $args := . | require "arg1" | require "arg2" "int" | args }}
{{with .Origin }} // Original dot
{{.Bar}}
{{$args.arg1}}
{{ end }}
{{ end }}
{{ end }}
{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" 42 }}
{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" "42" }} // will raise an error