diff --git a/lib/ui/html_widgets_test.go b/lib/ui/html_widgets_test.go
index 8726c9fa..4655c52d 100644
--- a/lib/ui/html_widgets_test.go
+++ b/lib/ui/html_widgets_test.go
@@ -4,6 +4,7 @@ import (
"errors"
"html/template"
"strings"
+ "sync"
"testing"
"github.com/linkdata/jaws"
@@ -72,11 +73,32 @@ func TestImg_RenderAndUpdate(t *testing.T) {
src := newTestSetter("image.png")
ui := NewImg(src)
elem, got := renderUI(t, rq, ui, "hidden")
- mustMatch(t, `^
$`, got)
+ mustMatch(t, `^
$`, got)
src.Set("image2.jpg")
ui.JawsUpdate(elem)
}
+func TestImg_RenderGetterSrcTakesPrecedence(t *testing.T) {
+ _, rq := newCoreRequest(t)
+ var mu sync.Mutex
+ src := "getter.png"
+ getter := bind.New(&mu, &src).InitialHTMLAttr(func(bind.Binder[string], *jaws.Element) (attr template.HTMLAttr) {
+ attr = `src="hook.png" data-getter="yes"`
+ return
+ })
+
+ _, got := renderUI(t, rq, NewImg(getter), template.HTMLAttr(`src="caller.png" data-caller="yes"`))
+ canonical := strings.Index(got, `src="getter.png"`)
+ caller := strings.Index(got, `src="caller.png"`)
+ hook := strings.Index(got, `src="hook.png"`)
+ if canonical < 0 || caller < 0 || hook < 0 || caller < canonical || hook < canonical {
+ t.Fatalf("getter src does not take precedence: %s", got)
+ }
+ if !strings.Contains(got, `data-caller="yes"`) || !strings.Contains(got, `data-getter="yes"`) {
+ t.Fatalf("rendered image missing non-src attributes: %s", got)
+ }
+}
+
func TestImg_RenderEscapesSrcAttr(t *testing.T) {
_, rq := newCoreRequest(t)
src := newTestSetter(`image"©='\` + "\n")
diff --git a/lib/ui/img.go b/lib/ui/img.go
index 2c085877..9ab4bc04 100644
--- a/lib/ui/img.go
+++ b/lib/ui/img.go
@@ -2,6 +2,7 @@ package ui
import (
"io"
+ "slices"
"github.com/linkdata/jaws"
"github.com/linkdata/jaws/lib/bind"
@@ -10,6 +11,9 @@ import (
// Img renders an HTML img element whose src is read from a string getter.
//
+// The getter-derived src takes precedence over any src attribute passed as a
+// render param or returned by the getter's [jaws.InitialHTMLAttrHandler].
+//
// One Img value may back multiple live [jaws.Element] values. Its getter is
// shared by those Elements and must be safe for their render, update and event
// calls.
@@ -23,7 +27,9 @@ func (u *Img) JawsRender(elem *jaws.Element, w io.Writer, params []any) (err err
_, getterAttrs := elem.ApplyGetter(u.Getter)
srcAttr := htmlio.Attr("src", u.JawsGet(elem))
attrs := append(elem.ApplyParams(params), getterAttrs...)
- attrs = append(attrs, srcAttr)
+ // HTML parsing keeps the first duplicate attribute, so emit the canonical
+ // src before caller and getter attributes.
+ attrs = slices.Insert(attrs, 0, srcAttr)
err = htmlio.WriteHTMLInner(w, elem.Jid(), "img", "", "", attrs...)
return
}