Skip to content

Commit

Permalink
Merge pull request #53 from linkdata/html-caps
Browse files Browse the repository at this point in the history
Html caps
  • Loading branch information
linkdata authored Dec 17, 2024
2 parents 70e6fc8 + 86e5963 commit 0d2c74a
Show file tree
Hide file tree
Showing 51 changed files with 211 additions and 207 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,11 @@ router.GET("/jaws/*", func(c echo.Context) error {

### 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()`.
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(*Element) template.HTML` to be used as-is.
* `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`.
Expand All @@ -169,14 +169,18 @@ In order of precedence, this can be:
* 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`.

You can use `jaws.HTMLGetterFunc` or `jaws.StringGetterFunc` to easily build a custom renderer
for those trivial rendering tasks.

### 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`.
Since all data access need to be protected with locks, you will usually use `jaws.Bind()` to create a `jaws.Binder[T]`
that combines a (RW)Locker and a pointer to a value of type `T`. It also allows you to add chained setters,
getters and on-success handlers.

### Session handling

Expand Down
4 changes: 2 additions & 2 deletions element.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,8 @@ func (e *Element) RemoveClass(cls string) {
// to the browser for the Element.
//
// Call this only during JawsRender() or JawsUpdate() processing.
func (e *Element) SetInner(innerHtml template.HTML) {
e.queue(what.Inner, string(innerHtml))
func (e *Element) SetInner(innerHTML template.HTML) {
e.queue(what.Inner, string(innerHTML))
}

// SetValue queues sending a new current input value in textual form
Expand Down
6 changes: 3 additions & 3 deletions element_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ func TestElement_Queued(t *testing.T) {
e.Append("<div></div>")
e.Remove("some-id")
e.Order([]jid.Jid{1, 2})
replaceHtml := template.HTML(fmt.Sprintf("<div id=\"%s\"></div>", e.Jid().String()))
e.Replace(replaceHtml)
replaceHTML := template.HTML(fmt.Sprintf("<div id=\"%s\"></div>", e.Jid().String()))
e.Replace(replaceHTML)
e.JsCall("\"test\\n1\"")
th.Equal(rq.wsQueue, []wsMsg{
{
Expand Down Expand Up @@ -151,7 +151,7 @@ func TestElement_Queued(t *testing.T) {
What: what.Order,
},
{
Data: string(replaceHtml),
Data: string(replaceHTML),
Jid: e.jid,
What: what.Replace,
},
Expand Down
7 changes: 3 additions & 4 deletions htmler.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ type htmler struct {
args []any
}

// JawsGetHtml implements HtmlGetter.
func (h htmler) JawsGetHtml(e *Element) template.HTML {
func (h htmler) JawsGetHTML(e *Element) template.HTML {
if rl, ok := h.l.(RWLocker); ok {
rl.RLock()
defer rl.RUnlock()
Expand Down Expand Up @@ -45,11 +44,11 @@ func (h htmler) JawsGetTag(*Request) any {
return tags
}

// HTMLer return a lock protected jaws.HtmlGetter using the given formatting
// HTMLer return a lock protected jaws.HTMLGetter using the given formatting
// and arguments. Arguments of type string or fmt.Stringer will be escaped
// using html.EscapeString().
//
// Returns all fmt.Stringer arguments as UI tags.
func HTMLer(l sync.Locker, formatting string, args ...any) HtmlGetter {
func HTMLer(l sync.Locker, formatting string, args ...any) HTMLGetter {
return htmler{l, formatting, args}
}
4 changes: 2 additions & 2 deletions htmler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func TestHTMLer(t *testing.T) {

txt := "<text>"
htmler := HTMLer(&mu, "<p>%s</p>", Fmt(&txt))
if s := htmler.JawsGetHtml(nil); s != "<p>&lt;text&gt;</p>" {
if s := htmler.JawsGetHTML(nil); s != "<p>&lt;text&gt;</p>" {
t.Error(s)
}
tags, err := TagExpand(nil, htmler)
Expand All @@ -25,7 +25,7 @@ func TestHTMLer(t *testing.T) {

num := 123
htmler = HTMLer(&mu2, "<p>%s%s%s</p>", Fmt(&txt), "<!>", Fmt(&num))
if s := htmler.JawsGetHtml(nil); s != "<p>&lt;text&gt;&lt;!&gt;123</p>" {
if s := htmler.JawsGetHTML(nil); s != "<p>&lt;text&gt;&lt;!&gt;123</p>" {
t.Error(s)
}
tags, err = TagExpand(nil, htmler)
Expand Down
5 changes: 3 additions & 2 deletions htmlgetter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"html/template"
)

type HtmlGetter interface {
JawsGetHtml(e *Element) template.HTML
// A HTMLGetter is the primary way to deliver generated HTML content to dynamic HTML nodes.
type HTMLGetter interface {
JawsGetHTML(e *Element) template.HTML
}
6 changes: 3 additions & 3 deletions htmlgetterfunc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ type htmlGetterFunc struct {

var _ TagGetter = &htmlGetterFunc{}

func (g *htmlGetterFunc) JawsGetHtml(e *Element) template.HTML {
func (g *htmlGetterFunc) JawsGetHTML(e *Element) template.HTML {
return g.fn(e)
}

func (g *htmlGetterFunc) JawsGetTag(e *Request) any {
return g.tags
}

// HtmlGetterFunc wraps a function and returns a HtmlGetter.
func HtmlGetterFunc(fn func(*Element) template.HTML, tags ...any) HtmlGetter {
// HTMLGetterFunc wraps a function and returns a HTMLGetter.
func HTMLGetterFunc(fn func(elem *Element) (tmpl template.HTML), tags ...any) HTMLGetter {
return &htmlGetterFunc{fn: fn, tags: tags}
}
6 changes: 3 additions & 3 deletions htmlgetterfunc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import (
"testing"
)

func TestHtmlGetterFunc(t *testing.T) {
func TestHTMLGetterFunc(t *testing.T) {
tt := &testSelfTagger{}
hg := HtmlGetterFunc(func(e *Element) template.HTML {
hg := HTMLGetterFunc(func(e *Element) template.HTML {
return "foo"
}, tt)
if s := hg.JawsGetHtml(nil); s != "foo" {
if s := hg.JawsGetHTML(nil); s != "foo" {
t.Error(s)
}
if tags := MustTagExpand(nil, hg); !reflect.DeepEqual(tags, []any{tt}) {
Expand Down
4 changes: 2 additions & 2 deletions jaws.go
Original file line number Diff line number Diff line change
Expand Up @@ -638,11 +638,11 @@ func maybePanic(err error) {

// SetInner sends a request to replace the inner HTML of
// all HTML elements matching target.
func (jw *Jaws) SetInner(target any, innerHtml template.HTML) {
func (jw *Jaws) SetInner(target any, innerHTML template.HTML) {
jw.Broadcast(Message{
Dest: target,
What: what.Inner,
Data: string(innerHtml),
Data: string(innerHTML),
})
}

Expand Down
6 changes: 3 additions & 3 deletions jaws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -443,8 +443,8 @@ func TestJaws_BroadcastsCallable(t *testing.T) {
jw.Redirect("foo")
jw.Alert("info", "bar")
someTags := []any{Tag("tag1"), Tag("tag2")}
jw.SetInner("regularHtmlId", template.HTML(""))
jw.SetValue("regularHtmlId", "value")
jw.SetInner("regularHTMLId", template.HTML(""))
jw.SetValue("regularHTMLId", "value")
jw.SetAttr(someTags, "attribute", "value")
jw.RemoveAttr(someTags, "attribute")
jw.SetClass(someTags, "classname")
Expand Down
8 changes: 4 additions & 4 deletions jsfunc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestJsFunc_JawsRender(t *testing.T) {
t.Error(err)
}

got := string(rq.BodyHtml())
got := string(rq.BodyHTML())
want := `<div id="Jid.3" data-jawsname="somefn" someattr hidden></div>`
if got != want {
t.Errorf("\n got: %q\nwant: %q\n", got, want)
Expand Down Expand Up @@ -87,9 +87,9 @@ func TestJsFunc_JawsEvent(t *testing.T) {
if err := dot.JawsRender(elem, &sb, []any{"fnname"}); err != nil {
t.Fatal(err)
}
wantHtml := "<div id=\"Jid.1\" data-jawsname=\"fnname\" hidden></div>"
if gotHtml := sb.String(); gotHtml != wantHtml {
t.Errorf("\n got %q\nwant %q\n", gotHtml, wantHtml)
wantHTML := "<div id=\"Jid.1\" data-jawsname=\"fnname\" hidden></div>"
if gotHTML := sb.String(); gotHTML != wantHTML {
t.Errorf("\n got %q\nwant %q\n", gotHTML, wantHTML)
}

th.Equal(retvval, "")
Expand Down
4 changes: 2 additions & 2 deletions jsvar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func Test_JsVar_JawsRender(t *testing.T) {
t.Error(err)
}

got := string(rq.BodyHtml())
got := string(rq.BodyHTML())
want := `<div id="Jid.3" data-jawsdata='{"String":"text","Number":1.23}' data-jawsname="myjsvar" hidden></div>`
if got != want {
t.Errorf("\n got: %q\nwant: %q\n", got, want)
Expand All @@ -78,7 +78,7 @@ func Test_JsVar_VarMaker(t *testing.T) {
if err := rq.JsVar(varname, dot); err != nil {
t.Error(err)
}
got := string(rq.BodyHtml())
got := string(rq.BodyHTML())
want := `<div id="Jid.1" data-jawsdata='"foo"' data-jawsname="myjsvar" hidden></div>`
if got != want {
t.Errorf("\n got: %q\nwant: %q\n", got, want)
Expand Down
18 changes: 9 additions & 9 deletions makehtmlgetter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

type htmlGetter struct{ v template.HTML }

func (g htmlGetter) JawsGetHtml(e *Element) template.HTML {
func (g htmlGetter) JawsGetHTML(e *Element) template.HTML {
return g.v
}

Expand All @@ -18,7 +18,7 @@ func (g htmlGetter) JawsGetTag(rq *Request) any {

type htmlGetterStringGetter struct{ sg StringGetter }

func (g htmlGetterStringGetter) JawsGetHtml(e *Element) template.HTML {
func (g htmlGetterStringGetter) JawsGetHTML(e *Element) template.HTML {
return template.HTML(html.EscapeString(g.sg.JawsGetString(e))) // #nosec G203
}

Expand All @@ -28,7 +28,7 @@ func (g htmlGetterStringGetter) JawsGetTag(rq *Request) any {

type htmlGetterHTML struct{ sg Getter[template.HTML] }

func (g htmlGetterHTML) JawsGetHtml(e *Element) template.HTML {
func (g htmlGetterHTML) JawsGetHTML(e *Element) template.HTML {
return g.sg.JawsGet(e)
}

Expand All @@ -38,7 +38,7 @@ func (g htmlGetterHTML) JawsGetTag(rq *Request) any {

type htmlGetterString struct{ sg Getter[string] }

func (g htmlGetterString) JawsGetHtml(e *Element) template.HTML {
func (g htmlGetterString) JawsGetHTML(e *Element) template.HTML {
return template.HTML(html.EscapeString(g.sg.JawsGet(e))) // #nosec G203
}

Expand All @@ -48,7 +48,7 @@ func (g htmlGetterString) JawsGetTag(rq *Request) any {

type htmlGetterAny struct{ ag AnyGetter }

func (g htmlGetterAny) JawsGetHtml(e *Element) template.HTML {
func (g htmlGetterAny) JawsGetHTML(e *Element) template.HTML {
s := fmt.Sprint(g.ag.JawsGetAny(e))
return template.HTML(html.EscapeString(s)) // #nosec G203
}
Expand All @@ -57,21 +57,21 @@ func (g htmlGetterAny) JawsGetTag(rq *Request) any {
return g.ag
}

// MakeHtmlGetter returns a HtmlGetter for v.
// 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.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 {
func MakeHTMLGetter(v any) HTMLGetter {
switch v := v.(type) {
case HtmlGetter:
case HTMLGetter:
return v
case Getter[template.HTML]:
return htmlGetterHTML{v}
Expand Down
18 changes: 9 additions & 9 deletions makehtmlgetter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"testing"
)

var _ HtmlGetter = (*testSetter[template.HTML])(nil)
var _ HTMLGetter = (*testSetter[template.HTML])(nil)

type testAnySetter struct {
Value any
Expand All @@ -24,7 +24,7 @@ func (ag *testAnySetter) JawsSetAny(e *Element, v any) error {
return nil
}

func Test_MakeHtmlGetter(t *testing.T) {
func Test_MakeHTMLGetter(t *testing.T) {
untypedText := "<span>"
typedText := template.HTML(untypedText)
escapedTypedText := template.HTML(html.EscapeString(untypedText))
Expand All @@ -41,12 +41,12 @@ func Test_MakeHtmlGetter(t *testing.T) {
tests := []struct {
name string
v any
want HtmlGetter
want HTMLGetter
out template.HTML
tag any
}{
{
name: "HtmlGetter",
name: "HTMLGetter",
v: htmlGetter{typedText},
want: htmlGetter{typedText},
out: typedText,
Expand Down Expand Up @@ -111,15 +111,15 @@ func Test_MakeHtmlGetter(t *testing.T) {
}
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()\n got %#v\n 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)
if txt := got.JawsGetHTML(nil); 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)
}
})
}
Expand Down
8 changes: 4 additions & 4 deletions namedbool.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (nb *NamedBool) Name() (s string) {
return
}

func (nb *NamedBool) Html() (h template.HTML) {
func (nb *NamedBool) HTML() (h template.HTML) {
h = nb.html
return
}
Expand All @@ -43,8 +43,8 @@ func (nb *NamedBool) JawsGetString(*Element) (name string) {
return nb.Name()
}

func (nb *NamedBool) JawsGetHtml(*Element) (h template.HTML) {
return nb.Html()
func (nb *NamedBool) JawsGetHTML(*Element) (h template.HTML) {
return nb.HTML()
}

func (nb *NamedBool) JawsGet(*Element) (v bool) {
Expand Down Expand Up @@ -93,5 +93,5 @@ func (nb *NamedBool) Set(checked bool) (changed bool) {

// String returns a string representation of the NamedBool suitable for debugging.
func (nb *NamedBool) String() string {
return fmt.Sprintf("&{%q,%q,%v}", nb.Name(), nb.Html(), nb.Checked())
return fmt.Sprintf("&{%q,%q,%v}", nb.Name(), nb.HTML(), nb.Checked())
}
4 changes: 2 additions & 2 deletions namedbool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ func TestNamedBool(t *testing.T) {

is.Equal(nba, nb.Array())
is.Equal("1", nb.Name())
is.Equal(template.HTML("one"), nb.Html())
is.Equal(template.HTML("one"), nb.HTML())

is.Equal(nb.Name(), nb.JawsGetString(nil))
is.Equal(nb.Html(), nb.JawsGetHtml(nil))
is.Equal(nb.HTML(), nb.JawsGetHTML(nil))

is.NoErr(nb.JawsSet(e, true))
is.True(nb.Checked())
Expand Down
Loading

0 comments on commit 0d2c74a

Please sign in to comment.