HTMLテンプレート

テンプレートエンジンは動的にHTMLを生成する仕組みです。 HTMLに変数を埋め込み、HTMLの生成に条件分岐や繰り返しの制御をすることで動的なコンテンツを提供します。 Goの標準ライブラリではテンプレートエンジンとしてhtml/templateパッケージが提供されています。

テンプレートエンジンの使い方

まずは小さなサンプルとその実行結果を見てみましょう。

package main

import (
    "html/template"
    "os"
)

func main() {
    tpl := `<p>{{ . }}</p>`
    data := "Hello"
    t := template.Must(template.New("a").Parse(tpl))
    t.Execute(os.Stdout, data)
}
<p>Hello</p>

このサンプルでは次の処理をしています。

  • template.New関数でテンプレートを作成する
  • template.Template.Parse関数でテンプレートを解析する
  • template.Must関数はParse関数が失敗したらパニックになるよう振る舞いを変更する
  • template.Template.Executeメソッドでテンプレートにデータを埋め込む

テンプレートの中の{{}}で括られた部分は、特別な意味があります。 {{}}の中の.Execute関数で指定したデータを指します。

引数の書き方

引数は単純な値のことです。 先ほど、データを埋め込む箇所に.と記述しましたが、これを引数と呼びます。 他にもマップのキーや構造体のフィールドを指定できます。

マップのキーは次の通り指定します。

テンプレート

<p>{{ .key1 }}</p>

データ

map[string]string{"key1": "val1", "key2": "val2"}

実行結果

<p>val1</p>

構造体のフィールドは次の通り指定します。

テンプレート

<p>{{ .Field1 }}</p>

データ

struct {
    Field1 string
    Field2 string
}{
    Field1: "val1",
    Field2: "val2",
}

実行結果

<p>val1</p>

アクションの書き方

アクションはデータの評価やフロー制御をします。

条件分岐は次の通り記述します。

テンプレート

{{ if .key1 }}
<p>a</p>
{{ else if .key2 }}
<p>b</p>
{{ else }}
<p>c</p>
{{ end }}

データ

map[string]bool{"key1": false, "key2": true}

実行結果

<p>b</p>

繰り返しは次の通り記述します。

テンプレート

{{ range . }}
<p>{{ . }}</p>
{{ end }}

データ

map[string]string{"key1": "val1", "key2": "val2"}

実行結果

<p>val1</p>
<p>val2</p>

繰り返すデータがない場合の処理は次の通り記述します。

テンプレート

{{ range . }}
<p>{{ . }}</p>
{{ else }}
<p>データなし</p>
{{ end }}

データ

map[string]string{}

実行結果

<p>データなし</p>

変数の書き方

テンプレートの中で変数を定義できます。

変数の宣言と初期化は次の通り記述します。

テンプレート

{{ $v := . }}<p>{{ $v }}</p>

データ

"Hello"

実行結果

<p>Hello</p>

rangeアクションでは次の通り記述します。

テンプレート

{{ range $k, $v := . }}
<p>{{ $k }}: {{ $v }}</p>
{{ end }}

データ

map[string]string{"key1": "val1", "key2": "val2"}

実行結果

<p>key1: val1</p>
<p>key2: val2</p>

関数の書き方

テンプレートの中で関数を実行できます。

printf関数を実行して数字をゼロ埋めするには、次の通り記述します。

テンプレート

<p>{{ printf "%03d" . }}</p>

データ

1

実行結果

<p>001</p>

テンプレートファイルの読み込み

次はテンプレートをファイルから読み込むサンプルです。これまでのParseメソッドの代わりにParseFilesメソッドを使います。

package main

import (
    "html/template"
    "os"
)

func main() {
    data := "Hello"
    t := template.Must(template.New("a.html").ParseFiles("a.html"))
    t.Execute(os.Stdout, data)
}

サンプルではtemplate.New関数とtemplate.Template.ParseFileメソッドを組み合わせて使用しました。 これをもっと短く書くためにはtemplate.ParseFiles関数が使えます。

// この2行は同等の処理
t := template.Must(template.New("a.html").ParseFiles("a.html"))
t := template.Must(template.ParseFiles("a.html"))

a.html

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

実行結果

<p>Hello</p>

テンプレートファイルの分割

ParseFilesは関数名の通り複数のファイルを読み込めます。 HTMLの各ページで共通なヘッダやフッタなどのレイアウトと各ページ固有のコンテンツを分割できます。

package main

import (
    "html/template"
    "os"
)

func main() {
    data := "メインコンテンツ"
    t := template.Must(template.ParseFiles("layout.html", "page1.html"))
    t.Execute(os.Stdout, data)
}

共通レイアウトのテンプレートからページ固有コンテンツのテンプレートを取り込むには、 {{ template "main" . }}のように記述します。 templateの引数は、1つ目はテンプレート名、2つ目はテンプレートに渡すデータを指定します。

layout.html

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <p>ヘッダ</p>
    {{ template "main" . }}
    <p>フッタ</p>
  </body>
</html>

取り込まれる側のコンテンツには、テンプレートに名前を付けます。 {{ define "main" }}テンプレート本体{{ end }}の形式で記述します。 defineの引数はテンプレート名を指定します。

page1.html

{{ define "main" }}
  <p>{{ . }}</p>
{{ end }}

実行結果

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <p>ヘッダ</p>
    <p>メインコンテンツ</p>
    <p>フッタ</p>
  </body>
</html>

Webサーバでの利用

次は実際にWebサーバで利用したサンプルです。

package main

import (
    "fmt"
    "html/template"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", handler)
    server := http.Server{
        Addr:    ":8080",
        Handler: mux,
    }
    server.ListenAndServe()
}

func handler(w http.ResponseWriter, r *http.Request) {
    t := template.Must(template.ParseFiles("layout.html", "page1.html"))
    err := t.Execute(w, map[string]interface{}{
        "Slice": []int{1, 2},
        "Map":   map[string]string{"k1": "v1", "k2": "v2"},
    })
    if err != nil {
        fmt.Fprintln(w, err)
    }
}

layout.html

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <p>ヘッダ</p>
    {{ template "main" . }}
    <p>フッタ</p>
  </body>
</html>

page1.html

{{ define "main" }}
  <p>スライス</p>
  <ul>
    {{ range .Slice }}
      {{ if eq . 1 }}
        <li></li>
      {{ else }}
        <li>{{ . }}</li>
      {{ end }}
    {{ else }}
      <li>データなし</li>
    {{ end }}
  </ul>
  <p>マップ</p>
  <ul>
    {{ range $k, $v := .Map }}
      <li>{{ $k }}: {{ $v }}</li>
    {{ end }}
  </ul>
{{ end }}

実行結果

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title></title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <p>ヘッダ</p>
    <p>スライス</p>
    <ul>
      <li></li>
      <li>2</li>
    </ul>
    <p>マップ</p>
    <ul>
      <li>k1: v1</li>
      <li>k2: v2</li>
    </ul>
    <p>フッタ</p>
  </body>
</html>