html/templateメモ

# html/templateメモ

## 目次

<!-- TOC -->

- [html/templateメモ](#htmltemplate%E3%83%A1%E3%83%A2)
    - [目次](#%E7%9B%AE%E6%AC%A1)
    - [html/templateの概要](#htmltemplate%E3%81%AE%E6%A6%82%E8%A6%81)
        - [templateとは](#template%E3%81%A8%E3%81%AF)
    - [基本文法](#%E5%9F%BA%E6%9C%AC%E6%96%87%E6%B3%95)
    - [プレーンなアクション](#%E3%83%97%E3%83%AC%E3%83%BC%E3%83%B3%E3%81%AA%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
        - [渡されるデータが構造体である場合](#%E6%B8%A1%E3%81%95%E3%82%8C%E3%82%8B%E3%83%87%E3%83%BC%E3%82%BF%E3%81%8C%E6%A7%8B%E9%80%A0%E4%BD%93%E3%81%A7%E3%81%82%E3%82%8B%E5%A0%B4%E5%90%88)
    - [その他アクション](#%E3%81%9D%E3%81%AE%E4%BB%96%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
        - [条件アクション](#%E6%9D%A1%E4%BB%B6%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
        - [イテレータアクション](#%E3%82%A4%E3%83%86%E3%83%AC%E3%83%BC%E3%82%BF%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
        - [代入アクション](#%E4%BB%A3%E5%85%A5%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
        - [インクルードアクション](#%E3%82%A4%E3%83%B3%E3%82%AF%E3%83%AB%E3%83%BC%E3%83%89%E3%82%A2%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3)
    - [テンプレートを分割したい](#%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%82%92%E5%88%86%E5%89%B2%E3%81%97%E3%81%9F%E3%81%84)
        - [静的なテンプレートの追加方法](#%E9%9D%99%E7%9A%84%E3%81%AA%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E8%BF%BD%E5%8A%A0%E6%96%B9%E6%B3%95)
        - [動的なテンプレートの追加方法](#%E5%8B%95%E7%9A%84%E3%81%AA%E3%83%86%E3%83%B3%E3%83%97%E3%83%AC%E3%83%BC%E3%83%88%E3%81%AE%E8%BF%BD%E5%8A%A0%E6%96%B9%E6%B3%95)
        - [スコープの話](#%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E3%81%AE%E8%A9%B1)
            - [余談](#%E4%BD%99%E8%AB%87)
    - [参考](#%E5%8F%82%E8%80%83)

<!-- /TOC -->

## html/templateの概要

htmlのテンプレートにこちらのプログラムで処理したデータを埋め込み返す。webサービスには必須の技術だが、それをgolangを実現するにはテンプレートエンジンである**html/template**を使う

正確には汎用テンプレートエンジンであるtext/template標準ライブラリと、さらにHTML専用のテンプレートエンジンであるhtml/templateライブラリの二種類があるが、ここでは後者を扱う

### templateとは


- **template(テンプレート)** とは予め用意されたhtmlのひな型

- **テンプレートエンジン** とはテンプレートとデータ [^1]を組み込んで、クライアントに返す最終的なhtmlを生成するプログラム
[^1]: 大抵はクライアントから渡される情報を指す。ユーザ名など。テンプレートは既に用意されたものであるため静的で、データはクライアントによって変わるものであるため動的（ただしこれから見ていく例では単純化するためサーバサイドで定義した文字列等を使っている）

- 通常、**ハンドラ**がテンプレートエンジンを呼び出してデータをテンプレートに組み込み、できあがったHTMLをクライアントに返す

イメージ図１[^2]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/652143/1c413c69-da3e-3389-633a-4b93ad403180.png)

[^2]: ハンドラがテンプレートエンジンを呼び出して、使用するテンプレートを通常はテンプレートのリストとして、動的なデータと一緒に渡します。テンプレートエンジンはHTMLを生成して、ResponseWriterにそれを書き込み、さらにResponseWriterはクライアントに送り返すHTTPレスポンスにそれを追加します。(イメージ図共に『Goプログラミング実践入門』より引用)


## 基本文法

- Go言語のテンプレートはテキストドキュメントであり（Webアプリの場合、通常はHTML
ファイル）、**アクション** と呼ばれる何らかのコマンドが埋め込まれたもの

- テンプレートはファイル全体であることもあれば、ファイルの一部しかテンプレートとして宣言されていないこともある

- アクションが埋め込まれたテキスト、すなわちテンプレートがテンプレートエンジンによって解析（構文解析）され、「実行」されて新たなテキストが生成される

- Go言語では任意のアクションは``{{...}}`` で囲って記述する

## プレーンなアクション

- 単純なテンプレートの例を示す

~~~html:templ.html
<!DOCTYPE html>
<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 <title>Go Web Programming</title>
 </head>
 <body>
 {{ . }}
 </body>
</html>
~~~

- `{{ . }}`の中のドット「 `.`」 がアクションであり、テンプレートエンジンがテンプレートを実行したときに、「その部分をデータで置換える」という意味を持つコマンド


- 次は上で定義したテンプレートから、最終的なhtmlを作成するgoスクリプトの方を見てみる

~~~go:main.go
package main
import (
 "net/http"
 "html/template"
)
func process(w http.ResponseWriter, r *http.Request) {
 t, _ := template.ParseFiles("tmpl.html")
 t.Execute(w, "hoge")
}
func main() {
 server := http.Server{
 Addr: "127.0.0.1:8080",
 }
 http.HandleFunc("/process", process)
 server.ListenAndServe()
}
~~~

- `t, _ := template.ParseFiles("tmpl.html")`<br>関数ParseFilesでテンプレートファイルtmpl.htmlを解析(コンパイル)する。<br>この関数は解析済みテンプレート（Template型）とエラーを返す

- 因みに、errorではなくパニックに変換するためにMust()を合わせて利用することもある
`var t = template.Must(template.ParseFiles("index.html"))`

- `t.Execute(w, "hoge")` 
    Executeメソッドを呼び出してデータ（この例では「hoge」）をコンパイル済みテンプレートに当てはめている。
    生成されたhtmlはt.Execute()の第一引数で指定したio.Writerである`http.ResponseWriter`に書き込まれる。
    Fprint系の関数とやっていることは似ている

- ├── main.go
└── templ.html
    以上二つのファイルを上のようなディレクトリ構成にしておくと、ビルド後に無事htmlファイルを返せるようになる

### 渡されるデータが構造体である場合

- 上の例で見たのは文字列"hoge"をデータとして渡す方法だった。「` . `」はそのまま"hoge"と置き換えられるので分かりやすい

- しかし一般的なのは構造体を渡すことで、そのメンバ変数がデータとして使われる。例えば先ほどのmain.goを次のように変えた場合だとどうするか

~~~go:main.go
...

type Person struct{
    Name:string
    Sex:string
}

var person Person = {
    Name:"HOGE" 
    Sex:"Male"
    //personのメンバ変数NameとSexをテンプレートに埋め込みたい
}

func process(w http.ResponseWriter, r *http.Request) {
 t, _ := template.ParseFiles("tmpl.html")
 t.Execute(w, person) //構造体personが渡されている

...
~~~

- 答えは次のようになる

~~~html:template.html
<!DOCTYPE html>
<html>
 <head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 <title>Go Web Programming</title>
 </head>
 <body>
 {{ .Name }} 
 {{ .Sex }}
 </body>
</html>
~~~

- ドット「`.`」単体では構造体そのものとの置き換えを指すが、`.Name`,`.Sex`とすることで特定のメンバ変数との置き換えという意味になる

- `構造体名.メンバ変数名`でメンバ変数の値にアクセスできるのと近いものを感じる

## その他アクション

テンプレートに渡されたデータと置き換えるドット「`.`」のコマンドは最も基本的なアクションだった。
しかし「`.`」以外にもアクションは存在する。それらは単純な置き換えとは違う、よりプログラム寄り（条件文やループ）の処理を行う

### 条件アクション 

- 条件アクションとは、多数のデータ評価値の中から引数の値に応じて1つを選択するものである。次の形式で表せる。（ただしargは任意の引数）

~~~
{{ if arg }}
 コンテンツ
{{ end }}
~~~

- もう1つのタイプは次の形式。

~~~
{{ if arg }}
 コンテンツ
{{ else }}
 他のコンテンツ
{{ end }}
~~~

### イテレータアクション

- イテレータアクションとは、配列やスライス、マップ、チャネルの要素ごとに反復処理を行うアクション。反復ループの内部では、ドットに配列やスライス、マップ、チャネルの要素が次々に設定される。
書式は次の通り

~~~
{{ range array }} 
#arrayは大抵データとして渡されたスライスやマップのイメージ
  {{ . }}
#range内でドットに要素が設定される
{{ end }}
~~~

- arrayがnilだった場合のフォールバックも存在する

### 代入アクション

代入アクションを使うと、そのアクションで囲まれたセクション内でargに指定した有効な値をドット「`.`」の値として設定できる。書式は次の通り

~~~
{{ with arg }}
 このセクション内でドットにargが設定される
{{ end }}

~~~

- argが空白だった場合のフォールバックも存在する

### インクルードアクション

- テンプレートに別のテンプレートを差し込むことができる。書式は次の通り

~~~
{{ template "name" }}
~~~

- このアクションは次の章で詳しく説明する

## テンプレートを分割したい

テンプレートAとテンプレートBがあったとき、bodyの要素は違ってもヘッダーは共通にしたい
というような、テンプレートを部品化して再利用する需要が出てくる。
この場合、共通部品であるヘッダーをテンプレートCとして作成してそれぞれのテンプレートに埋め込めばよい

### 静的なテンプレートの追加方法

- インクルードアクション`{{ template "name" }}`を使えばテンプレートに別のテンプレートを差し込むことができる。

- 単純な例を見る。
2つのテンプレートファイル、t1.htmlとt2.htmlを作成する。この例ではテンプレートt1.htmlがt2.htmlをインクルードする。

~~~html:t1.html
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=9">
 <title>Go Web Programming</title>
 </head> 
 <body>
 <div> ここは、t1.html（インクルードの前）</div>
 <div>t1.html内でのドットの値 - [{{ . }}]</div>
 <hr/>
 {{ template "t2.html" }}
 <hr/>
 <div> ここは、t1.html（インクルードの後）</div>
 </body>
~~~

~~~html:t2.html
<div style="background-color: yellow;">
 ここはt2.htmlです<br/>
</div>
~~~

- ハンドラは次のようにする

~~~go:main.go
package main

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

func process(w http.ResponseWriter, r *http.Request) {
	t, _ := template.ParseFiles("template/t1.html", "template/t2.html")
	t.Execute(w, "こんにちは")
}
func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/", process)
	server.ListenAndServe()
}

~~~


- `t, _ := template.ParseFiles("template/t1.html", "template/t2.html")`に注目すると、
今までと違い、ParseFilesメソッドの引数が二つになっている。
ParseFilesメソッドは可変長引数関数であり、コンパイルするファイルを複数指定することができる

- ParseFiles関数の引数が可変長でも、返り値としてのコンパイル済みテンプレートは一つしかなく、それは最終的にレスポンスとして返すhtmlのテンプレートである。
その主要素はもちろん親の（ここではt1.html）テンプレートであり、親のテンプレートは必ず第一引数に指定しなくてはならない。逆にインクルードされる部品としてのテンプレートは第二引数以降に指定する


- ディレクトリ構成はこのようになっている
├── main.go
└── template
&nbsp;&nbsp;&nbsp;&nbsp;└── t2.html
&nbsp;&nbsp;&nbsp;&nbsp;└──t1.html


- `http://127.0.0.1:8080/process`にアクセスすると以下のページが表示される

- ![image (23).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/652143/b28c598f-f260-ffeb-5732-ba6df65cf3fa.png)


### 動的なテンプレートの追加方法

- 先ほどの例では部品テンプレート(t2.html)は静的だった

- 部品テンプレートにも動的なデータを含みたい場合は次のような書式になる

~~~
{{ template "name" arg}}
~~~

- {{template "name"}}と違うのは後ろに引数argがくっついている点
こうすることで、インクルードされるテンプレートに渡すデータを指定できる

- さきほどの例を変更して、t2.htmlにもデータが渡るようにしてみる

~~~html:t1.html
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=9">
 <title>Go Web Programming</title>
 </head> 
 <body>
 <div> ここは、t1.html（インクルードの前）</div>
 <div>t1.html内でのドットの値 - [{{ . }}]</div>
 <hr/>
 {{ template "t2.html" . }} 
 #argにアクション「.」が指定されている

 <hr/>
 <div> ここは、t1.html（インクルードの後）</div>
 </body>
~~~

~~~html:t2.html
<div style="background-color: yellow;">
 ここはt2.htmlです<br/>
 t2.htmlに渡された値 - [{{ . }}]
</div>
~~~

- `http://127.0.0.1:8080/process`にアクセスすると以下のページが表示される

- ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/652143/adc5b39f-3bd6-85be-3780-1391f31018f4.png)

### スコープの話

- アクション「`.`」は`t.Execute(w, テンプレートに渡すデータ)`で指定したデータと置き換えられること、そのデータが構造体であれば「`.メンバ変数`」とするとそのメンバ変数と置き換えられることを既に伝えた

- その性質を利用して入れ子の構造体を自在に扱えたりもする

- 次のようなt1.html,t2.html,main.goを作成する

~~~html:t1.html
<!DOCTYPE html>
<html lang="en">
 <head>
 <meta charset="utf-8">
 <meta http-equiv="X-UA-Compatible" content="IE=9">
 <title>Go Web Programming</title>
 </head> 
 <body>
 <div> ここは、t1.html（インクルードの前）</div>
 <div>性別 - [{{ .Sex }}]</div>
 <hr/>
 {{ template "t2.html" .Name }}
 <hr/>
 <div> ここは、t1.html（インクルードの後）</div>
 </body>
~~~

~~~html:t2.html
<div style="background-color: yellow;">
    ここはt2.htmlです
    [{{ .Givenname }}]
    [{{ .Familyname }}]

</div>
~~~

~~~go:main.go
package main

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

type Name struct {
	Givenname  string
	Familyname string
}

type Person struct {
	Name Name
	Sex  string
}

func process(w http.ResponseWriter, r *http.Request) {
	person := Person{
		Name: Name{
			Givenname:  "小町",
			Familyname: "烏丸",
		},
		Sex: "Famale",
	}

	t, err := template.ParseFiles("template/t1.html", "template/t2.html")
	if err != nil {
		print(err)
	}
	t.Execute(w, person)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/", process)
	server.ListenAndServe()
}

~~~

- main.goで入れ子になった構造体を定義し、親の構造体のメンバ変数はt1.htmlへ、入れ子の構造体のメンバ変数はt2.htmlに渡している

- t1.htmlの`{{ template "t2.html" .Name }}`に注目すると、t2.htmlに渡されるデータが`.Name`になっている

- こうすることでt2.htmlでは「`.`」のスコープはName構造体に限定されるため、t2.htmlでは入れ子になっている構造体のメンバ変数へのアクセスは`[{{ .given_name }}]`とスマートになっている

- `http://127.0.0.1:8080/process`にアクセスすると以下のページが表示される

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/652143/147b6f95-0cf0-e4f8-bc09-b248b6a94d37.png)

#### 余談

- 頭文字が大文字であれば外部から参照できる、というのがGo言語の特徴

- 先ほどの例ではアクション「`.`」を通じて構造体を渡しているが、その過程でこちらのパッケージを外部に参照させる処理が含まれているのか
`main.go`で

~~~go
type Name struct {
	givenname  string
	familyname string
}
~~~

- のようにName構造体のメンバ変数の頭文字を小文字に変更すると、テンプレートに値が渡らない




## 参考

[Go の html/template でヘッダーやフッター等の共通化を実現する方法](https://mikan.github.io/2019/12/08/implementing-header-and-footer-with-golang-html-template/)

[Goプログラミング実践入門 ―標準ライブラリでゼロからWebアプリを作る―](https://elib.maruzen.co.jp/elib/html/BookDetail/Id/3000049505?2)