├── .gitignore
├── wasm
├── .gitignore
├── 002-querySelector
│ ├── demo
│ │ ├── wasm_exec.js
│ │ ├── query.wasm
│ │ └── index.html
│ ├── query.go
│ └── index.html
├── 003-querySelectorAll
│ ├── demo
│ │ ├── wasm_exec.js
│ │ ├── query.wasm
│ │ └── index.html
│ ├── query.go
│ └── index.html
├── 004-addEventListener
│ ├── demo
│ │ ├── wasm_exec.js
│ │ ├── click.wasm
│ │ └── index.html
│ ├── click.go
│ └── index.html
├── 005-xmlhttprequest-xhr
│ ├── demo
│ │ ├── wasm_exec.js
│ │ ├── xhr.wasm
│ │ └── index.html
│ ├── xhr.go
│ └── index.html
├── 001-hello-world
│ ├── demo
│ │ ├── alert.wasm
│ │ ├── index.html
│ │ └── wasm_exec.js
│ ├── hello.go
│ └── index.html
├── Makefile
└── README.rst
├── 002-javascript-new-keyword
├── app.js
├── appdom.go
├── app.go
└── index.html
├── 025-tab-url-chrome-extension
├── popup.html
├── popup.js
├── manifest.json
└── popup.go
├── 012-setTimeout
├── app.js
├── app.go
└── index.html
├── 013-navigator-language
├── app.js
├── appdom.go
├── index.html
└── app.go
├── 011-element-classlist
├── app.js
├── appdom.go
├── app.go
└── index.html
├── 022-toggle-element-gopherjs-vue
├── app.js
├── index.html
└── app.go
├── 005-addEventListener
├── app.js
├── appdom.go
├── index.html
└── app.go
├── 001-hello-world
├── appdom.go
├── app.go
└── index.html
├── 010-element-style
├── app.js
├── index.html
├── appdom.go
└── app.go
├── 007-create-append-element
├── app.js
├── index.html
├── appdom.go
└── app.go
├── 016-show-css-loader
├── app.js
├── appdom.go
├── app.go
└── index.html
├── 014-javascript-null-test
├── app.js
├── index.html
└── app.go
├── 015-javascript-undefined-test
├── app.js
├── index.html
└── app.go
├── 009-html-data-attribute
├── app.js
├── index.html
├── app.go
└── appdom.go
├── 008-xmlhttprequest-xhr
├── index.html
├── app.js
├── app.go
└── appnethttp.go
├── 004-innerHTML-textContent
├── index.html
├── appdom.go
└── app.go
├── 023-keyboard-event-keyCode-gopherjs-vue
├── app.js
├── index.html
└── app.go
├── 006-keyboard-event
├── index.html
├── app.js
├── appdom.go
└── app.go
├── 018-tooltip
├── style.css
├── index.html
├── app.js
└── appdom.go
├── 019-tooltip-with-close-delay
├── style.css
├── index.html
├── app.js
└── appdom.go
├── 029-input-text-integer-check
├── index.html
└── appdom.go
├── 003-querySelector-querySelectorAll-getElementById
├── index.html
├── app.js
├── appdom.go
└── app.go
├── 030-sieve-of-eratosthenes-gopherjs-vue
├── index.html
└── app.go
├── 028-lemoine-conjecture
├── index.html
├── lemoine.go
└── appdom.go
├── 031-watch-data-change-gopherjs-vue
├── index.html
└── app.go
├── 032-watch-data-change-with-option-gopherjs-vue
├── index.html
└── app.go
├── 017-element-position
├── app.js
├── index.html
├── appdom.go
└── app.go
├── 020-convert-text-to-link
├── app.js
├── index.html
└── appdom.go
├── server.go
├── 026-bulma-modal
├── index.html
├── appdom.go
└── app.js
├── 024-bulma-dropdown
├── appdom.go
├── app.js
└── index.html
├── README.rst
├── 027-virtula-keypad-gopherjs-vue
├── app.go
└── index.html
├── UNLICENSE
├── 021-bulma-navbar
├── appdom.go
├── app.js
└── index.html
└── Makefile
/.gitignore:
--------------------------------------------------------------------------------
1 | *.js
2 | *.js.map
3 |
--------------------------------------------------------------------------------
/wasm/.gitignore:
--------------------------------------------------------------------------------
1 | pkg/
2 | src/
3 |
--------------------------------------------------------------------------------
/002-javascript-new-keyword/app.js:
--------------------------------------------------------------------------------
1 | var d = new Date();
2 | console.log(d);
3 |
--------------------------------------------------------------------------------
/025-tab-url-chrome-extension/popup.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/wasm/002-querySelector/demo/wasm_exec.js:
--------------------------------------------------------------------------------
1 | ../../001-hello-world/demo/wasm_exec.js
--------------------------------------------------------------------------------
/wasm/003-querySelectorAll/demo/wasm_exec.js:
--------------------------------------------------------------------------------
1 | ../../001-hello-world/demo/wasm_exec.js
--------------------------------------------------------------------------------
/wasm/004-addEventListener/demo/wasm_exec.js:
--------------------------------------------------------------------------------
1 | ../../001-hello-world/demo/wasm_exec.js
--------------------------------------------------------------------------------
/wasm/005-xmlhttprequest-xhr/demo/wasm_exec.js:
--------------------------------------------------------------------------------
1 | ../../001-hello-world/demo/wasm_exec.js
--------------------------------------------------------------------------------
/012-setTimeout/app.js:
--------------------------------------------------------------------------------
1 | setTimeout(function() {
2 | console.log("3 seconds timeout");
3 | }, 3000);
4 |
--------------------------------------------------------------------------------
/013-navigator-language/app.js:
--------------------------------------------------------------------------------
1 | console.log(navigator.language);
2 | console.log(navigator.languages);
3 |
--------------------------------------------------------------------------------
/011-element-classlist/app.js:
--------------------------------------------------------------------------------
1 | var f = document.querySelector("#foo");
2 |
3 | f.classList.add("invisible");
4 |
--------------------------------------------------------------------------------
/wasm/001-hello-world/demo/alert.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siongui/frontend-programming-in-go/HEAD/wasm/001-hello-world/demo/alert.wasm
--------------------------------------------------------------------------------
/022-toggle-element-gopherjs-vue/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | new Vue({
4 | el: '#app',
5 | data: {
6 | isShow: false
7 | }
8 | })
9 |
--------------------------------------------------------------------------------
/wasm/002-querySelector/demo/query.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siongui/frontend-programming-in-go/HEAD/wasm/002-querySelector/demo/query.wasm
--------------------------------------------------------------------------------
/wasm/003-querySelectorAll/demo/query.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siongui/frontend-programming-in-go/HEAD/wasm/003-querySelectorAll/demo/query.wasm
--------------------------------------------------------------------------------
/wasm/004-addEventListener/demo/click.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siongui/frontend-programming-in-go/HEAD/wasm/004-addEventListener/demo/click.wasm
--------------------------------------------------------------------------------
/wasm/005-xmlhttprequest-xhr/demo/xhr.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/siongui/frontend-programming-in-go/HEAD/wasm/005-xmlhttprequest-xhr/demo/xhr.wasm
--------------------------------------------------------------------------------
/005-addEventListener/app.js:
--------------------------------------------------------------------------------
1 | var f = document.querySelector("#foo");
2 |
3 | f.addEventListener("click", function(e) {
4 | f.textContent = "I am clicked";
5 | })
6 |
--------------------------------------------------------------------------------
/001-hello-world/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | Window.Alert("Hello World")
9 | }
10 |
--------------------------------------------------------------------------------
/wasm/001-hello-world/hello.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom/wasm"
5 | )
6 |
7 | func main() {
8 | Window.Alert("hello world!")
9 | }
10 |
--------------------------------------------------------------------------------
/001-hello-world/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | )
6 |
7 | func main() {
8 | js.Global.Call("alert", "Hello World")
9 | }
10 |
--------------------------------------------------------------------------------
/025-tab-url-chrome-extension/popup.js:
--------------------------------------------------------------------------------
1 | chrome.tabs.query({'active': true, 'currentWindow': true}, function (tabs) {
2 | var tab = tabs[0];
3 | document.write(tab.url);
4 | });
5 |
--------------------------------------------------------------------------------
/002-javascript-new-keyword/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | d := Window.Get("Date").New()
9 | println(d)
10 | }
11 |
--------------------------------------------------------------------------------
/010-element-style/app.js:
--------------------------------------------------------------------------------
1 | var f = document.querySelector("#foo");
2 |
3 | // set the color of element
4 | f.style.color = "red";
5 |
6 | // get the color of element
7 | console.log(f.style.color);
8 |
--------------------------------------------------------------------------------
/012-setTimeout/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | func main() {
8 | time.AfterFunc(3*time.Second, func() {
9 | println("3 seconds timeout")
10 | })
11 | }
12 |
--------------------------------------------------------------------------------
/002-javascript-new-keyword/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | )
6 |
7 | func main() {
8 | d := js.Global.Get("Date").New()
9 | println(d)
10 | }
11 |
--------------------------------------------------------------------------------
/007-create-append-element/app.js:
--------------------------------------------------------------------------------
1 | var f = document.querySelector("#foo");
2 |
3 | var s = document.createElement("span");
4 | f.appendChild(s);
5 |
6 | var t = document.createTextNode("Hello World");
7 | s.appendChild(t);
8 |
--------------------------------------------------------------------------------
/016-show-css-loader/app.js:
--------------------------------------------------------------------------------
1 | window.addEventListener("load", function(event) {
2 | var s = document.querySelector(".loader");
3 | s.classList.add("invisible");
4 | console.log("All resources finished loading!");
5 | });
6 |
--------------------------------------------------------------------------------
/011-element-classlist/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | f := Document.QuerySelector("#foo")
9 |
10 | f.ClassList().Add("invisible")
11 | }
12 |
--------------------------------------------------------------------------------
/012-setTimeout/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | setTimeout in Go
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/014-javascript-null-test/app.js:
--------------------------------------------------------------------------------
1 | var f = document.querySelector("#foo");
2 |
3 | if (f === null) {
4 | console.log("querySelector #foo returns null");
5 | } else {
6 | console.log("querySelector #foo returns element");
7 | }
8 |
--------------------------------------------------------------------------------
/015-javascript-undefined-test/app.js:
--------------------------------------------------------------------------------
1 | if (window.localStorage === undefined) {
2 | console.log("Your browser does not support localStorage API");
3 | } else {
4 | console.log("Your browser supports localStorage API");
5 | }
6 |
--------------------------------------------------------------------------------
/009-html-data-attribute/app.js:
--------------------------------------------------------------------------------
1 | var f = document.querySelector("#foo");
2 |
3 | // get value
4 | console.log(f.dataset.demoValue);
5 |
6 | // set value
7 | f.dataset.demoValue = "world hello";
8 | console.log(f.dataset.demoValue);
9 |
--------------------------------------------------------------------------------
/013-navigator-language/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | println(Window.Navigator().Language())
9 | println(Window.Navigator().Languages())
10 | }
11 |
--------------------------------------------------------------------------------
/wasm/002-querySelector/query.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom/wasm"
5 | )
6 |
7 | func main() {
8 | testdiv := Document.QuerySelector("#testdiv")
9 | testdiv.Set("innerHTML", "hi")
10 | }
11 |
--------------------------------------------------------------------------------
/001-hello-world/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Hello World - First GopherJS App
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/008-xmlhttprequest-xhr/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | XMLHttpRequest (XHR) in Go
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/002-javascript-new-keyword/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JavaScript new Keyword in Go
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/014-javascript-null-test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JavaScript null Test in Go
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/013-navigator-language/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Detect Browser Language Preference in Go
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/015-javascript-undefined-test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JavaScript undefined Test in Go
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/011-element-classlist/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | )
6 |
7 | func main() {
8 | f := js.Global.Get("document").Call("querySelector", "#foo")
9 |
10 | f.Get("classList").Call("add", "invisible")
11 | }
12 |
--------------------------------------------------------------------------------
/004-innerHTML-textContent/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | innerHTML & textContent
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/010-element-style/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Element Style in Go
6 |
7 |
8 | Hello World
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/013-navigator-language/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | )
6 |
7 | func main() {
8 | println(js.Global.Get("navigator").Get("language").String())
9 | println(js.Global.Get("navigator").Get("languages").String())
10 | }
11 |
--------------------------------------------------------------------------------
/005-addEventListener/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | f := Document.QuerySelector("#foo")
9 |
10 | f.AddEventListener("click", func(e Event) {
11 | f.SetTextContent("I am clicked")
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/007-create-append-element/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Create and Append Element in Go
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/023-keyboard-event-keyCode-gopherjs-vue/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | new Vue({
4 | el: '#vueapp',
5 | data: {
6 | keypressed: ""
7 | },
8 | methods: {
9 | ShowKeyCode: function (event) {
10 | this.keypressed = event.keyCode;
11 | }
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/005-addEventListener/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Event Binding - addEventListener
6 |
7 |
8 | Click Here
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/006-keyboard-event/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Keyboard Event - Arrow Keys
6 |
7 |
8 | Press any arrow key
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/wasm/003-querySelectorAll/query.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom/wasm"
5 | )
6 |
7 | func main() {
8 | testdivs := Document.QuerySelectorAll("#testdivs > div")
9 | for _, testdiv := range testdivs {
10 | testdiv.Set("innerHTML", "hi")
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/008-xmlhttprequest-xhr/app.js:
--------------------------------------------------------------------------------
1 | var URL = "https://siongui.github.io/xemaauj9k5qn34x88m4h/sacca.json";
2 |
3 | function GetJSON() {
4 | console.log(this.responseText);
5 | }
6 |
7 | var req = new XMLHttpRequest();
8 | req.addEventListener("load", GetJSON);
9 | req.open("GET", URL);
10 | req.send();
11 |
--------------------------------------------------------------------------------
/009-html-data-attribute/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Access HTML data Attribute in Go
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/010-element-style/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | f := Document.QuerySelector("#foo")
9 |
10 | // set the color of element
11 | f.Style().SetColor("red")
12 |
13 | // get the color of element
14 | println(f.Style().Color())
15 | }
16 |
--------------------------------------------------------------------------------
/005-addEventListener/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | )
6 |
7 | func main() {
8 | f := js.Global.Get("document").Call("querySelector", "#foo")
9 |
10 | f.Call("addEventListener", "click", func(event *js.Object) {
11 | f.Set("textContent", "I am clicked")
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/016-show-css-loader/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | Window.Call("addEventListener", "load", func(e Event) {
9 | s := Document.QuerySelector(".loader")
10 | s.ClassList().Add("invisible")
11 | println("All resources finished loading!")
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/004-innerHTML-textContent/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | f := Document.QuerySelector("#foo")
9 | f.SetInnerHTML("Hello World")
10 | println(f.InnerHTML())
11 |
12 | f.SetTextContent("Hello World2")
13 |
14 | println(f.TextContent())
15 | }
16 |
--------------------------------------------------------------------------------
/011-element-classlist/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Element classList in Go
6 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/031-watch-data-change-gopherjs-vue/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | "github.com/oskca/gopherjs-vue"
6 | )
7 |
8 | type Model struct {
9 | *js.Object // this is needed for bidirectional data bindings
10 | UserInput string `js:"userinput"`
11 | OldValue string `js:"oldvalue"`
12 | NewValue string `js:"newvalue"`
13 | }
14 |
15 | func main() {
16 | m := &Model{
17 | Object: js.Global.Get("Object").New(),
18 | }
19 | // field assignment is required in this way to make data passing works
20 | m.UserInput = ""
21 | m.OldValue = ""
22 | m.NewValue = ""
23 |
24 | // create the VueJS viewModel using a struct pointer
25 | app := vue.New("#vueapp", m)
26 |
27 | app.Call("$watch", "userinput", func(newVal, oldVal string) {
28 | m.OldValue = oldVal
29 | m.NewValue = newVal
30 | })
31 | }
32 |
--------------------------------------------------------------------------------
/026-bulma-modal/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Bulma modal with Go toggle
7 |
8 |
9 |
10 |
11 | Launch image modal
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/024-bulma-dropdown/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | Document.AddEventListener("DOMContentLoaded", func(e Event) {
9 |
10 | // Dropdowns
11 | dds := Document.QuerySelectorAll(".dropdown:not(.is-hoverable)")
12 |
13 | closeDropdowns := func() {
14 | for _, dd := range dds {
15 | dd.ClassList().Remove("is-active")
16 | }
17 | }
18 |
19 | if len(dds) > 0 {
20 | for _, dd := range dds {
21 | dd.AddEventListener("click", func(e Event) {
22 | e.StopPropagation()
23 | dd.ClassList().Toggle("is-active")
24 | })
25 | }
26 |
27 | Document.AddEventListener("click", func(e Event) {
28 | closeDropdowns()
29 | })
30 | }
31 |
32 | // Close dropdowns if ESC pressed
33 | Document.AddEventListener("keydown", func(e Event) {
34 | if e.KeyCode() == 27 {
35 | closeDropdowns()
36 | }
37 | })
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/wasm/Makefile:
--------------------------------------------------------------------------------
1 | # cannot use relative path in GOROOT, otherwise 6g not found. For example,
2 | # export GOROOT=../go (=> 6g not found)
3 | # it is also not allowed to use relative path in GOPATH
4 | export GOROOT=$(realpath ../../go)
5 | export GOPATH=$(realpath .)
6 | export PATH := $(GOROOT)/bin:$(GOPATH)/bin:$(PATH)
7 |
8 |
9 | DIR=005-xmlhttprequest-xhr
10 | MODULE=xhr.wasm
11 |
12 | server: build
13 | @echo "\033[92mRunning dev server...\033[0m"
14 | go run ../server.go -dir=${DIR}/demo
15 |
16 | build: fmt
17 | @echo "\033[92mBuild wasm module...\033[0m"
18 | GOARCH=wasm GOOS=js go build -o ${DIR}/demo/${MODULE} ${DIR}/*.go
19 | cp ${DIR}/index.html ${DIR}/demo/
20 | cd ${DIR}/demo/ ; ln -sf ../../001-hello-world/demo/wasm_exec.js wasm_exec.js
21 |
22 | fmt:
23 | @echo "\033[92mGo fmt source code...\033[0m"
24 | @go fmt ${DIR}/*.go
25 |
26 | install:
27 | GOARCH=wasm GOOS=js go get -u github.com/siongui/godom/wasm
28 |
--------------------------------------------------------------------------------
/032-watch-data-change-with-option-gopherjs-vue/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | "github.com/oskca/gopherjs-vue"
6 | )
7 |
8 | type Model struct {
9 | *js.Object // this is needed for bidirectional data bindings
10 | UserInput string `js:"userinput"`
11 | OldValue string `js:"oldvalue"`
12 | NewValue string `js:"newvalue"`
13 | }
14 |
15 | func main() {
16 | m := &Model{
17 | Object: js.Global.Get("Object").New(),
18 | }
19 | // field assignment is required in this way to make data passing works
20 | m.UserInput = ""
21 | m.OldValue = ""
22 | m.NewValue = ""
23 |
24 | // create the VueJS viewModel using a struct pointer
25 | app := vue.New("#vueapp", m)
26 |
27 | option := js.Global.Get("Object").New()
28 | option.Set("immediate", true)
29 | app.Call("$watch", "userinput", func(newVal, oldVal string) {
30 | m.OldValue = oldVal
31 | m.NewValue = newVal
32 | }, option)
33 | }
34 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | ==========================
2 | Frontend Programming in Go
3 | ==========================
4 |
5 | .. image:: https://travis-ci.org/siongui/frontend-programming-in-go.png?branch=master
6 | :target: https://travis-ci.org/siongui/frontend-programming-in-go
7 |
8 | The sub-directories in root directory, except wasm_ sub-dir, contains examples
9 | for GopherJS_. For examples for Go WebAssembly_, visit wasm_ sub-dir.
10 |
11 | Development Environment:
12 |
13 | - `Ubuntu 18.04`_
14 | - Go_ (Go 1.11 or later for Go WebAssembly)
15 | - GopherJS_
16 |
17 |
18 | UNLICENSE
19 | +++++++++
20 |
21 | Released in public domain (except `Go WebAssembly JavaScript loader`_, which is
22 | released in BSD-style license). See UNLICENSE_.
23 |
24 |
25 | .. _Ubuntu 18.04: http://releases.ubuntu.com/18.04/
26 | .. _Go: https://golang.org/dl/
27 | .. _GopherJS: http://www.gopherjs.org/
28 | .. _Go WebAssembly JavaScript loader: https://github.com/golang/go/blob/master/misc/wasm/wasm_exec.js
29 | .. _WebAssembly: https://duckduckgo.com/?q=webassembly
30 | .. _wasm: wasm
31 |
--------------------------------------------------------------------------------
/027-virtula-keypad-gopherjs-vue/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "github.com/gopherjs/gopherjs/js"
5 | "github.com/oskca/gopherjs-vue"
6 | )
7 |
8 | type Model struct {
9 | *js.Object // this is needed for bidirectional data bindings
10 | PaliWord string `js:"paliWord"`
11 | Row1letters []string `js:"row1letters"`
12 | Row2letters []string `js:"row2letters"`
13 | Row3letters []string `js:"row3letters"`
14 | Row4letters []string `js:"row4letters"`
15 | }
16 |
17 | func main() {
18 | m := &Model{
19 | Object: js.Global.Get("Object").New(),
20 | }
21 | // field assignment is required in this way to make data passing works
22 | m.PaliWord = ""
23 | m.Row1letters = []string{"q", "w", "e", "r", "t", "y", "u", "i", "o", "p"}
24 | m.Row2letters = []string{"a", "s", "d", "f", "g", "h", "j", "k", "l"}
25 | m.Row3letters = []string{"z", "x", "c", "v", "b", "n", "m"}
26 | m.Row4letters = []string{"ā", "ḍ", "ī", "ḷ", "ṁ", "ṃ", "ñ", "ṇ", "ṭ", "ū", "ŋ", "ṅ"}
27 |
28 | // create the VueJS viewModel using a struct pointer
29 | vue.New("#container", m)
30 | }
31 |
--------------------------------------------------------------------------------
/024-bulma-dropdown/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | document.addEventListener('DOMContentLoaded', function () {
4 |
5 | // Dropdowns
6 |
7 | var $dropdowns = getAll('.dropdown:not(.is-hoverable)');
8 |
9 | if ($dropdowns.length > 0) {
10 | $dropdowns.forEach(function ($el) {
11 | $el.addEventListener('click', function (event) {
12 | event.stopPropagation();
13 | $el.classList.toggle('is-active');
14 | });
15 | });
16 |
17 | document.addEventListener('click', function (event) {
18 | closeDropdowns();
19 | });
20 | }
21 |
22 | function closeDropdowns() {
23 | $dropdowns.forEach(function ($el) {
24 | $el.classList.remove('is-active');
25 | });
26 | }
27 |
28 | // Close dropdowns if ESC pressed
29 | document.addEventListener('keydown', function (event) {
30 | var e = event || window.event;
31 | if (e.keyCode === 27) {
32 | closeDropdowns();
33 | }
34 | });
35 |
36 | // Functions
37 |
38 | function getAll(selector) {
39 | return Array.prototype.slice.call(document.querySelectorAll(selector), 0);
40 | }
41 | });
42 |
--------------------------------------------------------------------------------
/027-virtula-keypad-gopherjs-vue/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Virtual Keyboard via gopherjs-vue
6 |
21 |
22 |
23 |
24 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/018-tooltip/app.js:
--------------------------------------------------------------------------------
1 | // create and append invisible tooltip to DOM tree
2 | var tooltip = document.createElement("div");
3 | tooltip.classList.add("tooltip");
4 | tooltip.classList.add("invisible");
5 | document.querySelector("body").appendChild(tooltip);
6 |
7 | // event handler for mouseenter event of elements with data-descr attribute
8 | function ShowTooltip(e) {
9 | var elm = e.target;
10 | tooltip.textContent = elm.dataset.descr;
11 | tooltip.style.left = elm.getBoundingClientRect().left + window.pageXOffset + 'px';
12 | tooltip.style.top = (elm.getBoundingClientRect().top + window.pageYOffset + elm.offsetHeight + 5) + 'px';
13 | tooltip.classList.remove("invisible");
14 | }
15 |
16 | // event handler for mouseleave event of elements with data-descr attribute
17 | function HideTooltip(e) {
18 | tooltip.classList.add("invisible");
19 | }
20 |
21 | // select all elements with data-descr attribute and attach mouseenter and mouseleave event handler
22 | var els = document.querySelectorAll("*[data-descr]");
23 | for (var i = 0; i < els.length; ++i) {
24 | var el = els[i];
25 | el.addEventListener("mouseenter", ShowTooltip);
26 | el.addEventListener("mouseleave", HideTooltip);
27 | }
28 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/021-bulma-navbar/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func SetupNavbarBurgers() {
8 | nbs := Document.QuerySelectorAll(".navbar-burger")
9 |
10 | for _, nb := range nbs {
11 | nb.AddEventListener("click", func(e Event) {
12 | targetId := nb.Dataset().Get("target").String()
13 | target := Document.GetElementById(targetId)
14 |
15 | nb.ClassList().Toggle("is-active")
16 | target.ClassList().Toggle("is-active")
17 | })
18 | }
19 | }
20 |
21 | func main() {
22 | Document.AddEventListener("DOMContentLoaded", func(e Event) {
23 |
24 | // Dropdowns in navbar
25 | dds := Document.QuerySelectorAll(".navbar-item.has-dropdown:not(.is-hoverable)")
26 |
27 | closeDropdowns := func() {
28 | for _, dd := range dds {
29 | dd.ClassList().Remove("is-active")
30 | }
31 | }
32 |
33 | if len(dds) > 0 {
34 | for _, dd := range dds {
35 | dd.AddEventListener("click", func(e Event) {
36 | e.StopPropagation()
37 | dd.ClassList().Toggle("is-active")
38 | })
39 | }
40 |
41 | Document.AddEventListener("click", func(e Event) {
42 | closeDropdowns()
43 | })
44 | }
45 |
46 | // Close dropdowns if ESC pressed
47 | Document.AddEventListener("keydown", func(e Event) {
48 | if e.KeyCode() == 27 {
49 | closeDropdowns()
50 | }
51 | })
52 |
53 | // Toggles
54 | SetupNavbarBurgers()
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/wasm/README.rst:
--------------------------------------------------------------------------------
1 | ======================================
2 | Frontend Programming in Go WebAssembly
3 | ======================================
4 |
5 | This directory contains examples for Go WebAssembly_.
6 | For GopherJS_ examples, visit `root directory`_.
7 |
8 | Development Environment:
9 |
10 | - `Ubuntu 18.04`_
11 | - Go_ (Go 1.11 or later for Go WebAssembly)
12 |
13 |
14 | Install
15 | +++++++
16 |
17 | .. code-block:: bash
18 |
19 | $ GOARCH=wasm GOOS=js go get -u github.com/siongui/godom/wasm
20 |
21 | Examples:
22 |
23 | - Hello World (`demo `__)
24 | - querySelector (`demo `__)
25 | - querySelectorAll (`demo `__)
26 | - addEventListener (`demo `__)
27 | - XMLHttpRequest (XHR) (`demo `__)
28 |
29 | .. _Ubuntu 18.04: http://releases.ubuntu.com/18.04/
30 | .. _Go: https://golang.org/dl/
31 | .. _GopherJS: http://www.gopherjs.org/
32 | .. _WebAssembly: https://duckduckgo.com/?q=webassembly
33 | .. _root directory: https://github.com/siongui/frontend-programming-in-go
34 |
--------------------------------------------------------------------------------
/024-bulma-dropdown/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Bulma dropdown with Go toggle
7 |
8 |
9 |
10 |
11 |
12 |
13 |
19 |
20 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/026-bulma-modal/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | )
6 |
7 | func main() {
8 | Document.AddEventListener("DOMContentLoaded", func(e Event) {
9 |
10 | // Modals
11 |
12 | rootEl := Document.Get("documentElement")
13 | modals := Document.QuerySelectorAll(".modal")
14 | modalButtons := Document.QuerySelectorAll(".modal-button")
15 | modalCloses := Document.QuerySelectorAll(".modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button")
16 |
17 | closeModals := func() {
18 | rootEl.Get("classList").Call("remove", "is-clipped")
19 | for _, modal := range modals {
20 | modal.ClassList().Remove("is-active")
21 | }
22 | }
23 |
24 | if len(modalButtons) > 0 {
25 | for _, modalButton := range modalButtons {
26 | modalButton.AddEventListener("click", func(e Event) {
27 | targetId := modalButton.Dataset().Get("target").String()
28 | target := Document.GetElementById(targetId)
29 | rootEl.Get("classList").Call("add", "is-clipped")
30 | target.ClassList().Add("is-active")
31 | })
32 | }
33 | }
34 |
35 | if len(modalCloses) > 0 {
36 | for _, modalClose := range modalCloses {
37 | modalClose.AddEventListener("click", func(e Event) {
38 | closeModals()
39 | })
40 | }
41 | }
42 |
43 | // Close modals if ESC pressed
44 | Document.AddEventListener("keydown", func(e Event) {
45 | if e.KeyCode() == 27 {
46 | closeModals()
47 | }
48 | })
49 | })
50 | }
51 |
--------------------------------------------------------------------------------
/026-bulma-modal/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | document.addEventListener('DOMContentLoaded', function () {
4 |
5 | // Modals
6 |
7 | var rootEl = document.documentElement;
8 | var $modals = getAll('.modal');
9 | var $modalButtons = getAll('.modal-button');
10 | var $modalCloses = getAll('.modal-background, .modal-close, .modal-card-head .delete, .modal-card-foot .button');
11 |
12 | if ($modalButtons.length > 0) {
13 | $modalButtons.forEach(function ($el) {
14 | $el.addEventListener('click', function () {
15 | var target = $el.dataset.target;
16 | var $target = document.getElementById(target);
17 | rootEl.classList.add('is-clipped');
18 | $target.classList.add('is-active');
19 | });
20 | });
21 | }
22 |
23 | if ($modalCloses.length > 0) {
24 | $modalCloses.forEach(function ($el) {
25 | $el.addEventListener('click', function () {
26 | closeModals();
27 | });
28 | });
29 | }
30 |
31 | document.addEventListener('keydown', function (event) {
32 | var e = event || window.event;
33 | if (e.keyCode === 27) {
34 | closeModals();
35 | }
36 | });
37 |
38 | function closeModals() {
39 | rootEl.classList.remove('is-clipped');
40 | $modals.forEach(function ($el) {
41 | $el.classList.remove('is-active');
42 | });
43 | }
44 |
45 | // Functions
46 |
47 | function getAll(selector) {
48 | return Array.prototype.slice.call(document.querySelectorAll(selector), 0);
49 | }
50 |
51 | });
52 |
--------------------------------------------------------------------------------
/021-bulma-navbar/app.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | document.addEventListener('DOMContentLoaded', function () {
4 |
5 | // Dropdowns in navbar
6 |
7 | var $dropdowns = getAll('.navbar-item.has-dropdown:not(.is-hoverable)');
8 |
9 | if ($dropdowns.length > 0) {
10 | $dropdowns.forEach(function ($el) {
11 | $el.addEventListener('click', function (event) {
12 | event.stopPropagation();
13 | $el.classList.toggle('is-active');
14 | });
15 | });
16 |
17 | document.addEventListener('click', function (event) {
18 | closeDropdowns();
19 | });
20 | }
21 |
22 | function closeDropdowns() {
23 | $dropdowns.forEach(function ($el) {
24 | $el.classList.remove('is-active');
25 | });
26 | }
27 |
28 | // Close dropdowns if ESC pressed
29 | document.addEventListener('keydown', function (event) {
30 | var e = event || window.event;
31 | if (e.keyCode === 27) {
32 | closeDropdowns();
33 | }
34 | });
35 |
36 | // Toggles
37 |
38 | var $burgers = getAll('.burger');
39 |
40 | if ($burgers.length > 0) {
41 | $burgers.forEach(function ($el) {
42 | $el.addEventListener('click', function () {
43 | var target = $el.dataset.target;
44 | var $target = document.getElementById(target);
45 | $el.classList.toggle('is-active');
46 | $target.classList.toggle('is-active');
47 | });
48 | });
49 | }
50 |
51 | // Functions
52 |
53 | function getAll(selector) {
54 | return Array.prototype.slice.call(document.querySelectorAll(selector), 0);
55 | }
56 | });
57 |
--------------------------------------------------------------------------------
/018-tooltip/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | "strconv"
6 | )
7 |
8 | var tooltip *Object
9 |
10 | func GetPosition(elm *Object) (x, y float64) {
11 | x = elm.GetBoundingClientRect().Left() + Window.PageXOffset()
12 | y = elm.GetBoundingClientRect().Top() + Window.PageYOffset()
13 | return
14 | }
15 |
16 | // Create and append invisible tooltip to DOM tree
17 | func SetupTooltip() {
18 | tooltip = Document.CreateElement("div")
19 | tooltip.ClassList().Add("tooltip")
20 | tooltip.ClassList().Add("invisible")
21 | Document.QuerySelector("body").AppendChild(tooltip)
22 | }
23 |
24 | // event handler for mouseenter event of elements with data-descr attribute
25 | func ShowTooltip(e Event) {
26 | elm := e.Target()
27 | tooltip.SetInnerHTML(elm.Dataset().Get("descr").String())
28 |
29 | x, y := GetPosition(elm)
30 | xpx := strconv.FormatFloat(x, 'E', -1, 64) + "px"
31 | ypx := strconv.FormatFloat(y+elm.Get("OffsetHeight").Float()+5, 'E', -1, 64) + "px"
32 | tooltip.Style().SetLeft(xpx)
33 | tooltip.Style().SetTop(ypx)
34 |
35 | tooltip.ClassList().Remove("invisible")
36 | }
37 |
38 | // event handler for mouseleave event of elements with data-descr attribute
39 | func HideTooltip(e Event) {
40 | tooltip.ClassList().Add("invisible")
41 | }
42 |
43 | func main() {
44 | SetupTooltip()
45 |
46 | // select all elements with data-descr attribute and
47 | // attach mouseenter and mouseleave event handler
48 | els := Document.QuerySelectorAll("*[data-descr]")
49 | for _, el := range els {
50 | el.AddEventListener("mouseenter", ShowTooltip)
51 | el.AddEventListener("mouseleave", HideTooltip)
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/019-tooltip-with-close-delay/app.js:
--------------------------------------------------------------------------------
1 | // indicate if the mouse cursor is in the tooltip
2 | var isCursorInTooltip = false;
3 |
4 | // when cursor leaves the text, the delay time to close the tooltip if the
5 | // cursor is not in the tooltip. (milisecond)
6 | var delay = 250;
7 |
8 | // create and append invisible tooltip to DOM tree
9 | var tooltip = document.createElement("div");
10 | tooltip.classList.add("tooltip");
11 | tooltip.classList.add("invisible");
12 | tooltip.addEventListener("mouseenter", function() {
13 | isCursorInTooltip = true;
14 | });
15 | tooltip.addEventListener("mouseleave", function() {
16 | isCursorInTooltip = false;
17 | tooltip.classList.add("invisible");
18 | });
19 | document.querySelector("body").appendChild(tooltip);
20 |
21 | // event handler for mouseenter event of elements with data-descr attribute
22 | function ShowTooltip(e) {
23 | var elm = e.target;
24 | tooltip.textContent = elm.dataset.descr;
25 | tooltip.style.left = elm.getBoundingClientRect().left + window.pageXOffset + 'px';
26 | tooltip.style.top = (elm.getBoundingClientRect().top + window.pageYOffset + elm.offsetHeight + 5) + 'px';
27 | tooltip.classList.remove("invisible");
28 | }
29 |
30 | // event handler for mouseleave event of elements with data-descr attribute
31 | function HideTooltip(e) {
32 | setTimeout(function() {
33 | if (!isCursorInTooltip) {
34 | tooltip.classList.add("invisible");
35 | }
36 | }, delay);
37 | }
38 |
39 | // select all elements with data-descr attribute and attach mouseenter and mouseleave event handler
40 | var els = document.querySelectorAll("*[data-descr]");
41 | for (var i = 0; i < els.length; ++i) {
42 | var el = els[i];
43 | el.addEventListener("mouseenter", ShowTooltip);
44 | el.addEventListener("mouseleave", HideTooltip);
45 | }
46 |
--------------------------------------------------------------------------------
/030-sieve-of-eratosthenes-gopherjs-vue/app.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "strconv"
5 |
6 | "github.com/gopherjs/gopherjs/js"
7 | "github.com/oskca/gopherjs-vue"
8 | )
9 |
10 | type Model struct {
11 | *js.Object // this is needed for bidirectional data bindings
12 | Intn string `js:"intn"`
13 | Result string `js:"result"`
14 | }
15 |
16 | func SieveOfEratosthenes(n int) []int {
17 | // Create a boolean array "prime[0..n]" and initialize
18 | // all entries it as true. A value in prime[i] will
19 | // finally be false if i is Not a prime, else true.
20 | integers := make([]bool, n+1)
21 | for i := 2; i < n+1; i++ {
22 | integers[i] = true
23 | }
24 |
25 | for p := 2; p*p <= n; p++ {
26 | // If integers[p] is not changed, then it is a prime
27 | if integers[p] == true {
28 | // Update all multiples of p
29 | for i := p * 2; i <= n; i += p {
30 | integers[i] = false
31 | }
32 | }
33 | }
34 |
35 | // return all prime numbers <= n
36 | var primes []int
37 | for p := 2; p <= n; p++ {
38 | if integers[p] == true {
39 | primes = append(primes, p)
40 | }
41 | }
42 |
43 | return primes
44 | }
45 |
46 | func main() {
47 | m := &Model{
48 | Object: js.Global.Get("Object").New(),
49 | }
50 | // field assignment is required in this way to make data passing works
51 | m.Intn = "4"
52 | m.Result = ""
53 |
54 | // create the VueJS viewModel using a struct pointer
55 | app := vue.New("#vueapp", m)
56 |
57 | opt := js.Global.Get("Object").New()
58 | opt.Set("immediate", true)
59 | app.Call("$watch", "intn", func(newVal, oldVal string) {
60 | n, err := strconv.Atoi(newVal)
61 | if err != nil {
62 | m.Result = "input must be integer"
63 | return
64 | }
65 | m.Result = ""
66 |
67 | primes := SieveOfEratosthenes(n)
68 | for _, prime := range primes {
69 | m.Result += (strconv.Itoa(prime) + " ")
70 | }
71 | }, opt)
72 | }
73 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # cannot use relative path in GOROOT, otherwise 6g not found. For example,
2 | # export GOROOT=../go (=> 6g not found)
3 | # it is also not allowed to use relative path in GOPATH
4 | ifndef TRAVIS
5 | export GOROOT=$(realpath ../go)
6 | export GOPATH=$(realpath ../paligo)
7 | export PATH := $(GOROOT)/bin:$(GOPATH)/bin:$(PATH)
8 | endif
9 |
10 | GO_VERSION=1.10.2
11 |
12 | WEBSITE_DIR=032-watch-data-change-with-option-gopherjs-vue
13 |
14 | default: fmt js devserver
15 | godom: fmt domjs devserver
16 | rawjs: fmt devserver
17 |
18 | devserver:
19 | @# http://stackoverflow.com/a/5947779
20 | @echo "\033[92mDevelopment Server Running ...\033[0m"
21 | @go run server.go -dir=$(WEBSITE_DIR)
22 |
23 | js:
24 | @echo "\033[92mGenerating JavaScript ... (raw GopherJS)\033[0m"
25 | @gopherjs build $(WEBSITE_DIR)/app.go -o $(WEBSITE_DIR)/app.js
26 |
27 | domjs:
28 | @echo "\033[92mGenerating JavaScript ... (godom)\033[0m"
29 | @gopherjs build $(WEBSITE_DIR)/appdom.go -o $(WEBSITE_DIR)/app.js
30 |
31 | fmt:
32 | @echo "\033[92mGo fmt source code...\033[0m"
33 | @go fmt server.go
34 | @go fmt $(WEBSITE_DIR)/*.go
35 |
36 | lib_gopherjs:
37 | @echo "\033[92mInstalling GopherJS ...\033[0m"
38 | go get -u github.com/gopherjs/gopherjs
39 |
40 | lib_godom:
41 | @echo "\033[92mInstalling godom ...\033[0m"
42 | go get -u github.com/siongui/godom
43 |
44 | clean:
45 | rm $(WEBSITE_DIR)/*.js
46 | rm $(WEBSITE_DIR)/*.js.map
47 |
48 | install_gopherjs-vue:
49 | @echo "\033[92mInstall binding for Vue.js ...\033[0m"
50 | go get -u github.com/oskca/gopherjs-vue
51 |
52 | install_chrome:
53 | @echo "\033[92mInstall binding for Chrome extension ...\033[0m"
54 | go get -u github.com/fabioberger/chrome
55 |
56 | update_ubuntu:
57 | @echo "\033[92mUpdating Ubuntu ...\033[0m"
58 | @sudo apt-get update && sudo apt-get upgrade && sudo apt-get dist-upgrade
59 |
60 | download_go:
61 | @echo "\033[92mDownloading and Installing Go ...\033[0m"
62 | @cd ../ ; wget https://storage.googleapis.com/golang/go$(GO_VERSION).linux-amd64.tar.gz
63 | @cd ../ ; tar xvzf go$(GO_VERSION).linux-amd64.tar.gz
64 |
--------------------------------------------------------------------------------
/019-tooltip-with-close-delay/appdom.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | . "github.com/siongui/godom"
5 | "strconv"
6 | "time"
7 | )
8 |
9 | var tooltip *Object
10 |
11 | // indicate if the mouse cursor is in the tooltip
12 | var isCursorInTooltip = false
13 |
14 | // when cursor leaves the text, the delay time to close the tooltip if the
15 | // cursor is not in the tooltip. (milisecond)
16 | var delay = 250 * time.Millisecond
17 |
18 | func GetPosition(elm *Object) (x, y float64) {
19 | x = elm.GetBoundingClientRect().Left() + Window.PageXOffset()
20 | y = elm.GetBoundingClientRect().Top() + Window.PageYOffset()
21 | return
22 | }
23 |
24 | // Create and append invisible tooltip to DOM tree
25 | func SetupTooltip() {
26 | tooltip = Document.CreateElement("div")
27 | tooltip.ClassList().Add("tooltip")
28 | tooltip.ClassList().Add("invisible")
29 |
30 | tooltip.AddEventListener("mouseenter", func(e Event) {
31 | isCursorInTooltip = true
32 | })
33 | tooltip.AddEventListener("mouseleave", func(e Event) {
34 | isCursorInTooltip = false
35 | tooltip.ClassList().Add("invisible")
36 | })
37 |
38 | Document.QuerySelector("body").AppendChild(tooltip)
39 | }
40 |
41 | // event handler for mouseenter event of elements with data-descr attribute
42 | func ShowTooltip(e Event) {
43 | elm := e.Target()
44 | tooltip.SetInnerHTML(elm.Dataset().Get("descr").String())
45 |
46 | x, y := GetPosition(elm)
47 | xpx := strconv.FormatFloat(x, 'E', -1, 64) + "px"
48 | ypx := strconv.FormatFloat(y+elm.Get("OffsetHeight").Float()+5, 'E', -1, 64) + "px"
49 | tooltip.Style().SetLeft(xpx)
50 | tooltip.Style().SetTop(ypx)
51 |
52 | tooltip.ClassList().Remove("invisible")
53 | }
54 |
55 | // event handler for mouseleave event of elements with data-descr attribute
56 | func HideTooltip(e Event) {
57 | time.AfterFunc(delay, func() {
58 | if !isCursorInTooltip {
59 | tooltip.ClassList().Add("invisible")
60 | }
61 | })
62 | }
63 |
64 | func main() {
65 | SetupTooltip()
66 |
67 | // select all elements with data-descr attribute and
68 | // attach mouseenter and mouseleave event handler
69 | els := Document.QuerySelectorAll("*[data-descr]")
70 | for _, el := range els {
71 | el.AddEventListener("mouseenter", ShowTooltip)
72 | el.AddEventListener("mouseleave", HideTooltip)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/021-bulma-navbar/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Bulma navbar with Go toggle
7 |
8 |
9 |
10 |
11 |
12 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/wasm/001-hello-world/demo/wasm_exec.js:
--------------------------------------------------------------------------------
1 | // Copyright 2018 The Go Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style
3 | // license that can be found in the LICENSE file.
4 |
5 | (() => {
6 | // Map web browser API and Node.js API to a single common API (preferring web standards over Node.js API).
7 | const isNodeJS = typeof process !== "undefined";
8 | if (isNodeJS) {
9 | global.require = require;
10 | global.fs = require("fs");
11 |
12 | const nodeCrypto = require("crypto");
13 | global.crypto = {
14 | getRandomValues(b) {
15 | nodeCrypto.randomFillSync(b);
16 | },
17 | };
18 |
19 | global.performance = {
20 | now() {
21 | const [sec, nsec] = process.hrtime();
22 | return sec * 1000 + nsec / 1000000;
23 | },
24 | };
25 |
26 | const util = require("util");
27 | global.TextEncoder = util.TextEncoder;
28 | global.TextDecoder = util.TextDecoder;
29 | } else {
30 | if (typeof window !== "undefined") {
31 | window.global = window;
32 | } else if (typeof self !== "undefined") {
33 | self.global = self;
34 | } else {
35 | throw new Error("cannot export Go (neither window nor self is defined)");
36 | }
37 |
38 | let outputBuf = "";
39 | global.fs = {
40 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
41 | writeSync(fd, buf) {
42 | outputBuf += decoder.decode(buf);
43 | const nl = outputBuf.lastIndexOf("\n");
44 | if (nl != -1) {
45 | console.log(outputBuf.substr(0, nl));
46 | outputBuf = outputBuf.substr(nl + 1);
47 | }
48 | return buf.length;
49 | },
50 | write(fd, buf, offset, length, position, callback) {
51 | if (offset !== 0 || length !== buf.length || position !== null) {
52 | throw new Error("not implemented");
53 | }
54 | const n = this.writeSync(fd, buf);
55 | callback(null, n);
56 | },
57 | open(path, flags, mode, callback) {
58 | const err = new Error("not implemented");
59 | err.code = "ENOSYS";
60 | callback(err);
61 | },
62 | };
63 | }
64 |
65 | const encoder = new TextEncoder("utf-8");
66 | const decoder = new TextDecoder("utf-8");
67 |
68 | global.Go = class {
69 | constructor() {
70 | this.argv = ["js"];
71 | this.env = {};
72 | this.exit = (code) => {
73 | if (code !== 0) {
74 | console.warn("exit code:", code);
75 | }
76 | };
77 | this._callbackTimeouts = new Map();
78 | this._nextCallbackTimeoutID = 1;
79 |
80 | const mem = () => {
81 | // The buffer may change when requesting more memory.
82 | return new DataView(this._inst.exports.mem.buffer);
83 | }
84 |
85 | const setInt64 = (addr, v) => {
86 | mem().setUint32(addr + 0, v, true);
87 | mem().setUint32(addr + 4, Math.floor(v / 4294967296), true);
88 | }
89 |
90 | const getInt64 = (addr) => {
91 | const low = mem().getUint32(addr + 0, true);
92 | const high = mem().getInt32(addr + 4, true);
93 | return low + high * 4294967296;
94 | }
95 |
96 | const loadValue = (addr) => {
97 | const f = mem().getFloat64(addr, true);
98 | if (!isNaN(f)) {
99 | return f;
100 | }
101 |
102 | const id = mem().getUint32(addr, true);
103 | return this._values[id];
104 | }
105 |
106 | const storeValue = (addr, v) => {
107 | const nanHead = 0x7FF80000;
108 |
109 | if (typeof v === "number") {
110 | if (isNaN(v)) {
111 | mem().setUint32(addr + 4, nanHead, true);
112 | mem().setUint32(addr, 0, true);
113 | return;
114 | }
115 | mem().setFloat64(addr, v, true);
116 | return;
117 | }
118 |
119 | switch (v) {
120 | case undefined:
121 | mem().setUint32(addr + 4, nanHead, true);
122 | mem().setUint32(addr, 1, true);
123 | return;
124 | case null:
125 | mem().setUint32(addr + 4, nanHead, true);
126 | mem().setUint32(addr, 2, true);
127 | return;
128 | case true:
129 | mem().setUint32(addr + 4, nanHead, true);
130 | mem().setUint32(addr, 3, true);
131 | return;
132 | case false:
133 | mem().setUint32(addr + 4, nanHead, true);
134 | mem().setUint32(addr, 4, true);
135 | return;
136 | }
137 |
138 | let ref = this._refs.get(v);
139 | if (ref === undefined) {
140 | ref = this._values.length;
141 | this._values.push(v);
142 | this._refs.set(v, ref);
143 | }
144 | let typeFlag = 0;
145 | switch (typeof v) {
146 | case "string":
147 | typeFlag = 1;
148 | break;
149 | case "symbol":
150 | typeFlag = 2;
151 | break;
152 | case "function":
153 | typeFlag = 3;
154 | break;
155 | }
156 | mem().setUint32(addr + 4, nanHead | typeFlag, true);
157 | mem().setUint32(addr, ref, true);
158 | }
159 |
160 | const loadSlice = (addr) => {
161 | const array = getInt64(addr + 0);
162 | const len = getInt64(addr + 8);
163 | return new Uint8Array(this._inst.exports.mem.buffer, array, len);
164 | }
165 |
166 | const loadSliceOfValues = (addr) => {
167 | const array = getInt64(addr + 0);
168 | const len = getInt64(addr + 8);
169 | const a = new Array(len);
170 | for (let i = 0; i < len; i++) {
171 | a[i] = loadValue(array + i * 8);
172 | }
173 | return a;
174 | }
175 |
176 | const loadString = (addr) => {
177 | const saddr = getInt64(addr + 0);
178 | const len = getInt64(addr + 8);
179 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
180 | }
181 |
182 | const timeOrigin = Date.now() - performance.now();
183 | this.importObject = {
184 | go: {
185 | // func wasmExit(code int32)
186 | "runtime.wasmExit": (sp) => {
187 | const code = mem().getInt32(sp + 8, true);
188 | this.exited = true;
189 | delete this._inst;
190 | delete this._values;
191 | delete this._refs;
192 | this.exit(code);
193 | },
194 |
195 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
196 | "runtime.wasmWrite": (sp) => {
197 | const fd = getInt64(sp + 8);
198 | const p = getInt64(sp + 16);
199 | const n = mem().getInt32(sp + 24, true);
200 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
201 | },
202 |
203 | // func nanotime() int64
204 | "runtime.nanotime": (sp) => {
205 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
206 | },
207 |
208 | // func walltime() (sec int64, nsec int32)
209 | "runtime.walltime": (sp) => {
210 | const msec = (new Date).getTime();
211 | setInt64(sp + 8, msec / 1000);
212 | mem().setInt32(sp + 16, (msec % 1000) * 1000000, true);
213 | },
214 |
215 | // func scheduleCallback(delay int64) int32
216 | "runtime.scheduleCallback": (sp) => {
217 | const id = this._nextCallbackTimeoutID;
218 | this._nextCallbackTimeoutID++;
219 | this._callbackTimeouts.set(id, setTimeout(
220 | () => { this._resolveCallbackPromise(); },
221 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early
222 | ));
223 | mem().setInt32(sp + 16, id, true);
224 | },
225 |
226 | // func clearScheduledCallback(id int32)
227 | "runtime.clearScheduledCallback": (sp) => {
228 | const id = mem().getInt32(sp + 8, true);
229 | clearTimeout(this._callbackTimeouts.get(id));
230 | this._callbackTimeouts.delete(id);
231 | },
232 |
233 | // func getRandomData(r []byte)
234 | "runtime.getRandomData": (sp) => {
235 | crypto.getRandomValues(loadSlice(sp + 8));
236 | },
237 |
238 | // func stringVal(value string) ref
239 | "syscall/js.stringVal": (sp) => {
240 | storeValue(sp + 24, loadString(sp + 8));
241 | },
242 |
243 | // func valueGet(v ref, p string) ref
244 | "syscall/js.valueGet": (sp) => {
245 | storeValue(sp + 32, Reflect.get(loadValue(sp + 8), loadString(sp + 16)));
246 | },
247 |
248 | // func valueSet(v ref, p string, x ref)
249 | "syscall/js.valueSet": (sp) => {
250 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
251 | },
252 |
253 | // func valueIndex(v ref, i int) ref
254 | "syscall/js.valueIndex": (sp) => {
255 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
256 | },
257 |
258 | // valueSetIndex(v ref, i int, x ref)
259 | "syscall/js.valueSetIndex": (sp) => {
260 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
261 | },
262 |
263 | // func valueCall(v ref, m string, args []ref) (ref, bool)
264 | "syscall/js.valueCall": (sp) => {
265 | try {
266 | const v = loadValue(sp + 8);
267 | const m = Reflect.get(v, loadString(sp + 16));
268 | const args = loadSliceOfValues(sp + 32);
269 | storeValue(sp + 56, Reflect.apply(m, v, args));
270 | mem().setUint8(sp + 64, 1);
271 | } catch (err) {
272 | storeValue(sp + 56, err);
273 | mem().setUint8(sp + 64, 0);
274 | }
275 | },
276 |
277 | // func valueInvoke(v ref, args []ref) (ref, bool)
278 | "syscall/js.valueInvoke": (sp) => {
279 | try {
280 | const v = loadValue(sp + 8);
281 | const args = loadSliceOfValues(sp + 16);
282 | storeValue(sp + 40, Reflect.apply(v, undefined, args));
283 | mem().setUint8(sp + 48, 1);
284 | } catch (err) {
285 | storeValue(sp + 40, err);
286 | mem().setUint8(sp + 48, 0);
287 | }
288 | },
289 |
290 | // func valueNew(v ref, args []ref) (ref, bool)
291 | "syscall/js.valueNew": (sp) => {
292 | try {
293 | const v = loadValue(sp + 8);
294 | const args = loadSliceOfValues(sp + 16);
295 | storeValue(sp + 40, Reflect.construct(v, args));
296 | mem().setUint8(sp + 48, 1);
297 | } catch (err) {
298 | storeValue(sp + 40, err);
299 | mem().setUint8(sp + 48, 0);
300 | }
301 | },
302 |
303 | // func valueLength(v ref) int
304 | "syscall/js.valueLength": (sp) => {
305 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
306 | },
307 |
308 | // valuePrepareString(v ref) (ref, int)
309 | "syscall/js.valuePrepareString": (sp) => {
310 | const str = encoder.encode(String(loadValue(sp + 8)));
311 | storeValue(sp + 16, str);
312 | setInt64(sp + 24, str.length);
313 | },
314 |
315 | // valueLoadString(v ref, b []byte)
316 | "syscall/js.valueLoadString": (sp) => {
317 | const str = loadValue(sp + 8);
318 | loadSlice(sp + 16).set(str);
319 | },
320 |
321 | // func valueInstanceOf(v ref, t ref) bool
322 | "syscall/js.valueInstanceOf": (sp) => {
323 | mem().setUint8(sp + 24, loadValue(sp + 8) instanceof loadValue(sp + 16));
324 | },
325 |
326 | "debug": (value) => {
327 | console.log(value);
328 | },
329 | }
330 | };
331 | }
332 |
333 | async run(instance) {
334 | this._inst = instance;
335 | this._values = [ // TODO: garbage collection
336 | NaN,
337 | undefined,
338 | null,
339 | true,
340 | false,
341 | global,
342 | this._inst.exports.mem,
343 | this,
344 | ];
345 | this._refs = new Map();
346 | this._callbackShutdown = false;
347 | this.exited = false;
348 |
349 | const mem = new DataView(this._inst.exports.mem.buffer)
350 |
351 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
352 | let offset = 4096;
353 |
354 | const strPtr = (str) => {
355 | let ptr = offset;
356 | new Uint8Array(mem.buffer, offset, str.length + 1).set(encoder.encode(str + "\0"));
357 | offset += str.length + (8 - (str.length % 8));
358 | return ptr;
359 | };
360 |
361 | const argc = this.argv.length;
362 |
363 | const argvPtrs = [];
364 | this.argv.forEach((arg) => {
365 | argvPtrs.push(strPtr(arg));
366 | });
367 |
368 | const keys = Object.keys(this.env).sort();
369 | argvPtrs.push(keys.length);
370 | keys.forEach((key) => {
371 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
372 | });
373 |
374 | const argv = offset;
375 | argvPtrs.forEach((ptr) => {
376 | mem.setUint32(offset, ptr, true);
377 | mem.setUint32(offset + 4, 0, true);
378 | offset += 8;
379 | });
380 |
381 | while (true) {
382 | const callbackPromise = new Promise((resolve) => {
383 | this._resolveCallbackPromise = () => {
384 | if (this.exited) {
385 | throw new Error("bad callback: Go program has already exited");
386 | }
387 | setTimeout(resolve, 0); // make sure it is asynchronous
388 | };
389 | });
390 | this._inst.exports.run(argc, argv);
391 | if (this.exited) {
392 | break;
393 | }
394 | await callbackPromise;
395 | }
396 | }
397 |
398 | static _makeCallbackHelper(id, pendingCallbacks, go) {
399 | return function() {
400 | pendingCallbacks.push({ id: id, args: arguments });
401 | go._resolveCallbackPromise();
402 | };
403 | }
404 |
405 | static _makeEventCallbackHelper(preventDefault, stopPropagation, stopImmediatePropagation, fn) {
406 | return function(event) {
407 | if (preventDefault) {
408 | event.preventDefault();
409 | }
410 | if (stopPropagation) {
411 | event.stopPropagation();
412 | }
413 | if (stopImmediatePropagation) {
414 | event.stopImmediatePropagation();
415 | }
416 | fn(event);
417 | };
418 | }
419 | }
420 |
421 | if (isNodeJS) {
422 | if (process.argv.length < 3) {
423 | process.stderr.write("usage: go_js_wasm_exec [wasm binary] [arguments]\n");
424 | process.exit(1);
425 | }
426 |
427 | const go = new Go();
428 | go.argv = process.argv.slice(2);
429 | go.env = process.env;
430 | go.exit = process.exit;
431 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => {
432 | process.on("exit", (code) => { // Node.js exits if no callback is pending
433 | if (code === 0 && !go.exited) {
434 | // deadlock, make Go print error and stack traces
435 | go._callbackShutdown = true;
436 | go._inst.exports.run();
437 | }
438 | });
439 | return go.run(result.instance);
440 | }).catch((err) => {
441 | throw err;
442 | });
443 | }
444 | })();
445 |
--------------------------------------------------------------------------------