diff --git a/context.go b/context.go index 7ff2f9d..195d54c 100644 --- a/context.go +++ b/context.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "log" - "reflect" "sync" "github.com/go-via/via/h" @@ -138,18 +137,15 @@ func (c *Context) Signal(v any) *signal { sigID := genRandID() if v == nil { c.app.logErr(c, "failed to bind signal: nil signal value") - dummy := "Error" return &signal{ id: sigID, - v: reflect.ValueOf(dummy), - t: reflect.TypeOf(dummy), + val: "error", err: fmt.Errorf("context '%s' failed to bind signal '%s': nil signal value", c.id, sigID), } } sig := &signal{ id: sigID, - v: reflect.ValueOf(v), - t: reflect.TypeOf(v), + val: v, changed: true, } @@ -175,15 +171,14 @@ func (c *Context) injectSignals(sigs map[string]any) { for sigID, val := range sigs { if _, ok := c.signals.Load(sigID); !ok { c.signals.Store(sigID, &signal{ - id: sigID, - t: reflect.TypeOf(val), - v: reflect.ValueOf(val), + id: sigID, + val: val, }) continue } item, _ := c.signals.Load(sigID) if sig, ok := item.(*signal); ok { - sig.v = reflect.ValueOf(val) + sig.val = val sig.changed = false } } @@ -209,7 +204,7 @@ func (c *Context) prepareSignalsForPatch() map[string]any { return true } if sig.changed { - updatedSigs[sigID.(string)] = fmt.Sprintf("%v", sig.v) + updatedSigs[sigID.(string)] = fmt.Sprintf("%v", sig.val) } } return true @@ -288,7 +283,7 @@ func (c *Context) SyncSignals() { c.app.logWarn(c, "signal out of sync'%s': %v", sig.id, sig.err) } if sig.changed && sig.err == nil { - updatedSigs[id] = fmt.Sprintf("%v", sig.v) + updatedSigs[id] = fmt.Sprintf("%v", sig.val) sig.changed = false } return true // continue iteration diff --git a/go.mod b/go.mod index c77a16f..8e5b175 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.25.4 require maragu.dev/gomponents v1.2.0 require ( - github.com/andybalholm/brotli v1.2.0 github.com/fsnotify/fsnotify v1.9.0 github.com/go-via/via-plugin-picocss v0.1.0 github.com/starfederation/datastar-go v1.0.3 @@ -14,6 +13,7 @@ require ( require ( github.com/CAFxX/httpcompression v0.0.9 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/kr/pretty v0.1.0 // indirect diff --git a/internal/examples/chatroom/main.go b/internal/examples/chatroom/main.go index 23e09df..e5a53ed 100644 --- a/internal/examples/chatroom/main.go +++ b/internal/examples/chatroom/main.go @@ -153,9 +153,7 @@ func main() { chat := currentRoom.GetData(func(c *Chat) Chat { n := len(c.Entries) start := n - 50 - if start < 0 { - start = 0 - } + start = max(start, 0) trimmed := make([]ChatEntry, n-start) copy(trimmed, c.Entries[start:]) out := *c diff --git a/signal.go b/signal.go index 710e938..d814958 100644 --- a/signal.go +++ b/signal.go @@ -2,7 +2,6 @@ package via import ( "fmt" - "reflect" "strconv" "strings" @@ -16,8 +15,7 @@ import ( // reactively on an html element. type signal struct { id string - v reflect.Value - t reflect.Type + val any changed bool err error } @@ -45,7 +43,7 @@ func (s *signal) Bind() h.H { return h.Data("bind", s.id) } -// Text binds the signal value to an html element as text. +// Text binds the signal value to an html span element as text. // // Example: // @@ -57,130 +55,52 @@ func (s *signal) Text() h.H { // SetValue updates the signal’s value and marks it for synchronization with the browser. // The change will be propagated to the browser using *Context.Sync() or *Context.SyncSignals(). func (s *signal) SetValue(v any) { - val := reflect.ValueOf(v) - typ := reflect.TypeOf(v) - if typ != s.t { - s.err = fmt.Errorf("expected type '%s', got '%s'", s.t.String(), typ.String()) - return - } - s.v = val + s.val = v s.changed = true s.err = nil } // String return the signal value as a string. func (s *signal) String() string { - return fmt.Sprintf("%v", s.v) + return fmt.Sprintf("%v", s.val) } // Bool tries to read the signal value as a bool. // Returns the value or false on failure. func (s *signal) Bool() bool { - switch s.v.Kind() { - case reflect.Bool: - return s.v.Bool() - case reflect.String: - val := strings.ToLower(s.v.String()) - return val == "true" || val == "1" || val == "yes" || val == "on" - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return s.v.Int() != 0 - case reflect.Float32, reflect.Float64: - return s.v.Float() != 0 - default: - return false - } + val := strings.ToLower(s.String()) + return val == "true" || val == "1" || val == "yes" || val == "on" } // Int tries to read the signal value as an int. // Returns the value or 0 on failure. func (s *signal) Int() int { - if n, err := strconv.Atoi(s.v.String()); err == nil { + if n, err := strconv.Atoi(s.String()); err == nil { return n } - if s.v.CanInt() { - return int(s.v.Int()) - } - if s.v.CanFloat() { - return int(s.v.Float()) - } return 0 } // Int64 tries to read the signal value as an int64. // Returns the value or 0 on failure. func (s *signal) Int64() int64 { - if n, err := strconv.ParseInt(s.v.String(), 10, 64); err == nil { + if n, err := strconv.ParseInt(s.String(), 10, 64); err == nil { return n } - if s.v.CanInt() { - return s.v.Int() - } - if s.v.CanFloat() { - return int64(s.v.Float()) - } - return 0 -} - -// Uint64 tries to read the signal value as an uint64. -// Returns the value or 0 on failure. -func (s *signal) Uint64() uint64 { - if n, err := strconv.ParseUint(s.v.String(), 10, 64); err == nil { - return n - } - if s.v.CanUint() { - return s.v.Uint() - } - if s.v.CanFloat() { - return uint64(s.v.Float()) - } return 0 } // Float64 tries to read the signal value as a float64. // Returns the value or 0.0 on failure. -func (s *signal) Float64() float64 { - if n, err := strconv.ParseFloat(s.v.String(), 64); err == nil { +func (s *signal) Float() float64 { + if n, err := strconv.ParseFloat(s.String(), 64); err == nil { return n } - if s.v.CanFloat() { - return s.v.Float() - } - if s.v.CanInt() { - return float64(s.v.Int()) - } return 0.0 } -// Complex128 tries to read the signal value as a complex128. -// Returns the value or 0 on failure. -func (s *signal) Complex128() complex128 { - if s.v.Kind() == reflect.Complex128 { - return s.v.Complex() - } - if s.v.Kind() == reflect.String { - if n, err := strconv.ParseComplex(s.v.String(), 128); err == nil { - return n - } - } - if s.v.CanFloat() { - return complex(s.v.Float(), 0) - } - if s.v.CanInt() { - return complex(float64(s.v.Int()), 0) - } - return complex(0, 0) -} - // Bytes tries to read the signal value as a []byte // Returns the value or an empty []byte on failure. func (s *signal) Bytes() []byte { - switch s.v.Kind() { - case reflect.Slice: - if s.v.Type().Elem().Kind() == reflect.Uint8 { - return s.v.Bytes() - } - case reflect.String: - return []byte(s.v.String()) - } - return make([]byte, 0) + return []byte(s.String()) } diff --git a/via.go b/via.go index 7b12636..1d1deed 100644 --- a/via.go +++ b/via.go @@ -145,24 +145,21 @@ func (v *V) Page(route string, initContextFn func(c *Context)) { if v.cfg.DevMode { v.devModePersist(c) } - headElements := v.documentHeadIncludes - headElements = append(headElements, h.Meta(h.Data("signals", fmt.Sprintf("{'via-ctx':'%s'}", id)))) - unloadJS := fmt.Sprintf(` -window.addEventListener('beforeunload', (evt) => { - navigator.sendBeacon('/_session/close', '%s'); -});`, c.id) - headElements = append(headElements, h.Meta(h.Data("init", unloadJS))) - headElements = append(headElements, h.Meta(h.Data("init", "@get('/_sse')"))) - bottomBodyElements := []h.H{c.view()} - bottomBodyElements = append(bottomBodyElements, v.documentFootIncludes...) + v.AppendToHead(h.Meta(h.Data("signals", fmt.Sprintf("{'via-ctx':'%s'}", id)))) + v.AppendToHead(h.Meta(h.Data("init", "@get('/_sse')"))) + v.AppendToHead(h.Meta(h.Data("init", fmt.Sprintf(`window.addEventListener('beforeunload', (evt) => { + navigator.sendBeacon('/_session/close', '%s');});`, c.id)))) + bodyElements := []h.H{c.view()} + bodyElements = append(bodyElements, v.documentFootIncludes...) if v.cfg.DevMode { - bottomBodyElements = append(bottomBodyElements, h.Script(h.Type("module"), h.Src("https://cdn.jsdelivr.net/gh/dataSPA/dataSPA-inspector@latest/dataspa-inspector.bundled.js"))) - bottomBodyElements = append(bottomBodyElements, h.Raw("")) + bodyElements = append(bodyElements, h.Script(h.Type("module"), + h.Src("https://cdn.jsdelivr.net/gh/dataSPA/dataSPA-inspector@latest/dataspa-inspector.bundled.js"))) + bodyElements = append(bodyElements, h.Raw("")) } view := h.HTML5(h.HTML5Props{ Title: v.cfg.DocumentTitle, - Head: headElements, - Body: bottomBodyElements, + Head: v.documentHeadIncludes, + Body: bodyElements, HTMLAttrs: []h.H{}, }) _ = view.Render(w) diff --git a/via_test.go b/via_test.go index 668f1da..ed816ed 100644 --- a/via_test.go +++ b/via_test.go @@ -49,7 +49,7 @@ func TestSignal(t *testing.T) { w := httptest.NewRecorder() v.mux.ServeHTTP(w, req) - assert.Equal(t, "test", sig.v.Interface()) + assert.Equal(t, "test", sig.String()) } func TestAction(t *testing.T) {