- 投稿日:2019-10-09T17:30:28+09:00
GolangでHTMLのtemplateに値を渡す方法
はじめに
Goでは
http/template
を使用してhtmlのテンプレートに値を渡すことができます。具体的には
- 変数
- スライス
- マップ
- 構造体
などの値です。
今回は、基本となる変数と、使用機会が一番多いであろう構造体について説明していきます。
Web開発に使用する技術ですが、検索しても情報があまり出てこなかったので書きました。
環境
- macOS Catalina 10.15
- go 1.12.9
0.何も渡さず実行
メインのGoとhtmlファイルです。
これらを基本に説明していきます。
ディレクトリ構造. ├── main.go └── tpls.htmlmain.gopackage main import ( "html/template" "log" "os" ) var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { err := tpl.Execute(os.Stdout, nil) // dataは渡さない if err != nil { log.Fatalln(err) } }tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>hoge</h1> </body> </html>main.go を
go run main.go
で実行した結果がこちらです。実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>hoge</h1> </body> </html>⏎なんの変哲もありませんね。
今回はターミナルに標準出力しているので全文表示されますが、実際にはwebブラウザに読み込ませるので、hoge だけが表示されるはずです。
ではテンプレートの使い方を見ていきましょう。
1.変数の値を渡す
まずは変数を渡します。
tpl.Executeの第2引数に変数を指定することで渡せます。main.gopackage main import ( "html/template" "log" "os" ) var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { var Age = 235 err := tpl.Execute(os.Stdout, Age) // 変数を指定 if err != nil { log.Fatalln(err) } }次にhtmlです。
値を受け取るには{{.}}
の表記が必要になります。
独特の表記ですが基本となるので慣れてください。tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{{.}}</h1> <!-- 値を受け取る --> </body> </html>実行結果がこちらです。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>235</h1> </body> </html>⏎値が渡されているのがわかります。
複数回値を受け取れる
なお、受け取った値は複数回表示したり、変数に代入することができます。
tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{{.}}</h1> <!-- 同じ値を複数回受け取る事もできる --> <h1>{{.}}</h1> <h1>{{$Age := .}}</h1> <!-- 代入したときは表示されない --> <h1>{{$Age}}</h1> <!-- 値を表示 --> </body> </html>実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>235</h1> <h1>235</h1> <h1></h1> <h1>235</h1> </body> </html>⏎2.構造体の値を渡す
次に構造体の値を渡します。
実際の開発では構造体を使うことが一番多いでしょう。
具体的にはjsonから受け取った値を構造体に入れて、それを表示するといった感じでしょう。では、実行して行きます。
main.gopackage main import ( "html/template" "log" "os" ) type Person struct { Name string Age int } var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { p1 := Person{ Name: "hogefuga", Age: 28, } err := tpl.Execute(os.Stdout, p1) if err != nil { log.Fatalln(err) } }今回は
Name
とAge
の2つのフィールドを持った構造体を用意しました。
ベタですね。htmlはこちらです。
tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{{.}}</h1> <h2>{{.Name}}</h2> <!-- フィールドにアクセスする --> <h2>{{.Age}}</h2> </body> </html>フィールドにアクセスするには
{{.Name}}
のようにドットに続けてフィールド名を書きます。一方で、
{{.}}
のみだと構造体がそのまま表示されます。
これは感覚そのままですね。では実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <h1>{hogefuga 28}</h1> <h2>hogefuga</h2> <h2>28</h2> </body> </html>⏎全く説明した通りの結果です。
3.構造体のスライスを渡す
実際の開発では、
[]hogestruct
として構造体を複数使用したい場合があるでしょう。こちらのケースも見ていきます。
main.gopackage main import ( "html/template" "log" "os" ) type Person struct { Name string Age int } var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { p1 := Person{ Name: "hogefuga", Age: 28, } p2 := Person{ Name: "hogeeeeeee", Age: 51, } p3 := Person{ Name: "fugagagaga", Age: 3, } var persons = []Person{p1, p2, p3} err := tpl.Execute(os.Stdout, persons) if err != nil { log.Fatalln(err) } }やっているのは構造体のスライスを渡しているだけです。
次にhtmlです。
スライスの全要素を順に表示するには{{range .}}
を使用します。
実際にコードを見るのが速いでしょう。tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> {{range .}} <li>{{.Name}} - {{.Age}}</li> {{end}} </ul> </body> </html>
range
を使用するときは{{end}}
を忘れないようにしてください。では実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> <li>hogefuga - 28</li> <li>hogeeeeeee - 51</li> <li>fugagagaga - 3</li> </ul> </body> </html>⏎正しく表示されたと思います。
スライス要素に個別にアクセス
ちなみにスライスの要素に個別にアクセスしたいときは、html内でデフォルト関数の
index
を使用します。
これもコードを見たほうが速いでしょう。tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> {{index . 1}} <!-- スライスの要素に個別にアクセス --> {{index . 2}} {{(index . 0).Name}} <!-- 要素のフィールドにアクセスすることも可能--> </ul> </body> </html>個別要素のフィールドにアクセスするには
{{(index . 0).Name}}
のように()
でくくる必要があります。それでは実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> {hogeeeeeee 51} {fugagagaga 3} hogefuga </ul> </body> </html>デフォルト関数は
index
の他にも多数存在します。
詳細はgolangのtemplateページのFunctionの部分を参照してください。
ページがtext/templateとなっていますが、同じように使えます。(html/templateのページで詳細が見当たらなかった)4.構造体のメソッドについて
templateでは構造体のメソッドを実行することも可能です。
ただしメソッドの実装は値渡しでないといけません。(ポインタ渡しはエラー)main.gopackage main import ( "html/template" "log" "os" ) type Person struct { Name string Age int } // func (p *Person) のようなポインタ渡しはエラーが出る func (p Person) DoubleAge() int { return p.Age * 2 } var tpl *template.Template func init() { tpl = template.Must(template.ParseFiles("tpls.html")) } func main() { p1 := Person{ Name: "hogefuga", Age: 28, } err := tpl.Execute(os.Stdout, p1) if err != nil { log.Fatalln(err) } }tpls.html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> {{.DoubleAge}} <!-- methodも書き方は同じ --> </body> </html>実行結果です。
実行結果$ go run main.go <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Golang Template Test</title> </head> <body> <ul> 56 </ul> </body> </html>⏎Ageを2倍した値が表示されます。
おわりに
今回の記事は以上になります。
基本的な開発はこの記事の内容で十分行えると思います。
html/template
には関数を渡すfuncmap
や、template内でパイプ|
を使用する方法など、他にも様々な手法があります。
反響があれば記事を書こうと思います。最後まで読んでいただきありがとうございました。
- 投稿日:2019-10-09T16:51:10+09:00
Go の Web フレームワーク Echo で IT 用語クイズアプリを作ってみた
はじめに
近年、世間ではクイズが流行っています(?)が、IT 用語に特化したクイズのアプリってあんまりないような気がしたので作ってみました。
Go の勉強を最近始めたので、 echo でクイズのデータの JSON を返すだけの Web サーバをたてることにします。作ったもの
https://go-itquiz.herokuapp.com/
Github: https://github.com/chgzm/go-itquizサーバ側
Go そのものについては、始めて 3 日くらいなのでよくわかってませんが、echo の Guite を読めば大体問題なくできました。
- main 部分. heroku にデプロイするために、環境変数 PORT からポート番号を取得するようにする
main.gopackage main import ( "html/template" "io" "os" "github.com/chgzm/go-itquiz/data" "github.com/chgzm/go-itquiz/handler" "github.com/labstack/echo" ) type TemplateRenderer struct { templates *template.Template } func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error { if viewContext, isMap := data.(map[string]interface{}); isMap { viewContext["reverse"] = c.Echo().Reverse } return t.templates.ExecuteTemplate(w, name, data) } func setRouting(e *echo.Echo) { e.GET("/", handler.HandleIndex) e.GET("/question", handler.HandleQuestion) e.POST("/data", handler.HandleData) } func setStatic(e *echo.Echo) { e.Static("/public/css", "./public/css/") e.Static("/public/js", "./public/js/") } func main() { e := echo.New() renderer := &TemplateRenderer{ templates: template.Must(template.ParseGlob("public/*.html")), } e.Renderer = renderer err := data.InitTable(e, "./data") if err != nil { e.Logger.Error(err) return } setRouting(e) setStatic(e) port := os.Getenv("PORT") if port == "" { port = "1323" } e.Logger.Fatal(e.Start(":" + port)) }
- ハンドラの処理. /data へのアクセスでランダムに 10 問選んで JSON 形式で返す
handler/handler.gopackage handler import ( "math/rand" "net/http" "time" "github.com/chgzm/go-itquiz/data" "github.com/labstack/echo" ) func HandleIndex(c echo.Context) error { return c.Render(http.StatusOK, "index.html", "") } func HandleQuestion(c echo.Context) error { return c.Render(http.StatusOK, "question.html", "") } func HandleData(c echo.Context) error { entries := data.GetAllEntries() extract := extractEntry(entries) return c.JSON(http.StatusOK, extract) } func extractEntry(entries []data.Entry) []data.Entry { extract := make([]data.Entry, 0, 10) selected := make(map[int]bool) rand.Seed(time.Now().UnixNano()) entLen := len(entries) i := 0 for { index := rand.Intn(entLen) if selected[index] == true { continue } selected[index] = true extract = append(extract, entries[index]) i++ if i == 10 { break } } return extract }
- テキストからクイズのデータを読み込んでメモリ上に保持する処理
data/table.gopackage data import ( "bufio" "io/ioutil" "os" "path/filepath" "strings" "github.com/labstack/echo" ) type Entry struct { ID int `json:"id"` Question string `json:"question"` Answer string `json:"answer"` Dummy1 string `json:"dummy1"` Dummy2 string `json:"dummy2"` Dummy3 string `json:"dummy3"` } type Tag struct { ID int Name string EntryIDs []int } var entryTable []Entry var tagTable []Tag func InitTable(e *echo.Echo, dir string) error { entryTable = make([]Entry, 0, 2048) tagTable = make([]Tag, 0, 64) // // init entry // ent := Entry{} tagIDMap := make(map[string]int) entryID := 0 tagID := 0 for _, path := range getFilePaths(dir) { f, err := os.Open(path) if err != nil { e.Logger.Error(err) return err } scanner := bufio.NewScanner(f) for scanner.Scan() { str := scanner.Text() if len(str) == 0 { continue } else if strings.HasPrefix(str, "##") { // add to TagTable tagNames := strings.Split(strings.TrimLeft(strings.TrimPrefix(str, "##"), " "), ",") for _, tagName := range tagNames { if id, ok := tagIDMap[tagName]; ok == false { tagIDMap[tagName] = tagID tag := Tag{} tag.ID = tagID tag.Name = tagName tag.EntryIDs = make([]int, 0, 256) tag.EntryIDs = append(tag.EntryIDs, entryID) tagTable = append(tagTable, tag) tagID++ } else { tag := tagTable[id] tag.EntryIDs = append(tag.EntryIDs, entryID) } } } else if str[0] == '#' { ent.Question = strings.TrimLeft(strings.TrimPrefix(str, "#"), " ") } else if str[0] == '1' { ent.Answer = strings.TrimLeft(strings.TrimPrefix(str, "1."), " ") } else if str[0] == '2' { ent.Dummy1 = strings.TrimLeft(strings.TrimPrefix(str, "2."), " ") } else if str[0] == '3' { ent.Dummy2 = strings.TrimLeft(strings.TrimPrefix(str, "3."), " ") } else if str[0] == '4' { ent.Dummy3 = strings.TrimLeft(strings.TrimPrefix(str, "4."), " ") // add to EntryTable ent.ID = entryID entryID++ entryTable = append(entryTable, ent) ent = Entry{} } } f.Close() } return nil } func GetAllEntries() []Entry { return entryTable } func GetEntries(tagID int) []Entry { entries := make([]Entry, 0, 1024) for _, entryID := range tagTable[tagID].EntryIDs { entries = append(entries, entryTable[entryID]) } return entries } func getFilePaths(dir string) []string { files, err := ioutil.ReadDir(dir) if err != nil { panic(err) } paths := make([]string, 0, 32) for _, file := range files { if file.IsDir() { paths = append(paths, getFilePaths(filepath.Join(dir, file.Name()))...) continue } if file.Name() == "question.md" { paths = append(paths, filepath.Join(dir, file.Name())) } } return paths }クライアント側
CSS や Javascript のフレームワークは使わず実装しました(使うほどの規模でもないので)。作ってる途中に気づきましたが、ドットインストール にクイズアプリ作るレッスンがあったので大いに参考にしました。
おわりに
- echo は簡単でよい。Go もよい感じ。
- 肝心のクイズを作るのが面倒なので誰か作って欲しい
- 投稿日:2019-10-09T15:59:09+09:00
<script async> で DOMContentLoaded が発火しないことがある
async属性をつけた
<script async>
は非同期で読み込まれるため、
ドキュメントの読み込みが完了した時点でscript
が読み込まれていないことがあるその場合、読み込みの状況次第では
DOMContentLoaded
が発火しないold.jsfunction main() { // 読み込み後に動かしたいコード } document.addEventListener("DOMContentLoaded", main, false);改善策として、ドキュメントの読み込み状態をチェックする
document.readyState を見れば、状況が分かるNEW.jsfunction main() { // 読み込み後に動かしたいコード } if (document.readyState !== "loading") { main(); } else { document.addEventListener("DOMContentLoaded", main, false); }
- 投稿日:2019-10-09T11:16:19+09:00
HTML CSS勉強するときにオススメな教材
はじめに
こんにちは。
現在デザイナーをやりながらフロントエンドエンジニアを目指して
勉強中のあくしおです。アウトプットするって言ったのにかなり久しぶりの投稿です。。。
気持ちを切り替えて今日はHTML CSSを勉強するときにおすすめの教材を紹介していきたいと思います。
私のように遠回りして欲しくないので書きます。
基礎編
対象者:
・HTML CSSってなにそれ。
・名前は聞いたことあるよって人オススメ教材:
Progate
・めっちゃ分かりやすいです。無料プランでもいいですが有料会員になって
サッと2週くらいやるといいと思います。
・本とかだとモチベ上がらない人とかは特におすすめ。おすすめサイト:
Webliker
・凄く丁寧に説明してくれてる。
こちらも初学者の方にかなりオススメドットインストール
・動画形式で一緒に基礎を学びたい方向け
上記教材をやれば基礎の基礎は身につくと思います。
次のステップ
対象者:基礎は学んだよ!って人
UdemyのBuild Responsive Real World Websites with HTML5 and CSS3コース
こちらはUdemyのHTML CSSコースの中でもベストセラーの教材となっており僕自身もこの教材をやって大変満足しております。
実際に作るサイトもオシャレなのでテンションも上がります。
上記の事が終わったらあとはひたすら自分でサイトを作ってみるしかないと思います。
何も作るものが思いつかない人は既存のサイトの模写コーディングがオススメです。「模写コーディング おすすめ」とかで検索したらたくさん出てくるのではないでしょうか。
実際のコーディング作業に移った時に手が止まってしまうということはあると思いますがググりながら一つずつ潰していけば必ずできるようになると思います。
まとめ
結局のところ基礎の基礎を勉強したら調べながら自分で手を動かすがオススメ。
もしこの記事がいまいち勉強方法がわからないという方の参考になれば幸いです。Qiita自体書き慣れていないので間違い等あれば指摘していただけると大変助かります。
またこの次のステップも需要があれば書きたいと思います。
- 投稿日:2019-10-09T10:39:26+09:00
JavaScriptで自販機を作る方法
JavaScriptで自動販売機のブログラムを作りたいのですが、以下をできるようなプログラムを作りたいです。
1.お金を投入
2.商品を選択
3.商品とお釣りを受け取る。
まだ、JavaScriptを始めたばかりなので分かる方いましたら、教えていただきたいです。
- 投稿日:2019-10-09T10:13:45+09:00
[CSS] marginに値を4つ指定する。
CSSでmarginやpaddingに4つ値を指定すると、記述した順に[上][右][下][左]のマージンになります。
p.sample5 {background-color: #f8dce0; margin: 10px 20px 30px 40px;}HTMLソース
<p class="sample5"> 上10px、右20px、下30px、左40px </p>
- 投稿日:2019-10-09T01:45:36+09:00
勉強して150時間目の進捗
youtubeでKENTAさんが準備期間は大体150時間くらいでその後はどんどんポートフォリオ作っていった方が良いよってことで
自分がそろそろそれくらいなので作ってみることにしました。勉強した教材は、Progate、paizaラーニング、はじめてのふりがなプログラミングJS編、それぞれやってることは一緒ですが、教材によって説明の解釈や見せ方が違うので似た内容でも良い復習になりました。
とりあえずトップページを作ってみました。大体イメージ通りです。
もっとスライドとか使ってヌルヌル動かしたいんですが、お手本にしたいHPの検証モードが何書いてあるのか全然わからないので調べてできる範囲でなるべく似せて作って行こうと思います。トップページできたらそれぞれコンテンツページを作っていくんですが、HOME、YouTube、Twitterはリンクにするだけで完了
galleryとproductionの部分は作っていくんですが、galleryを見せ方によっては超かっこよくしたいですが、なんかサンプル素材がググったら沢山出てきて、コピペするだけでまんまパクれるかなって思ってやってみたんですが、なんか上手くいかないので、シンプルなので良いから形にはしていかないかんので簡単そうなものを色々試していってみることにします。
production(制作代行)の部分は注文フォームの見せ方をどうするか、ある程度お客さんが注文しやすいようにJSで作り込んでいく必要がありそうです、てゆうかここくらいでしかJSの出番がなさそうなので、これもできる範囲でやっていきます。僕の大好きなプラモデル制作もそうですが、細かい部分が全然綺麗に作れない、塗れないからって、何回もやり直していたらいつまでたっても完成しません、ある程度自分の実力と見切りをつけて、なるべくパーツの少ないキャラを選んで、荒くても良いからガツガツ作っていって次の作品でまた学べば良いかなと考えています。
今の営業の仕事はどんなけ売りまくって稼げないので、もうウンザリしてます。はよプログラム覚えて仕事の幅を広げていきたいです。