From 4d66096faa3e3c13e5d03a3f2ebfc37a6fb601ff Mon Sep 17 00:00:00 2001 From: Johan Lindh Date: Fri, 13 Dec 2024 10:27:59 +0100 Subject: [PATCH 1/3] wip --- README.md | 21 +++++ anygetter.go | 19 ++++ anysetter.go | 17 +--- bind_test.go | 92 +++++++++++++------ binder.go | 1 + binding.go | 63 +++++++------ bindinghook.go | 34 ++----- getter.go | 5 ++ htmlgetter.go | 42 --------- jaws_test.go | 2 +- makehtmlgetter.go | 93 +++++++++++++++++++ htmlgetter_test.go => makehtmlgetter_test.go | 94 ++++++++++++-------- setter.go | 4 - stringsetter_test.go | 8 ++ uia.go | 2 +- uibutton.go | 2 +- uicontainer_test.go | 6 +- uidiv.go | 2 +- uilabel.go | 2 +- uili.go | 2 +- uispan.go | 2 +- uitd.go | 2 +- uitr.go | 2 +- 23 files changed, 326 insertions(+), 191 deletions(-) create mode 100644 anygetter.go create mode 100644 getter.go create mode 100644 makehtmlgetter.go rename htmlgetter_test.go => makehtmlgetter_test.go (53%) diff --git a/README.md b/README.md index f4f47c0..b79a1d7 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,27 @@ router.GET("/jaws/*", func(c echo.Context) error { }) ``` +### Data binding + +HTML input elements (e.g. `jaws.RequestWriter.Range()`) require bi-directional data flow between the server and the browser. +The first argument to these is usually a `Binder[T]` where `T` is one of `string, float64, bool` or `time.Time`. + +A `Binder[T]` combines a (RW)Locker and a pointer to the value, and allows you to add chained setters, +getters and on-success handlers. + +### HTML rendering + +HTML output elements (e.g. `jaws.RequestWriter.Div()`) require a `jaws.HtmlGetter` or something that can +be made into one using `jaws.MakeHtmlGetter()`. In order of precedence, this can be: +* `jaws.HtmlGetter`: `JawsGetHtml(e *Element) template.HTML` to be used as-is. +* `jaws.Getter[template.HTML]`: `JawsGet(elem *Element) template.HTML` to be used as-is. +* `jaws.StringGetter`: `JawsGetString(e *Element) string` that will be escaped using `html.EscapeString`. +* `jaws.Getter[string]`: `JawsGet(elem *Element) string` that will be escaped using `html.EscapeString`. +* `jaws.AnyGetter`: `JawsGetAny(elem *Element) any` that will be rendered using `fmt.Sprint()` and escaped using `html.EscapeString`. +* `fmt.Stringer`: `String() string` that will be escaped using `html.EscapeString`. +* a static `template.HTML` or `string` to be used as-is with no HTML escaping. +* everything else is rendered using `fmt.Sprint()` and escaped using `html.EscapeString`. + ### Session handling JaWS has non-persistent session handling integrated. Sessions won't diff --git a/anygetter.go b/anygetter.go new file mode 100644 index 0000000..2d3c322 --- /dev/null +++ b/anygetter.go @@ -0,0 +1,19 @@ +package jaws + +type AnyGetter interface { + JawsGetAny(elem *Element) (value any) +} + +type anyGetter struct{ v any } + +func (g anyGetter) JawsGetAny(e *Element) any { + return g.v +} + +func (g anyGetter) JawsSetAny(*Element, any) error { + return ErrValueNotSettable +} + +func (g anyGetter) JawsGetTag(rq *Request) any { + return nil +} diff --git a/anysetter.go b/anysetter.go index 909aaac..b68f4ef 100644 --- a/anysetter.go +++ b/anysetter.go @@ -5,25 +5,12 @@ import ( ) type AnySetter interface { - JawsGetAny(e *Element) any + AnyGetter // JawsSetAny may return ErrValueUnchanged to indicate value was already set. + // It may panic if the type of v cannot be handled. JawsSetAny(e *Element, v any) (err error) } -type anyGetter struct{ v any } - -func (g anyGetter) JawsGetAny(e *Element) any { - return g.v -} - -func (g anyGetter) JawsSetAny(*Element, any) error { - return ErrValueNotSettable -} - -func (g anyGetter) JawsGetTag(rq *Request) any { - return nil -} - func makeAnySetter(v any) AnySetter { switch v := v.(type) { case AnySetter: diff --git a/bind_test.go b/bind_test.go index 04e49f6..53d7bdf 100644 --- a/bind_test.go +++ b/bind_test.go @@ -183,7 +183,7 @@ func testBind_Hook_Set[T comparable](t *testing.T, testval T) { calls2++ return bind.JawsSetLocked(elem, value) }) - if err := bind2.JawsSet(nil, blankval); err != nil { + if err := bind2.JawsSetAny(nil, blankval); err != nil { t.Error(err) } if calls1 != 3 { @@ -235,7 +235,7 @@ func testBind_Hook_Get[T comparable](t *testing.T, testval T) { if err := bind2.JawsSet(nil, blankval); err != nil { t.Error(err) } - if x := bind2.JawsGet(nil); x != blankval { + if x := bind2.JawsGetAny(nil); x != blankval { t.Error(x) } if calls1 != 2 { @@ -256,12 +256,22 @@ func testBind_Hooks[T comparable](t *testing.T, testval T) { testBind_Hook_Get(t, testval) } -func testBind_StringSetter(t *testing.T, v StringSetter) { - val := v.JawsGetString(nil) + "!" - if err := v.JawsSetString(nil, val); err != nil { +func testBind_StringSetter(t *testing.T, v Setter[string]) { + val := v.JawsGet(nil) + "!" + if err := v.JawsSet(nil, val); err != nil { + t.Error(err) + } + if x := v.JawsGet(nil); x != val { + t.Error(x) + } + as := v.(AnySetter) + if x := as.JawsGetAny(nil); x != val { + t.Error(x) + } + if err := as.JawsSetAny(nil, val+"?"); err != nil { t.Error(err) } - if x := v.JawsGetString(nil); x != val { + if x := as.JawsGetAny(nil); x != val+"?" { t.Error(x) } } @@ -271,16 +281,26 @@ func TestBindFunc_String(t *testing.T) { var val string testBind_Hooks(t, "foo") - testBind_StringSetter(t, Bind(&mu, &val).(StringSetter)) - testBind_StringSetter(t, Bind(&mu, &val).Success(func() {}).(StringSetter)) + testBind_StringSetter(t, Bind(&mu, &val)) + testBind_StringSetter(t, Bind(&mu, &val).Success(func() {})) } -func testBind_FloatSetter(t *testing.T, v FloatSetter) { - val := v.JawsGetFloat(nil) + 1 - if err := v.JawsSetFloat(nil, val); err != nil { +func testBind_FloatSetter(t *testing.T, v Setter[float64]) { + val := v.JawsGet(nil) + 1 + if err := v.JawsSet(nil, val); err != nil { + t.Error(err) + } + if x := v.JawsGet(nil); x != val { + t.Error(x) + } + as := v.(AnySetter) + if x := as.JawsGetAny(nil); x != val { + t.Error(x) + } + if err := as.JawsSetAny(nil, val+1); err != nil { t.Error(err) } - if x := v.JawsGetFloat(nil); x != val { + if x := as.JawsGetAny(nil); x != val+1 { t.Error(x) } } @@ -290,16 +310,26 @@ func TestBindFunc_Float(t *testing.T) { var val float64 testBind_Hooks(t, float64(1.23)) - testBind_FloatSetter(t, Bind(&mu, &val).(FloatSetter)) - testBind_FloatSetter(t, Bind(&mu, &val).Success(func() {}).(FloatSetter)) + testBind_FloatSetter(t, Bind(&mu, &val)) + testBind_FloatSetter(t, Bind(&mu, &val).Success(func() {})) } -func testBind_BoolSetter(t *testing.T, v BoolSetter) { - val := !v.JawsGetBool(nil) - if err := v.JawsSetBool(nil, val); err != nil { +func testBind_BoolSetter(t *testing.T, v Setter[bool]) { + val := !v.JawsGet(nil) + if err := v.JawsSet(nil, val); err != nil { t.Error(err) } - if x := v.JawsGetBool(nil); x != val { + if x := v.JawsGet(nil); x != val { + t.Error(x) + } + as := v.(AnySetter) + if x := as.JawsGetAny(nil); x != val { + t.Error(x) + } + if err := as.JawsSetAny(nil, !val); err != nil { + t.Error(err) + } + if x := as.JawsGetAny(nil); x != !val { t.Error(x) } } @@ -309,16 +339,26 @@ func TestBindFunc_Bool(t *testing.T) { var val bool testBind_Hooks(t, true) - testBind_BoolSetter(t, Bind(&mu, &val).(BoolSetter)) - testBind_BoolSetter(t, Bind(&mu, &val).Success(func() {}).(BoolSetter)) + testBind_BoolSetter(t, Bind(&mu, &val)) + testBind_BoolSetter(t, Bind(&mu, &val).Success(func() {})) } -func testBind_TimeSetter(t *testing.T, v TimeSetter) { - val := v.JawsGetTime(nil).Add(time.Second) - if err := v.JawsSetTime(nil, val); err != nil { +func testBind_TimeSetter(t *testing.T, v Setter[time.Time]) { + val := v.JawsGet(nil).Add(time.Second) + if err := v.JawsSet(nil, val); err != nil { + t.Error(err) + } + if x := v.JawsGet(nil); x != val { + t.Error(x) + } + as := v.(AnySetter) + if x := as.JawsGetAny(nil); x != val { + t.Error(x) + } + if err := as.JawsSetAny(nil, val.Add(time.Second)); err != nil { t.Error(err) } - if x := v.JawsGetTime(nil); x != val { + if x := as.JawsGetAny(nil); x != val.Add(time.Second) { t.Error(x) } } @@ -328,6 +368,6 @@ func TestBindFunc_Time(t *testing.T) { var val time.Time testBind_Hooks(t, time.Now()) - testBind_TimeSetter(t, Bind(&mu, &val).(TimeSetter)) - testBind_TimeSetter(t, Bind(&mu, &val).Success(func() {}).(TimeSetter)) + testBind_TimeSetter(t, Bind(&mu, &val)) + testBind_TimeSetter(t, Bind(&mu, &val).Success(func() {})) } diff --git a/binder.go b/binder.go index 8e7d27c..bf83272 100644 --- a/binder.go +++ b/binder.go @@ -30,6 +30,7 @@ type BindSuccessHook func(*Element) (err error) type Binder[T comparable] interface { RWLocker Setter[T] + AnySetter JawsGetTag(*Request) any JawsBinderPrev() Binder[T] // returns the previous Binder in the chain, or nil diff --git a/binding.go b/binding.go index 8b9d2a9..2422eed 100644 --- a/binding.go +++ b/binding.go @@ -1,7 +1,5 @@ package jaws -import "time" - type binding[T comparable] struct { lock RWLocker ptr *T @@ -30,6 +28,10 @@ func (bind binding[T]) JawsGet(elem *Element) (value T) { return } +func (bind binding[T]) JawsGetAny(elem *Element) (value any) { + return bind.JawsGet(elem) +} + func (bind binding[T]) JawsSet(elem *Element, value T) (err error) { bind.lock.Lock() err = bind.JawsSetLocked(elem, value) @@ -37,6 +39,10 @@ func (bind binding[T]) JawsSet(elem *Element, value T) (err error) { return } +func (bind binding[T]) JawsSetAny(elem *Element, value any) (err error) { + return bind.JawsSet(elem, value.(T)) +} + func (bind binding[T]) JawsGetTag(*Request) any { return bind.ptr } @@ -101,34 +107,39 @@ func (bind binding[T]) Success(fn any) Binder[T] { } } -func (bind binding[T]) JawsGetString(elem *Element) string { - return any(bind.JawsGet(elem)).(string) -} -func (bind binding[T]) JawsSetString(e *Element, val string) (err error) { - return bind.JawsSet(e, any(val).(T)) -} +/* + func (bind binding[T]) JawsGetString(elem *Element) string { + return any(bind.JawsGet(elem)).(string) + } -func (bind binding[T]) JawsGetFloat(elem *Element) float64 { - return any(bind.JawsGet(elem)).(float64) -} -func (bind binding[T]) JawsSetFloat(e *Element, val float64) (err error) { - return bind.JawsSet(e, any(val).(T)) -} + func (bind binding[T]) JawsSetString(e *Element, val string) (err error) { + return bind.JawsSet(e, any(val).(T)) + } -func (bind binding[T]) JawsGetBool(elem *Element) bool { - return any(bind.JawsGet(elem)).(bool) -} -func (bind binding[T]) JawsSetBool(e *Element, val bool) (err error) { - return bind.JawsSet(e, any(val).(T)) -} + func (bind binding[T]) JawsGetFloat(elem *Element) float64 { + return any(bind.JawsGet(elem)).(float64) + } -func (bind binding[T]) JawsGetTime(elem *Element) time.Time { - return any(bind.JawsGet(elem)).(time.Time) -} -func (bind binding[T]) JawsSetTime(elem *Element, value time.Time) error { - return bind.JawsSet(elem, any(value).(T)) -} + func (bind binding[T]) JawsSetFloat(e *Element, val float64) (err error) { + return bind.JawsSet(e, any(val).(T)) + } + + func (bind binding[T]) JawsGetBool(elem *Element) bool { + return any(bind.JawsGet(elem)).(bool) + } + + func (bind binding[T]) JawsSetBool(e *Element, val bool) (err error) { + return bind.JawsSet(e, any(val).(T)) + } + func (bind binding[T]) JawsGetTime(elem *Element) time.Time { + return any(bind.JawsGet(elem)).(time.Time) + } + + func (bind binding[T]) JawsSetTime(elem *Element, value time.Time) error { + return bind.JawsSet(elem, any(value).(T)) + } +*/ func wrapSuccessHook(fn any) (hook BindSuccessHook) { switch fn := fn.(type) { case func(): diff --git a/bindinghook.go b/bindinghook.go index 486fd34..a98b6b6 100644 --- a/bindinghook.go +++ b/bindinghook.go @@ -1,7 +1,5 @@ package jaws -import "time" - type successHooker interface { JawsBinderSuccess(elem *Element) (err error) } @@ -30,6 +28,10 @@ func (bind *BindingHook[T]) JawsGet(elem *Element) T { return bind.JawsGetLocked(elem) } +func (bind *BindingHook[T]) JawsGetAny(elem *Element) (value any) { + return bind.JawsGet(elem) +} + func (bind *BindingHook[T]) JawsSetLocked(elem *Element, value T) error { if bind.BindSetHook != nil { return bind.BindSetHook(bind.Binder, elem, value) @@ -62,32 +64,8 @@ func (bind *BindingHook[T]) JawsSet(elem *Element, value T) (err error) { return } -func (bind *BindingHook[T]) JawsGetString(elem *Element) string { - return any(bind.JawsGet(elem)).(string) -} -func (bind *BindingHook[T]) JawsSetString(e *Element, val string) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind *BindingHook[T]) JawsGetFloat(elem *Element) float64 { - return any(bind.JawsGet(elem)).(float64) -} -func (bind *BindingHook[T]) JawsSetFloat(e *Element, val float64) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind *BindingHook[T]) JawsGetBool(elem *Element) bool { - return any(bind.JawsGet(elem)).(bool) -} -func (bind *BindingHook[T]) JawsSetBool(e *Element, val bool) (err error) { - return bind.JawsSet(e, any(val).(T)) -} - -func (bind *BindingHook[T]) JawsGetTime(elem *Element) time.Time { - return any(bind.JawsGet(elem)).(time.Time) -} -func (bind *BindingHook[T]) JawsSetTime(elem *Element, value time.Time) error { - return bind.JawsSet(elem, any(value).(T)) +func (bind *BindingHook[T]) JawsSetAny(elem *Element, value any) error { + return bind.JawsSet(elem, value.(T)) } // SetLocked returns a Binder[T] that will call fn instead of JawsSetLocked. diff --git a/getter.go b/getter.go new file mode 100644 index 0000000..17307af --- /dev/null +++ b/getter.go @@ -0,0 +1,5 @@ +package jaws + +type Getter[T comparable] interface { + JawsGet(elem *Element) (value T) +} diff --git a/htmlgetter.go b/htmlgetter.go index eabb762..ab82f46 100644 --- a/htmlgetter.go +++ b/htmlgetter.go @@ -1,51 +1,9 @@ package jaws import ( - "fmt" - "html" "html/template" - "sync/atomic" ) type HtmlGetter interface { JawsGetHtml(e *Element) template.HTML } - -type htmlGetter struct{ v template.HTML } - -func (g htmlGetter) JawsGetHtml(e *Element) template.HTML { - return g.v -} - -func (g htmlGetter) JawsGetTag(rq *Request) any { - return nil -} - -type htmlStringGetter struct{ sg StringGetter } - -func (g htmlStringGetter) JawsGetHtml(e *Element) template.HTML { - return template.HTML(html.EscapeString(g.sg.JawsGetString(e))) // #nosec G203 -} - -func (g htmlStringGetter) JawsGetTag(rq *Request) any { - return g.sg -} - -func makeHtmlGetter(v any) HtmlGetter { - switch v := v.(type) { - case HtmlGetter: - return v - case StringGetter: - return htmlStringGetter{v} - case *atomic.Value: - return atomicSetter{v} - case template.HTML: - return htmlGetter{v} - case fmt.Stringer: - return htmlStringGetter{stringerGetter{v}} - case string: - h := template.HTML(v) // #nosec G203 - return htmlGetter{h} - } - panic(fmt.Errorf("expected string, jaws.StringGetter or jaws.HtmlGetter, not %T", v)) -} diff --git a/jaws_test.go b/jaws_test.go index e804279..66468e8 100644 --- a/jaws_test.go +++ b/jaws_test.go @@ -299,7 +299,7 @@ func TestJaws_CleansUpUnconnected(t *testing.T) { for i := 0; i < numReqs; i++ { rq := jw.NewRequest(hr) if (i % (numReqs / 5)) == 0 { - rq.NewElement(NewUiDiv(makeHtmlGetter("meh"))) + rq.NewElement(NewUiDiv(MakeHtmlGetter("meh"))) } err := context.Cause(rq.ctx) if err == nil && rq.lastWrite.Before(deadline) { diff --git a/makehtmlgetter.go b/makehtmlgetter.go new file mode 100644 index 0000000..5accfdc --- /dev/null +++ b/makehtmlgetter.go @@ -0,0 +1,93 @@ +package jaws + +import ( + "fmt" + "html" + "html/template" +) + +type htmlGetter struct{ v template.HTML } + +func (g htmlGetter) JawsGetHtml(e *Element) template.HTML { + return g.v +} + +func (g htmlGetter) JawsGetTag(rq *Request) any { + return nil +} + +type htmlGetterStringGetter struct{ sg StringGetter } + +func (g htmlGetterStringGetter) JawsGetHtml(e *Element) template.HTML { + return template.HTML(html.EscapeString(g.sg.JawsGetString(e))) // #nosec G203 +} + +func (g htmlGetterStringGetter) JawsGetTag(rq *Request) any { + return g.sg +} + +type htmlGetterHTML struct{ sg Getter[template.HTML] } + +func (g htmlGetterHTML) JawsGetHtml(e *Element) template.HTML { + return g.sg.JawsGet(e) +} + +func (g htmlGetterHTML) JawsGetTag(rq *Request) any { + return g.sg +} + +type htmlGetterString struct{ sg Getter[string] } + +func (g htmlGetterString) JawsGetHtml(e *Element) template.HTML { + return template.HTML(html.EscapeString(g.sg.JawsGet(e))) // #nosec G203 +} + +func (g htmlGetterString) JawsGetTag(rq *Request) any { + return g.sg +} + +type htmlGetterAny struct{ ag AnyGetter } + +func (g htmlGetterAny) JawsGetHtml(e *Element) template.HTML { + s := fmt.Sprint(g.ag.JawsGetAny(e)) + return template.HTML(html.EscapeString(s)) // #nosec G203 +} + +func (g htmlGetterAny) JawsGetTag(rq *Request) any { + return g.ag +} + +// MakeHtmlGetter returns a HtmlGetter for v. +// +// Depending on the type of v, we return: +// +// - jaws.HtmlGetter: `JawsGetHtml(e *Element) template.HTML` to be used as-is. +// - jaws.Getter[template.HTML]: `JawsGet(elem *Element) template.HTML` to be used as-is. +// - jaws.StringGetter: `JawsGetString(e *Element) string` that will be escaped using `html.EscapeString`. +// - jaws.Getter[string]: `JawsGet(elem *Element) string` that will be escaped using `html.EscapeString`. +// - jaws.AnyGetter: `JawsGetAny(elem *Element) any` that will be rendered using `fmt.Sprint()` and escaped using `html.EscapeString`. +// - fmt.Stringer: `String() string` that will be escaped using `html.EscapeString`. +// - a static `template.HTML` or `string` to be used as-is with no HTML escaping. +// - everything else is rendered using `fmt.Sprint()` and escaped using `html.EscapeString`. +func MakeHtmlGetter(v any) HtmlGetter { + switch v := v.(type) { + case HtmlGetter: + return v + case Getter[template.HTML]: + return htmlGetterHTML{v} + case StringGetter: + return htmlGetterStringGetter{v} + case Getter[string]: + return htmlGetterString{v} + case AnyGetter: + return htmlGetterAny{v} + case fmt.Stringer: + return htmlGetterStringGetter{stringerGetter{v}} + case template.HTML: + return htmlGetter{v} + case string: + return htmlGetter{template.HTML(v)} // #nosec G203 + default: + return htmlGetter{template.HTML(html.EscapeString(fmt.Sprint(v)))} // #nosec G203 + } +} diff --git a/htmlgetter_test.go b/makehtmlgetter_test.go similarity index 53% rename from htmlgetter_test.go rename to makehtmlgetter_test.go index 86e6907..093c18d 100644 --- a/htmlgetter_test.go +++ b/makehtmlgetter_test.go @@ -4,28 +4,27 @@ import ( "html" "html/template" "reflect" - "strings" + "sync" "sync/atomic" "testing" ) var _ HtmlGetter = (*testSetter[template.HTML])(nil) -func Test_makeHtmlGetter_panic(t *testing.T) { - defer func() { - if x := recover(); x != nil { - if err, ok := x.(error); ok { - if strings.Contains(err.Error(), "uint32") { - return - } - } - } - t.Fail() - }() - makeHtmlGetter(uint32(42)) +type testAnySetter struct { + Value any +} + +func (ag *testAnySetter) JawsGetAny(*Element) any { + return ag.Value +} + +func (ag *testAnySetter) JawsSetAny(e *Element, v any) error { + ag.Value = v + return nil } -func Test_makeHtmlGetter(t *testing.T) { +func Test_MakeHtmlGetter(t *testing.T) { untypedText := "" typedText := template.HTML(untypedText) escapedTypedText := template.HTML(html.EscapeString(untypedText)) @@ -34,6 +33,11 @@ func Test_makeHtmlGetter(t *testing.T) { avTyped.Store(typedText) stringer := testStringer{} + var mu sync.Mutex + getterHTML := Bind(&mu, &escapedTypedText) + getterString := Bind(&mu, &untypedText) + getterAny := &testAnySetter{Value: untypedText} + tests := []struct { name string v any @@ -41,13 +45,6 @@ func Test_makeHtmlGetter(t *testing.T) { out template.HTML tag any }{ - { - name: "StringerGetter", - v: stringer, - want: htmlStringGetter{stringerGetter{stringer}}, - out: template.HTML(testStringer{}.String()), - tag: stringerGetter{stringer}, - }, { name: "HtmlGetter", v: htmlGetter{typedText}, @@ -55,13 +52,41 @@ func Test_makeHtmlGetter(t *testing.T) { out: typedText, tag: nil, }, + { + name: "Getter[template.HTML]", + v: getterHTML, + want: htmlGetterHTML{getterHTML}, + out: escapedTypedText, + tag: getterHTML, + }, { name: "StringGetter", v: stringGetter{untypedText}, - want: htmlStringGetter{stringGetter{untypedText}}, + want: htmlGetterStringGetter{stringGetter{untypedText}}, out: escapedTypedText, tag: stringGetter{untypedText}, }, + { + name: "Getter[string]", + v: getterString, + want: htmlGetterString{getterString}, + out: escapedTypedText, + tag: getterString, + }, + { + name: "Getter[any]", + v: getterAny, + want: htmlGetterAny{getterAny}, + out: escapedTypedText, + tag: getterAny, + }, + { + name: "StringerGetter", + v: stringer, + want: htmlGetterStringGetter{stringerGetter{stringer}}, + out: template.HTML(testStringer{}.String()), + tag: stringerGetter{stringer}, + }, { name: "template.HTML", v: typedText, @@ -77,31 +102,24 @@ func Test_makeHtmlGetter(t *testing.T) { tag: nil, }, { - name: "*atomic.Value(string)", - v: &avUntyped, - want: atomicSetter{&avUntyped}, - out: escapedTypedText, - tag: &avUntyped, - }, - { - name: "*atomic.Value(template.HTML)", - v: &avTyped, - want: atomicSetter{&avTyped}, - out: typedText, - tag: &avTyped, + name: "int", + v: 123, + want: htmlGetter{template.HTML("123")}, + out: template.HTML("123"), + tag: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := makeHtmlGetter(tt.v) + got := MakeHtmlGetter(tt.v) if !reflect.DeepEqual(got, tt.want) { - t.Errorf("makeHtmlGetter() = %v, want %v", got, tt.want) + t.Errorf("MakeHtmlGetter()\n got %#v\n want %#v", got, tt.want) } if txt := got.JawsGetHtml(nil); txt != tt.out { - t.Errorf("makeHtmlGetter().JawsGetHtml() = %v, want %v", txt, tt.out) + t.Errorf("MakeHtmlGetter().JawsGetHtml() = %v, want %v", txt, tt.out) } if tag := got.(TagGetter).JawsGetTag(nil); tag != tt.tag { - t.Errorf("makeHtmlGetter().JawsGetTag() = %v, want %v", tag, tt.tag) + t.Errorf("MakeHtmlGetter().JawsGetTag() = %v, want %v", tag, tt.tag) } }) } diff --git a/setter.go b/setter.go index be9e47b..d0697f6 100644 --- a/setter.go +++ b/setter.go @@ -1,9 +1,5 @@ package jaws -type Getter[T comparable] interface { - JawsGet(elem *Element) (value T) -} - type Setter[T comparable] interface { Getter[T] // JawsSet may return ErrValueUnchanged to indicate value was already set. diff --git a/stringsetter_test.go b/stringsetter_test.go index 8dbb99f..e5c524b 100644 --- a/stringsetter_test.go +++ b/stringsetter_test.go @@ -52,6 +52,14 @@ func (g simpleSetterT) JawsSet(e *Element, v string) error { return nil } +func (g simpleSetterT) JawsGetAny(e *Element) any { + return g.Value +} + +func (g simpleSetterT) JawsSetAny(e *Element, v any) error { + return nil +} + type simpleNotagGetter struct { v string } diff --git a/uia.go b/uia.go index 5ceb170..847264d 100644 --- a/uia.go +++ b/uia.go @@ -21,5 +21,5 @@ func NewUiA(innerHtml HtmlGetter) *UiA { } func (rq RequestWriter) A(innerHtml any, params ...any) error { - return rq.UI(NewUiA(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiA(MakeHtmlGetter(innerHtml)), params...) } diff --git a/uibutton.go b/uibutton.go index e064729..30489fe 100644 --- a/uibutton.go +++ b/uibutton.go @@ -21,5 +21,5 @@ func NewUiButton(innerHtml HtmlGetter) *UiButton { } func (rq RequestWriter) Button(innerHtml any, params ...any) error { - return rq.UI(NewUiButton(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiButton(MakeHtmlGetter(innerHtml)), params...) } diff --git a/uicontainer_test.go b/uicontainer_test.go index 2a2b313..8653fd0 100644 --- a/uicontainer_test.go +++ b/uicontainer_test.go @@ -72,9 +72,9 @@ func TestRequest_Container(t *testing.T) { } func TestRequest_Container_Alteration(t *testing.T) { - span1 := NewUiSpan(makeHtmlGetter("span1")) - span2 := NewUiSpan(makeHtmlGetter("span2")) - span3 := NewUiSpan(makeHtmlGetter("span3")) + span1 := NewUiSpan(MakeHtmlGetter("span1")) + span2 := NewUiSpan(MakeHtmlGetter("span2")) + span3 := NewUiSpan(MakeHtmlGetter("span3")) tests := []struct { name string c *testContainer diff --git a/uidiv.go b/uidiv.go index 5673b6c..32263ea 100644 --- a/uidiv.go +++ b/uidiv.go @@ -21,5 +21,5 @@ func NewUiDiv(innerHtml HtmlGetter) *UiDiv { } func (rq RequestWriter) Div(innerHtml any, params ...any) error { - return rq.UI(NewUiDiv(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiDiv(MakeHtmlGetter(innerHtml)), params...) } diff --git a/uilabel.go b/uilabel.go index 4e015dc..5c8fa7f 100644 --- a/uilabel.go +++ b/uilabel.go @@ -21,5 +21,5 @@ func NewUiLabel(innerHtml HtmlGetter) *UiLabel { } func (rq RequestWriter) Label(innerHtml any, params ...any) error { - return rq.UI(NewUiLabel(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiLabel(MakeHtmlGetter(innerHtml)), params...) } diff --git a/uili.go b/uili.go index 9064fe6..2450a2b 100644 --- a/uili.go +++ b/uili.go @@ -21,5 +21,5 @@ func NewUiLi(innerHtml HtmlGetter) *UiLi { } func (rq RequestWriter) Li(innerHtml any, params ...any) error { - return rq.UI(NewUiLi(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiLi(MakeHtmlGetter(innerHtml)), params...) } diff --git a/uispan.go b/uispan.go index 1bebdbe..b43fc0e 100644 --- a/uispan.go +++ b/uispan.go @@ -21,5 +21,5 @@ func NewUiSpan(innerHtml HtmlGetter) *UiSpan { } func (rq RequestWriter) Span(innerHtml any, params ...any) error { - return rq.UI(NewUiSpan(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiSpan(MakeHtmlGetter(innerHtml)), params...) } diff --git a/uitd.go b/uitd.go index 43d8918..39eb515 100644 --- a/uitd.go +++ b/uitd.go @@ -21,5 +21,5 @@ func NewUiTd(innerHtml HtmlGetter) *UiTd { } func (rq RequestWriter) Td(innerHtml any, params ...any) error { - return rq.UI(NewUiTd(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiTd(MakeHtmlGetter(innerHtml)), params...) } diff --git a/uitr.go b/uitr.go index 19c02c3..e21772b 100644 --- a/uitr.go +++ b/uitr.go @@ -21,5 +21,5 @@ func NewUiTr(innerHtml HtmlGetter) *UiTr { } func (rq RequestWriter) Tr(innerHtml any, params ...any) error { - return rq.UI(NewUiTr(makeHtmlGetter(innerHtml)), params...) + return rq.UI(NewUiTr(MakeHtmlGetter(innerHtml)), params...) } From 81af3c9855d5b5ac5f745a987052a1d3c0f92e22 Mon Sep 17 00:00:00 2001 From: Johan Lindh Date: Fri, 13 Dec 2024 11:09:18 +0100 Subject: [PATCH 2/3] wip --- README.md | 31 +++++++++++++++-------------- binding.go | 57 ++++++++++++------------------------------------------ 2 files changed, 29 insertions(+), 59 deletions(-) diff --git a/README.md b/README.md index b79a1d7..9dd6bd1 100644 --- a/README.md +++ b/README.md @@ -154,27 +154,30 @@ router.GET("/jaws/*", func(c echo.Context) error { }) ``` -### Data binding - -HTML input elements (e.g. `jaws.RequestWriter.Range()`) require bi-directional data flow between the server and the browser. -The first argument to these is usually a `Binder[T]` where `T` is one of `string, float64, bool` or `time.Time`. - -A `Binder[T]` combines a (RW)Locker and a pointer to the value, and allows you to add chained setters, -getters and on-success handlers. - ### HTML rendering HTML output elements (e.g. `jaws.RequestWriter.Div()`) require a `jaws.HtmlGetter` or something that can -be made into one using `jaws.MakeHtmlGetter()`. In order of precedence, this can be: -* `jaws.HtmlGetter`: `JawsGetHtml(e *Element) template.HTML` to be used as-is. -* `jaws.Getter[template.HTML]`: `JawsGet(elem *Element) template.HTML` to be used as-is. -* `jaws.StringGetter`: `JawsGetString(e *Element) string` that will be escaped using `html.EscapeString`. -* `jaws.Getter[string]`: `JawsGet(elem *Element) string` that will be escaped using `html.EscapeString`. -* `jaws.AnyGetter`: `JawsGetAny(elem *Element) any` that will be rendered using `fmt.Sprint()` and escaped using `html.EscapeString`. +be made into one using `jaws.MakeHtmlGetter()`. + +In order of precedence, this can be: +* `jaws.HtmlGetter`: `JawsGetHtml(*Element) template.HTML` to be used as-is. +* `jaws.Getter[template.HTML]`: `JawsGet(*Element) template.HTML` to be used as-is. +* `jaws.StringGetter`: `JawsGetString(*Element) string` that will be escaped using `html.EscapeString`. +* `jaws.Getter[string]`: `JawsGet(*Element) string` that will be escaped using `html.EscapeString`. +* `jaws.AnyGetter`: `JawsGetAny(*Element) any` that will be rendered using `fmt.Sprint()` and escaped using `html.EscapeString`. * `fmt.Stringer`: `String() string` that will be escaped using `html.EscapeString`. * a static `template.HTML` or `string` to be used as-is with no HTML escaping. * everything else is rendered using `fmt.Sprint()` and escaped using `html.EscapeString`. +### Data binding + +HTML input elements (e.g. `jaws.RequestWriter.Range()`) require bi-directional data flow between the server and the browser. +The first argument to these is usually a `Setter[T]` where `T` is one of `string`, `float64`, `bool` or `time.Time`. It can +also be a `Getter[T]`, in which case the HTML element should be made read-only. + +You can also use a `Binder[T]` that combines a (RW)Locker and a pointer to the value, and allows you to add chained setters, +getters and on-success handlers. It can be used as a `jaws.HtmlGetter`. + ### Session handling JaWS has non-persistent session handling integrated. Sessions won't diff --git a/binding.go b/binding.go index 2422eed..60b85c4 100644 --- a/binding.go +++ b/binding.go @@ -13,14 +13,6 @@ func (bind binding[T]) JawsGetLocked(*Element) T { return *bind.ptr } -func (bind binding[T]) JawsSetLocked(elem *Element, value T) (err error) { - if value == *bind.ptr { - return ErrValueUnchanged - } - *bind.ptr = value - return nil -} - func (bind binding[T]) JawsGet(elem *Element) (value T) { bind.lock.RLock() value = bind.JawsGetLocked(elem) @@ -32,6 +24,14 @@ func (bind binding[T]) JawsGetAny(elem *Element) (value any) { return bind.JawsGet(elem) } +func (bind binding[T]) JawsSetLocked(elem *Element, value T) (err error) { + if value == *bind.ptr { + return ErrValueUnchanged + } + *bind.ptr = value + return nil +} + func (bind binding[T]) JawsSet(elem *Element, value T) (err error) { bind.lock.Lock() err = bind.JawsSetLocked(elem, value) @@ -70,10 +70,10 @@ func (bind binding[T]) RUnlock() { // // The bind argument to the function is the previous Binder in the chain, // and you probably want to call it's JawsSetLocked first. -func (bind binding[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { +func (bind binding[T]) SetLocked(fn BindSetHook[T]) Binder[T] { return &BindingHook[T]{ Binder: bind, - BindSetHook: setFn, + BindSetHook: fn, } } @@ -84,10 +84,10 @@ func (bind binding[T]) SetLocked(setFn BindSetHook[T]) Binder[T] { // // The bind argument to the function is the previous Binder in the chain, // and you probably want to call it's JawsGetLocked first. -func (bind binding[T]) GetLocked(setFn BindGetHook[T]) Binder[T] { +func (bind binding[T]) GetLocked(fn BindGetHook[T]) Binder[T] { return &BindingHook[T]{ Binder: bind, - BindGetHook: setFn, + BindGetHook: fn, } } @@ -107,39 +107,6 @@ func (bind binding[T]) Success(fn any) Binder[T] { } } -/* - func (bind binding[T]) JawsGetString(elem *Element) string { - return any(bind.JawsGet(elem)).(string) - } - - func (bind binding[T]) JawsSetString(e *Element, val string) (err error) { - return bind.JawsSet(e, any(val).(T)) - } - - func (bind binding[T]) JawsGetFloat(elem *Element) float64 { - return any(bind.JawsGet(elem)).(float64) - } - - func (bind binding[T]) JawsSetFloat(e *Element, val float64) (err error) { - return bind.JawsSet(e, any(val).(T)) - } - - func (bind binding[T]) JawsGetBool(elem *Element) bool { - return any(bind.JawsGet(elem)).(bool) - } - - func (bind binding[T]) JawsSetBool(e *Element, val bool) (err error) { - return bind.JawsSet(e, any(val).(T)) - } - - func (bind binding[T]) JawsGetTime(elem *Element) time.Time { - return any(bind.JawsGet(elem)).(time.Time) - } - - func (bind binding[T]) JawsSetTime(elem *Element, value time.Time) error { - return bind.JawsSet(elem, any(value).(T)) - } -*/ func wrapSuccessHook(fn any) (hook BindSuccessHook) { switch fn := fn.(type) { case func(): From ee6be7066e1404eafbc3730c8bbf3062efd3d9d9 Mon Sep 17 00:00:00 2001 From: Johan Lindh Date: Fri, 13 Dec 2024 12:31:23 +0100 Subject: [PATCH 3/3] wip --- anygetter.go | 4 + anysetter.go | 6 -- anysetter_test.go | 8 -- atomicsetter.go | 94 -------------------- atomicsetter_test.go | 156 -------------------------------- bool.go | 74 ---------------- bool_test.go | 106 ---------------------- boolsetter.go | 41 --------- boolsetter_test.go | 84 ------------------ element_test.go | 6 +- example_test.go | 7 +- float.go | 61 ------------- float_test.go | 47 ---------- floatsetter.go | 42 --------- floatsetter_test.go | 101 --------------------- getter.go | 33 +++++++ getter_test.go | 20 +++++ makehtmlgetter_test.go | 6 +- namedbool.go | 4 +- namedbool_test.go | 6 +- namedboolarray.go | 4 +- namedboolarray_test.go | 8 +- selecthandler.go | 2 +- setter.go | 51 +++++++++++ setter_test.go | 46 ++++++++++ string.go | 63 ------------- string_test.go | 50 ----------- stringergetter.go | 17 ---- stringgetter.go | 46 +++++----- stringgetter_test.go | 16 +++- stringsetter.go | 65 -------------- stringsetter_test.go | 196 ----------------------------------------- tag_test.go | 5 -- testpage_test.go | 8 +- testsetter_test.go | 97 -------------------- timesetter.go | 39 -------- timesetter_test.go | 85 ------------------ uibool.go | 35 -------- uibool_test.go | 32 ------- uicheckbox.go | 6 +- uidate.go | 7 +- uifloat.go | 37 -------- uifloat_test.go | 32 ------- uiimg.go | 14 +-- uiinputbool.go | 10 +-- uiinputdate.go | 10 +-- uiinputfloat.go | 10 +-- uiinputtext.go | 10 +-- uinumber.go | 6 +- uipassword.go | 6 +- uiradio.go | 6 +- uirange.go | 6 +- uiselect.go | 4 +- uiselect_test.go | 4 +- uistring.go | 44 --------- uistring_test.go | 32 ------- uitext.go | 6 +- uitextarea.go | 12 +-- 58 files changed, 276 insertions(+), 1757 deletions(-) delete mode 100644 atomicsetter.go delete mode 100644 atomicsetter_test.go delete mode 100644 bool.go delete mode 100644 bool_test.go delete mode 100644 boolsetter.go delete mode 100644 boolsetter_test.go delete mode 100644 float.go delete mode 100644 float_test.go delete mode 100644 floatsetter.go delete mode 100644 floatsetter_test.go create mode 100644 getter_test.go create mode 100644 setter_test.go delete mode 100644 string.go delete mode 100644 string_test.go delete mode 100644 stringergetter.go delete mode 100644 stringsetter.go delete mode 100644 stringsetter_test.go delete mode 100644 timesetter.go delete mode 100644 timesetter_test.go delete mode 100644 uibool.go delete mode 100644 uibool_test.go delete mode 100644 uifloat.go delete mode 100644 uifloat_test.go delete mode 100644 uistring.go delete mode 100644 uistring_test.go diff --git a/anygetter.go b/anygetter.go index 2d3c322..9625511 100644 --- a/anygetter.go +++ b/anygetter.go @@ -1,5 +1,9 @@ package jaws +import "errors" + +var ErrValueNotSettable = errors.New("value not settable") + type AnyGetter interface { JawsGetAny(elem *Element) (value any) } diff --git a/anysetter.go b/anysetter.go index b68f4ef..fcdbdda 100644 --- a/anysetter.go +++ b/anysetter.go @@ -1,9 +1,5 @@ package jaws -import ( - "sync/atomic" -) - type AnySetter interface { AnyGetter // JawsSetAny may return ErrValueUnchanged to indicate value was already set. @@ -15,8 +11,6 @@ func makeAnySetter(v any) AnySetter { switch v := v.(type) { case AnySetter: return v - case *atomic.Value: - return atomicSetter{v} } return anyGetter{v} } diff --git a/anysetter_test.go b/anysetter_test.go index 54fb282..2b0407a 100644 --- a/anysetter_test.go +++ b/anysetter_test.go @@ -39,14 +39,6 @@ func Test_makeAnySetter(t *testing.T) { err: ErrValueNotSettable, tag: nil, }, - { - name: "*atomic.Value", - v: &av, - want: atomicSetter{&av}, - in: -val, - out: val, - tag: &av, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/atomicsetter.go b/atomicsetter.go deleted file mode 100644 index 8783913..0000000 --- a/atomicsetter.go +++ /dev/null @@ -1,94 +0,0 @@ -package jaws - -import ( - "fmt" - "html" - "html/template" - "sync/atomic" - "time" -) - -type atomicSetter struct{ v *atomic.Value } - -func (g atomicSetter) JawsGetBool(e *Element) (v bool) { - if x := g.v.Load(); x != nil { - v = x.(bool) - } - return -} - -func (g atomicSetter) JawsSetBool(e *Element, v bool) (err error) { - if g.v.Swap(v) == v { - err = ErrValueUnchanged - } - return -} - -func (g atomicSetter) JawsGetFloat(e *Element) (v float64) { - if x := g.v.Load(); x != nil { - v = x.(float64) - } - return -} - -func (g atomicSetter) JawsSetFloat(e *Element, v float64) (err error) { - if g.v.Swap(v) == v { - err = ErrValueUnchanged - } - return -} - -func (g atomicSetter) JawsGetString(e *Element) (v string) { - if x := g.v.Load(); x != nil { - v = x.(string) - } - return -} - -func (g atomicSetter) JawsSetString(e *Element, v string) (err error) { - if g.v.Swap(v) == v { - err = ErrValueUnchanged - } - return -} - -func (g atomicSetter) JawsGetTime(e *Element) (v time.Time) { - if x := g.v.Load(); x != nil { - v = x.(time.Time) - } - return -} - -func (g atomicSetter) JawsSetTime(e *Element, v time.Time) (err error) { - if g.v.Swap(v) == v { - err = ErrValueUnchanged - } - return -} - -func (g atomicSetter) JawsGetAny(e *Element) (v any) { - return g.v.Load() -} - -func (g atomicSetter) JawsSetAny(e *Element, v any) (err error) { - if g.v.Swap(v) == v { - err = ErrValueUnchanged - } - return -} - -func (g atomicSetter) JawsGetHtml(e *Element) template.HTML { - switch v := g.v.Load().(type) { - case nil: - return "" - case template.HTML: - return v - default: - h := template.HTML(html.EscapeString(fmt.Sprint(v))) // #nosec G203 - return h - } -} - -func (g atomicSetter) JawsGetTag(rq *Request) any { - return g.v -} diff --git a/atomicsetter_test.go b/atomicsetter_test.go deleted file mode 100644 index 53779fe..0000000 --- a/atomicsetter_test.go +++ /dev/null @@ -1,156 +0,0 @@ -package jaws - -import ( - "fmt" - "html/template" - "reflect" - "sync/atomic" - "testing" - "time" -) - -var _ BoolSetter = (atomicSetter{}) -var _ FloatSetter = (atomicSetter{}) -var _ StringSetter = (atomicSetter{}) -var _ TimeSetter = (atomicSetter{}) -var _ HtmlGetter = (atomicSetter{}) - -func Test_atomicSetter_UninitializedDefaults(t *testing.T) { - var av atomic.Value - g := atomicSetter{v: &av} - - if g.JawsGetBool(nil) != false { - t.Fail() - } - if g.JawsGetFloat(nil) != 0 { - t.Fail() - } - if g.JawsGetString(nil) != "" { - t.Fail() - } - if !g.JawsGetTime(nil).IsZero() { - t.Fail() - } - if g.JawsGetHtml(nil) != "" { - t.Fail() - } -} - -func Test_atomicSetter_bool(t *testing.T) { - var av atomic.Value - g := atomicSetter{v: &av} - val := true - if err := g.JawsSetBool(nil, val); err != nil { - t.Error(err) - } - if g.JawsGetBool(nil) != val { - t.Fail() - } - if err := g.JawsSetBool(nil, val); err != ErrValueUnchanged { - t.Error(err) - } -} - -func Test_atomicSetter_float64(t *testing.T) { - var av atomic.Value - g := atomicSetter{v: &av} - val := float64(1.2) - if err := g.JawsSetFloat(nil, val); err != nil { - t.Error(err) - } - if g.JawsGetFloat(nil) != val { - t.Fail() - } - if err := g.JawsSetFloat(nil, val); err != ErrValueUnchanged { - t.Error(err) - } -} - -func Test_atomicSetter_string(t *testing.T) { - var av atomic.Value - g := atomicSetter{v: &av} - val := "str" - if err := g.JawsSetString(nil, val); err != nil { - t.Error(err) - } - if g.JawsGetString(nil) != val { - t.Fail() - } - if err := g.JawsSetString(nil, val); err != ErrValueUnchanged { - t.Error(err) - } -} - -func Test_atomicSetter_time(t *testing.T) { - var av atomic.Value - g := atomicSetter{v: &av} - val := time.Now() - if err := g.JawsSetTime(nil, val); err != nil { - t.Error(err) - } - if g.JawsGetTime(nil) != val { - t.Fail() - } - if err := g.JawsSetTime(nil, val); err != ErrValueUnchanged { - t.Error(err) - } -} - -func Test_atomicSetter_any(t *testing.T) { - var av atomic.Value - g := atomicSetter{v: &av} - val := "str" - if err := g.JawsSetAny(nil, val); err != nil { - t.Error(err) - } - if g.JawsGetAny(nil) != val { - t.Fail() - } - if err := g.JawsSetAny(nil, val); err != ErrValueUnchanged { - t.Error(err) - } -} - -func Test_atomicSetter_JawsGetHtml(t *testing.T) { - tests := []struct { - name string - av atomic.Value - v any - want template.HTML - }{ - { - name: "html", - v: template.HTML("html"), - want: "html", - }, - { - name: "bool", - v: bool(true), - want: "true", - }, - { - name: "float64", - v: float64(1.2), - want: "1.2", - }, - { - name: "time.Time", - v: time.Now().Round(time.Minute), - want: template.HTML(fmt.Sprint(time.Now().Round(time.Minute))), - }, - { - name: "html-escaped string", - v: "", - want: "<span>", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt.av.Store(tt.v) - g := atomicSetter{v: &tt.av} - if got := g.JawsGetHtml(nil); !reflect.DeepEqual(got, tt.want) { - t.Errorf("atomicGetter.JawsGetHtml() for %#v = %v, want %v", tt.v, got, tt.want) - } - }) - } -} diff --git a/bool.go b/bool.go deleted file mode 100644 index 30905c6..0000000 --- a/bool.go +++ /dev/null @@ -1,74 +0,0 @@ -package jaws - -import ( - "strconv" - "sync" -) - -// Bool wraps a mutex and a boolean, and implements jaws.BoolSetter. -type Bool struct { - mu sync.Mutex - Value bool -} - -func (s *Bool) Set(val bool) { - s.mu.Lock() - s.Value = val - s.mu.Unlock() -} - -func (s *Bool) Get() (val bool) { - s.mu.Lock() - val = s.Value - s.mu.Unlock() - return -} - -func (s *Bool) Swap(val bool) (old bool) { - s.mu.Lock() - old, s.Value = s.Value, val - s.mu.Unlock() - return -} - -func (s *Bool) String() string { - if s.Get() { - return "true" - } - return "false" -} - -func (s *Bool) JawsGetBool(*Element) bool { - return s.Get() -} - -func (s *Bool) JawsSetBool(e *Element, val bool) error { - if s.Swap(val) == val { - return ErrValueUnchanged - } - return nil -} - -func (s *Bool) MarshalJSON() ([]byte, error) { - return []byte(s.String()), nil -} - -func (s *Bool) UnmarshalJSON(b []byte) (err error) { - var val bool - if val, err = strconv.ParseBool(string(b)); err == nil { - s.Set(val) - } - return -} - -func (s *Bool) MarshalText() ([]byte, error) { - return []byte(s.String()), nil -} - -func (s *Bool) UnmarshalText(b []byte) (err error) { - var val bool - if val, err = strconv.ParseBool(string(b)); err == nil { - s.Set(val) - } - return -} diff --git a/bool_test.go b/bool_test.go deleted file mode 100644 index 8adeb6d..0000000 --- a/bool_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package jaws - -import ( - "encoding/json" - "reflect" - "testing" -) - -func TestBool_BoolSetter(t *testing.T) { - var s Bool - if s.String() != "false" { - t.Fail() - } - if err := s.JawsSetBool(nil, true); err != nil { - t.Error(err) - } - if s.JawsGetBool(nil) != true { - t.Fail() - } - if s.String() != "true" { - t.Fail() - } - if err := s.JawsSetBool(nil, true); err != ErrValueUnchanged { - t.Error(err) - } -} - -func TestBool_MarshalJSON(t *testing.T) { - var s, s2 Bool - s.Set(true) - b, err := json.Marshal(&s) - if err != nil { - t.Error(err) - } else { - x := string(b) - if x != "true" { - t.Errorf("%T %q", x, x) - } - } - err = json.Unmarshal(b, &s2) - if err != nil { - t.Error(err) - } else { - if s2.Value != true { - t.Error(s2.Value) - } - } -} - -func TestBool_MarshalText(t *testing.T) { - tests := []struct { - name string - Value bool - want []byte - wantErr bool - }{ - {"false", false, []byte("false"), false}, - {"true", true, []byte("true"), false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Bool{ - Value: tt.Value, - } - got, err := s.MarshalText() - if (err != nil) != tt.wantErr { - t.Errorf("Bool.MarshalText() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Bool.MarshalText() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestBool_UnmarshalText(t *testing.T) { - tests := []struct { - name string - Value bool - str string - wantErr bool - }{ - {"false", false, "false", false}, - {"true", true, "true", false}, - {"false/err", false, "foo", true}, - {"true/err", true, "foo", true}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - s := &Bool{ - Value: !tt.Value, - } - if err := s.UnmarshalText([]byte(tt.str)); (err != nil) != tt.wantErr { - t.Errorf("Bool.UnmarshalText() error = %v, wantErr %v", err, tt.wantErr) - return - } - if tt.wantErr && s.Value != !tt.Value { - t.Errorf("Bool.UnmarshalText() = %v, want %v", s.Value, !tt.Value) - } - if !tt.wantErr && s.Value != tt.Value { - t.Errorf("Bool.UnmarshalText() = %v, want %v", s.Value, tt.Value) - } - }) - } -} diff --git a/boolsetter.go b/boolsetter.go deleted file mode 100644 index fb4c488..0000000 --- a/boolsetter.go +++ /dev/null @@ -1,41 +0,0 @@ -package jaws - -import ( - "errors" - "fmt" - "sync/atomic" -) - -type BoolSetter interface { - JawsGetBool(e *Element) bool - // JawsSetBool may return ErrValueUnchanged to indicate value was already set. - JawsSetBool(e *Element, v bool) (err error) -} - -type boolGetter struct{ v bool } - -var ErrValueNotSettable = errors.New("value not settable") - -func (g boolGetter) JawsGetBool(*Element) bool { - return g.v -} - -func (g boolGetter) JawsSetBool(*Element, bool) error { - return ErrValueNotSettable -} - -func (g boolGetter) JawsGetTag(rq *Request) any { - return nil -} - -func makeBoolSetter(v any) BoolSetter { - switch v := v.(type) { - case BoolSetter: - return v - case bool: - return boolGetter{v} - case *atomic.Value: - return atomicSetter{v} - } - panic(fmt.Errorf("expected jaws.BoolSetter or bool, not %T", v)) -} diff --git a/boolsetter_test.go b/boolsetter_test.go deleted file mode 100644 index 32522ce..0000000 --- a/boolsetter_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package jaws - -import ( - "reflect" - "strings" - "sync/atomic" - "testing" -) - -var _ BoolSetter = (*testSetter[bool])(nil) - -func Test_makeBoolSetter_panic(t *testing.T) { - defer func() { - if x := recover(); x != nil { - if err, ok := x.(error); ok { - if strings.Contains(err.Error(), "uint32") { - return - } - } - } - t.Fail() - }() - makeBoolSetter(uint32(1)) -} - -func Test_makeBoolSetter(t *testing.T) { - val := true - var av atomic.Value - av.Store(val) - ts := newTestSetter(val) - - tests := []struct { - name string - v any - want BoolSetter - out bool - err error - tag any - }{ - { - name: "BoolSetter", - v: ts, - want: ts, - out: val, - tag: ts, - }, - { - name: "bool", - v: val, - want: boolGetter{val}, - out: val, - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "*atomic.Value", - v: &av, - want: atomicSetter{&av}, - out: val, - tag: &av, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := makeBoolSetter(tt.v) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("makeBoolSetter() = %v, want %v", got, tt.want) - } - if out := got.JawsGetBool(nil); out != tt.out { - t.Errorf("makeBoolSetter().JawsGetBool() = %v, want %v", out, tt.out) - } - if err := got.JawsSetBool(nil, !val); err != tt.err { - t.Errorf("makeBoolSetter().JawsSetBool() = %v, want %v", err, tt.err) - } - gotTag := any(got) - if tg, ok := got.(TagGetter); ok { - gotTag = tg.JawsGetTag(nil) - } - if gotTag != tt.tag { - t.Errorf("makeBoolSetter().tag = %v, want %v", gotTag, tt.tag) - } - }) - } -} diff --git a/element_test.go b/element_test.go index f3cefd7..65a6395 100644 --- a/element_test.go +++ b/element_test.go @@ -26,14 +26,14 @@ type testUi struct { } var _ UI = (*testUi)(nil) -var _ StringSetter = (*testUi)(nil) +var _ Setter[string] = (*testUi)(nil) -func (tss *testUi) JawsGetString(e *Element) string { +func (tss *testUi) JawsGet(e *Element) string { atomic.AddInt32(&tss.getCalled, 1) return tss.s } -func (tss *testUi) JawsSetString(e *Element, s string) error { +func (tss *testUi) JawsSet(e *Element, s string) error { atomic.AddInt32(&tss.setCalled, 1) tss.s = s return nil diff --git a/example_test.go b/example_test.go index cb4a6ab..d638e60 100644 --- a/example_test.go +++ b/example_test.go @@ -4,6 +4,7 @@ import ( "html/template" "log/slog" "net/http" + "sync" "github.com/linkdata/jaws" ) @@ -27,7 +28,9 @@ func Example() { go jw.Serve() // start the JaWS processing loop http.DefaultServeMux.Handle("/jaws/", jw) // ensure the JaWS routes are handled - var f jaws.Float // somewhere to store the slider data - http.DefaultServeMux.Handle("/", jw.Handler("index", &f)) + var mu sync.Mutex + var f float64 + + http.DefaultServeMux.Handle("/", jw.Handler("index", jaws.Bind(&mu, &f))) slog.Error(http.ListenAndServe("localhost:8080", nil).Error()) } diff --git a/float.go b/float.go deleted file mode 100644 index 50c946a..0000000 --- a/float.go +++ /dev/null @@ -1,61 +0,0 @@ -package jaws - -import ( - "strconv" - "sync" -) - -var _ FloatSetter = &Float{} - -// Float wraps a mutex and a float64, and implements jaws.FloatSetter. -type Float struct { - mu sync.Mutex - Value float64 -} - -func (s *Float) Set(val float64) { - s.mu.Lock() - s.Value = val - s.mu.Unlock() -} - -func (s *Float) Get() (val float64) { - s.mu.Lock() - val = s.Value - s.mu.Unlock() - return -} - -func (s *Float) Swap(val float64) (old float64) { - s.mu.Lock() - old, s.Value = s.Value, val - s.mu.Unlock() - return -} - -func (s *Float) String() string { - return strconv.FormatFloat(s.Get(), 'f', -1, 64) -} - -func (s *Float) JawsGetFloat(*Element) float64 { - return s.Get() -} - -func (s *Float) JawsSetFloat(e *Element, val float64) error { - if s.Swap(val) == val { - return ErrValueUnchanged - } - return nil -} - -func (s *Float) MarshalJSON() ([]byte, error) { - return []byte(s.String()), nil -} - -func (s *Float) UnmarshalJSON(b []byte) (err error) { - var val float64 - if val, err = strconv.ParseFloat(string(b), 64); err == nil { - s.Set(val) - } - return -} diff --git a/float_test.go b/float_test.go deleted file mode 100644 index 4c906c8..0000000 --- a/float_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package jaws - -import ( - "encoding/json" - "testing" -) - -func TestFloat_FloatSetter(t *testing.T) { - var s Float - if s.String() != "0" { - t.Fail() - } - if err := s.JawsSetFloat(nil, 1); err != nil { - t.Error(err) - } - if s.JawsGetFloat(nil) != 1 { - t.Fail() - } - if s.String() != "1" { - t.Fail() - } - if err := s.JawsSetFloat(nil, 1); err != ErrValueUnchanged { - t.Error(err) - } -} - -func TestFloat_MarshalJSON(t *testing.T) { - var s, s2 Float - s.Set(1) - b, err := json.Marshal(&s) - if err != nil { - t.Error(err) - } else { - x := string(b) - if x != "1" { - t.Errorf("%T %q", x, x) - } - } - err = json.Unmarshal(b, &s2) - if err != nil { - t.Error(err) - } else { - if s2.Value != 1 { - t.Error(s2.Value) - } - } -} diff --git a/floatsetter.go b/floatsetter.go deleted file mode 100644 index 45003f4..0000000 --- a/floatsetter.go +++ /dev/null @@ -1,42 +0,0 @@ -package jaws - -import ( - "fmt" - "sync/atomic" -) - -type FloatSetter interface { - JawsGetFloat(e *Element) float64 - // JawsSetFloat may return ErrValueUnchanged to indicate value was already set. - JawsSetFloat(e *Element, v float64) (err error) -} - -type floatGetter struct{ v float64 } - -func (g floatGetter) JawsGetFloat(e *Element) float64 { - return g.v -} - -func (g floatGetter) JawsSetFloat(*Element, float64) error { - return ErrValueNotSettable -} - -func (g floatGetter) JawsGetTag(rq *Request) any { - return nil -} - -func makeFloatSetter(v any) FloatSetter { - switch v := v.(type) { - case FloatSetter: - return v - case float64: - return floatGetter{v} - case float32: - return floatGetter{float64(v)} - case int: - return floatGetter{float64(v)} - case *atomic.Value: - return atomicSetter{v} - } - panic(fmt.Errorf("expected jaws.FloatSetter, float or int, not %T", v)) -} diff --git a/floatsetter_test.go b/floatsetter_test.go deleted file mode 100644 index 5ff97b9..0000000 --- a/floatsetter_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package jaws - -import ( - "reflect" - "strings" - "sync/atomic" - "testing" -) - -var _ FloatSetter = (*testSetter[float64])(nil) - -func Test_makeFloatSetter_panic(t *testing.T) { - defer func() { - if x := recover(); x != nil { - if err, ok := x.(error); ok { - if strings.Contains(err.Error(), "string") { - return - } - } - } - t.Fail() - }() - makeFloatSetter("meh") -} - -func Test_makeFloatSetter(t *testing.T) { - val := float64(12.34) - var av atomic.Value - av.Store(val) - ts := newTestSetter(val) - - tests := []struct { - name string - v any - want FloatSetter - out float64 - err error - tag any - }{ - { - name: "FloatSetter", - v: ts, - want: ts, - out: val, - tag: ts, - }, - { - name: "float64", - v: val, - want: floatGetter{val}, - out: val, - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "float32", - v: float32(val), - want: floatGetter{float64(float32(val))}, - out: float64(float32(val)), - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "int", - v: int(val), - want: floatGetter{float64(int(val))}, - out: float64(int(val)), - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "*atomic.Value", - v: &av, - want: atomicSetter{&av}, - out: val, - tag: &av, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := makeFloatSetter(tt.v) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("makeFloatSetter() = %v, want %v", got, tt.want) - } - if out := got.JawsGetFloat(nil); out != tt.out { - t.Errorf("makeFloatSetter().JawsGetFloat() = %v, want %v", out, tt.out) - } - if err := got.JawsSetFloat(nil, -val); err != tt.err { - t.Errorf("makeFloatSetter().JawsSetFloat() = %v, want %v", err, tt.err) - } - gotTag := any(got) - if tg, ok := got.(TagGetter); ok { - gotTag = tg.JawsGetTag(nil) - } - if gotTag != tt.tag { - t.Errorf("makeFloatSetter().tag = %v, want %v", gotTag, tt.tag) - } - - }) - } -} diff --git a/getter.go b/getter.go index 17307af..f2d959a 100644 --- a/getter.go +++ b/getter.go @@ -1,5 +1,38 @@ package jaws +import "fmt" + type Getter[T comparable] interface { JawsGet(elem *Element) (value T) } + +type getterStatic[T comparable] struct { + v T +} + +func (getterStatic[T]) JawsSet(*Element, T) error { + return ErrValueNotSettable +} + +func (s getterStatic[T]) JawsGet(*Element) T { + return s.v +} + +func (s getterStatic[T]) JawsGetTag(*Request) any { + return nil +} + +func makeStaticGetter[T comparable](v T) Getter[T] { + return getterStatic[T]{v} +} + +func makeGetter[T comparable](v any) Getter[T] { + switch v := v.(type) { + case Getter[T]: + return v + case T: + return makeStaticGetter(v) + } + var blank T + panic(fmt.Errorf("expected jaws.Getter[%T] or %T not %T", blank, blank, v)) +} diff --git a/getter_test.go b/getter_test.go new file mode 100644 index 0000000..61adcad --- /dev/null +++ b/getter_test.go @@ -0,0 +1,20 @@ +package jaws + +import "testing" + +func Test_makeGetter_panic(t *testing.T) { + defer func() { + if x := recover(); x == nil { + t.Fail() + } + }() + setter2 := makeGetter[string](123) + t.Error(setter2) +} + +func Test_makeGetter(t *testing.T) { + setter := makeGetter[string]("foo") + if err := setter.(Setter[string]).JawsSet(nil, "bar"); err != ErrValueNotSettable { + t.Error(err) + } +} diff --git a/makehtmlgetter_test.go b/makehtmlgetter_test.go index 093c18d..7a308ec 100644 --- a/makehtmlgetter_test.go +++ b/makehtmlgetter_test.go @@ -61,10 +61,10 @@ func Test_MakeHtmlGetter(t *testing.T) { }, { name: "StringGetter", - v: stringGetter{untypedText}, - want: htmlGetterStringGetter{stringGetter{untypedText}}, + v: stringGetterStatic{untypedText}, + want: htmlGetterStringGetter{stringGetterStatic{untypedText}}, out: escapedTypedText, - tag: stringGetter{untypedText}, + tag: stringGetterStatic{untypedText}, }, { name: "Getter[string]", diff --git a/namedbool.go b/namedbool.go index d0d4382..868608e 100644 --- a/namedbool.go +++ b/namedbool.go @@ -47,14 +47,14 @@ func (nb *NamedBool) JawsGetHtml(*Element) (h template.HTML) { return nb.Html() } -func (nb *NamedBool) JawsGetBool(*Element) (v bool) { +func (nb *NamedBool) JawsGet(*Element) (v bool) { nb.mu.RLock() v = nb.checked nb.mu.RUnlock() return } -func (nb *NamedBool) JawsSetBool(e *Element, checked bool) (err error) { +func (nb *NamedBool) JawsSet(e *Element, checked bool) (err error) { var nba *NamedBoolArray nb.mu.Lock() if nb.checked != checked { diff --git a/namedbool_test.go b/namedbool_test.go index 71b427e..69efac8 100644 --- a/namedbool_test.go +++ b/namedbool_test.go @@ -23,8 +23,8 @@ func TestNamedBool(t *testing.T) { is.Equal(nb.Name(), nb.JawsGetString(nil)) is.Equal(nb.Html(), nb.JawsGetHtml(nil)) - is.NoErr(nb.JawsSetBool(e, true)) + is.NoErr(nb.JawsSet(e, true)) is.True(nb.Checked()) - is.Equal(nb.Checked(), nb.JawsGetBool(nil)) - is.Equal(nb.JawsSetBool(e, true), ErrValueUnchanged) + is.Equal(nb.Checked(), nb.JawsGet(nil)) + is.Equal(nb.JawsSet(e, true), ErrValueUnchanged) } diff --git a/namedboolarray.go b/namedboolarray.go index 0872b88..75e8b36 100644 --- a/namedboolarray.go +++ b/namedboolarray.go @@ -135,11 +135,11 @@ func (nba *NamedBoolArray) String() string { return sb.String() } -func (nba *NamedBoolArray) JawsGetString(e *Element) string { +func (nba *NamedBoolArray) JawsGet(e *Element) string { return nba.Get() } -func (nba *NamedBoolArray) JawsSetString(e *Element, name string) (err error) { +func (nba *NamedBoolArray) JawsSet(e *Element, name string) (err error) { if nba.Set(name, true) { e.Dirty(nba) } else { diff --git a/namedboolarray_test.go b/namedboolarray_test.go index a55a060..2b391b0 100644 --- a/namedboolarray_test.go +++ b/namedboolarray_test.go @@ -82,8 +82,8 @@ func Test_NamedBoolArray(t *testing.T) { e := rq.NewElement(NewUiSelect(nba)) defer rq.Close() - is.Equal(nba.JawsGetString(e), "2") - is.NoErr(nba.JawsSetString(e, "1")) - is.Equal(nba.JawsGetString(e), "1") - is.Equal(nba.JawsSetString(e, "1"), ErrValueUnchanged) + is.Equal(nba.JawsGet(e), "2") + is.NoErr(nba.JawsSet(e, "1")) + is.Equal(nba.JawsGet(e), "1") + is.Equal(nba.JawsSet(e, "1"), ErrValueUnchanged) } diff --git a/selecthandler.go b/selecthandler.go index 832b9d1..6e72def 100644 --- a/selecthandler.go +++ b/selecthandler.go @@ -2,5 +2,5 @@ package jaws type SelectHandler interface { Container - StringSetter + Setter[string] } diff --git a/setter.go b/setter.go index d0697f6..e71a1f7 100644 --- a/setter.go +++ b/setter.go @@ -1,7 +1,58 @@ package jaws +import "fmt" + type Setter[T comparable] interface { Getter[T] // JawsSet may return ErrValueUnchanged to indicate value was already set. JawsSet(elem *Element, value T) (err error) } + +type setterReadOnly[T comparable] struct { + Getter[T] +} + +func (setterReadOnly[T]) JawsSet(*Element, T) error { + return ErrValueNotSettable +} + +func (s setterReadOnly[T]) JawsGetTag(*Request) any { + return s.Getter +} + +func makeReadOnlySetter[T comparable](g Getter[T]) Setter[T] { + return setterReadOnly[T]{g} +} + +type setterStatic[T comparable] struct { + v T +} + +func (setterStatic[T]) JawsSet(*Element, T) error { + return ErrValueNotSettable +} + +func (s setterStatic[T]) JawsGet(*Element) T { + return s.v +} + +func (s setterStatic[T]) JawsGetTag(*Request) any { + return nil +} + +func makeStaticSetter[T comparable](v T) Setter[T] { + return setterStatic[T]{v} +} + +func makeSetter[T comparable](v any) Setter[T] { + switch v := v.(type) { + case Setter[T]: + return v + case Getter[T]: + return makeReadOnlySetter(v) + case T: + return makeStaticSetter(v) + } + var blank T + panic(fmt.Errorf("jaws.Setter[%T], jaws.Getter[%T] or %T not %T", blank, blank, blank, v)) +} diff --git a/setter_test.go b/setter_test.go new file mode 100644 index 0000000..a2d813d --- /dev/null +++ b/setter_test.go @@ -0,0 +1,46 @@ +package jaws + +import ( + "testing" +) + +type testStringGetter struct{} + +func (testStringGetter) JawsGet(*Element) string { + return "static" +} + +func Test_makeSetter(t *testing.T) { + tsg := testStringGetter{} + setter1 := makeSetter[string](tsg) + if err := setter1.JawsSet(nil, "foo"); err != ErrValueNotSettable { + t.Error(err) + } + if s := setter1.JawsGet(nil); s != "static" { + t.Error(s) + } + if tag := setter1.(TagGetter).JawsGetTag(nil); tag != tsg { + t.Error(tag) + } + + setter2 := makeSetter[string]("quux") + if err := setter2.JawsSet(nil, "foo"); err != ErrValueNotSettable { + t.Error(err) + } + if s := setter2.JawsGet(nil); s != "quux" { + t.Error(s) + } + if tag := setter2.(TagGetter).JawsGetTag(nil); tag != nil { + t.Error(tag) + } +} + +func Test_makeSetter_panic(t *testing.T) { + defer func() { + if x := recover(); x == nil { + t.Fail() + } + }() + setter2 := makeSetter[string](123) + t.Error(setter2) +} diff --git a/string.go b/string.go deleted file mode 100644 index 5a8ac85..0000000 --- a/string.go +++ /dev/null @@ -1,63 +0,0 @@ -package jaws - -import ( - "html" - "html/template" - "sync" -) - -// String wraps a mutex and a string, and implements jaws.StringSetter and jaws.HtmlGetter. -// String.JawsGetHtml() will escape the string before returning it. -type String struct { - mu sync.Mutex - Value string -} - -func (s *String) Set(val string) { - s.mu.Lock() - s.Value = val - s.mu.Unlock() -} - -func (s *String) Swap(val string) (old string) { - s.mu.Lock() - old, s.Value = s.Value, val - s.mu.Unlock() - return -} - -func (s *String) Get() (val string) { - s.mu.Lock() - val = s.Value - s.mu.Unlock() - return -} - -func (s *String) String() string { - return s.Get() -} - -func (s *String) JawsGetHtml(*Element) (val template.HTML) { - val = template.HTML(html.EscapeString(s.Get())) // #nosec G203 - return -} - -func (s *String) JawsGetString(*Element) string { - return s.String() -} - -func (s *String) JawsSetString(e *Element, val string) (err error) { - if s.Swap(val) == val { - err = ErrValueUnchanged - } - return -} - -func (s *String) MarshalText() ([]byte, error) { - return []byte(s.Get()), nil -} - -func (s *String) UnmarshalText(b []byte) (err error) { - s.Set(string(b)) - return -} diff --git a/string_test.go b/string_test.go deleted file mode 100644 index 1e4d5f6..0000000 --- a/string_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package jaws - -import ( - "encoding/json" - "testing" -) - -func TestString_StringSetter(t *testing.T) { - var s String - if err := s.JawsSetString(nil, "foo"); err != nil { - t.Error(err) - } - if s.JawsGetString(nil) != "foo" { - t.Fail() - } - if s.String() != s.Get() { - t.Fail() - } - if err := s.JawsSetString(nil, "foo"); err != ErrValueUnchanged { - t.Error(err) - } -} - -func TestString_HtmlGetter(t *testing.T) { - s := String{Value: ""} - if v := s.JawsGetHtml(nil); v != "<foo>" { - t.Errorf("%q", v) - } -} - -func TestString_Marshalling(t *testing.T) { - var s, s2 String - s.Set("foo") - b, err := json.Marshal(&s) - if err != nil { - t.Error(err) - } else { - if string(b) != "\"foo\"" { - t.Error(string(b)) - } - } - err = json.Unmarshal(b, &s2) - if err != nil { - t.Error(err) - } else { - if s2.Value != "foo" { - t.Error(s2.Value) - } - } -} diff --git a/stringergetter.go b/stringergetter.go deleted file mode 100644 index c49db76..0000000 --- a/stringergetter.go +++ /dev/null @@ -1,17 +0,0 @@ -package jaws - -import "fmt" - -type stringerGetter struct{ v fmt.Stringer } - -func (g stringerGetter) JawsGetString(e *Element) string { - return g.v.String() -} - -func (g stringerGetter) JawsSetString(*Element, string) error { - return ErrValueNotSettable -} - -func (g stringerGetter) JawsGetTag(rq *Request) any { - return g.v -} diff --git a/stringgetter.go b/stringgetter.go index e1d9b44..b65d70e 100644 --- a/stringgetter.go +++ b/stringgetter.go @@ -9,34 +9,36 @@ type StringGetter interface { JawsGetString(e *Element) string } -type stringGetter struct{ v string } - -func (g stringGetter) JawsGetString(e *Element) string { - return g.v +type stringGetterT struct { + Getter[string] } -func (g stringGetter) JawsSetString(*Element, string) error { - return ErrValueNotSettable +func (g stringGetterT) JawsGetString(e *Element) string { + return g.JawsGet(e) } -func (g stringGetter) JawsGetTag(rq *Request) any { - return nil +func (g stringGetterT) JawsGetTag(rq *Request) any { + return g.Getter } -type stringGetterT struct { - Getter[string] +type stringerGetter struct{ v fmt.Stringer } + +func (g stringerGetter) JawsGetString(e *Element) string { + return g.v.String() } -func (g stringGetterT) JawsGetString(e *Element) string { - return g.JawsGet(e) +func (g stringerGetter) JawsGetTag(rq *Request) any { + return g.v } -func (stringGetterT) JawsSetString(e *Element, v string) (err error) { - return ErrValueNotSettable +type stringGetterStatic struct{ v string } + +func (g stringGetterStatic) JawsGetString(e *Element) string { + return g.v } -func (g stringGetterT) JawsGetTag(rq *Request) any { - return g.Getter +func (g stringGetterStatic) JawsGetTag(rq *Request) any { + return nil } func makeStringGetter(v any) StringGetter { @@ -45,14 +47,14 @@ func makeStringGetter(v any) StringGetter { return v case Getter[string]: return stringGetterT{v} + case fmt.Stringer: + return stringerGetter{v} case string: - return stringGetter{v} + return stringGetterStatic{v} case template.HTML: - return stringGetter{string(v)} + return stringGetterStatic{string(v)} case template.HTMLAttr: - return stringGetter{string(v)} - case fmt.Stringer: - return stringerGetter{v} + return stringGetterStatic{string(v)} } - panic(fmt.Errorf("expected jaws.StringGetter or string, not %T", v)) + panic(fmt.Errorf("expected jaws.StringGetter, jaws.Getter[string], fmt.Stringer or string, not %T", v)) } diff --git a/stringgetter_test.go b/stringgetter_test.go index 170ace9..8f88e7c 100644 --- a/stringgetter_test.go +++ b/stringgetter_test.go @@ -23,10 +23,18 @@ func Test_makeStringGetter_panic(t *testing.T) { makeStringGetter(uint32(42)) } +type simpleGetterT[T comparable] struct { + Value T +} + +func (sg *simpleGetterT[T]) JawsGet(*Element) T { + return sg.Value +} + func Test_makeStringGetter(t *testing.T) { val := "" ts := newTestSetter(val) - sgt := simpleGetterT{Value: val} + sgt := &simpleGetterT[string]{Value: val} stringer := testStringer{} tests := []struct { @@ -61,7 +69,7 @@ func Test_makeStringGetter(t *testing.T) { { name: "string", v: val, - want: stringGetter{val}, + want: stringGetterStatic{val}, out: val, err: ErrValueNotSettable, tag: nil, @@ -69,7 +77,7 @@ func Test_makeStringGetter(t *testing.T) { { name: "template.HTML", v: template.HTML(val), - want: stringGetter{val}, + want: stringGetterStatic{val}, out: val, err: ErrValueNotSettable, tag: nil, @@ -77,7 +85,7 @@ func Test_makeStringGetter(t *testing.T) { { name: "template.HTMLAttr", v: template.HTMLAttr(val), - want: stringGetter{val}, + want: stringGetterStatic{val}, out: val, err: ErrValueNotSettable, tag: nil, diff --git a/stringsetter.go b/stringsetter.go deleted file mode 100644 index 1991b28..0000000 --- a/stringsetter.go +++ /dev/null @@ -1,65 +0,0 @@ -package jaws - -import ( - "fmt" - "html/template" - "sync/atomic" -) - -type StringSetter interface { - StringGetter - // JawsSetString may return ErrValueUnchanged to indicate value was already set. - JawsSetString(e *Element, v string) (err error) -} - -type stringSetter struct { - StringGetter -} - -func (stringSetter) JawsSetString(e *Element, v string) (err error) { - return ErrValueNotSettable -} - -func (g stringSetter) JawsGetTag(rq *Request) any { - return g.StringGetter -} - -type stringSetterT struct { - Setter[string] -} - -func (g stringSetterT) JawsGetString(e *Element) string { - return g.JawsGet(e) -} - -func (g stringSetterT) JawsSetString(e *Element, v string) (err error) { - return g.JawsSet(e, v) -} - -func (g stringSetterT) JawsGetTag(rq *Request) any { - return g.Setter -} - -func makeStringSetter(v any) StringSetter { - switch v := v.(type) { - case StringSetter: - return v - case StringGetter: - return stringSetter{v} - case Setter[string]: - return stringSetterT{v} - case Getter[string]: - return stringGetterT{v} - case fmt.Stringer: - return stringerGetter{v} - case string: - return stringGetter{v} - case template.HTML: - return stringGetter{string(v)} - case template.HTMLAttr: - return stringGetter{string(v)} - case *atomic.Value: - return atomicSetter{v} - } - panic(fmt.Errorf("expected jaws.StringSetter or string, not %T", v)) -} diff --git a/stringsetter_test.go b/stringsetter_test.go deleted file mode 100644 index e5c524b..0000000 --- a/stringsetter_test.go +++ /dev/null @@ -1,196 +0,0 @@ -package jaws - -import ( - "html/template" - "reflect" - "strings" - "sync/atomic" - "testing" -) - -var _ StringSetter = (*testSetter[string])(nil) - -func Test_makeStringSetter_panic(t *testing.T) { - defer func() { - if x := recover(); x != nil { - if err, ok := x.(error); ok { - if strings.Contains(err.Error(), "uint32") { - return - } - } - } - t.Fail() - }() - makeStringSetter(uint32(42)) -} - -type simpleGetter struct { - Value string -} - -func (g simpleGetter) JawsGetString(e *Element) string { - return g.Value -} - -type simpleGetterT struct { - Value string -} - -func (g simpleGetterT) JawsGet(e *Element) string { - return g.Value -} - -type simpleSetterT struct { - Value string -} - -func (g simpleSetterT) JawsGet(e *Element) string { - return g.Value -} - -func (g simpleSetterT) JawsSet(e *Element, v string) error { - return nil -} - -func (g simpleSetterT) JawsGetAny(e *Element) any { - return g.Value -} - -func (g simpleSetterT) JawsSetAny(e *Element, v any) error { - return nil -} - -type simpleNotagGetter struct { - v string -} - -func (g simpleNotagGetter) JawsGetString(e *Element) string { - return g.v -} - -func (g simpleNotagGetter) JawsGetTag(rq *Request) any { - return nil -} - -func Test_makeStringSetter(t *testing.T) { - val := "" - var av atomic.Value - av.Store(val) - ts := newTestSetter(val) - stringer := testStringer{} - - sg := simpleGetter{Value: val} - sgt := simpleGetterT{Value: val} - sst := simpleSetterT{Value: val} - sng := simpleNotagGetter{v: val} - - tests := []struct { - name string - v any - want StringSetter - out string - err error - tag any - }{ - { - name: "StringGetter_untagged", - v: sng, - want: stringSetter{sng}, - out: val, - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "StringSetter", - v: ts, - want: ts, - out: val, - tag: ts, - }, - { - name: "StringGetter", - v: sg, - want: stringSetter{sg}, - out: val, - err: ErrValueNotSettable, - tag: sg, - }, - { - name: "StringerGetter", - v: stringer, - want: stringerGetter{stringer}, - out: testStringer{}.String(), - err: ErrValueNotSettable, - tag: stringer, - }, - { - name: "Setter[string]", - v: sst, - want: stringSetterT{sst}, - out: val, - tag: sst, - }, - { - name: "Getter[string]", - v: sgt, - want: stringGetterT{sgt}, - out: val, - err: ErrValueNotSettable, - tag: sgt, - }, - { - name: "string", - v: val, - want: stringGetter{val}, - out: val, - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "template.HTML", - v: template.HTML(val), - want: stringGetter{val}, - out: val, - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "template.HTMLAttr", - v: template.HTMLAttr(val), - want: stringGetter{val}, - out: val, - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "*atomic.Value", - v: &av, - want: atomicSetter{&av}, - out: val, - tag: &av, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := makeStringSetter(tt.v) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("makeStringSetter() = %v, want %v", got, tt.want) - } - if out := got.JawsGetString(nil); out != tt.out { - t.Errorf("makeStringSetter().JawsGetString() = %v, want %v", out, tt.out) - } - if err := got.JawsSetString(nil, "str"); err != tt.err { - t.Errorf("makeStringSetter().JawsSetString() = %v, want %v", err, tt.err) - } - - gotTag := MustTagExpand(nil, got) - if len(gotTag) == 1 { - if gotTag[0] != tt.tag { - t.Errorf("makeStringSetter().tag = %v, want %v", gotTag, tt.tag) - } - } else if tt.tag != nil { - t.Error(len(gotTag)) - } - }) - } -} diff --git a/tag_test.go b/tag_test.go index 9ddb37d..6d499a1 100644 --- a/tag_test.go +++ b/tag_test.go @@ -37,11 +37,6 @@ func TestTagExpand(t *testing.T) { tag: Tag("foo"), want: []any{Tag("foo")}, }, - { - name: "TagGetter", - tag: atomicSetter{&av}, - want: []any{&av}, - }, { name: "TagGetter(Self)", tag: selftagger, diff --git a/testpage_test.go b/testpage_test.go index 70d4f52..98c8d15 100644 --- a/testpage_test.go +++ b/testpage_test.go @@ -63,11 +63,11 @@ const testPageWant = "(" + type testPage struct { RequestWriter Normal *template.Template - TheBool BoolSetter + TheBool Setter[bool] TheContainer Container - TheTime TimeSetter - TheNumber FloatSetter - TheString StringSetter + TheTime Setter[time.Time] + TheNumber Setter[float64] + TheString Setter[string] TheSelector SelectHandler TheDot any } diff --git a/testsetter_test.go b/testsetter_test.go index a614fce..a1f4c97 100644 --- a/testsetter_test.go +++ b/testsetter_test.go @@ -76,33 +76,6 @@ func (ts *testSetter[T]) JawsSet(e *Element, val T) (err error) { return } -func (ts *testSetter[T]) JawsGetTime(e *Element) (val T) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.getCount++ - if ts.getCount == 1 { - close(ts.getCalled) - } - val = ts.val - return -} - -func (ts *testSetter[T]) JawsSetTime(e *Element, val T) (err error) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.setCount++ - if ts.setCount == 1 { - close(ts.setCalled) - } - if err = ts.err; err == nil { - if ts.val == val { - err = ErrValueUnchanged - } - ts.val = val - } - return -} - func (ts *testSetter[string]) JawsGetString(e *Element) (val string) { ts.mu.Lock() defer ts.mu.Unlock() @@ -114,76 +87,6 @@ func (ts *testSetter[string]) JawsGetString(e *Element) (val string) { return } -func (ts *testSetter[string]) JawsSetString(e *Element, val string) (err error) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.setCount++ - if ts.setCount == 1 { - close(ts.setCalled) - } - if err = ts.err; err == nil { - if ts.val == val { - err = ErrValueUnchanged - } - ts.val = val - } - return -} - -func (ts *testSetter[bool]) JawsGetBool(e *Element) (val bool) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.getCount++ - if ts.getCount == 1 { - close(ts.getCalled) - } - val = ts.val - return -} - -func (ts *testSetter[bool]) JawsSetBool(e *Element, val bool) (err error) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.setCount++ - if ts.setCount == 1 { - close(ts.setCalled) - } - if err = ts.err; err == nil { - if ts.val == val { - err = ErrValueUnchanged - } - ts.val = val - } - return -} - -func (ts *testSetter[float64]) JawsGetFloat(e *Element) (val float64) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.getCount++ - if ts.getCount == 1 { - close(ts.getCalled) - } - val = ts.val - return -} - -func (ts *testSetter[float64]) JawsSetFloat(e *Element, val float64) (err error) { - ts.mu.Lock() - defer ts.mu.Unlock() - ts.setCount++ - if ts.setCount == 1 { - close(ts.setCalled) - } - if err = ts.err; err == nil { - if ts.val == val { - err = ErrValueUnchanged - } - ts.val = val - } - return -} - func (ts *testSetter[any]) JawsGetAny(e *Element) (val any) { ts.mu.Lock() defer ts.mu.Unlock() diff --git a/timesetter.go b/timesetter.go deleted file mode 100644 index b75af66..0000000 --- a/timesetter.go +++ /dev/null @@ -1,39 +0,0 @@ -package jaws - -import ( - "fmt" - "sync/atomic" - "time" -) - -type TimeSetter interface { - JawsGetTime(e *Element) time.Time - // JawsSetTime may return ErrValueUnchanged to indicate value was already set. - JawsSetTime(e *Element, v time.Time) (err error) -} - -type timeGetter struct{ v time.Time } - -func (g timeGetter) JawsGetTime(e *Element) time.Time { - return g.v -} - -func (g timeGetter) JawsSetTime(*Element, time.Time) error { - return ErrValueNotSettable -} - -func (g timeGetter) JawsGetTag(rq *Request) any { - return nil -} - -func makeTimeSetter(v any) TimeSetter { - switch v := v.(type) { - case TimeSetter: - return v - case time.Time: - return timeGetter{v} - case *atomic.Value: - return atomicSetter{v} - } - panic(fmt.Errorf("expected jaws.TimeGetter or time.Time, not %T", v)) -} diff --git a/timesetter_test.go b/timesetter_test.go deleted file mode 100644 index 6fc57c7..0000000 --- a/timesetter_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package jaws - -import ( - "reflect" - "strings" - "sync/atomic" - "testing" - "time" -) - -var _ TimeSetter = (*testSetter[time.Time])(nil) - -func Test_makeTimeSetter_panic(t *testing.T) { - defer func() { - if x := recover(); x != nil { - if err, ok := x.(error); ok { - if strings.Contains(err.Error(), "uint32") { - return - } - } - } - t.Fail() - }() - makeTimeSetter(uint32(42)) -} - -func Test_makeTimeSetter(t *testing.T) { - val := time.Now() - var av atomic.Value - av.Store(val) - ts := newTestSetter(val) - - tests := []struct { - name string - v any - want TimeSetter - out time.Time - err error - tag any - }{ - { - name: "TimeSetter", - v: ts, - want: ts, - out: val, - tag: ts, - }, - { - name: "time.Time", - v: val, - want: timeGetter{val}, - out: val, - err: ErrValueNotSettable, - tag: nil, - }, - { - name: "*atomic.Value", - v: &av, - want: atomicSetter{&av}, - out: val, - tag: &av, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := makeTimeSetter(tt.v) - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("makeTimeSetter() = %v, want %v", got, tt.want) - } - if out := got.JawsGetTime(nil); out != tt.out { - t.Errorf("makeTimeSetter().JawsGetTime() = %v, want %v", out, tt.out) - } - if err := got.JawsSetTime(nil, val.Add(time.Minute)); err != tt.err { - t.Errorf("makeTimeSetter().JawsSetTime() = %v, want %v", err, tt.err) - } - gotTag := any(got) - if tg, ok := got.(TagGetter); ok { - gotTag = tg.JawsGetTag(nil) - } - if gotTag != tt.tag { - t.Errorf("makeTimeSetter().tag = %v, want %v", gotTag, tt.tag) - } - }) - } -} diff --git a/uibool.go b/uibool.go deleted file mode 100644 index 2d4d772..0000000 --- a/uibool.go +++ /dev/null @@ -1,35 +0,0 @@ -package jaws - -import "sync" - -var _ BoolSetter = UiBool{} - -// UiBool implements BoolSetter given a sync.Locker (or RWLocker) and a bool pointer. -type UiBool struct { - L sync.Locker - P *bool -} - -func (ui UiBool) JawsGetBool(e *Element) (val bool) { - if rl, ok := ui.L.(RWLocker); ok { - rl.RLock() - val = *ui.P - rl.RUnlock() - return - } - ui.L.Lock() - val = *ui.P - ui.L.Unlock() - return -} - -func (ui UiBool) JawsSetBool(e *Element, val bool) (err error) { - ui.L.Lock() - if *ui.P == val { - err = ErrValueUnchanged - } else { - *ui.P = val - } - ui.L.Unlock() - return -} diff --git a/uibool_test.go b/uibool_test.go deleted file mode 100644 index db04b2d..0000000 --- a/uibool_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package jaws - -import ( - "sync" - "testing" -) - -func TestUiBool(t *testing.T) { - var l sync.Mutex - var rl sync.RWMutex - var val bool - - ui := UiBool{L: &l, P: &val} - - if ui.JawsGetBool(nil) { - t.Fail() - } - - if x := ui.JawsSetBool(nil, true); x != nil { - t.Error(x) - } - - if x := ui.JawsSetBool(nil, ui.JawsGetBool(nil)); x != ErrValueUnchanged { - t.Error(x) - } - - ui.L = &rl - - if !ui.JawsGetBool(nil) { - t.Fail() - } -} diff --git a/uicheckbox.go b/uicheckbox.go index 8aec352..df19cf8 100644 --- a/uicheckbox.go +++ b/uicheckbox.go @@ -12,14 +12,14 @@ func (ui *UiCheckbox) JawsRender(e *Element, w io.Writer, params []any) error { return ui.renderBoolInput(e, w, "checkbox", params...) } -func NewUiCheckbox(g BoolSetter) *UiCheckbox { +func NewUiCheckbox(g Setter[bool]) *UiCheckbox { return &UiCheckbox{ UiInputBool{ - BoolSetter: g, + Setter: g, }, } } func (rq RequestWriter) Checkbox(value any, params ...any) error { - return rq.UI(NewUiCheckbox(makeBoolSetter(value)), params...) + return rq.UI(NewUiCheckbox(makeSetter[bool](value)), params...) } diff --git a/uidate.go b/uidate.go index 41a7da5..7b72523 100644 --- a/uidate.go +++ b/uidate.go @@ -2,6 +2,7 @@ package jaws import ( "io" + "time" ) const ISO8601 = "2006-01-02" @@ -14,14 +15,14 @@ func (ui *UiDate) JawsRender(e *Element, w io.Writer, params []any) error { return ui.renderDateInput(e, w, "date", params...) } -func NewUiDate(g TimeSetter) *UiDate { +func NewUiDate(g Setter[time.Time]) *UiDate { return &UiDate{ UiInputDate{ - TimeSetter: g, + Setter: g, }, } } func (rq RequestWriter) Date(value any, params ...any) error { - return rq.UI(NewUiDate(makeTimeSetter(value)), params...) + return rq.UI(NewUiDate(makeSetter[time.Time](value)), params...) } diff --git a/uifloat.go b/uifloat.go deleted file mode 100644 index e825fd2..0000000 --- a/uifloat.go +++ /dev/null @@ -1,37 +0,0 @@ -package jaws - -import ( - "sync" -) - -var _ FloatSetter = UiFloat{} - -// UiFloat implements FloatSetter given a sync.Locker (or RWLocker) and a float64 pointer. -type UiFloat struct { - L sync.Locker - P *float64 -} - -func (ui UiFloat) JawsGetFloat(e *Element) (val float64) { - if rl, ok := ui.L.(RWLocker); ok { - rl.RLock() - val = *ui.P - rl.RUnlock() - return - } - ui.L.Lock() - val = *ui.P - ui.L.Unlock() - return -} - -func (ui UiFloat) JawsSetFloat(e *Element, val float64) (err error) { - ui.L.Lock() - if *ui.P == val { - err = ErrValueUnchanged - } else { - *ui.P = val - } - ui.L.Unlock() - return -} diff --git a/uifloat_test.go b/uifloat_test.go deleted file mode 100644 index d7a554e..0000000 --- a/uifloat_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package jaws - -import ( - "sync" - "testing" -) - -func TestUiFloat(t *testing.T) { - var l sync.Mutex - var rl sync.RWMutex - var val float64 - - ui := UiFloat{L: &l, P: &val} - - if ui.JawsGetFloat(nil) != 0 { - t.Fail() - } - - if x := ui.JawsSetFloat(nil, -1); x != nil { - t.Error(x) - } - - if x := ui.JawsSetFloat(nil, ui.JawsGetFloat(nil)); x != ErrValueUnchanged { - t.Error(x) - } - - ui.L = &rl - - if ui.JawsGetFloat(nil) != -1 { - t.Fail() - } -} diff --git a/uiimg.go b/uiimg.go index 4f95497..4e015a2 100644 --- a/uiimg.go +++ b/uiimg.go @@ -8,26 +8,26 @@ import ( type UiImg struct { UiHtml - StringGetter + Getter[string] } func (ui *UiImg) JawsRender(e *Element, w io.Writer, params []any) error { - ui.applyGetter(e, ui.StringGetter) - srcattr := template.HTMLAttr("src=" + strconv.Quote(ui.JawsGetString(e))) // #nosec G203 + ui.applyGetter(e, ui.Getter) + srcattr := template.HTMLAttr("src=" + strconv.Quote(ui.JawsGet(e))) // #nosec G203 attrs := append(e.ApplyParams(params), srcattr) return WriteHtmlInner(w, e.Jid(), "img", "", "", attrs...) } func (ui *UiImg) JawsUpdate(e *Element) { - e.SetAttr("src", ui.JawsGetString(e)) + e.SetAttr("src", ui.JawsGet(e)) } -func NewUiImg(g StringGetter) *UiImg { +func NewUiImg(g Getter[string]) *UiImg { return &UiImg{ - StringGetter: g, + Getter: g, } } func (rq RequestWriter) Img(imageSrc any, params ...any) error { - return rq.UI(NewUiImg(makeStringGetter(imageSrc)), params...) + return rq.UI(NewUiImg(makeGetter[string](imageSrc)), params...) } diff --git a/uiinputbool.go b/uiinputbool.go index 9df48c7..f622293 100644 --- a/uiinputbool.go +++ b/uiinputbool.go @@ -9,13 +9,13 @@ import ( type UiInputBool struct { UiInput - BoolSetter + Setter[bool] } func (ui *UiInputBool) renderBoolInput(e *Element, w io.Writer, htmltype string, params ...any) error { - ui.applyGetter(e, ui.BoolSetter) + ui.applyGetter(e, ui.Setter) attrs := e.ApplyParams(params) - v := ui.JawsGetBool(e) + v := ui.JawsGet(e) ui.Last.Store(v) if v { attrs = append(attrs, "checked") @@ -24,7 +24,7 @@ func (ui *UiInputBool) renderBoolInput(e *Element, w io.Writer, htmltype string, } func (ui *UiInputBool) JawsUpdate(e *Element) { - v := ui.JawsGetBool(e) + v := ui.JawsGet(e) if ui.Last.Swap(v) != v { txt := "false" if v { @@ -43,7 +43,7 @@ func (ui *UiInputBool) JawsEvent(e *Element, wht what.What, val string) (err err return } } - err = ui.maybeDirty(v, e, ui.BoolSetter.JawsSetBool(e, v)) + err = ui.maybeDirty(v, e, ui.Setter.JawsSet(e, v)) } return } diff --git a/uiinputdate.go b/uiinputdate.go index 9b984dc..5bf9586 100644 --- a/uiinputdate.go +++ b/uiinputdate.go @@ -9,7 +9,7 @@ import ( type UiInputDate struct { UiInput - TimeSetter + Setter[time.Time] } func (ui *UiInputDate) str() string { @@ -17,14 +17,14 @@ func (ui *UiInputDate) str() string { } func (ui *UiInputDate) renderDateInput(e *Element, w io.Writer, htmltype string, params ...any) error { - ui.applyGetter(e, ui.TimeSetter) + ui.applyGetter(e, ui.Setter) attrs := e.ApplyParams(params) - ui.Last.Store(ui.JawsGetTime(e)) + ui.Last.Store(ui.JawsGet(e)) return WriteHtmlInput(w, e.Jid(), htmltype, ui.str(), attrs) } func (ui *UiInputDate) JawsUpdate(e *Element) { - if t := ui.JawsGetTime(e); ui.Last.Swap(t) != t { + if t := ui.JawsGet(e); ui.Last.Swap(t) != t { e.SetValue(ui.str()) } } @@ -38,7 +38,7 @@ func (ui *UiInputDate) JawsEvent(e *Element, wht what.What, val string) (err err return } } - err = ui.maybeDirty(v, e, ui.TimeSetter.JawsSetTime(e, v)) + err = ui.maybeDirty(v, e, ui.Setter.JawsSet(e, v)) } return } diff --git a/uiinputfloat.go b/uiinputfloat.go index 2eb8946..9a05ce5 100644 --- a/uiinputfloat.go +++ b/uiinputfloat.go @@ -9,7 +9,7 @@ import ( type UiInputFloat struct { UiInput - FloatSetter + Setter[float64] } func (ui *UiInputFloat) str() string { @@ -17,14 +17,14 @@ func (ui *UiInputFloat) str() string { } func (ui *UiInputFloat) renderFloatInput(e *Element, w io.Writer, htmltype string, params ...any) error { - ui.applyGetter(e, ui.FloatSetter) + ui.applyGetter(e, ui.Setter) attrs := e.ApplyParams(params) - ui.Last.Store(ui.JawsGetFloat(e)) + ui.Last.Store(ui.JawsGet(e)) return WriteHtmlInput(w, e.Jid(), htmltype, ui.str(), attrs) } func (ui *UiInputFloat) JawsUpdate(e *Element) { - if f := ui.JawsGetFloat(e); ui.Last.Swap(f) != f { + if f := ui.JawsGet(e); ui.Last.Swap(f) != f { e.SetValue(ui.str()) } } @@ -38,7 +38,7 @@ func (ui *UiInputFloat) JawsEvent(e *Element, wht what.What, val string) (err er return } } - err = ui.maybeDirty(v, e, ui.FloatSetter.JawsSetFloat(e, v)) + err = ui.maybeDirty(v, e, ui.Setter.JawsSet(e, v)) } return } diff --git a/uiinputtext.go b/uiinputtext.go index 4ca4449..760e9f1 100644 --- a/uiinputtext.go +++ b/uiinputtext.go @@ -8,19 +8,19 @@ import ( type UiInputText struct { UiInput - StringSetter + Setter[string] } func (ui *UiInputText) renderStringInput(e *Element, w io.Writer, htmltype string, params ...any) error { - ui.applyGetter(e, ui.StringSetter) + ui.applyGetter(e, ui.Setter) attrs := e.ApplyParams(params) - v := ui.JawsGetString(e) + v := ui.JawsGet(e) ui.Last.Store(v) return WriteHtmlInput(w, e.Jid(), htmltype, v, attrs) } func (ui *UiInputText) JawsUpdate(e *Element) { - if v := ui.JawsGetString(e); ui.Last.Swap(v) != v { + if v := ui.JawsGet(e); ui.Last.Swap(v) != v { e.SetValue(v) } } @@ -28,7 +28,7 @@ func (ui *UiInputText) JawsUpdate(e *Element) { func (ui *UiInputText) JawsEvent(e *Element, wht what.What, val string) (err error) { err = ErrEventUnhandled if wht == what.Input { - err = ui.maybeDirty(val, e, ui.StringSetter.JawsSetString(e, val)) + err = ui.maybeDirty(val, e, ui.Setter.JawsSet(e, val)) } return } diff --git a/uinumber.go b/uinumber.go index 24bda2b..23662c8 100644 --- a/uinumber.go +++ b/uinumber.go @@ -12,14 +12,14 @@ func (ui *UiNumber) JawsRender(e *Element, w io.Writer, params []any) error { return ui.renderFloatInput(e, w, "number", params...) } -func NewUiNumber(g FloatSetter) *UiNumber { +func NewUiNumber(g Setter[float64]) *UiNumber { return &UiNumber{ UiInputFloat{ - FloatSetter: g, + Setter: g, }, } } func (rq RequestWriter) Number(value any, params ...any) error { - return rq.UI(NewUiNumber(makeFloatSetter(value)), params...) + return rq.UI(NewUiNumber(makeSetter[float64](value)), params...) } diff --git a/uipassword.go b/uipassword.go index 3cf8e51..b3d1a28 100644 --- a/uipassword.go +++ b/uipassword.go @@ -12,14 +12,14 @@ func (ui *UiPassword) JawsRender(e *Element, w io.Writer, params []any) error { return ui.renderStringInput(e, w, "password", params...) } -func NewUiPassword(g StringSetter) *UiPassword { +func NewUiPassword(g Setter[string]) *UiPassword { return &UiPassword{ UiInputText{ - StringSetter: g, + Setter: g, }, } } func (rq RequestWriter) Password(value any, params ...any) error { - return rq.UI(NewUiPassword(makeStringSetter(value)), params...) + return rq.UI(NewUiPassword(makeSetter[string](value)), params...) } diff --git a/uiradio.go b/uiradio.go index 41e01bb..a321a1e 100644 --- a/uiradio.go +++ b/uiradio.go @@ -12,14 +12,14 @@ func (ui *UiRadio) JawsRender(e *Element, w io.Writer, params []any) error { return ui.renderBoolInput(e, w, "radio", params...) } -func NewUiRadio(vp BoolSetter) *UiRadio { +func NewUiRadio(vp Setter[bool]) *UiRadio { return &UiRadio{ UiInputBool{ - BoolSetter: vp, + Setter: vp, }, } } func (rq RequestWriter) Radio(value any, params ...any) error { - return rq.UI(NewUiRadio(makeBoolSetter(value)), params...) + return rq.UI(NewUiRadio(makeSetter[bool](value)), params...) } diff --git a/uirange.go b/uirange.go index d6d69c7..143b96f 100644 --- a/uirange.go +++ b/uirange.go @@ -12,14 +12,14 @@ func (ui *UiRange) JawsRender(e *Element, w io.Writer, params []any) error { return ui.renderFloatInput(e, w, "range", params...) } -func NewUiRange(g FloatSetter) *UiRange { +func NewUiRange(g Setter[float64]) *UiRange { return &UiRange{ UiInputFloat{ - FloatSetter: g, + Setter: g, }, } } func (rq RequestWriter) Range(value any, params ...any) error { - return rq.UI(NewUiRange(makeFloatSetter(value)), params...) + return rq.UI(NewUiRange(makeSetter[float64](value)), params...) } diff --git a/uiselect.go b/uiselect.go index cd5e0c2..c725209 100644 --- a/uiselect.go +++ b/uiselect.go @@ -23,14 +23,14 @@ func (ui *UiSelect) JawsRender(e *Element, w io.Writer, params []any) error { } func (ui *UiSelect) JawsUpdate(e *Element) { - e.SetValue(ui.uiWrapContainer.Container.(StringSetter).JawsGetString(e)) + e.SetValue(ui.uiWrapContainer.Container.(Getter[string]).JawsGet(e)) ui.uiWrapContainer.JawsUpdate(e) } func (ui *UiSelect) JawsEvent(e *Element, wht what.What, val string) (err error) { err = ErrEventUnhandled if wht == what.Input { - _, err = e.maybeDirty(ui.Tag, ui.uiWrapContainer.Container.(StringSetter).JawsSetString(e, val)) + _, err = e.maybeDirty(ui.Tag, ui.uiWrapContainer.Container.(Setter[string]).JawsSet(e, val)) } return } diff --git a/uiselect_test.go b/uiselect_test.go index f5b18b0..6a9c466 100644 --- a/uiselect_test.go +++ b/uiselect_test.go @@ -16,11 +16,11 @@ type testNamedBoolArray struct { *NamedBoolArray } -func (ts *testNamedBoolArray) JawsSetString(e *Element, val string) (err error) { +func (ts *testNamedBoolArray) JawsSet(e *Element, val string) (err error) { ts.mu.Lock() defer ts.mu.Unlock() if err = ts.err; err == nil { - err = ts.NamedBoolArray.JawsSetString(e, val) + err = ts.NamedBoolArray.JawsSet(e, val) ts.setCount++ if ts.setCount == 1 { close(ts.setCalled) diff --git a/uistring.go b/uistring.go deleted file mode 100644 index 09182dd..0000000 --- a/uistring.go +++ /dev/null @@ -1,44 +0,0 @@ -package jaws - -import ( - "html" - "html/template" - "sync" -) - -var _ StringSetter = UiString{} - -// UiString implements StringSetter and HtmlGetter given a sync.Locker (or RWLocker) and a string pointer. -type UiString struct { - L sync.Locker - P *string -} - -func (ui UiString) JawsGetString(e *Element) (val string) { - if rl, ok := ui.L.(RWLocker); ok { - rl.RLock() - val = *ui.P - rl.RUnlock() - return - } - ui.L.Lock() - val = *ui.P - ui.L.Unlock() - return -} - -func (ui UiString) JawsSetString(e *Element, val string) (err error) { - ui.L.Lock() - if *ui.P == val { - err = ErrValueUnchanged - } else { - *ui.P = val - } - ui.L.Unlock() - return -} - -func (ui UiString) JawsGetHtml(e *Element) (val template.HTML) { - val = template.HTML(html.EscapeString(ui.JawsGetString(e))) // #nosec G203 - return -} diff --git a/uistring_test.go b/uistring_test.go deleted file mode 100644 index 01cb3b1..0000000 --- a/uistring_test.go +++ /dev/null @@ -1,32 +0,0 @@ -package jaws - -import ( - "sync" - "testing" -) - -func TestUiString(t *testing.T) { - var l sync.Mutex - var rl sync.RWMutex - var val string - - ui := UiString{L: &l, P: &val} - - if ui.JawsGetString(nil) != "" { - t.Fail() - } - - if x := ui.JawsSetString(nil, "foo<"); x != nil { - t.Error(x) - } - - if x := ui.JawsSetString(nil, ui.JawsGetString(nil)); x != ErrValueUnchanged { - t.Error(x) - } - - ui.L = &rl - - if ui.JawsGetHtml(nil) != "foo<" { - t.Fail() - } -} diff --git a/uitext.go b/uitext.go index a1f8988..9ce4a58 100644 --- a/uitext.go +++ b/uitext.go @@ -12,14 +12,14 @@ func (ui *UiText) JawsRender(e *Element, w io.Writer, params []any) error { return ui.renderStringInput(e, w, "text", params...) } -func NewUiText(vp StringSetter) (ui *UiText) { +func NewUiText(vp Setter[string]) (ui *UiText) { return &UiText{ UiInputText{ - StringSetter: vp, + Setter: vp, }, } } func (rq RequestWriter) Text(value any, params ...any) error { - return rq.UI(NewUiText(makeStringSetter(value)), params...) + return rq.UI(NewUiText(makeSetter[string](value)), params...) } diff --git a/uitextarea.go b/uitextarea.go index d8b42a4..2c13b67 100644 --- a/uitextarea.go +++ b/uitextarea.go @@ -10,23 +10,23 @@ type UiTextarea struct { } func (ui *UiTextarea) JawsRender(e *Element, w io.Writer, params []any) error { - ui.applyGetter(e, ui.StringSetter) + ui.applyGetter(e, ui.Setter) attrs := e.ApplyParams(params) - return WriteHtmlInner(w, e.Jid(), "textarea", "", template.HTML(ui.JawsGetString(e)), attrs...) // #nosec G203 + return WriteHtmlInner(w, e.Jid(), "textarea", "", template.HTML(ui.JawsGet(e)), attrs...) // #nosec G203 } func (ui *UiTextarea) JawsUpdate(e *Element) { - e.SetValue(ui.JawsGetString(e)) + e.SetValue(ui.JawsGet(e)) } -func NewUiTextarea(g StringSetter) (ui *UiTextarea) { +func NewUiTextarea(g Setter[string]) (ui *UiTextarea) { return &UiTextarea{ UiInputText{ - StringSetter: g, + Setter: g, }, } } func (rq RequestWriter) Textarea(value any, params ...any) error { - return rq.UI(NewUiTextarea(makeStringSetter(value)), params...) + return rq.UI(NewUiTextarea(makeSetter[string](value)), params...) }