feat: add path params; add pathparams example
This commit is contained in:
53
context.go
53
context.go
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"maps"
|
||||||
"reflect"
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -20,12 +21,13 @@ type Context struct {
|
|||||||
route string
|
route string
|
||||||
app *V
|
app *V
|
||||||
view func() h.H
|
view func() h.H
|
||||||
|
routeParams map[string]string
|
||||||
componentRegistry map[string]*Context
|
componentRegistry map[string]*Context
|
||||||
parentPageCtx *Context
|
parentPageCtx *Context
|
||||||
patchChan chan patch
|
patchChan chan patch
|
||||||
actionRegistry map[string]func()
|
actionRegistry map[string]func()
|
||||||
signals *sync.Map
|
signals *sync.Map
|
||||||
mutex sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctxDisposedChan chan struct{}
|
ctxDisposedChan chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,8 +172,8 @@ func (c *Context) Signal(v any) *signal {
|
|||||||
changed: true,
|
changed: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mutex.Lock()
|
c.mu.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.isComponent() { // components register signals on parent page
|
if c.isComponent() { // components register signals on parent page
|
||||||
c.parentPageCtx.signals.Store(sigID, sig)
|
c.parentPageCtx.signals.Store(sigID, sig)
|
||||||
} else {
|
} else {
|
||||||
@@ -187,8 +189,8 @@ func (c *Context) injectSignals(sigs map[string]any) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.mutex.Lock()
|
c.mu.Lock()
|
||||||
defer c.mutex.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
for sigID, val := range sigs {
|
for sigID, val := range sigs {
|
||||||
if _, ok := c.signals.Load(sigID); !ok {
|
if _, ok := c.signals.Load(sigID); !ok {
|
||||||
@@ -218,8 +220,8 @@ func (c *Context) getPatchChan() chan patch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Context) prepareSignalsForPatch() map[string]any {
|
func (c *Context) prepareSignalsForPatch() map[string]any {
|
||||||
c.mutex.RLock()
|
c.mu.RLock()
|
||||||
defer c.mutex.RUnlock()
|
defer c.mu.RUnlock()
|
||||||
updatedSigs := make(map[string]any)
|
updatedSigs := make(map[string]any)
|
||||||
c.signals.Range(func(sigID, value any) bool {
|
c.signals.Range(func(sigID, value any) bool {
|
||||||
if sig, ok := value.(*signal); ok {
|
if sig, ok := value.(*signal); ok {
|
||||||
@@ -322,6 +324,42 @@ func (c *Context) stopAllRoutines() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Context) injectRouteParams(params map[string]string) {
|
||||||
|
if params == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m := make(map[string]string)
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
maps.Copy(m, params)
|
||||||
|
c.routeParams = m
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPathParam retrieves the value from the page request URL for the given parameter name
|
||||||
|
// or an empty string if not found.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// v.Page("/users/{user_id}", func(c *via.Context) {
|
||||||
|
//
|
||||||
|
// userID := GetPathParam("user_id")
|
||||||
|
//
|
||||||
|
// c.View(func() h.H {
|
||||||
|
// return h.Div(
|
||||||
|
// h.H1(h.Textf("User ID: %s", userID)),
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
func (c *Context) GetPathParam(param string) string {
|
||||||
|
c.mu.RLock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
if p, ok := c.routeParams[param]; ok {
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func newContext(id string, route string, v *V) *Context {
|
func newContext(id string, route string, v *V) *Context {
|
||||||
if v == nil {
|
if v == nil {
|
||||||
log.Fatal("create context failed: app pointer is nil")
|
log.Fatal("create context failed: app pointer is nil")
|
||||||
@@ -330,6 +368,7 @@ func newContext(id string, route string, v *V) *Context {
|
|||||||
return &Context{
|
return &Context{
|
||||||
id: id,
|
id: id,
|
||||||
route: route,
|
route: route,
|
||||||
|
routeParams: make(map[string]string),
|
||||||
app: v,
|
app: v,
|
||||||
componentRegistry: make(map[string]*Context),
|
componentRegistry: make(map[string]*Context),
|
||||||
actionRegistry: make(map[string]func()),
|
actionRegistry: make(map[string]func()),
|
||||||
|
|||||||
69
internal/examples/pathparams/main.go
Normal file
69
internal/examples/pathparams/main.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-via/via"
|
||||||
|
"github.com/go-via/via-plugin-picocss/picocss"
|
||||||
|
. "github.com/go-via/via/h"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := via.New()
|
||||||
|
|
||||||
|
v.Config(via.Options{
|
||||||
|
Plugins: []via.Plugin{picocss.Default},
|
||||||
|
})
|
||||||
|
|
||||||
|
v.Page("/counters/{counter_id}/{start_at_step}", func(c *via.Context) {
|
||||||
|
|
||||||
|
counterID := c.GetPathParam("counter_id")
|
||||||
|
startAtStep, _ := strconv.Atoi(c.GetPathParam("start_at_step"))
|
||||||
|
|
||||||
|
count := 0
|
||||||
|
step := c.Signal(startAtStep)
|
||||||
|
|
||||||
|
increment := c.Action(func() {
|
||||||
|
count += step.Int()
|
||||||
|
c.Sync()
|
||||||
|
})
|
||||||
|
|
||||||
|
c.View(func() H {
|
||||||
|
return Main(Class("container"),
|
||||||
|
|
||||||
|
Nav(
|
||||||
|
Ul(
|
||||||
|
Li(
|
||||||
|
Strong(Text("⚡Via Example")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Ul(
|
||||||
|
Li(
|
||||||
|
Raw(`<svg xmlns="http://www.w3.org/2000/svg" height="18" width="24.25" viewBox="0 0 496 512" class="icon-github"><path d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"></path></svg>`),
|
||||||
|
A(Class("contrast"),
|
||||||
|
Text(" GitHub"),
|
||||||
|
Href("https://github.com/go-via/via"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
Section(
|
||||||
|
Article(
|
||||||
|
H3(Text(counterID)),
|
||||||
|
Hr(),
|
||||||
|
H5(Textf("Count %d", count)),
|
||||||
|
H6(Text("Step "), step.Text()),
|
||||||
|
FieldSet(Role("group"),
|
||||||
|
Input(Type("number"), step.Bind()),
|
||||||
|
Button(Text("Increment"), increment.OnClick()),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
v.Start()
|
||||||
|
}
|
||||||
@@ -46,7 +46,8 @@ func (r *OnIntervalRoutine) Stop() {
|
|||||||
r.localInterrupt <- struct{}{}
|
r.localInterrupt <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOnIntervalRoutine(ctxDisposedChan chan struct{}, duration time.Duration, handler func()) *OnIntervalRoutine {
|
func newOnIntervalRoutine(ctxDisposedChan chan struct{},
|
||||||
|
duration time.Duration, handler func()) *OnIntervalRoutine {
|
||||||
r := &OnIntervalRoutine{
|
r := &OnIntervalRoutine{
|
||||||
ctxDisposed: ctxDisposedChan,
|
ctxDisposed: ctxDisposedChan,
|
||||||
localInterrupt: make(chan struct{}),
|
localInterrupt: make(chan struct{}),
|
||||||
|
|||||||
20
via.go
20
via.go
@@ -161,6 +161,8 @@ func (v *V) Page(route string, initContextFn func(c *Context)) {
|
|||||||
}
|
}
|
||||||
id := fmt.Sprintf("%s_/%s", route, genRandID())
|
id := fmt.Sprintf("%s_/%s", route, genRandID())
|
||||||
c := newContext(id, route, v)
|
c := newContext(id, route, v)
|
||||||
|
routeParams := extractParams(route, r.URL.Path)
|
||||||
|
c.injectRouteParams(routeParams)
|
||||||
initContextFn(c)
|
initContextFn(c)
|
||||||
v.registerCtx(c)
|
v.registerCtx(c)
|
||||||
if v.cfg.DevMode {
|
if v.cfg.DevMode {
|
||||||
@@ -484,3 +486,21 @@ func genRandID() string {
|
|||||||
rand.Read(b)
|
rand.Read(b)
|
||||||
return hex.EncodeToString(b)[:8]
|
return hex.EncodeToString(b)[:8]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractParams(pattern, path string) map[string]string {
|
||||||
|
p := strings.Split(strings.Trim(pattern, "/"), "/")
|
||||||
|
u := strings.Split(strings.Trim(path, "/"), "/")
|
||||||
|
if len(p) != len(u) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
params := make(map[string]string)
|
||||||
|
for i := range p {
|
||||||
|
if strings.HasPrefix(p[i], "{") && strings.HasSuffix(p[i], "}") {
|
||||||
|
key := p[i][1 : len(p[i])-1] // remove {}
|
||||||
|
params[key] = u[i]
|
||||||
|
} else if p[i] != u[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user