Skip to content

Commit 883c47e

Browse files
committed
simplify
1 parent 21ed16a commit 883c47e

5 files changed

Lines changed: 108 additions & 68 deletions

File tree

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,12 +296,11 @@ be made into one using `bind.MakeHTMLGetter()`.
296296
In order of precedence, this can be:
297297
* `bind.HTMLGetter`: `JawsGetHTML(*Element) template.HTML` to be used as-is.
298298
* `bind.Getter[string]`: `JawsGet(*Element) string` that will be escaped using `html.EscapeString`.
299-
* `bind.Formatter`: `Format("%v") string` that will be escaped using `html.EscapeString`.
300299
* `fmt.Stringer`: `String() string` that will be escaped using `html.EscapeString`.
301300
* a static `template.HTML` or `string` to be used as-is with no HTML escaping.
302301
* everything else is rendered using `fmt.Sprint()` and escaped using `html.EscapeString`.
303302

304-
You can use `bind.New(...).FormatHTML()`, `bind.HTMLGetterFunc()` or `bind.StringGetterFunc()` to build a custom renderer
303+
You can use `bind.New(...).GetHTML(...)`, `bind.HTMLGetterFunc()` or `bind.StringGetterFunc()` to build a custom renderer
305304
for trivial rendering tasks, or define a custom type implementing `HTMLGetter`.
306305

307306
### Data binding

lib/bind/bind_test.go

Lines changed: 46 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package bind
22

33
import (
44
"errors"
5+
"html/template"
56
"io"
67
"reflect"
78
"testing"
@@ -698,63 +699,67 @@ func TestBindFunc_Time(t *testing.T) {
698699
testBind_TimeSetter(t, New(&mu, &val).Success(func() {}))
699700
}
700701

701-
func TestBindFormat(t *testing.T) {
702-
var mu deadlock.Mutex
703-
val := 12
704-
705-
bind := New(&mu, &val)
706-
if v := MakeHTMLGetter(bind).JawsGetHTML(nil); v != "12" {
707-
t.Errorf("%T %#v", v, v)
702+
func TestBind_GetHTML_Default(t *testing.T) {
703+
var mu1 deadlock.Mutex
704+
val1 := 12
705+
bind1 := New(&mu1, &val1)
706+
if got, want := MakeHTMLGetter(bind1).JawsGetHTML(nil), template.HTML("12"); got != want {
707+
t.Fatalf("want %q got %q", want, got)
708708
}
709-
710-
getter := bind.Format("%3v")
711-
if s := getter.JawsGet(nil); s != " 12" {
712-
t.Errorf("%q", s)
713-
}
714-
tags := jtag.MustTagExpand(nil, getter)
715-
if !reflect.DeepEqual(tags, []any{&val}) {
716-
t.Error(tags)
709+
if tags := jtag.MustTagExpand(nil, bind1); !reflect.DeepEqual(tags, []any{&val1}) {
710+
t.Fatal(tags)
717711
}
718712

719-
bind2 := bind.Success(func() {})
720-
getter = bind2.Format("%3v")
721-
if s := getter.JawsGet(nil); s != " 12" {
722-
t.Errorf("%q", s)
713+
var mu2 deadlock.Mutex
714+
val2 := "<span>"
715+
bind2 := New(&mu2, &val2)
716+
if got, want := MakeHTMLGetter(bind2).JawsGetHTML(nil), template.HTML("&lt;span&gt;"); got != want {
717+
t.Fatalf("want %q got %q", want, got)
723718
}
724-
tags = jtag.MustTagExpand(nil, getter)
725-
if !reflect.DeepEqual(tags, []any{&val}) {
726-
t.Error(tags)
719+
if tags := jtag.MustTagExpand(nil, bind2); !reflect.DeepEqual(tags, []any{&val2}) {
720+
t.Fatal(tags)
721+
}
722+
bind3 := bind2.Success(func() {})
723+
if got, want := MakeHTMLGetter(bind3).JawsGetHTML(nil), template.HTML("&lt;span&gt;"); got != want {
724+
t.Fatalf("want %q got %q", want, got)
727725
}
728726
}
729727

730-
func TestBindFormatHTML(t *testing.T) {
728+
func TestBind_Hook_GetHTML(t *testing.T) {
731729
var mu deadlock.Mutex
732730
val := "<span>"
733731

734-
bind := New(&mu, &val)
735-
if s := MakeHTMLGetter(bind).JawsGetHTML(nil); s != "&lt;span&gt;" {
736-
t.Errorf("%q", s)
732+
root := New(&mu, &val)
733+
if got, want := MakeHTMLGetter(root).JawsGetHTML(nil), template.HTML("&lt;span&gt;"); got != want {
734+
t.Fatalf("want %q got %q", want, got)
737735
}
738736

739-
getter := bind.FormatHTML("%v")
740-
if s := getter.JawsGetHTML(nil); s != "<span>" {
741-
t.Errorf("%q", s)
737+
getHTML1PrevOK := false
738+
getHTML1 := root.GetHTML(func(bind Binder[string], elem *jaws.Element) (s template.HTML) {
739+
getHTML1PrevOK = bind == root
740+
s = template.HTML(bind.JawsGetLocked(elem))
741+
return
742+
})
743+
if got, want := MakeHTMLGetter(getHTML1).JawsGetHTML(nil), template.HTML("<span>"); got != want {
744+
t.Fatalf("want %q got %q", want, got)
742745
}
743-
tags := jtag.MustTagExpand(nil, getter)
744-
if !reflect.DeepEqual(tags, []any{&val}) {
745-
t.Error(tags)
746+
if !getHTML1PrevOK {
747+
t.Fatal("GetHTML first hook previous binder mismatch")
746748
}
747749

748-
bind2 := bind.Success(func() {})
749-
if s := bind2.JawsGet(nil); s != "<span>" {
750-
t.Errorf("%q", s)
750+
getHTML2PrevOK := false
751+
getHTML2 := getHTML1.GetHTML(func(bind Binder[string], elem *jaws.Element) (s template.HTML) {
752+
getHTML2PrevOK = bind == getHTML1
753+
s = template.HTML("<b>" + bind.JawsGetLocked(elem) + "</b>")
754+
return
755+
})
756+
if got, want := MakeHTMLGetter(getHTML2).JawsGetHTML(nil), template.HTML("<b><span></b>"); got != want {
757+
t.Fatalf("want %q got %q", want, got)
751758
}
752-
getter = bind2.FormatHTML("%q")
753-
if s := getter.JawsGetHTML(nil); s != "\"<span>\"" {
754-
t.Errorf("%q", s)
759+
if !getHTML2PrevOK {
760+
t.Fatal("GetHTML second hook previous binder mismatch")
755761
}
756-
tags = jtag.MustTagExpand(nil, getter)
757-
if !reflect.DeepEqual(tags, []any{&val}) {
758-
t.Error(tags)
762+
if tags := jtag.MustTagExpand(nil, getHTML2); !reflect.DeepEqual(tags, []any{&val}) {
763+
t.Fatal(tags)
759764
}
760765
}

lib/bind/binder.go

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bind
33
import (
44
"errors"
55
"fmt"
6+
"html"
67
"html/template"
78

89
"github.com/linkdata/jaws"
@@ -27,6 +28,12 @@ type SetHook[T comparable] func(bind Binder[T], elem *jaws.Element, value T) (er
2728
// want to call it's JawsGetLocked first.
2829
type GetHook[T comparable] func(bind Binder[T], elem *jaws.Element) (value T)
2930

31+
// GetHTMLHook is a function to call when JawsGetHTML() is called.
32+
//
33+
// The lock will be held before calling the function, preferring RLock over Lock, if available.
34+
// Do not lock or unlock the Binder in the function. Do not call JawsGet.
35+
type GetHTMLHook[T comparable] func(bind Binder[T], elem *jaws.Element) (s template.HTML)
36+
3037
// ClickedHook is a function to call when a click event is received.
3138
//
3239
// The Binder locks are not held when the function is called.
@@ -46,16 +53,10 @@ type ContextMenuHook[T comparable] func(bind Binder[T], elem *jaws.Element, clic
4653
// no more success hooks are called.
4754
type SuccessHook func(*jaws.Element) (err error)
4855

49-
type Formatter interface {
50-
// Format returns a Getter[string] using fmt.Sprintf(f, JawsGet[T](elem))
51-
Format(f string) (getter Getter[string])
52-
}
53-
5456
type Binder[T comparable] interface {
5557
RWLocker
5658
Setter[T]
5759
jtag.TagGetter
58-
Formatter
5960
jaws.ClickHandler
6061
jaws.ContextMenuHandler
6162

@@ -91,8 +92,12 @@ type Binder[T comparable] interface {
9192
// * func(*Element) error
9293
Success(fn any) (newbind Binder[T])
9394

94-
// FormatHTML returns a HTMLGetter using fmt.Sprintf(f, JawsGet[T](elem))
95-
FormatHTML(f string) (getter HTMLGetter)
95+
// GetHTML returns a Binder[T] that will call fn instead of the default
96+
// escaped fmt.Sprintf("%v", JawsGetLocked(elem)) HTML rendering.
97+
//
98+
// The lock will be held at this point, preferring RLock over Lock, if available.
99+
// Do not lock or unlock the Binder within fn. Do not call JawsGet.
100+
GetHTML(fn GetHTMLHook[T]) (newbind Binder[T])
96101

97102
// Clicked returns a Binder[T] that will call fn when JawsClick is invoked.
98103
//
@@ -110,7 +115,7 @@ type binder[T comparable] struct {
110115
prev *binder[T]
111116
RWLocker
112117
ptr *T
113-
hook any // one of: BindGetHook[T] BindSetHook[T] BindClickedHook[T] BindContextMenuHook[T] BindSuccessHook
118+
hook any // one of: SetHook[T] GetHook[T] GetHTMLHook[T] ClickedHook[T] ContextMenuHook[T] SuccessHook
114119
}
115120

116121
func (bind *binder[T]) walk(fn func(*binder[T]) bool) bool {
@@ -140,6 +145,24 @@ func (bind *binder[T]) JawsGet(elem *jaws.Element) (value T) {
140145
return
141146
}
142147

148+
func (bind *binder[T]) jawsGetHTMLLocked(elem *jaws.Element) (s template.HTML) {
149+
if fn, ok := bind.hook.(GetHTMLHook[T]); ok {
150+
s = fn(bind.prev, elem)
151+
} else if bind.prev != nil {
152+
s = bind.prev.jawsGetHTMLLocked(elem)
153+
} else {
154+
s = template.HTML(html.EscapeString(fmt.Sprintf("%v", *bind.ptr))) // #nosec G203
155+
}
156+
return
157+
}
158+
159+
func (bind *binder[T]) JawsGetHTML(elem *jaws.Element) (s template.HTML) {
160+
bind.RWLocker.RLock()
161+
defer bind.RWLocker.RUnlock()
162+
s = bind.jawsGetHTMLLocked(elem)
163+
return
164+
}
165+
143166
func (bind *binder[T]) JawsSetLocked(elem *jaws.Element, value T) (err error) {
144167
if fn, ok := bind.hook.(SetHook[T]); ok {
145168
err = fn(bind.prev, elem, value)
@@ -241,6 +264,20 @@ func (bind *binder[T]) GetLocked(fn GetHook[T]) Binder[T] {
241264
}
242265
}
243266

267+
// GetHTML returns a Binder[T] that will call fn instead of the default escaped
268+
// fmt.Sprintf("%v", JawsGetLocked(elem)) HTML rendering.
269+
//
270+
// The lock will be held at this point, preferring RLock over Lock, if available.
271+
// Do not lock or unlock the Binder within fn. Do not call JawsGet.
272+
func (bind *binder[T]) GetHTML(fn GetHTMLHook[T]) Binder[T] {
273+
return &binder[T]{
274+
prev: bind,
275+
RWLocker: bind.RWLocker,
276+
ptr: bind.ptr,
277+
hook: fn,
278+
}
279+
}
280+
244281
// Clicked returns a Binder[T] that will call fn when JawsClick is invoked.
245282
//
246283
// The Binder locks are not held when the function is called.
@@ -283,19 +320,6 @@ func (bind *binder[T]) Success(fn any) Binder[T] {
283320
}
284321
}
285322

286-
// Format returns a Getter[string] using fmt.Sprintf(f, JawsGet[T](elem))
287-
func (bind *binder[T]) Format(f string) (getter Getter[string]) {
288-
return StringGetterFunc(func(elem *jaws.Element) (s string) { return fmt.Sprintf(f, bind.JawsGet(elem)) }, bind)
289-
}
290-
291-
// FormatHTML returns a HTMLGetter using fmt.Sprintf(f, JawsGet[T](elem)).
292-
// Ensure that the generated string is valid HTML.
293-
func (bind *binder[T]) FormatHTML(f string) (getter HTMLGetter) {
294-
return HTMLGetterFunc(func(elem *jaws.Element) (tmpl template.HTML) {
295-
return template.HTML( /*#nosec G203*/ fmt.Sprintf(f, bind.JawsGet(elem)))
296-
}, bind)
297-
}
298-
299323
func wrapSuccessHook(fn any) (hook SuccessHook) {
300324
switch fn := fn.(type) {
301325
case func():

lib/bind/makehtmlgetter.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ func (g htmlGetterString) JawsGetTag(jtag.Context) any {
5151
//
5252
// - HTMLGetter: `JawsGetHTML(e *Element) template.HTML` to be used as-is.
5353
// - Getter[string]: `JawsGet(elem *Element) string` that will be escaped using `html.EscapeString`.
54-
// - Formatter: `Format("%v") string` that will be escaped using `html.EscapeString`.
5554
// - fmt.Stringer: `String() string` that will be escaped using `html.EscapeString`.
5655
// - a static `template.HTML` or `string` to be used as-is with no HTML escaping.
5756
// - everything else is rendered using `fmt.Sprint()` and escaped using `html.EscapeString`.
@@ -71,8 +70,6 @@ func MakeHTMLGetter(v any) HTMLGetter {
7170
return htmlBinderString{v}
7271
case Getter[string]:
7372
return htmlGetterString{v}
74-
case Formatter:
75-
return htmlGetterString{v.Format("%v")}
7673
case fmt.Stringer:
7774
return htmlStringerGetter{v}
7875
case string:

lib/bind/makehtmlgetter_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"html"
55
"html/template"
66
"reflect"
7+
"sync"
78
"sync/atomic"
89
"testing"
910

@@ -16,6 +17,10 @@ func (testStringer) String() string {
1617
return "<x>"
1718
}
1819

20+
type testBinderStringNoHTML struct {
21+
Binder[string]
22+
}
23+
1924
/*type testAnySetter struct {
2025
Value any
2126
}
@@ -36,6 +41,9 @@ func Test_MakeHTMLGetter(t *testing.T) {
3641
avUntyped.Store(untypedText)
3742
avTyped.Store(typedText)
3843
stringer := testStringer{}
44+
var binderVal = "<b>"
45+
var binderMu sync.Mutex
46+
binderNoHTML := testBinderStringNoHTML{New(&binderMu, &binderVal)}
3947

4048
getterString := testGetterString{}
4149

@@ -60,6 +68,13 @@ func Test_MakeHTMLGetter(t *testing.T) {
6068
out: template.HTML(html.EscapeString(getterString.JawsGet(nil))),
6169
tag: getterString,
6270
},
71+
{
72+
name: "Binder[string]",
73+
v: binderNoHTML,
74+
want: htmlBinderString{binderNoHTML},
75+
out: template.HTML(html.EscapeString(binderNoHTML.JawsGet(nil))),
76+
tag: &binderVal,
77+
},
6378
/*{
6479
name: "Getter[any]",
6580
v: getterAny,

0 commit comments

Comments
 (0)