feat(html): apply attrs through iterator wrappers
All checks were successful
Security Scan / security (push) Successful in 10s
Test / test (push) Successful in 59s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 20:40:44 +00:00
parent 60d8225a83
commit c2ff591ec9
2 changed files with 46 additions and 1 deletions

19
node.go
View file

@ -91,7 +91,7 @@ func El(tag string, children ...Node) Node {
// Attr sets an attribute on an El node. Returns the node for chaining.
// Usage example: Attr(El("a", Text("docs")), "href", "/docs")
// It recursively traverses through wrappers like If, Unless, and Entitled.
// It recursively traverses through wrappers like If, Unless, Entitled, and Each.
func Attr(n Node, key, value string) Node {
if n == nil {
return n
@ -110,6 +110,8 @@ func Attr(n Node, key, value string) Node {
for _, child := range t.cases {
Attr(child, key, value)
}
case attrApplier:
t.applyAttr(key, value)
}
return n
}
@ -314,6 +316,10 @@ type eachNode[T any] struct {
fn func(T) Node
}
type attrApplier interface {
applyAttr(key, value string)
}
// Each iterates items and renders each via fn.
// Usage example: Each([]string{"a", "b"}, func(v string) Node { return Text(v) })
func Each[T any](items []T, fn func(T) Node) Node {
@ -330,6 +336,17 @@ func (n *eachNode[T]) Render(ctx *Context) string {
return n.renderWithLayoutPath(ctx, "")
}
func (n *eachNode[T]) applyAttr(key, value string) {
if n == nil || n.fn == nil {
return
}
prev := n.fn
n.fn = func(item T) Node {
return Attr(prev(item), key, value)
}
}
func (n *eachNode[T]) renderWithLayoutPath(ctx *Context, path string) string {
if n == nil || n.fn == nil || n.items == nil {
return ""

View file

@ -318,6 +318,34 @@ func TestAttr_ThroughSwitchNode_Good(t *testing.T) {
}
}
func TestAttr_ThroughEachNode_Good(t *testing.T) {
ctx := NewContext()
node := Each([]string{"a", "b"}, func(item string) Node {
return El("span", Raw(item))
})
Attr(node, "class", "item")
got := node.Render(ctx)
want := `<span class="item">a</span><span class="item">b</span>`
if got != want {
t.Errorf("Attr through Each = %q, want %q", got, want)
}
}
func TestAttr_ThroughEachSeqNode_Good(t *testing.T) {
ctx := NewContext()
node := EachSeq(slices.Values([]string{"a", "b"}), func(item string) Node {
return El("span", Raw(item))
})
Attr(node, "data-kind", "item")
got := node.Render(ctx)
want := `<span data-kind="item">a</span><span data-kind="item">b</span>`
if got != want {
t.Errorf("Attr through EachSeq = %q, want %q", got, want)
}
}
func TestTextNode_WithService_Good(t *testing.T) {
svc, _ := i18n.New()
ctx := NewContextWithService(svc)