JavaScript Interoperability

Intro

Since WebAssembly is browser-based technology, some scenarios may require DOM access and JavaScript calls.

This is usually done with the help of syscall/js but for compatibility and tooling reasons, go-app wraps the JS standard package. Interacting with JavaScript is done by using the Value interface.

This article provides examples that show common interactions with JavaScript.

Include JS files

Building UIs can sometimes require the need of third-party JavaScript libraries. Those libraries can either be included at the page level or inlined in a component.

Page’s scope

JS files can be included on a page by using the Handler Scripts field:

handler := &app.Handler{
	Name: "My App",
	Scripts: []string{
		"/web/myscript.js",                // Local script
		"https://foo.com/remoteScript.js", // Remote script
	},
}

Or by directly putting JS markup in the RawHeaders field:

handler := &app.Handler{
	Name: "My App",
	RawHeaders: []string{
		`<!-- Global site tag (gtag.js) - Google Analytics -->
		<script async src="https://www.googletagmanager.com/gtag/js?id=UA-xxxxxxx-x"></script>
		<script>
		  window.dataLayer = window.dataLayer || [];
		  function gtag(){dataLayer.push(arguments);}
		  gtag('js', new Date());

		  gtag('config', 'UA-xxxxxx-x');
		</script>
		`,
	},
}

Inlined in Components

JS files can also be included directly inlined into components in the Render() method by using the <script> HTML element.

The following example asynchronously loads a YouTube video into an <iframe>, using a YouTube JavaScript file:

type youtubePlayer struct {
	app.Compo
}

func (p *youtubePlayer) Render() app.UI {
	return app.Div().Body(
		app.Script().
			Src("//www.youtube.com/iframe_api").
			Async(true),
		app.IFrame().
			ID("yt-container").
			Allow("autoplay").
			Allow("accelerometer").
			Allow("encrypted-media").
			Allow("picture-in-picture").
			Sandbox("allow-presentation allow-same-origin allow-scripts allow-popups").
			Src("https://www.youtube.com/embed/LqeRF_0DDCg"),
	)
}

Using window global object

The window JS global object is usable from the Window function.

app.Window()

Get element by ID

GetElementByID() is to get a DOM element from an ID.

// JS version:
let elem = document.getElementById("YOUR_ID");
// Go equivalent:
elem := app.Window().GetElementByID("YOUR_ID")

It is a helper function equivalent to:

elem := app.Window().
    Get("document").
    Call("getElementById","YOUR_ID")

Create JS object

Creating an object from a library is done by getting its name from the Window and call the New() function.

Here is an example about how to create a Youtube player:

// JS version:
let player = new YT.Player("yt-container", {
  height: "390",
  width: "640",
  videoId: "M7lc1UVf-VE",
});
// Go equivalent:
player := app.Window().
	Get("YT").
	Get("Player").
	New("yt-container", map[string]interface{}{
		"height":  390,
		"width":   640,
		"videoId": "M7lc1UVf-VE",
    })

Cancel an event

When implementing an event handler, the event can be canceled by calling PreventDefault().

type foo struct {
	app.Compo
}

func (f *foo) Render() app.UI {
	return app.Div().
		OnChange(f.onContextMenu).
		Text("Don't copy me!")
}

func (f *foo) onContextMenu(ctx app.Context, e app.Event) {
	e.PreventDefault()
}

Get input value

Input are usually used to get a user inputed value. Here is how to get that value when implementing an event handler:

type foo struct {
    app.Compo
}

func (f *foo) Render() app.UI {
    return app.Input().OnChange(f.onInputChange)
}

func (f *foo) onInputChange(ctx app.Context, e app.Event) {
    v := ctx.JSSrc().Get("value").String()
}

Next

☰