更新履歴

  1. : 公開

TL;DR

常にトップレベルを指す特殊変数 $ を使えばよい。

はじめに

Go には、標準ライブラリにテンプレートライブラリ text/template がある。 この text/template における制御構造、withrange は次のように使われる。

# {{ .Title }}

# User

{{ with .User }}
  {{ .Name }} ({{ .ID }})
{{ end }}

# Items

{{ range .Items }}
  - {{ . }}
{{ end }}

text/template. は、現在の操作対象を表す特殊なオブジェクトである。

withrange は、. を変更する効果を持つ。 with は引数に渡されたオブジェクトを . へセットして、内部のテンプレートを実行する。 range は引数に渡されたイテレート可能なオブジェクトに対し、それぞれの要素を . へセットして、要素の個数だけ内部のテンプレートを実行する。

つまりこのテンプレートは、次のような構造をレンダリングしている (Execute() の第2引数)。

tmpl.Execute(out, Params{
    Title: "foo",
    User: User{
        ID:   123,
        Name: "john",
    },
    Items: []string{
        "hoge",
        "piyo",
        "fuga",
    },
})

やりたいこと

今回おこないたいのは、withrange の中で、その外側で使われていたトップレベルのオブジェクトを参照することだ。

{{ with .User }}
  ここから .Title を参照するには?
{{ end }}

{{ range .Items }}
  ここから .User を参照するには?
{{ end }}

withrange は、. を自身の対象オブジェクトに変更するので、 単に {{ with .User }} の中で .Title と書いても、それは UserTitle プロパティを参照しているとみなされる。

text/template では変数が使えるので、テンプレートの先頭で

{{ $params := . }}

とでもしておけば実現は可能である。

しかしながら、頻発するシチュエーションにしてはあまりに不恰好である。よりスマートな方法が用意されているはずだ。

解決方法

常にトップレベルを指す特殊変数 $ を使えばよい。

{{ with .User }}
  {{ $.Title }}
{{ end }}

{{ range .Items }}
  {{ $.User.Name }}
{{ end }}

$ は、テンプレートが実行されるときに渡されたオブジェクトを指す。 これを使えば現在の . に関係なくトップレベルを参照できる。

このことは、text/template の公式ドキュメントにも以下のように記載されている。

When execution begins, $ is set to the data argument passed to Execute, that is, to the starting value of dot.

参考