feat: introduce component support
This commit is contained in:
@@ -59,7 +59,7 @@ func main() {
|
|||||||
|
|
||||||
## 🚧 Experimental
|
## 🚧 Experimental
|
||||||
Via is still a newborn.
|
Via is still a newborn.
|
||||||
- `v0.1` nears.
|
- Version `0.1.0` nears.
|
||||||
- Expect chaos.
|
- Expect chaos.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
@@ -76,6 +76,6 @@ Via is still a newborn.
|
|||||||
Via builds upon the work of these amazing projects:
|
Via builds upon the work of these amazing projects:
|
||||||
|
|
||||||
- 🚀 [Datastar](https://data-star.dev) - The hypermedia powerhouse at the core of Via. It powers browser reactivity through Signals and enables real-time HTML/Signal patches over an always-on SSE event stream.
|
- 🚀 [Datastar](https://data-star.dev) - The hypermedia powerhouse at the core of Via. It powers browser reactivity through Signals and enables real-time HTML/Signal patches over an always-on SSE event stream.
|
||||||
- 🧩 [Gomponents](https://maragu.dev/gomponents) - The awesome project that enables Vias Go-native HTML composition through the `via/h` package.
|
- 🧩 [Gomponents](https://maragu.dev/gomponents) - The awesome project that gifts Via with Go-native HTML composition superpowers through the `via/h` package.
|
||||||
|
|
||||||
> Thank you for building something that doesn’t just function — it inspires. 🫶
|
> Thank you for building something that doesn’t just function — it inspires. 🫶
|
||||||
|
|||||||
58
context.go
58
context.go
@@ -17,9 +17,10 @@ import (
|
|||||||
// It binds user state and actions, manages reactive signals, and defines UI through View.
|
// It binds user state and actions, manages reactive signals, and defines UI through View.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
id string
|
id string
|
||||||
route string
|
|
||||||
app *via
|
app *via
|
||||||
view func() h.H
|
view func() h.H
|
||||||
|
components map[string]*Context
|
||||||
|
componentsMux sync.RWMutex
|
||||||
sse *datastar.ServerSentEventGenerator
|
sse *datastar.ServerSentEventGenerator
|
||||||
actionRegistry map[string]func()
|
actionRegistry map[string]func()
|
||||||
signals map[string]*signal
|
signals map[string]*signal
|
||||||
@@ -34,23 +35,47 @@ type Context struct {
|
|||||||
func (c *Context) View(f func() h.H) {
|
func (c *Context) View(f func() h.H) {
|
||||||
if f == nil {
|
if f == nil {
|
||||||
c.app.logErr(c, "failed to bind view to context: nil func")
|
c.app.logErr(c, "failed to bind view to context: nil func")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
c.view = func() h.H { return h.Div(h.ID(c.id), f()) }
|
c.view = func() h.H { return h.Div(h.ID(c.id), f()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
type actionTrigger struct {
|
// Component registers a sub context that is self contained self contained with it's own
|
||||||
id string
|
// view, state actions and signals and returns the DOM node that can be added to the view
|
||||||
|
// of the parent.
|
||||||
|
func (c *Context) Component(f func(c *Context)) func() h.H {
|
||||||
|
id := c.id + "/_component/" + genRandID()
|
||||||
|
compCtx := newContext(id, c.app)
|
||||||
|
f(compCtx)
|
||||||
|
compCtx.sse = c.sse
|
||||||
|
// c.componentsMux.Lock()
|
||||||
|
// defer c.componentsMux.Unlock()
|
||||||
|
//
|
||||||
|
// c.components[id] = compCtx
|
||||||
|
c.app.contextRegistryMutex.Lock()
|
||||||
|
defer c.app.contextRegistryMutex.Unlock()
|
||||||
|
c.app.contextRegistry[id] = compCtx
|
||||||
|
|
||||||
|
return compCtx.view
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *actionTrigger) OnClick() h.H {
|
// Action registers an event handler and returns a trigger to that event that
|
||||||
return h.Data("on:click", fmt.Sprintf("@get('/_action/%s')", a.id))
|
// that can be added to the view fn as any other via.h element.
|
||||||
}
|
|
||||||
|
|
||||||
// Action registers a named event handler callable from the browser.
|
|
||||||
//
|
//
|
||||||
// Use h.OnClick("actionName") or similar event bindings to trigger actions.
|
// Example:
|
||||||
// Signal updates from the browser are automatically injected in the context before the
|
//
|
||||||
// handler function executes.
|
// n := 0
|
||||||
|
// increment := c.Action(func(){
|
||||||
|
// n++
|
||||||
|
// c.Sync()
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// c.View(func() h.H {
|
||||||
|
// return h.Div(
|
||||||
|
// h.P(h.Textf("Value of n: %d", n)),
|
||||||
|
// h.Button(h.Text("Increment n"), increment.OnClick()),
|
||||||
|
// )
|
||||||
|
// })
|
||||||
func (c *Context) Action(f func()) *actionTrigger {
|
func (c *Context) Action(f func()) *actionTrigger {
|
||||||
// if id == "" {
|
// if id == "" {
|
||||||
// c.app.logErr(c, "failed to bind action to context: id is ''")
|
// c.app.logErr(c, "failed to bind action to context: id is ''")
|
||||||
@@ -132,7 +157,7 @@ func (c *Context) injectSignals(sigs map[string]any) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync pushes the current view state and signal changes to the browser immediately
|
// Sync pushes the current view state and signal changes to the browser immediately
|
||||||
// over the live SSE connection.
|
// over the live SSE event stream.
|
||||||
func (c *Context) Sync() {
|
func (c *Context) Sync() {
|
||||||
if c.sse == nil {
|
if c.sse == nil {
|
||||||
c.app.logErr(c, "sync view failed: no sse connection")
|
c.app.logErr(c, "sync view failed: no sse connection")
|
||||||
@@ -157,7 +182,8 @@ func (c *Context) Sync() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncElements pushes an immediate html patch to the browser that merges DOM
|
// SyncElements pushes an immediate html patch over the live SSE stream to the
|
||||||
|
// browser that merges with the DOM
|
||||||
//
|
//
|
||||||
// For the merge to occur, the top level element in the patch needs to have
|
// For the merge to occur, the top level element in the patch needs to have
|
||||||
// an ID that matches the ID of an element that already sits in the view.
|
// an ID that matches the ID of an element that already sits in the view.
|
||||||
@@ -171,7 +197,8 @@ func (c *Context) Sync() {
|
|||||||
// h.P(h.Text("Hello from Via!"))
|
// h.P(h.Text("Hello from Via!"))
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// Then, the merge will only occur if the ID of the top level element mattches 'my-element'.
|
// Then, the merge will only occur if the ID of the top level element in the patch
|
||||||
|
// matches 'my-element'.
|
||||||
func (c *Context) SyncElements(elem h.H) {
|
func (c *Context) SyncElements(elem h.H) {
|
||||||
if c.sse == nil {
|
if c.sse == nil {
|
||||||
c.app.logErr(c, "sync element failed: no sse connection")
|
c.app.logErr(c, "sync element failed: no sse connection")
|
||||||
@@ -190,7 +217,7 @@ func (c *Context) SyncElements(elem h.H) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SyncSignals pushes the current signal changes to the browser immediately
|
// SyncSignals pushes the current signal changes to the browser immediately
|
||||||
// over the live SSE connection.
|
// over the live SSE event stream.
|
||||||
func (c *Context) SyncSignals() {
|
func (c *Context) SyncSignals() {
|
||||||
if c.sse == nil {
|
if c.sse == nil {
|
||||||
c.app.logErr(c, "sync signals failed: sse connection not found")
|
c.app.logErr(c, "sync signals failed: sse connection not found")
|
||||||
@@ -217,6 +244,7 @@ func newContext(id string, a *via) *Context {
|
|||||||
return &Context{
|
return &Context{
|
||||||
id: id,
|
id: id,
|
||||||
app: a,
|
app: a,
|
||||||
|
components: make(map[string]*Context),
|
||||||
actionRegistry: make(map[string]func()),
|
actionRegistry: make(map[string]func()),
|
||||||
signals: make(map[string]*signal),
|
signals: make(map[string]*signal),
|
||||||
createdAt: time.Now(),
|
createdAt: time.Now(),
|
||||||
|
|||||||
@@ -1 +1,95 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-via/via"
|
||||||
|
"github.com/go-via/via/h"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
v := via.New()
|
||||||
|
|
||||||
|
v.Page("/", func(c *via.Context) {
|
||||||
|
counter1 := c.Component(counterComponent)
|
||||||
|
counter2 := c.Component(counterComponent)
|
||||||
|
|
||||||
|
c.View(func() h.H {
|
||||||
|
return h.Div(
|
||||||
|
counter1(),
|
||||||
|
counter2(),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
v.Start(":3000")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Counter struct{ Count int }
|
||||||
|
|
||||||
|
func counterComponent(c *via.Context) {
|
||||||
|
|
||||||
|
s := Counter{Count: 0}
|
||||||
|
|
||||||
|
step := c.Signal(1)
|
||||||
|
|
||||||
|
increment := c.Action(func() {
|
||||||
|
s.Count += step.Int()
|
||||||
|
c.Sync()
|
||||||
|
})
|
||||||
|
|
||||||
|
c.View(func() h.H {
|
||||||
|
return h.Div(
|
||||||
|
h.P(h.Textf("Count: %d", s.Count)),
|
||||||
|
h.P(h.Span(h.Text("Step: ")), h.Span(step.Text())),
|
||||||
|
h.Label(
|
||||||
|
h.Text("Update Step: "),
|
||||||
|
h.Input(h.Type("number"), step.Bind()),
|
||||||
|
),
|
||||||
|
h.Button(h.Text("Increment"), increment.OnClick()),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// c.View(func() h.H {
|
||||||
|
// return Layout(
|
||||||
|
// h.Div(
|
||||||
|
// h.Meta(h.Data("init", "@get('/_sse')")),
|
||||||
|
// h.P(h.Data("text", "$via-ctx")),
|
||||||
|
// h.Div(
|
||||||
|
// counter(),
|
||||||
|
// h.Data("signals:step", "1"),
|
||||||
|
// h.Label(h.Text("Step")),
|
||||||
|
// h.Input(h.Data("bind", "step")),
|
||||||
|
// h.Button(
|
||||||
|
// h.Text("Trigger foo"),
|
||||||
|
// h.Data("on:click", "@get('/_action/foo')"),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
|
||||||
|
// conterComponent := c.Component("counter1", CounterComponent)
|
||||||
|
//
|
||||||
|
// in c.View of page add CounterComponent
|
||||||
|
//
|
||||||
|
// func CounterComponent(c *via.Context){
|
||||||
|
// s := CounterState{ Count: 1 }
|
||||||
|
// step := c.Signal(1)
|
||||||
|
//
|
||||||
|
// c.View(func() h.H {
|
||||||
|
// return h.Div(
|
||||||
|
// h.P(h.Textf("Count: %d", s.Count)),
|
||||||
|
// h.Label(
|
||||||
|
// h.Text("Step"),
|
||||||
|
// h.Input(h.Type("number"), step.Bind()),
|
||||||
|
// ),
|
||||||
|
// h.Button(h.Text("Increment"), h.OnClick("inc")),
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
//
|
||||||
|
// c.Action("inc", func() {
|
||||||
|
// s.Count += step
|
||||||
|
// c.Sync()
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user