URL Routing with Vugu

The Router

The package github.com/vugu/vgrouter provides routing functionality for Vugu applications. A vgrouter.Router provides functionality for both server-side (by reading an http.Request) and client-side (by reading the browser's path or #fragment) routing. If you're not yet familiar with Vugu wiring, you'll likely want to read up on that before continuing.

Routes are registered and when processing are examined in the sequence they were registered. Matches for routes can be exact or partial and the code for that route can then perform an appropriate action. The usual approach will be to have one or more sections of your user interface be dynamic based on which route is currently active. Then the route handler(s) which match for a given route assign those sections as fields of type vugu.Builder, which are then rendered via a vg-comp tag. Let's look at an example. Follow the comments for a an as-you-go explanation of the parts:

// setup.go

package main

import (
    "path"
    "strings"

    "github.com/vugu/vgrouter"
    "github.com/vugu/vugu"
    "github.com/vugu/vugu/js"
)

// OVERALL APPLICATION WIRING IN vuguSetup
func vuguSetup(buildEnv *vugu.BuildEnv, eventEnv vugu.EventEnv) vugu.Builder {

    // CREATE A NEW ROUTER INSTANCE
    router := vgrouter.New(eventEnv)

    // MAKE OUR WIRE FUNCTION POPULATE ANYTHING THAT WANTS A "NAVIGATOR".
    buildEnv.SetWireFunc(func(b vugu.Builder) {
        if c, ok := b.(vgrouter.NavigatorSetter); ok {
            c.NavigatorSet(router)
        }
    })

    // CREATE THE ROOT COMPONENT
    root := &Root{}
    buildEnv.WireComponent(root) // WIRE IT

    // ADD ROUTES FOR EACH PAGE.  NOTE THAT THESE ARE "EXACT" ROUTES.
    // YOU CAN ALSO ADD ROUTES THAT MATCH ANYTHING WITH THE SPECIFIED PREFIX.
    router.MustAddRouteExact("/", 
        vgrouter.RouteHandlerFunc(func(rm *vgrouter.RouteMatch) {
        root.Body = &Page1{} // A COMPONENT WITH PAGE CONTENTS
    }))
    router.MustAddRouteExact("/page2", 
        vgrouter.RouteHandlerFunc(func(rm *vgrouter.RouteMatch) {
        root.Body = &Page2{} // A COMPONENT WITH PAGE CONTENTS
    }))
    router.SetNotFound(vgrouter.RouteHandlerFunc(
        func(rm *vgrouter.RouteMatch) {
        root.Body = &PageNotFound{} // A PAGE FOR THE NOT-FOUND CASE
    }))

    // TELL THE ROUTER TO LISTEN FOR THE BROWSER CHANGING URLS
    err := router.ListenForPopState()
    if err != nil {
        panic(err)
    }

    // GRAB THE CURRENT BROWSER URL AND PROCESS IT AS A ROUTE
    err = router.Pull()
    if err != nil {
        panic(err)
    }

    return root
}

And our Root component which houses the Body field and renders it.

<!-- root.vugu -->

<div>
    <h1>Test App</h1>

    <!-- RENDER THE BODY -->
    <vg-comp expr="c.Body"/>

    <!-- USE Navigator.Navigate() TO CHANGE TO A DIFFERENT URL-->
    <button @click="c.Navigate("/", nil)">Home</button>
    <button @click="c.Navigate("/page2", nil)">Page2</button>

</div>

<script type="application/x-go">

import "github.com/vugu/vgrouter"

type Root struct {

    // ANYTHING THAT MUST NAVIGATE NEED ONLY EMBED THIS
    vgrouter.NavigatorRef

    // THE BODY COMPONENT, GETS SET BY THE APPROPRIATE ROUTE ABOVE
    Body vugu.Builder
}

</script>

TODO: BindParams also exist and should be documented. Also give a more detailed explanation of the Navigator interface (minimal interface of Router that pages need to know about to perform navigation).