diff --git a/node.go b/node.go
index 305f9ea..9fd9aed 100644
--- a/node.go
+++ b/node.go
@@ -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 ""
diff --git a/node_test.go b/node_test.go
index 91defdd..e527c4e 100644
--- a/node_test.go
+++ b/node_test.go
@@ -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 := `ab`
+ 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 := `ab`
+ 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)