Query Params
Model
In the weather API, besides the city, we also have two variables: units (metric / imperial) and forecast days.
Add them to the path model as query params, and prepare a settings struct:
Query values are part of the Path Model.
main.go
type Path struct {
Home bool `path:"/"`
Dashboard bool `path:"/:CityID"`
CityID int
Units *driver.Units `query:"units"`
Days *int `query:"days"`
}
// function to derive settings from path
func NewSettigns(p Path) Settings {
// default values
s := Settings{
Days: 7,
Units: driver.Metric,
}
if p.Days != nil {
s.Days = max(*p.Days, 1)
}
if p.Units != nil && *p.Units == driver.Imperial {
s.Units = driver.Imperial
}
return s
}
type Settings struct {
Units driver.Units
Days int
}
func (s Settings) DaysQuery() *int {
// omit default
if s.Days == 7 {
return nil
}
return &s.Days
}
func (s Settings) UnitsQuery() *driver.Units {
// omit default
if s.Units == driver.Metric {
return nil
}
return &s.Units
}
Notice that query params use reference types. Otherwise, they get a zero value and always appear in the URI.
State
Next, inject this state into the app.
This uses the same derived-state pattern from State.
app.gox
type App struct {
/* ... */
settings doors.Beam[Settings]
}
main.go
func main() {
/* ... */
doors.UseModel(r, func(r doors.RequestModel, s doors.Source[Path]) doors.Response {
/* ... */
// new state piece
settings := doors.NewBeam(s, NewSettigns)
return doors.ResponseComp(App{path: s, city: city, settings: settings})
})
/* ... */
}
Next, we need it in the dashboard:
dashboard.gox
type dashboard struct {
city int
settings doors.Beam[Settings]
}
app.gox
elem (a App) Main() {
~/* ... */
~dashboard{
city: city,
// new field
settings: a.settings,
}
~/* ... */
}
Code
main.go
package main
import (
"net/http"
"github.com/doors-dev/doors"
"github.com/doors-dev/tutorial/driver"
)
type Path struct {
Home bool `path:"/"`
Dashboard bool `path:"/:CityID"`
CityID int
Units *driver.Units `query:"units"`
Days *int `query:"days"`
}
func NewSettigns(p Path) Settings {
s := Settings{
Days: 7,
Units: driver.Metric,
}
if p.Days != nil {
s.Days = max(*p.Days, 1)
}
if p.Units != nil && *p.Units == driver.Imperial {
s.Units = driver.Imperial
}
return s
}
type Settings struct {
Units driver.Units
Days int
}
func (s Settings) DaysQuery() *int {
if s.Days == 7 {
return nil
}
return &s.Days
}
func (s Settings) UnitsQuery() *driver.Units {
if s.Units == driver.Metric {
return nil
}
return &s.Units
}
func main() {
r := doors.NewRouter()
doors.UseModel(r, func(r doors.RequestModel, s doors.Source[Path]) doors.Response {
city := doors.NewBeam(s, func(p Path) int {
if p.Home {
return -1
}
return p.CityID
})
settings := doors.NewBeam(s, NewSettigns)
return doors.ResponseComp(App{path: s, city: city, settings: settings})
})
if err := http.ListenAndServe(":8080", r); err != nil {
panic(err)
}
}
app.gox
package main
import (
"context"
"github.com/doors-dev/doors"
"github.com/doors-dev/gox"
)
type App struct {
path doors.Source[Path]
city doors.Beam[int]
settings doors.Beam[Settings]
}
elem (a App) Main() {
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link
(doors.ResourceExternal("https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"))
rel="stylesheet">
</head>
<body>
<main class="container">
~(doors.Sub(a.city, elem(city int) {
~(if city == -1 {
~(LocationSelector(func(ctx context.Context, city int) {
a.path.Mutate(ctx, func(p Path) Path {
p.Home = false
p.Dashboard = true
p.CityID = city
return p
})
}))
} else {
~dashboard{
city: city,
settings: a.settings,
}
})
}))
</main>
</body>
</html>
}
dashboard.gox
package main
import (
"github.com/doors-dev/doors"
"github.com/doors-dev/gox"
"github.com/doors-dev/tutorial/driver"
)
type dashboard struct {
city int
settings doors.Beam[Settings]
}
elem (d dashboard) Main() {
~{
city, _ := driver.Locations.CitiesGet(d.city)
}
~(d.header(city))
~(doors.Sub(d.settings, elem(s Settings) {
~(d.menu(s))
}))
}
elem (d dashboard) header(city driver.City) {
~(if !city.IsValid() {
~(doors.Status(404))
<title>Not Found</title>
<h1>City Not Found</h1>
} else {
<title>~(city.Name) Weather</title>
<h1>Weather in ~(city.Name, ", ", city.Country.Name)</h1>
})
}
elem (d dashboard) menu(s Settings) {
<a
class="secondary"
(doors.ALink{
Model: Path{
Home: true,
Days: s.DaysQuery(),
Units: s.UnitsQuery(),
},
})
role="button">
Change
</a>
<nav>
~(d.navDays(s))
~(d.navUnits(s))
</nav>
}
elem (d dashboard) navDays(s Settings) {
<ul>
~(for i := range 7 {
~{
s.Days = i + 1
}
<li>
<a
class="secondary"
(doors.ALink{
Model: Path{
Dashboard: true,
CityID: d.city,
Days: s.DaysQuery(),
Units: s.UnitsQuery(),
},
})>
~(s.Days)
~(if s.Days == 1 {
day
} else {
days
})
</a>
</li>
})
</ul>
}
elem (d dashboard) navUnits(s Settings) {
<ul>
~(for _, unit := range []driver.Units{driver.Metric, driver.Imperial} {
<li>
~{
s.Units = unit
}
<a
class="secondary"
(doors.ALink{
Model: Path{
Dashboard: true,
CityID: d.city,
Days: s.DaysQuery(),
Units: s.UnitsQuery(),
},
})>
~(unit.String())
</a>
</li>
})
</ul>
}
