Creating a GO GUI with Alpine.js and Webview

There are a lot of options for building a GUI for Go applications. Coming from the web development world building the front end with HTML seems like a no-brainer.

Webview

Webview is a tiny cross-platform library for C/C++/Golang to build modern cross-platform GUIs. The goal of the project is to create a common HTML5 UI abstraction layer for the most widely used platforms.

To start using Webview you need to install Webview: go get github.com/webview/webview

On Windows, you need to have these two dlls in the project root folder.

It supports two-way JavaScript bindings (to call JavaScript from C/C++/Go and to call C/C++/Go from JavaScript). But writing pure JavaScript code for the interactivity is awful.

Alpine.js to the rescue

"Alpine.js is a rugged, minimal tool for composing behavior directly in your markup." It fits perfectly for our use case.

You can load Alpine inline or from a file. The newest version is available at unpkg.com/alpinejs

func loadAlpine() string {
    return "paste alpine.js source here"
}

First, you must initialize Webview.

func main() {
    webView := webview.New(true)
    defer webView.Destroy()

    webView.SetSize(600, 600, webview.HintNone)
    webView.Init(loadAlpine())

To execute go code with Alpine we need to call webView.bind("functionName").

webView.Bind("extractSubDirectories", func(sourceFolder string) string {
  folderUrls = extractSubDirectories(sourceFolder)
    tmpl := template.Must(template.New("html").Parse(
      // language=GoTemplate
    `<div>
      {{range $vendor, $folderDetailsArray := .}}
        <div>
          <h3>Vendor: {{$vendor}}</h2>
        {{range $folderDetails := $folderDetailsArray}}
          <ul>
            <li>{{ .Path }} filecount:: {{ .FileCount }}</li>
          </ul>
        {{end}}
        </div>
      {{end}}
     </div>`))
  var html bytes.Buffer
  err := tmpl.Execute(&html, folderUrls)
  if err != nil {
    logger.WritePrint("ERROR: " + err.Error())
  }
  return html.String()
})

To create your first page you call webView.Navigate() and supply it with your HTML. Then call webView.Run()

webView.Navigate(`data:text/html` + `<!doctype html>
<html lang="de" x-data="{ pathInput: '', table : ''}">
    <body style="padding: 2rem">
        <h1>JPEG Sorter</h1>
        <p>Input the folder where the images are stored</p>
        <input type="text" x-model="pathInput"/>

        <button @click="table = ''; table = await extractSubDirectories(pathInput);">analyse folder</button>

        <div x-html=table></div>
    </body>
</html>`)
webView.Run()

Alpine.js

As you can see there are quite a lot of non-standard html attributes.

This is the magic of alpine.js. You can create local alpine data variables in the scope of the element:

<html lang="de" x-data="{ pathInput: '', table : ''}">

You can bind input data to the local variables with x-model

<input type="text" x-model="pathInput"/>

But the coolest part comes now. With an @click alpine attribute, we can call our go functions from the HTML. The extractSubDirectories() function we bound earlier in this example.

<button @click="table = await extractSubDirectories(pathInput);">
  analyse folder
</button>

With x-html we can bind the returned HTML from the go function into our GUI.

<div x-html=table></div>

These are the basic steps to get Webview and alpine.js working with Go.

GUI Example

GUI Example