├── .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 | 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 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
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 | 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 | --------------------------------------------------------------------------------