Vugu で Todo アプリ作って Try WebAssembly!!

#はじめに

[技術書典7](https://techbookfest.org/event/tbf07) に行った際、[Try WebAssembly!!](https://techbookfest.org/event/tbf07/circle/6013777778573312) という本に出会い、
前々から WebAssembly に興味持っていたのもあり購入して勉強していました :book: 

一般的に Go で WebAssembly アプリを作る際は [syscall/js](https://golang.org/pkg/syscall/js/) を利用するようなのですが、
Vugu を利用すると [Vue.js](https://jp.vuejs.org/index.html) の手触りで WebAssembly アプリの開発進めていけるので、
UI を組む手間などが大幅に省けて楽に開発を進めていけるようです :star2: 

今回は勉強のため Vugu で Todo アプリを作る際は、
なるべく Vugu に用意されている便利な機能を取り入れて、
理解を深めながら開発を進めることを意識しました :arrow_heading_up: 

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/0ad56ccf-dc05-b8d8-7c1a-32b40059cadc.gif)

実際のソースコードは GitHub にアップしておきました :arrow_down: 
https://github.com/nikaera/vugu-todo

#動作環境

- Go 1.12.5 ([Modules](https://github.com/golang/go/wiki/Modules) が使えるバージョン)
- Vugu v0.1.0

#環境構築

公式サイトの [Getting Started](https://www.vugu.org/doc/start) に沿って構築していきます :construction_site: 

- 適当な場所に作業用フォルダ `testapp` を作成します
- `testapp` フォルダ内で `GO111MODULE=on go mod init nikaera.com/vugu/testapp` を実行して `go.mod` ファイルを作成します
- `testapp` フォルダ内に Vugu のテンプレートである `root.vugu` ファイルを作成する (中身及び詳細は :arrow_down: )

```vue:testapp/root.vugu
<!-- Vugu のテンプレート構文 (Vue.js とほぼ一緒) -->
<!-- HTML テンプレートを定義する -->
<div class="my-first-vugu-comp">
    <!-- @<イベント名> で js のイベントを Go でハンドリング出来る -->
    <button @click="data.Toggle()">Test</button>

    <!-- vg-if で条件付きレンダリング出来、その条件は Go で定義することが出来る -->
    <div vg-if="data.Show">I am here!</div>
</div>

<!-- HTML の style を定義する -->
<style>
.my-first-vugu-comp { background: #eee; }
</style>

<!-- Vugu では Go で script タグ内にビジネスロジックを書く -->
<script type="application/x-go">
// 1. Vugu の命名規則に従い `${ファイル名のキャメルケース}Data` で struct を作成する
// 今回はファイル名が root.vugu だから RootData という名前で struct を宣言
// 2. Vugu の命名規則に従っている struct は自動でインスタンス生成され、
// HTML テンプレート内から data 変数でアクセス出来るようになる
// 3. HTML テンプレート内から RootData の変数の参照や関数の呼び出しが data 変数で可能になる
type RootData struct { Show bool }
func (data *RootData) Toggle() { data.Show = !data.Show }
</script>
```

- `testapp` フォルダ内に Vugu の開発用サーバーを起動するため `devserver.go` を作成する

```go:testapp/devserver.go
// +build ignore

package main

import (
	"log"
	"net/http"
	"os"

// Vugu アプリケーションを動かすための HTTP サーバー
	"github.com/vugu/vugu/simplehttp"
)

func main() {
	wd, _ := os.Getwd()
	l := "127.0.0.1:8844"
	log.Printf("Starting HTTP Server at %q", l)
	h := simplehttp.New(wd, true)
	// include a CSS file
	// simplehttp.DefaultStaticData["CSSFiles"] = []string{ "/my/file.css" }
	log.Fatal(http.ListenAndServe(l, h))
}
```

- `go run devserver.go` で開発用サーバーを起動する

すると、ターミナルに下記が出力されているはずです :arrow_down: 

```shell
$ go run devserver.go
go: finding github.com/vugu/vugu/simplehttp latest
2019/09/28 17:16:23 Starting HTTP Server at "127.0.0.1:8844"
```

ブラウザから http://127.0.0.1:8844 にアクセスすると、
`root.vugu` 内で定義した内容が反映されたページを確認できるはずです :tada: 

![demo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/199acfe8-791c-35b0-0596-4806110f3f07.gif)

これで Vugu の開発環境構築が出来ました :clap: 

試しに `root.vugu` ファイルで定義している `I am here!` という文字列を
`I am here NOW!!` とかに変更してからブラウザをリロードすると
表示されている文字列が変わっていることが確認出来るはずです :white_check_mark: 

# Todo アプリの作成手順

## Todo の文字列入力フィールドとその登録ボタンを用意する

`root.vugu` を書き換えていきます :pencil: 

```vue:testapp/root.vugu
<div class="my-first-vugu-comp">
    <!-- input タグでテキストフィールドを用意する -->
    <!-- @change でテキストの変更イベントを Go 側の InputTodo 関数でハンドリングする -->
    <!-- 引数に event を指定することで JavaScript の event 変数を Go 側に渡すことが可能 -->
    <input type="text" id="todotext" @change='data.InputTodo(event)'>

    <!-- 上記テキストフィールド内の文字列から Todo を登録するためのボタン -->
    <!-- @click でボタンのクリックイベントを Go 側の AddTodo 関数でハンドリングする -->
    <button @click="data.AddTodo()">Add Todo</button>
</div>

<script type="application/x-go">

type RootData struct {
    // テキストフィールドの内容を保持しておく変数
    TodoText string
}

// テキストフィールドの入力内容を検知して RootData の TodoText に代入するための関数
func (data *RootData) InputTodo(event *vugu.DOMEvent) {
    // event.JSEvent() で JavaScript の event を取得出来る
    // Get 関数を利用してテキストフィールド内の文字列を取得して TodoText に代入する
    data.TodoText = event.JSEvent().Get("target").Get("value").String()
}

// "Add Todo" ボタンのクリックを検知して Todo リストに登録するための関数
func (data *RootData) AddTodo() {
    // テキストフィールド内の文字列をコンソールに出力する
    // (Todo リストへの登録処理は後の項目で実装する)
    fmt.Println("TodoText: " + data.TodoText);
}

</script>
```

`root.vugu` を書き換えたら `go run devserver.go` で HTTP サーバーを起動して、
ブラウザから http://127.0.0.1:8844 にアクセスします :earth_africa:
(既に HTTP サーバーを起動済みであればブラウザをリロードすれば大丈夫です)

テキスト入力フィールドに文字列を入力し、`Add Todo` ボタンをクリックすることで、
JavaScript コンソールに入力フィールドの文字列が出力されていれば OK です :thumbsup: :arrow_down: 

![demo3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/07e26b21-99bb-e015-b570-2d0030c08843.gif)

## Todo の内容が画面に表示されるようにする

Vugu では Vue.js のようにコンポーネントを作成することが可能です。
`root.vugu` もコンポーネントであり、トップレベルのコンポーネントとして利用しています。

今回は Todo を表示するためのコンポーネントを作成します :notepad_spiral:
`testapp` フォルダ内に `todo.vugu` ファイルを作成します :arrow_down:

```vue:testapp/todo.vugu
<div class="todo">
    <!-- Todo の完了ステータスを変更するために利用するためのチェックボックス -->
    <input type="checkbox" name="done" @click="data.Done(event)">

    <!-- Todo の内容を表示するための span タグ -->
    <!-- :style のように : を接頭辞につけることで style 属性を Go 側で変更出来るようにする -->
    <!-- vg-html で Go 側で HTML 要素のテキストを Go 側で出力することが出来る -->
    <span :style='"text-decoration:" + data.TextStyle' vg-html='data.Date + " -> " + data.Text'></span>
</div>

<style>
  .todo {
    display: inline;
  }
</style>

<script type="application/x-go">

// コンポーネントファイルを todo.vugu で作成したので TodoData で struct を宣言
type TodoData struct {
    // 登録した Todo 内容を表す文字列
    Text string

    // Todo を登録した日付の unixtime 文字列
    Date string

    // Todo テキストを表示する際の span タグの style の
    // text-decoration の値を保持する文字列
    // (Todo が完了したら、文字に取り消し線を適用したかったため利用)
    TextStyle string
}

// コンポーネント生成時に呼ばれるコンストラクタ関数
// props は Vue.js の props と同義で HTML タグの属性値が取得出来るもの
func (data *Todo) NewData(props vugu.Props) (interface{}, error) {
  ret := &TodoData{}

  // <todo text='test' date='1569666420'/> と宣言していた場合
  // props["text"] には "test" 文字列が入り、
  // props["date"] には "1569666420" が入る
  ret.Text, _ = props["text"].(string)
  ret.Date, _ = props["date"].(string)
  ret.TextStyle = "none"

  return ret, nil
}

// チェックボックスの On/Off を切り替えるたびに呼び出される関数
func (data *TodoData) Done(event *vugu.DOMEvent) {
    // Todo に紐付いたチェックボックスが On か Off かを bool で取得
    done := event.JSEvent().Get("target").Get("checked").Bool()

    // チェックボックスが on なら Todo が完了済みとみなし、取り消し線を表示する
    // チェックボックスが off なら Todo が未完了とみなし、普通に文字列を表示する
    if done {
        data.TextStyle = "line-through"
    } else {
        data.TextStyle = "none"
    }
}

</script>
```

その後、`todo.vugu` で定義したコンポーネントが正しく使えるようになったか、
`root.vugu` を改修して動作検証してみます :arrow_down:

```vue:testapp/root.vugu
<div class="my-first-vugu-comp">
    <input type="text" id="todotext" @change='data.InputTodo(event)'>
    <button @click="data.AddTodo()">Add Todo</button>
    <ul>
        <!-- todo.vugu で定義した Todo コンポーネントの表示を確かめる -->
        <li><todo text="Test" date="1569666420"/></li>
    </ul>
</div>

<script type="application/x-go">

type RootData struct {
    TodoText string
}

func (data *RootData) InputTodo(event *vugu.DOMEvent) {
    data.TodoText = event.JSEvent().Get("target").Get("value").String()
}

func (data *RootData) AddTodo() {
    fmt.Println("TodoText: " + data.TodoText);
}

</script>
```

http://127.0.0.1:8844 にアクセスすると、
`todo.vugu` で定義した内容の表示が確認出来るはずです :arrow_down: 

<img width="253" alt="スクリーンショット 2019-09-28 20.51.50.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/abb8cf97-88a6-3110-33de-96692424b224.png">

## Todo の登録 & 登録した Todo をリスト表示する

こちら保留にしていた `root.vugu` 内の `AddTodo` 関数の実装を行っていきます :fire:

```vue:testapp/root.vugu
<div class="my-first-vugu-comp">
    <input type="text" id="todotext" @change='data.InputTodo(event)'>
    <button @click="data.AddTodo(event)">Add Todo</button>
    <ul>
        <!-- vg-for で Go の配列サイズ数だけタグを繰り返し生成する -->
        <!-- TodoData の配列 data.Todos の配列サイズ数分 li タグを繰り返し生成する -->
        <li vg-for='data.Todos'>
            <!-- vg-for 内では value で配列内の要素にアクセスすることが出来る -->
            <!-- data.Todos は TodoData の配列なので value には TodoData インスタンスが設定されている -->
            <todo :date='value.Date' :text='value.Text'></todo>
        </li>
    </ul>
</div>

<script type="application/x-go">

// Vugu 内では Go のライブラリを利用することが出来る
import (
    "time"
    "strconv"
)

type RootData struct {
    TodoText string

    // Todo リストを TodoData の配列をして保持する
    Todos []TodoData
}

func (data *RootData) InputTodo(event *vugu.DOMEvent) {
    data.TodoText = event.JSEvent().Get("target").Get("value").String()
}

func (data *RootData) AddTodo(event *vugu.DOMEvent) {
    ee := event.EventEnv()

    // メインスレッドに影響を出さずにレンダリング内容をアップデートするため、
    // Goroutine 内で Todo リストデータの更新を行う
    go func() {
        // レンダリング内容をロックする
        ee.Lock()

        // スコープ内の処理が完了次第、
        // レンダリング内容をアンロックし、
        // 再びレンダリングを開始する
        defer ee.UnlockRender()

        // テキスト入力フィールドの文字列と
        // データ生成時の unixtime スタンプで
        // TodoData を新たに生成する
        todo := TodoData{
            Text: data.TodoText,
            Date: strconv.FormatInt(time.Now().Unix(), 10),
        }

        // data.Todos 配列に新たに生成した TodoData を追加する
        // (ee.Lock() でロック処理を呼んでいないと ↓ の代入処理でエラーが発生する)
        data.Todos = append(data.Todos, todo)
    }()
}

</script>
```

これで Todo アプリの完成です:bangbang: 再度 http://127.0.0.1:8844 にアクセスしてみます :arrow_down: 

![demo4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/86070/9f68c510-f1ff-1e27-c60e-5a67a0f7dbac.gif)

無事 Todo を複数登録することに成功しました :clap: :clap: 

#おわりに

今回は Vugu 入門のための Todo アプリの作り方をご紹介しました。

今後は WebAssembly の利点をキチンと活かせるアプリとして、
JavaScript では実行するのに重い処理を go get したライブラリで実行したり、
高 FPS で計算処理走らせて WebGL で描画するアプリとか作ってみたいなと考えてます。

技術書典7 で購入した本はまだまだあるので、
引き続きインプットもアウトプットも頑張るぞい :bangbang: :fire: 

#参考リンク
- [Try WebAssembly!!](https://techbookfest.org/event/tbf07/circle/6013777778573312)
- [What is Vugu?](https://www.vugu.org/doc)