gob fetcher for wasm example
This commit is contained in:
147
go/cmd/examples/go-wasm-web/app/data.go
Normal file
147
go/cmd/examples/go-wasm-web/app/data.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"kjol/httputil"
|
||||
. "kjol/vdom"
|
||||
ui "kjol/webui"
|
||||
)
|
||||
|
||||
// Quote is the payload the /api/quotes endpoint returns. The server encodes a
|
||||
// []Quote with httputil.RespondGob; the client decodes it straight back into
|
||||
// []Quote — the SAME Go type, no JSON, no hand-written unmarshalling.
|
||||
type Quote struct {
|
||||
Author string
|
||||
Text string
|
||||
}
|
||||
|
||||
// repoInfo is a subset of GitHub's repo JSON (a third-party API), tagged for
|
||||
// json decoding.
|
||||
type repoInfo struct {
|
||||
FullName string `json:"full_name"`
|
||||
Description string `json:"description"`
|
||||
Stars int `json:"stargazers_count"`
|
||||
}
|
||||
|
||||
//gowasm:page /data layout=app
|
||||
func DataPage(d Deps) func() *VNode {
|
||||
// (1) gob from our own server via httputil.RespondGob / FetchGob.
|
||||
quotes := NewSignal([]Quote{})
|
||||
qLoading := NewSignal(true)
|
||||
qErr := NewSignal("")
|
||||
// (2) JSON from a third-party API (GitHub), for a user-entered repo.
|
||||
repo := NewSignal(repoInfo{})
|
||||
rLoading := NewSignal(true)
|
||||
rErr := NewSignal("")
|
||||
repoQuery := NewSignal("golang/go")
|
||||
started := false
|
||||
|
||||
// fetchRepo loads owner/name from the GitHub API into the repo signal.
|
||||
fetchRepo := func(q string) {
|
||||
q = strings.Trim(strings.TrimSpace(q), "/")
|
||||
if q == "" {
|
||||
rErr.Set("enter a repo as owner/name")
|
||||
rLoading.Set(false)
|
||||
return
|
||||
}
|
||||
rErr.Set("")
|
||||
rLoading.Set(true)
|
||||
httputil.FetchJSON("https://api.github.com/repos/"+q, func(r repoInfo, err error) {
|
||||
if err != nil {
|
||||
rErr.Set(err.Error())
|
||||
} else {
|
||||
repo.Set(r)
|
||||
}
|
||||
rLoading.Set(false)
|
||||
})
|
||||
}
|
||||
|
||||
return func() *VNode {
|
||||
// Fire the initial fetches once, on the client (no transport on the server,
|
||||
// so SSR ships the loading state and the client takes over).
|
||||
if !started {
|
||||
started = true
|
||||
httputil.FetchGob("/api/quotes", func(qs []Quote, err error) {
|
||||
if err != nil {
|
||||
qErr.Set(err.Error())
|
||||
} else {
|
||||
quotes.Set(qs)
|
||||
}
|
||||
qLoading.Set(false)
|
||||
})
|
||||
fetchRepo(repoQuery.Get())
|
||||
}
|
||||
|
||||
return Div(Attr("class", "space-y-8"),
|
||||
Div(
|
||||
H2(Attr("class", "text-2xl font-semibold tracking-tight text-text-heading"), Text("Data fetching")),
|
||||
P(Attr("class", "mt-1 text-neutral-500"), Text("Two client-side fetches: gob from our own server, and JSON from a third-party API you choose.")),
|
||||
),
|
||||
|
||||
ui.Card("",
|
||||
ui.CardHeader("", Text("gob — from our server")),
|
||||
P(Attr("class", "mb-3 text-sm text-neutral-500"),
|
||||
Text("The client GETs /api/quotes; the server responds with httputil.RespondGob "+
|
||||
"(a gob-encoded []Quote) and httputil.FetchGob decodes it straight into []Quote — "+
|
||||
"the same Go type on both ends, no JSON.")),
|
||||
quotesBody(qLoading.Get(), qErr.Get(), quotes.Get()),
|
||||
),
|
||||
|
||||
ui.Card("",
|
||||
ui.CardHeader("", Text("JSON — from a third-party API")),
|
||||
P(Attr("class", "mb-3 text-sm text-neutral-500"),
|
||||
Text("Enter a GitHub repo; the client GETs api.github.com and httputil.FetchJSON "+
|
||||
"decodes the response into a Go struct with `json:\"…\"` tags.")),
|
||||
row("mb-4 flex items-end gap-2",
|
||||
row("flex grow flex-col gap-1 max-w-sm",
|
||||
ui.FormLabel(ui.FormLabelProps{}, Text("GitHub repo (owner/name)")),
|
||||
ui.FormInput(ui.FormInputProps{
|
||||
Value: repoQuery.Get(),
|
||||
Placeholder: "golang/go",
|
||||
OnInput: func(v string) { repoQuery.Set(v) },
|
||||
}),
|
||||
),
|
||||
ui.Button(ui.ButtonProps{Color: ui.ButtonPrimary, Text: "Fetch", OnClick: func() { fetchRepo(repoQuery.Get()) }}),
|
||||
),
|
||||
repoBody(rLoading.Get(), rErr.Get(), repo.Get()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func quotesBody(loading bool, failed string, quotes []Quote) *VNode {
|
||||
switch {
|
||||
case failed != "":
|
||||
return ui.Alert(ui.AlertRed, "Fetch failed", Text(failed))
|
||||
case loading:
|
||||
return ui.Loader()
|
||||
default:
|
||||
cards := make([]*VNode, 0, len(quotes))
|
||||
for _, q := range quotes {
|
||||
cards = append(cards, ui.BorderCard("",
|
||||
P(Attr("class", "text-neutral-800"), Text("“"+q.Text+"”")),
|
||||
P(Attr("class", "mt-2 text-sm text-neutral-500"), Text("— "+q.Author)),
|
||||
))
|
||||
}
|
||||
return row("grid gap-3 sm:grid-cols-2", cards...)
|
||||
}
|
||||
}
|
||||
|
||||
func repoBody(loading bool, failed string, r repoInfo) *VNode {
|
||||
switch {
|
||||
case failed != "":
|
||||
return ui.Alert(ui.AlertRed, "Fetch failed", Text(failed))
|
||||
case loading:
|
||||
return ui.Loader()
|
||||
default:
|
||||
return ui.BorderCard("",
|
||||
row("flex items-center gap-2",
|
||||
Strong(Attr("class", "text-neutral-800"), Text(r.FullName)),
|
||||
ui.Badge(ui.BadgeProps{Color: ui.BadgeAmber}, Text("★ "+strconv.Itoa(r.Stars))),
|
||||
),
|
||||
P(Attr("class", "mt-2 text-sm text-neutral-600"), Text(r.Description)),
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user