diff options
author | Markus Wüstenberg | 2020-11-16 12:38:24 +0100 |
---|---|---|
committer | GitHub | 2020-11-16 12:38:24 +0100 |
commit | 794c3b26acbd3931b7973ff7e09a42b0ac414b1c (patch) | |
tree | ac909b96ce6cca65b71828deca179db8e22e2995 /gomponents.go | |
parent | 87d09c382434c9198c50356954fedf0ab8e5576f (diff) | |
download | gomponents-794c3b26acbd3931b7973ff7e09a42b0ac414b1c.tar.lz gomponents-794c3b26acbd3931b7973ff7e09a42b0ac414b1c.tar.zst gomponents-794c3b26acbd3931b7973ff7e09a42b0ac414b1c.zip |
Render correct HTML5 (#44)
Previously, elements of kind void and empty elements generally would be rendered auto-closing (with a final `/` character in the start tag), which is allowed sometimes but arguably wrong. See https://dev.w3.org/html5/spec-LC/syntax.html#end-tags This created problems with for example `textarea` and `script`, which cannot be auto-closing, or the browser renders it wrong. Also clarified in the docs that this library outputs HTML5. Fixes #42.
Diffstat (limited to 'gomponents.go')
-rw-r--r-- | gomponents.go | 66 |
1 files changed, 37 insertions, 29 deletions
diff --git a/gomponents.go b/gomponents.go index 02e52ca..14df153 100644 --- a/gomponents.go +++ b/gomponents.go @@ -1,4 +1,4 @@ -// Package gomponents provides declarative view components in Go, that can render to HTML. +// Package gomponents provides declarative view components in Go, that can render to HTML5. // The primary interface is a Node, which has a single function Render, which should render // the Node to a string. Furthermore, NodeFunc is a function which implements the Node interface // by calling itself on Render. @@ -14,6 +14,10 @@ import ( "strings" ) +// voidElements don't have end tags and must be treated differently in the rendering. +// See https://dev.w3.org/html5/spec-LC/syntax.html#void-elements +var voidElements = []string{"area", "base", "br", "col", "command", "embed", "hr", "img", "input", "keygen", "link", "meta", "param", "source", "track", "wbr"} + // Node is a DOM node that can Render itself to a io.Writer. type Node interface { Render(w io.Writer) error @@ -51,33 +55,32 @@ func (n NodeFunc) String() string { return b.String() } +// nodeType is for DOM Nodes that are either an element or an attribute. type nodeType int const ( - attrType = nodeType(iota) - elementType + elementType = nodeType(iota) + attributeType ) // El creates an element DOM Node with a name and child Nodes. // Use this if no convenience creator exists. +// See https://dev.w3.org/html5/spec-LC/syntax.html#elements-0 for how elements are rendered. +// No tags are ever omitted from normal tags, even though it's allowed for elements given at +// https://dev.w3.org/html5/spec-LC/syntax.html#optional-tags +// If an element is a void kind, non-attribute nodes are ignored. func El(name string, children ...Node) NodeFunc { return func(w2 io.Writer) error { w := &statefulWriter{w: w2} w.Write([]byte("<" + name)) - if len(children) == 0 { - w.Write([]byte(" />")) - return w.err - } - - hasOutsideChildren := false for _, c := range children { - hasOutsideChildren = renderChild(w, c, attrType) || hasOutsideChildren + renderChild(w, c, attributeType) } - if !hasOutsideChildren { - w.Write([]byte(" />")) + if isVoidKind(name) { + w.Write([]byte(">")) return w.err } @@ -92,33 +95,38 @@ func El(name string, children ...Node) NodeFunc { } } +func isVoidKind(name string) bool { + for _, e := range voidElements { + if name == e { + return true + } + } + return false +} + // renderChild c to the given writer w if the node type is t. -// Returns whether the child would be written Outside, regardless of whether it is actually written. -func renderChild(w *statefulWriter, c Node, t nodeType) bool { +func renderChild(w *statefulWriter, c Node, t nodeType) { if w.err != nil || c == nil { - return false + return } - isOutside := false if g, ok := c.(group); ok { for _, groupC := range g.children { - isOutside = renderChild(w, groupC, t) || isOutside + renderChild(w, groupC, t) } - return isOutside - } - - if p, ok := c.(Placer); !ok || p.Place() == Outside { - isOutside = true + return } - switch { - case t == attrType && !isOutside: - w.err = c.Render(w.w) - case t == elementType && isOutside: - w.err = c.Render(w.w) + switch t { + case elementType: + if p, ok := c.(Placer); !ok || p.Place() == Outside { + w.err = c.Render(w.w) + } + case attributeType: + if p, ok := c.(Placer); ok && p.Place() == Inside { + w.err = c.Render(w.w) + } } - - return isOutside } // statefulWriter only writes if no errors have occured earlier in its lifetime. |