WebAssemblyの勉強、GolangでTodoListを作成してみた

# 参考

- [サクッと Go → WebAssembly を試す - Qiita](https://qiita.com/cia_rana/items/bbb4112b480636ab9d87)
- [GolangでWebAssemblyを触る -Qiita](https://qiita.com/t29wuQ/items/eacd113ee25bc46b3bd0)
- [[Go WebAssembly] First Wasm Program - Hello World](https://siongui.github.io/2018/07/30/golang-wasm-hello-world/)
- [Go WebAssembly Tutorial - Building a Calculator Tutorial | TutorialEdge.net](https://tutorialedge.net/golang/go-webassembly-tutorial/)
- [go/misc/wasm at master · golang/go](https://github.com/golang/go/tree/master/misc/wasm)
- [syscall/js: build constraints exclude all Go files in C:\Go\src\syscall\js · Issue #26598 · golang/go](https://github.com/golang/go/issues/26598)
- [How do you serve a static html file using a go web server? - Stack Overflow](https://stackoverflow.com/questions/26559557/how-do-you-serve-a-static-html-file-using-a-go-web-server)
- [Using Go for WebAssembly applications - JAXenter](https://jaxenter.com/using-go-webassembly-applications-147272.html)
- [Go 1.11: WebAssembly for the gophers – Zenika](https://medium.zenika.com/go-1-11-webassembly-for-the-gophers-ae4bb8b1ee03)
- [templateタグを使う - Qiita](https://qiita.com/keiskimu/items/649fd3de770ba88802e3)
- [go - print readable variables with golang - Stack Overflow](https://stackoverflow.com/questions/26045106/print-readable-variables-with-golang)

# バージョン

- macOS Mojave Version 10.14.3
- go version go1.11.5 darwin/amd64

# ソースコード

[最終的に作ったコードはこちらです](https://github.com/okumurakengo/00007_golang_wasm_todolist)

# Hello World

```go:main.go
package main

import "fmt"

func main() {
    fmt.Println("Hello wasm!")
}
```

```bash:bash
GOARCH=wasm GOOS=js go build -o test.wasm main.go
```

wasmの動作サンプルがgoのインストールディレクトリにあったのでそれを使いました

```bash:bash
sudo cp /usr/local/go/misc/wasm/wasm_exec.html .
sudo cp /usr/local/go/misc/wasm/wasm_exec.js .
sudo chmod 666 wasm_exec.html wasm_exec.js
```

簡単にサーバーを作成

```go:server.go
package main

import (
    "net/http"
)

func main() {
    http.Handle("/", http.FileServer(http.Dir("./")))
    http.ListenAndServe(":8080", nil)
}
```

```bash:bash
go run server.go 
```

http://localhost:8080/wasm_exec.html にアクセスし、Runボタンを押すとgoでWebAssemblyを動かせたのを確認できました

![image.png](https://qiita-image-store.s3.amazonaws.com/0/142910/3fc51b98-94d6-4e2d-2d2d-1c0a742e0926.png)

このサンプルの実行環境を元に、TodoListを作っていこうと思いますm(_ _)m

# TodoList作成

```html:wasm_exec.html
<!doctype html>
<html>

<head>
	<meta charset="utf-8">
	<title>Go wasm</title>
</head>
<body>
	<form action="" method="post" id="add">
		<input type="text" id="addText">
		<input type="submit" value="submit" id="">
	</form>

	<ul id="todos"></ul>

	<template id="todo">
		<li>
			<input type="checkbox" name="" class="todoCheck">
			<span class="todoBody">予定ホゲホゲ</span>
			<a href="#" class="todoDelete">delete</a>
		</li>
	</template>

	<script src="wasm_exec.js"></script>
	<script>
		const go = new Go();
		let mod, inst;
		WebAssembly.instantiateStreaming(fetch("test.wasm"), go.importObject).then(async (result) => {
			mod = result.module;
			inst = result.instance;
			await go.run(inst);
		});
	</script>
</body>
</html>
```

```go:main.go
package main

import (
	"syscall/js"
)

func load() {
	document := js.Global().Get("document")
	templateElement := document.Call("getElementById", "todo").Get("content")
	
	document.
		Call("getElementById", "add").
		Call("addEventListener", "submit", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
			if(document.Call("getElementById", "addText").Get("value").String() == "") {
				return
			}

			// 新しいtodo用のDOMを作成
			cloneElement := templateElement.Call("cloneNode", true)

			// チェックボックスのイベント設定
			cloneElement.
				Call("querySelector", ".todoCheck").
				Call("addEventListener", "change", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
					e.Get("target").
						Get("parentNode").
						Call("querySelector", ".todoBody").
						Get("style").
						Set("text-decoration", map[bool]string{true: "line-through", false: "none"}[e.Get("target").Get("checked").Bool()])
				}))
		
			// todoの内容を設定
			cloneElement.
				Call("querySelector", ".todoBody").
				Set("textContent", document.Call("getElementById", "addText").Get("value").String())

			// 削除イベントを設定
			cloneElement.
				Call("querySelector", ".todoDelete").
				Call("addEventListener", "click", js.NewEventCallback(js.PreventDefault, func(e js.Value) {
					if(js.Global().Get("confirm").Invoke("削除しますか?").Bool()) {
						e.Get("target").
							Get("parentNode").
							Call("remove")
					}
				}))

			// 新しいtodoをhtmlに表示
			document.Call("getElementById", "todos").Call("appendChild", cloneElement)

			// 入力ボックスを空にしておく
			document.Call("getElementById", "addText").Set("value", "")
		}))
}

func main() {
	c := make(chan struct{}, 0)

	println("WASM Go Initialized")
	load()
	<-c
}
```

![pY6F3POETq.gif](https://qiita-image-store.s3.amazonaws.com/0/142910/205f3a40-11ed-b39f-ada3-5ea2f7ccf1cc.gif)


なんとかtodolistを作るまでできました。

最後まで見ていただいてありがとうございましたm(_ _)m