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())
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.
NOTE: Wiring and Granularity
While it is tempting to place the various pieces of your application needed by components into one large struct which is then made available to components via the above mechanism, this can quickly lead to unnecessarily tight coupling in your application. Given several different pieces of your application such e.g. a Router, an APIClient and a ObjectStore (just a made-up example), it is likely better to inject each of these into your UI components separately, rather than having one big "App" struct which each of these as members and only injecting the App. As your application grows this allows each component to only be aware of the individual things it needs, which can greatly simplify program design and other concerns like testing.