all repos — gomponents @ c97605aca761985ac45adfe19365801ec2682657

HTML components in pure Go

Make Groups renderable (#181) This change makes the result of `Group` renderable directly, instead of panicking, with the important caveat that root-level attributes are _ignored_. I don't think this will give problems in practice, as the main use case for rendering `Group` is basically to return root-level elements to the client using something like HTMX. I tried adding a `Fragment`, but it was weird and confusing having two functions (`Group` and `Fragment`) do essentially the same thing, the only difference being whether the argument was a slice of `Node`s or varargs. Fixes #162

Markus Wüstenberg
commit

c97605aca761985ac45adfe19365801ec2682657

parent

9c29bfccdb1a193721f710b45166aad5345ff75a

1 file changed, 29 insertions(+), 16 deletions(-)

changed files
M gomponents.gogomponents.go
@@ -72,10 +72,16 @@ // https://dev.w3.org/html5/spec-LC/syntax.html#optional-tags
// If an element is a void element, non-attribute children nodes are ignored. // Use this if no convenience creator exists in the html package. func El(name string, children ...Node) Node { - return NodeFunc(func(w2 io.Writer) error { - w := &statefulWriter{w: w2} + return NodeFunc(func(w io.Writer) error { + return render(w, &name, children...) + }) +} - w.Write([]byte("<" + name)) +func render(w2 io.Writer, name *string, children ...Node) error { + w := &statefulWriter{w: w2} + + if name != nil { + w.Write([]byte("<" + *name)) for _, c := range children { renderChild(w, c, AttributeType)
@@ -83,17 +89,20 @@ }
w.Write([]byte(">")) - if isVoidElement(name) { + if isVoidElement(*name) { return w.err } + } - for _, c := range children { - renderChild(w, c, ElementType) - } + for _, c := range children { + renderChild(w, c, ElementType) + } + + if name != nil { + w.Write([]byte("</" + *name + ">")) + } - w.Write([]byte("</" + name + ">")) - return w.err - }) + return w.err } // renderChild c to the given writer w if the node type is t.
@@ -102,6 +111,8 @@ if w.err != nil || c == nil {
return } + // Rendering groups like this is still important even though a group can render itself, + // since otherwise attributes will sometimes be ignored. if g, ok := c.(group); ok { for _, groupC := range g.children { renderChild(w, groupC, t)
@@ -241,17 +252,19 @@ }
// String satisfies [fmt.Stringer]. func (g group) String() string { - panic("cannot render group directly") + var b strings.Builder + _ = g.Render(&b) + return b.String() } // Render satisfies [Node]. -func (g group) Render(io.Writer) error { - panic("cannot render group directly") +func (g group) Render(w io.Writer) error { + return render(w, nil, g.children...) } -// Group multiple Nodes into one Node. Useful for concatenation of Nodes in variadic functions. -// The resulting Node cannot Render directly, trying it will panic. -// Render must happen through a parent element created with El or a helper. +// Group a slice of Nodes into one Node. Useful for grouping the result of [Map] into one [Node]. +// A [Group] can render directly, but if any of the direct children are [AttributeType], they will be ignored, +// to not produce invalid HTML. func Group(children []Node) Node { return group{children: children} }