vugu / vugu

Vugu: A modern UI library for Go+WebAssembly (experimental)
https://www.vugu.org
MIT License
4.8k stars 175 forks source link

Multiple elements to wire #199

Closed Xumeiquer closed 3 years ago

Xumeiquer commented 3 years ago

Question I am trying to wire 2 components in the buildEnv.SetWireFunc method. For that I was following this. The code I come up with is:

func VuguSetup(buildEnv *vugu.BuildEnv, eventEnv vugu.EventEnv, opts *VuguSetupOptions) vugu.Builder {

    router := vgrouter.New(eventEnv)
    api := middleware.NewAPI("http://127.0.0.1")
    state := middleware.NewState()

    buildEnv.SetWireFunc(func(b vugu.Builder) {
        if c, ok := b.(vgrouter.NavigatorSetter); ok {
            fmt.Println("Setting Router")
            c.NavigatorSet(router)
        }

        if c, ok := b.(middleware.APISetter); ok {
            fmt.Println("Setting API")
            c.APISet(api)
        }

        if c, ok := b.(middleware.StateSetter); ok {
            fmt.Println("Setting State")
            c.StateSet(state)
        }
    })

    root := &components.Root{}
    buildEnv.WireComponent(root)

    router.MustAddRouteExact("/", vgrouter.RouteHandlerFunc(func(rm *vgrouter.RouteMatch) {
        root.Body = &pages.Index{}
    }))

    if router.BrowserAvail() {
        err := router.ListenForPopState()
        if err != nil {
            panic(err)
        }

        err = router.Pull()
        if err != nil {
            panic(err)
        }
    }

    return root
}

If you have realized inside buildEnv.SetWireFunc there are three conditionals where the argument b type of vugu.Builder is being casted into different types to see if any of them match. If it does, then a print statement is displaying a message and the setter is called.

The Root struct looks like this:

type Root struct {
    vgrouter.NavigatorRef
    middleware.StateRef
    middleware.APIRef

    Body vugu.Builder
}

The Router, State, and API are there. However, I only see Setting Router message, instead of all three.

Is your question related to a problem? Please describe. No, as far as I know.

Suggested Changes None at the moment

Additional context The setter interface is as defined in the link I said before.

bradleypeabody commented 3 years ago

if c, ok := b.(middleware.APISetter); ok {

Are you sure middleware.APIRef implements middleware.APISetter? Please show the definitions of those.

Xumeiquer commented 3 years ago

I am quite sure, here is the code for the API and the State.

midlleware/api.go

package middleware

import (
    "io/ioutil"
    "net/http"
    "net/url"
)

type APIRef struct{ *API }
type APISetter interface{ APISet(*API) }

func (cr *APIRef) APISet(c *API) { cr.API = c }

type API struct {
    Endpoint string
}

func NewAPI(endpoint string) *API {
    return &API{
        Endpoint: endpoint,
    }
}

[...]

midlleware/state.go

package middleware

import (
    "errors"
)

type StateRef struct{ *State }
type StateSetter interface{ StateSet(*State) }

func (cr *StateRef) StateSet(c *State) {
    cr.State = c
}

type State struct {
    Data map[string]interface{}
}

func NewState() *State {
    return &State{
        Data: make(map[string]interface{}),
    }
}

[...]

As I said I copy and renamed the example from the documentation website.

bradleypeabody commented 3 years ago

I see. I would try adding some log statements right inside buildEnv.SetWireFunc(func(b vugu.Builder) { to look closely at the value of b. Statements like if c, ok := b.(middleware.APISetter); ok { are just straight Go, there's no magic that Vugu is doing with them. So either that if statement is not firing because b does not implement the interface, or that line is not getting called. Add log statements until you and find which of those cases is occurring and go from there.

Xumeiquer commented 3 years ago

Hi, I've just create a new project from scratch to be able to spot the issue. Well, I didn't find the issue, but Vugu does its job really well so there is no issue at the end. It might me something I did wrong.

Another thing I saw is the following. I have a Body field in the Root object and the Vugu router sets when the URI is /. However, that component is not being rendered.

The issue is in webapp.go and in particular this code of here

router.MustAddRouteExact("/", vgrouter.RouteHandlerFunc(func(rm *vgrouter.RouteMatch) {
    fmt.Println("Setting Root.Body")
    root.Body = &Index{}
}))

I am able to see the output Setting Root.Body but the DOM does no change and the Index component is not initialized as Vugu object only as Golang object. Thus, Vugu does not call SetWireFunc for that component. I don't know if this is wrong or if it is Vugu issue.

Basically the thing is that when I include the Index component in the root.vugu as <main:Index></main:Index> That component is initialized through SetWireFunc and it is initialized correct. However, if I initialize it in the MustAddRouteExact callback it does not go through SetWireFunc.

I'll add all the details down below so it maybe helpful for someone else.

I create a new project from scratch only for testing this. The components are getting wired to the root component but they are not getting injected/wired into the other components.

Let's put the code and the output.

main_wasm.go the wasm application entrypoint. Here is where Vugu mounts or render the DOM and it calls the VuguSetup to initialize the webapp.

// +build wasm

package main

import (
    "flag"
    "fmt"

    "github.com/vugu/vugu"
    "github.com/vugu/vugu/domrender"
)

func main() {
    mountPoint := flag.String("mount-point", "#vugu_mount_point", "The query selector for the mount point for the root component, if it is not a full HTML component")
    flag.Parse()

    fmt.Printf("Entering main(), -mount-point=%q\n", *mountPoint)
    defer fmt.Printf("Exiting main()\n")

    buildEnv, err := vugu.NewBuildEnv()
    if err != nil {
        panic(err)
    }

    renderer, err := domrender.New(*mountPoint)
    if err != nil {
        panic(err)
    }
    defer renderer.Release()

    rootBuilder := VuguSetup(buildEnv, renderer.EventEnv())

    for ok := true; ok; ok = renderer.EventWait() {

        buildResults := buildEnv.RunBuild(rootBuilder)

        err = renderer.Render(buildResults)
        if err != nil {
            panic(err)
        }
    }
}

webapp.go, this is where the VuguSetup function resides. You can see here how in buildEnv.SetWireFunc I am printing the type of the object I Vugu is about to wire and also the components that Vugu accomplishes to wire.

package main

import (
    "fmt"

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

func VuguSetup(buildEnv *vugu.BuildEnv, eventEnv vugu.EventEnv) vugu.Builder {
    router := vgrouter.New(eventEnv)
    api := &API{}
    state := &State{}

    buildEnv.SetWireFunc(func(b vugu.Builder) {
        fmt.Printf("Wiring: %T\n", b)
        if c, ok := b.(vgrouter.NavigatorSetter); ok {
            fmt.Println("Setting Router")
            c.NavigatorSet(router)
        }

        if c, ok := b.(APISetter); ok {
            fmt.Println("Setting API")
            c.APISet(api)
        }

        if c, ok := b.(StateSetter); ok {
            fmt.Println("Setting State")
            c.StateSet(state)
        }
    })

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

    router.MustAddRouteExact("/", vgrouter.RouteHandlerFunc(func(rm *vgrouter.RouteMatch) {
                fmt.Println("Setting Root.Body")
        root.Body = &Index{}
    }))

    if router.BrowserAvail() {
        err := router.ListenForPopState()
        if err != nil {
            panic(err)
        }

        err = router.Pull()
        if err != nil {
            panic(err)
        }
    }

    return root
}

root.vugu this is the main component. This component has a router (vgrouter.NavigatorRef), an API (APIRef), and a Stage (StateRef).

<div>
    <p>Hi!</p>
    <main:Index></main:Index>
</div>

<script type="application/x-go">
import (
    "github.com/vugu/vgrouter"
)
type Root struct {
    vgrouter.NavigatorRef
    StateRef
    APIRef
    Body vugu.Builder
}

func (c *Root) Init() {
    fmt.Println("Called: Root Init")
}
</script>

index.vugu. This component also has a router (vgrouter.NavigatorRef), an API (APIRef), and a Stage (StateRef).

<div>
    <p>I'm here!</p>
</div>

<script type="application/x-go">
import (
    "github.com/vugu/vgrouter"
)
type Index struct {
    vgrouter.NavigatorRef
    StateRef
    APIRef
}
func (c *Index) Init() {
    fmt.Println("Called: Index Init")
}
</script>

api.go is where the API implementation resides.

package main

type API struct {
    Endpoint string
}
type APIRef struct{ *API }
type APISetter interface{ APISet(*API) }

func (cr *APIRef) APISet(c *API) { cr.API = c }

state.go is where the state implementation resides.

package main

type State struct {
    S map[string]interface{}
}
type StateRef struct{ *State }
type StateSetter interface{ StateSet(*State) }

func (cr *StateRef) StateSet(c *State) { cr.State = c }

The output when the Index component is initialized through SetWireFunc

Entering main(), -mount-point="#vugu_mount_point"
13:00:51.960 wasm_exec.js:51 Wiring: *main.Root
13:00:51.961 wasm_exec.js:51 Setting Router
13:00:51.961 wasm_exec.js:51 Setting API
13:00:51.962 wasm_exec.js:51 Setting State
13:00:51.963 wasm_exec.js:51 Setting Root.Body
13:00:51.967 wasm_exec.js:51 Called: Root Init
13:00:51.968 wasm_exec.js:51 Wiring: *main.Index
13:00:51.968 wasm_exec.js:51 Setting Router
13:00:51.969 wasm_exec.js:51 Setting API
13:00:51.969 wasm_exec.js:51 Setting State
13:00:51.989 wasm_exec.js:51 Called: Index Init

The output when the Index component is initialized in the router MustAddRouteExact callback.

Entering main(), -mount-point="#vugu_mount_point"
12:47:16.303 wasm_exec.js:51 Wiring: *main.Root
12:47:16.312 wasm_exec.js:51 Setting Router
12:47:16.312 wasm_exec.js:51 Setting API
12:47:16.314 wasm_exec.js:51 Setting State
12:47:16.318 wasm_exec.js:51 Setting Root.Body
12:47:16.320 wasm_exec.js:51 Called: Root Init
bradleypeabody commented 3 years ago

I see, thanks for the detailed explanation. How is the navigation to the "/" page being done? Are you calling Navigate? Or is this just when you first browse to the page, or some other way?

It sounds like there is no render pass that is occurring after the navigation takes place, which should address what you're talking about.

Xumeiquer commented 3 years ago

The / is the first place where the browser navigates. And I am using <vg-comp expr="c.Body"></vg-comp> in the Root component to render the component assigned during the routing.

Xumeiquer commented 3 years ago

I think we are fine to close this as everything is fixed.