Wiring Vugu Applications

Unlike many Go programs which would benefit from static wiring using tools such as wire, Vugu programs involve UI components which are created and destroyed throughout the lifetime of the application in response to user action. As such, an effective mechanism for wiring applications at runtime is needed, and ideally with as much type-safety and help from the Go compiler as possible.

Wiring in a Vugu application is accomplished by proving a wire function which is called each time a component is created or re-used. The wire function should be kept lean and simple as they are called a lot.

If your main package includes a function called vuguSetup, then when vugugen creates your main_wasm.go file it will include a call to that function. (You can also easily add this manually.) The call from main_wasm.go is pretty simple: rootBuilder := vuguSetup(buildEnv, renderer.EventEnv()). And we'll take a look at the contents of vuguSetup in a moment.

In this example we're going to have a simple shared counter which can be injected into any UI component which wants a reference to it. The counter.go file looks like so:

// counter.go
package main

type Counter struct { c int }

func (c *Counter) NextCount() int { c.c++; return c.c }

type CounterRef struct{ *Counter }
type CounterSetter interface{ CounterSet(c *Counter) }

func (cr *CounterRef) CounterSet(c *Counter) { cr.Counter = c }

The Counter type is what we want to inject an instance of into UI components. And the CounterRef and CounterSetter are tools to help us do this. In setup.go, vuguSetup looks like so:

// setup.go
package main

import "github.com/vugu/vugu"

func vuguSetup(buildEnv *vugu.BuildEnv, eventEnv vugu.EventEnv) vugu.Builder {

	var counter Counter

	buildEnv.SetWireFunc(func(b vugu.Builder) {
		if c, ok := b.(CounterSetter); ok {
			c.CounterSet(&counter)
		}
	})

	ret := &Root{}
	buildEnv.WireComponent(ret)

	return ret
}

And then to make the example complete, let's look at a component which needs a reference to Counter.

<!-- demo-comp1.vugu -->

<div class="demo-comp1">
    <div vg-content="c.NextCount()"></div>
</div>

<script type="application/x-go">
type DemoComp1 struct {
    CounterRef
}
</script>

The SetWireFunc call provides a function to wire up each component as it created. Vugu handles calling this function automatically for each component that is instanciated or re-used. In this example, you can see that DemoComp1 embeds a CounterRef, which holds a pointer to a Counter and implements the CounterSetter interface. And this interface is what is checked for in vuguSetup.

The above shows a useful pattern that can be applied keep wiring clean:

  • given type X struct { /* ... */ }
  • there is also a type XRef struct { *X } (which components can embed)
  • an interface XSetter
  • and a method on XRef that implements the interface i.e. func (r *XRef) XSet(x *X)

TODO: It might be appropriate to have a //vugugen:wireref X or similar mechanism that would alleviate most of the boiler plate.