20191009のHTMLに関する記事は7件です。

GolangでHTMLのtemplateに値を渡す方法

はじめに

Goではhttp/templateを使用してhtmlのテンプレートに値を渡すことができます。

具体的には

  • 変数
  • スライス
  • マップ
  • 構造体

などの値です。

今回は、基本となる変数と、使用機会が一番多いであろう構造体について説明していきます。

Web開発に使用する技術ですが、検索しても情報があまり出てこなかったので書きました。

環境

  • macOS Catalina 10.15
  • go 1.12.9

0.何も渡さず実行

メインのGoとhtmlファイルです。

これらを基本に説明していきます。

ディレクトリ構造
.
├── main.go
└── tpls.html
main.go
package 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.go
package 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.go
package 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)
    }
}

今回はNameAgeの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.go
package 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.go
package 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内でパイプ|を使用する方法など、他にも様々な手法があります。

反響があれば記事を書こうと思います。

最後まで読んでいただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go の Web フレームワーク Echo で IT 用語クイズアプリを作ってみた

はじめに

近年、世間ではクイズが流行っています(?)が、IT 用語に特化したクイズのアプリってあんまりないような気がしたので作ってみました。
Go の勉強を最近始めたので、 echo でクイズのデータの JSON を返すだけの Web サーバをたてることにします。

作ったもの

https://go-itquiz.herokuapp.com/
Github: https://github.com/chgzm/go-itquiz

  • 動作イメージ itquiz-screenshot.png

サーバ側

Go そのものについては、始めて 3 日くらいなのでよくわかってませんが、echo の Guite を読めば大体問題なくできました。

  • main 部分. heroku にデプロイするために、環境変数 PORT からポート番号を取得するようにする
main.go
package 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.go
package 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.go
package 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 のフレームワークは使わず実装しました(使うほどの規模でもないので)。作ってる途中に気づきましたが、ドットインストール にクイズアプリ作るレッスンがあったので大いに参考にしました。

おわりに

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

<script async> で DOMContentLoaded が発火しないことがある

async属性をつけた <script async> は非同期で読み込まれるため、
ドキュメントの読み込みが完了した時点で script が読み込まれていないことがある

その場合、読み込みの状況次第では DOMContentLoaded が発火しない

old.js
function main() {
  // 読み込み後に動かしたいコード
}

document.addEventListener("DOMContentLoaded", main, false);

改善策として、ドキュメントの読み込み状態をチェックする
document.readyState を見れば、状況が分かる

NEW.js
function main() {
  // 読み込み後に動かしたいコード
}

if (document.readyState !== "loading") {
  main();
} else {
  document.addEventListener("DOMContentLoaded", main, false);
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

HTML CSS勉強するときにオススメな教材

はじめに

こんにちは。
現在デザイナーをやりながらフロントエンドエンジニアを目指して
勉強中のあくしおです。

アウトプットするって言ったのにかなり久しぶりの投稿です。。。

気持ちを切り替えて今日はHTML CSSを勉強するときにおすすめの教材を紹介していきたいと思います。

私のように遠回りして欲しくないので書きます。

基礎編

対象者:

・HTML CSSってなにそれ。
・名前は聞いたことあるよって人

オススメ教材:

Progate
・めっちゃ分かりやすいです。無料プランでもいいですが有料会員になって
 サッと2週くらいやるといいと思います。
・本とかだとモチベ上がらない人とかは特におすすめ。

おすすめサイト:
Webliker
・凄く丁寧に説明してくれてる。
 こちらも初学者の方にかなりオススメ

ドットインストール
・動画形式で一緒に基礎を学びたい方向け

 

上記教材をやれば基礎の基礎は身につくと思います。

次のステップ

対象者:基礎は学んだよ!って人

UdemyのBuild Responsive Real World Websites with HTML5 and CSS3コース

こちらはUdemyのHTML CSSコースの中でもベストセラーの教材となっており僕自身もこの教材をやって大変満足しております。

実際に作るサイトもオシャレなのでテンションも上がります。

上記の事が終わったらあとはひたすら自分でサイトを作ってみるしかないと思います。
何も作るものが思いつかない人は既存のサイトの模写コーディングがオススメです。

「模写コーディング おすすめ」とかで検索したらたくさん出てくるのではないでしょうか。

実際のコーディング作業に移った時に手が止まってしまうということはあると思いますがググりながら一つずつ潰していけば必ずできるようになると思います。

まとめ

結局のところ基礎の基礎を勉強したら調べながら自分で手を動かすがオススメ。
もしこの記事がいまいち勉強方法がわからないという方の参考になれば幸いです。

Qiita自体書き慣れていないので間違い等あれば指摘していただけると大変助かります。
またこの次のステップも需要があれば書きたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで自販機を作る方法

JavaScriptで自動販売機のブログラムを作りたいのですが、以下をできるようなプログラムを作りたいです。
1.お金を投入
2.商品を選択
3.商品とお釣りを受け取る。
まだ、JavaScriptを始めたばかりなので分かる方いましたら、教えていただきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

勉強して150時間目の進捗

youtubeでKENTAさんが準備期間は大体150時間くらいでその後はどんどんポートフォリオ作っていった方が良いよってことで
自分がそろそろそれくらいなので作ってみることにしました。

スクリーンショット 2019-10-07 23.38.48.png

勉強した教材は、Progate、paizaラーニング、はじめてのふりがなプログラミングJS編、それぞれやってることは一緒ですが、教材によって説明の解釈や見せ方が違うので似た内容でも良い復習になりました。

とりあえずトップページを作ってみました。大体イメージ通りです。
もっとスライドとか使ってヌルヌル動かしたいんですが、お手本にしたいHPの検証モードが何書いてあるのか全然わからないので調べてできる範囲でなるべく似せて作って行こうと思います。

トップページできたらそれぞれコンテンツページを作っていくんですが、HOME、YouTube、Twitterはリンクにするだけで完了
galleryとproductionの部分は作っていくんですが、galleryを見せ方によっては超かっこよくしたいですが、なんかサンプル素材がググったら沢山出てきて、コピペするだけでまんまパクれるかなって思ってやってみたんですが、なんか上手くいかないので、シンプルなので良いから形にはしていかないかんので簡単そうなものを色々試していってみることにします。
production(制作代行)の部分は注文フォームの見せ方をどうするか、ある程度お客さんが注文しやすいようにJSで作り込んでいく必要がありそうです、てゆうかここくらいでしかJSの出番がなさそうなので、これもできる範囲でやっていきます。

僕の大好きなプラモデル制作もそうですが、細かい部分が全然綺麗に作れない、塗れないからって、何回もやり直していたらいつまでたっても完成しません、ある程度自分の実力と見切りをつけて、なるべくパーツの少ないキャラを選んで、荒くても良いからガツガツ作っていって次の作品でまた学べば良いかなと考えています。
今の営業の仕事はどんなけ売りまくって稼げないので、もうウンザリしてます。はよプログラム覚えて仕事の幅を広げていきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む