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)