LoginSignup
1
0

More than 3 years have passed since last update.

html/templateメモ

Last updated at Posted at 2021-02-16

html/templateメモ

目次

html/templateの概要

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

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

templateとは

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

  • テンプレートエンジン とはテンプレートとデータ 1を組み込んで、クライアントに返す最終的なhtmlを生成するプログラム

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

イメージ図12

image.png

基本文法

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

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

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

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

プレーンなアクション

  • 単純なテンプレートの例を示す
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スクリプトの方を見てみる

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")
    関数ParseFilesでテンプレートファイルtmpl.htmlを解析(コンパイル)する。
    この関数は解析済みテンプレート(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を次のように変えた場合だとどうするか

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が渡されている

...
  • 答えは次のようになる
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をインクルードする。

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>
t2.html
<div style="background-color: yellow;">
 ここはt2.htmlです<br/>
</div>
  • ハンドラは次のようにする
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
        └── t2.html
        └──t1.html

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

  • image (23).png

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

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

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

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

  • さきほどの例を変更して、t2.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>
t2.html
<div style="background-color: yellow;">
 ここはt2.htmlです<br/>
 t2.htmlに渡された値 - [{{ . }}]
</div>
  • http://127.0.0.1:8080/processにアクセスすると以下のページが表示される

  • image.png

スコープの話

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

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

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

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

</div>
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

余談

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

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

type Name struct {
    givenname  string
    familyname string
}
  • のようにName構造体のメンバ変数の頭文字を小文字に変更すると、テンプレートに値が渡らない

参考

Go の html/template でヘッダーやフッター等の共通化を実現する方法

Goプログラミング実践入門 ―標準ライブラリでゼロからWebアプリを作る―


  1. 大抵はクライアントから渡される情報を指す。ユーザ名など。テンプレートは既に用意されたものであるため静的で、データはクライアントによって変わるものであるため動的(ただしこれから見ていく例では単純化するためサーバサイドで定義した文字列等を使っている) 

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

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0