Go, JavaScript and You

Nebraska JavaScript Meetup

6 March 2018

John Hobbs

Infrastructure Engineer, Flywheel

GopherJS

GopherJS compiles Go code (golang.org) to pure JavaScript code. Its main purpose is to give you the opportunity to write front-end code in Go which will still run in all browsers.

Good God, why would you do this?

How does it work?

// +build js
package main

func main() {
    println("Hello From Go")
}
examples/minimal/main.go
$ gopherjs serve
serving at http://localhost:8080 and on port 8080 of any available addresses

Bindings

DOM - honnef.co/go/js/dom

<button id="click-me">Click Me</button>
<div id="target"></div>
<script src="dom.js"></script>
examples/dom/index.html
func main() {
    d := dom.GetWindow().Document()

    container := d.GetElementByID("target").(*dom.HTMLDivElement)
    button := d.GetElementByID("click-me").(*dom.HTMLButtonElement)

    button.AddEventListener("click", false, func(event dom.Event) {
        div := d.CreateElement("div").(*dom.HTMLDivElement)
        div.SetTextContent(fmt.Sprintf("I was created at %v", time.Now()))
        container.AppendChild(div)
    })
}
examples/dom/main.go

net/http Requests

func main() {
    resp, err := http.Get("http://localhost:9090/tweets")
    if err != nil {
        println(err)
        return
    }

    if resp.StatusCode == 200 {
        b, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            println(err)
            return
        }

        println(string(b))
    }
    println(resp.Status)
}
examples/twitter/net-http.go

XHR - honnef.co/go/js/xhr

func main() {
    req := xhr.NewRequest("GET", "/tweets")
    req.ResponseType = "json"
    err := req.Send(nil)
    if err != nil {
        println(err)
        return
    }

    if req.Status == 200 {
        println(req.Response)
    }
    println(req.StatusText)
}
examples/twitter/xhr.go
6.8M    net-http.js
132K    xhr.js
Yeah, so maybe don't bring in all of net/http

jsTodoTxt

// Fresh new item!
var newItem = new TodoTxtItem();
newItem.text = "Cool!";

// Create an item from a string
var existingItem = new TodoTxtItem( "(A) Try out jsTodoTxt" );
console.log( existingItem.priority ); // Logs "A"
Usage example from github.com/jmhobbs/jsTodoTxt
type TodoTxtItem struct {
    *js.Object
    Text       string    `js:"text"`
    Priority   string    `js:"priority"`
    Complete   bool      `js:"complete"`
    Completed  time.Time `js:"completed"`
    Date       time.Time `js:"date"`
    Contexts   []string  `js:"contexts"`
    Projects   []string  `js:"projects"`
}
examples/binding/main.go

jsTodoTxt

func NewTodoTxtItem() *TodoTxtItem {
    return &TodoTxtItem{Object: js.Global.Get("TodoTxtItem").New()}
}

func (ti *TodoTxtItem) Parse(item string) {
    ti.Call("parse", item)
}

func (ti *TodoTxtItem) ToString() string {
    return ti.Call("toString").String()
}

func main() {
    ti := NewTodoTxtItem()
    ti.Parse("(A) Try out jsTodoTxt @Computer")
    println("Text:", ti.Text)
    println("Priority:", ti.Priority)
    println("Context:", ti.Contexts[0])
    ti.Priority = "B"
    println(ti.ToString())
}
examples/binding/main.go

js.MakeWrapper

type Router struct {
    routes map[string]*js.Object
}

func (r Router) Set(route string, target *js.Object) {
    r.routes[route] = target
}

func (r Router) Get(route string) *js.Object {
    return r.routes[route]
}

func NewRouter() *js.Object {
    router := Router{make(map[string]*js.Object)}
    return js.MakeWrapper(router)
}
examples/export/main.go

js.Global

func main() {
    js.Global.Set("Router", NewRouter)
}
examples/export/main.go
<script src="export.js"></script>
<script>
  var router = new Router();
  router.Set("/hello", function () { console.log("Hello World!"); });
  router.Get("/hello")();
  console.log(router.Get("/goodbye"));
</script>
examples/export/index.html

Vecty

Unstable!

Vecty - Hello World

package main

import (
    "github.com/gopherjs/vecty"
    "github.com/gopherjs/vecty/elem"
)

type HelloWorldComponent struct {
    vecty.Core
}

func main() {
    vecty.RenderBody(&HelloWorldComponent{})
}

func (hw *HelloWorldComponent) Render() vecty.ComponentOrHTML {
    return elem.Body(
        vecty.Markup(vecty.Class("vecty-root-container")),
        vecty.Text("Hello World!"),
    )
}
examples/vecty/hello/main.go

Vecty - Child Components

func (hw *HelloWorldComponent) Render() vecty.ComponentOrHTML {
    return elem.Body(
        elem.Heading1(vecty.Text("Hello World!")),
        &ImageComponent{Image: "gopher.jpg", Caption: "Flight Ready Gopher"},
    )
}

type ImageComponent struct {
    vecty.Core
    Image   string
    Caption string
}

func (im *ImageComponent) Render() vecty.ComponentOrHTML {
    return elem.Div(
        elem.Image(vecty.Markup(prop.Src(im.Image))),
        elem.Span(vecty.Text(im.Caption)),
    )
}
examples/vecty/image/main.go

Vecty - Events

func main() {
    vecty.RenderBody(&HelloWorldComponent{HelloWhat: "World!"})
}

func (hw *HelloWorldComponent) Render() vecty.ComponentOrHTML {
    return elem.Body(
        elem.Heading1(vecty.Text("Hello "+hw.HelloWhat)),
        elem.Input(
            vecty.Markup(
                prop.Value(hw.HelloWhat),
                event.Input(func(e *vecty.Event) {
                    hw.HelloWhat = e.Target.Get("value").String()
                    vecty.Rerender(hw)
                }),
            ),
        ),
    )
}
examples/vecty/event/main.go

Glue That Mess Together

type PageView struct {
    vecty.Core
    Tweets []*TweetView
}

type TweetView struct {
    vecty.Core
    Text       string
    Name       string
    Screenname string
    Avatar     string
}

func main() {
    pv := &PageView{}
    vecty.RenderBody(pv)
    pv.refresh()
}
examples/twitter/demo.go

Glue That Mess Together

func (tv *TweetView) Render() vecty.ComponentOrHTML {
    return elem.ListItem(
        elem.Image(vecty.Markup(prop.Src(tv.Avatar))),
        elem.Div(
            vecty.Markup(vecty.Class("user")),
            elem.Span(
                vecty.Markup(vecty.Class("username")),
                vecty.Text(tv.Name),
            ),
            elem.Span(
                vecty.Markup(vecty.Class("screenname")),
                vecty.Text("@"+tv.Screenname),
            ),
        ),
        elem.Div(
            vecty.Markup(vecty.Class("tweet")),
            vecty.Text(tv.Text),
        ),
    )
}
examples/twitter/demo.go

Go + JavaScript = ❤️

Shameless Plug

Omaha Gophers
March 27th
Flywheel Underground

Thank you

John Hobbs

Infrastructure Engineer, Flywheel