diff --git a/README.md b/README.md index a0d94fe..569305f 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ With `elem`, you can easily generate lists of elements from slices of data using ```go items := []string{"Item 1", "Item 2", "Item 3"} -liElements := elem.TransformEach(items, func(item string) *Element { +liElements := elem.TransformEach(items, func(item string) Node { return elem.Li(nil, elem.Text(item)) }) diff --git a/elem.go b/elem.go index b42249b..8ad2471 100644 --- a/elem.go +++ b/elem.go @@ -29,10 +29,25 @@ var voidElements = map[string]struct{}{ type Attrs map[string]string +type Node interface { + RenderTo(builder *strings.Builder) + Render() string +} + +type TextNode string + +func (t TextNode) RenderTo(builder *strings.Builder) { + builder.WriteString(string(t)) +} + +func (t TextNode) Render() string { + return string(t) +} + type Element struct { Tag string Attrs Attrs - Children []interface{} // Can be either string (for text) or another Element + Children []Node } func (e *Element) RenderTo(builder *strings.Builder) { @@ -65,14 +80,9 @@ func (e *Element) RenderTo(builder *strings.Builder) { // Close opening tag builder.WriteString(`>`) - // Build the content (either child text or nested elements) + // Build the content for _, child := range e.Children { - switch c := child.(type) { - case string: - builder.WriteString(c) - case *Element: - c.RenderTo(builder) - } + child.RenderTo(builder) } // Append closing tag @@ -87,7 +97,7 @@ func (e *Element) Render() string { return builder.String() } -func NewElement(tag string, attrs Attrs, children ...interface{}) *Element { +func NewElement(tag string, attrs Attrs, children ...Node) *Element { return &Element{ Tag: tag, Attrs: attrs, diff --git a/elements.go b/elements.go index e36f220..9af90ad 100644 --- a/elements.go +++ b/elements.go @@ -2,25 +2,25 @@ package elem // ========== Document Structure ========== -func Body(props Attrs, children ...interface{}) *Element { +func Body(props Attrs, children ...Node) *Element { return NewElement("body", props, children...) } -func Head(props Attrs, children ...interface{}) *Element { +func Head(props Attrs, children ...Node) *Element { return NewElement("head", props, children...) } -func Html(props Attrs, children ...interface{}) *Element { +func Html(props Attrs, children ...Node) *Element { return NewElement("html", props, children...) } -func Title(props Attrs, children ...interface{}) *Element { +func Title(props Attrs, children ...Node) *Element { return NewElement("title", props, children...) } // ========== Text Formatting and Structure ========== -func A(props Attrs, children ...interface{}) *Element { +func A(props Attrs, children ...Node) *Element { return NewElement("a", props, children...) } @@ -28,31 +28,31 @@ func Br(props Attrs) *Element { return NewElement("br", props) } -func Blockquote(props Attrs, children ...interface{}) *Element { +func Blockquote(props Attrs, children ...Node) *Element { return NewElement("blockquote", props, children...) } -func Code(props Attrs, children ...interface{}) *Element { +func Code(props Attrs, children ...Node) *Element { return NewElement("code", props, children...) } -func Div(props Attrs, children ...interface{}) *Element { +func Div(props Attrs, children ...Node) *Element { return NewElement("div", props, children...) } -func Em(props Attrs, children ...interface{}) *Element { +func Em(props Attrs, children ...Node) *Element { return NewElement("em", props, children...) } -func H1(props Attrs, children ...interface{}) *Element { +func H1(props Attrs, children ...Node) *Element { return NewElement("h1", props, children...) } -func H2(props Attrs, children ...interface{}) *Element { +func H2(props Attrs, children ...Node) *Element { return NewElement("h2", props, children...) } -func H3(props Attrs, children ...interface{}) *Element { +func H3(props Attrs, children ...Node) *Element { return NewElement("h3", props, children...) } @@ -60,43 +60,43 @@ func Hr(props Attrs) *Element { return NewElement("hr", props) } -func P(props Attrs, children ...interface{}) *Element { +func P(props Attrs, children ...Node) *Element { return NewElement("p", props, children...) } -func Pre(props Attrs, children ...interface{}) *Element { +func Pre(props Attrs, children ...Node) *Element { return NewElement("pre", props, children...) } -func Span(props Attrs, children ...interface{}) *Element { +func Span(props Attrs, children ...Node) *Element { return NewElement("span", props, children...) } -func Strong(props Attrs, children ...interface{}) *Element { +func Strong(props Attrs, children ...Node) *Element { return NewElement("strong", props, children...) } -func Text(content string) string { - return content +func Text(content string) TextNode { + return TextNode(content) } // ========== Lists ========== -func Li(props Attrs, children ...interface{}) *Element { +func Li(props Attrs, children ...Node) *Element { return NewElement("li", props, children...) } -func Ul(props Attrs, children ...interface{}) *Element { +func Ul(props Attrs, children ...Node) *Element { return NewElement("ul", props, children...) } // ========== Forms ========== -func Button(props Attrs, children ...interface{}) *Element { +func Button(props Attrs, children ...Node) *Element { return NewElement("button", props, children...) } -func Form(attrs Attrs, children ...interface{}) *Element { +func Form(attrs Attrs, children ...Node) *Element { return NewElement("form", attrs, children...) } @@ -104,19 +104,19 @@ func Input(attrs Attrs) *Element { return NewElement("input", attrs) } -func Label(attrs Attrs, children ...interface{}) *Element { +func Label(attrs Attrs, children ...Node) *Element { return NewElement("label", attrs, children...) } -func Option(attrs Attrs, content string) *Element { +func Option(attrs Attrs, content TextNode) *Element { return NewElement("option", attrs, content) } -func Select(attrs Attrs, children ...interface{}) *Element { +func Select(attrs Attrs, children ...Node) *Element { return NewElement("select", attrs, children...) } -func Textarea(attrs Attrs, content string) *Element { +func Textarea(attrs Attrs, content TextNode) *Element { return NewElement("textarea", attrs, content) } @@ -132,6 +132,6 @@ func Meta(props Attrs) *Element { return NewElement("meta", props) } -func Script(props Attrs, children ...interface{}) *Element { +func Script(props Attrs, children ...Node) *Element { return NewElement("script", props, children...) } diff --git a/elements_test.go b/elements_test.go index 130243f..c9e39aa 100644 --- a/elements_test.go +++ b/elements_test.go @@ -152,19 +152,19 @@ func TestInput(t *testing.T) { func TestLabel(t *testing.T) { expected := `` - el := Label(Attrs{attrs.For: "username"}, "Username") + el := Label(Attrs{attrs.For: "username"}, Text("Username")) assert.Equal(t, expected, el.Render()) } func TestSelectAndOption(t *testing.T) { expected := `` - el := Select(Attrs{attrs.Name: "color"}, Option(Attrs{attrs.Value: "red"}, "Red"), Option(Attrs{attrs.Value: "blue"}, "Blue")) + el := Select(Attrs{attrs.Name: "color"}, Option(Attrs{attrs.Value: "red"}, Text("Red")), Option(Attrs{attrs.Value: "blue"}, Text("Blue"))) assert.Equal(t, expected, el.Render()) } func TestTextarea(t *testing.T) { expected := `` - el := Textarea(Attrs{attrs.Name: "comment", attrs.Rows: "5"}, "Leave a comment...") + el := Textarea(Attrs{attrs.Name: "comment", attrs.Rows: "5"}, Text("Leave a comment...")) assert.Equal(t, expected, el.Render()) } diff --git a/examples/htmx-fiber-form/main.go b/examples/htmx-fiber-form/main.go index d1a36ae..2a8da0c 100644 --- a/examples/htmx-fiber-form/main.go +++ b/examples/htmx-fiber-form/main.go @@ -42,14 +42,14 @@ func main() { htmx.HXPost: "/submit-form", htmx.HXSwap: "outerHTML", }, - elem.Label(elem.Attrs{attrs.For: "name"}, "Name: "), + elem.Label(elem.Attrs{attrs.For: "name"}, elem.Text("Name: ")), elem.Input(elem.Attrs{ attrs.Type: "text", attrs.Name: "name", attrs.ID: "name", }), elem.Br(nil), - elem.Label(elem.Attrs{attrs.For: "email"}, "Email: "), + elem.Label(elem.Attrs{attrs.For: "email"}, elem.Text("Email: ")), elem.Input(elem.Attrs{ attrs.Type: "email", attrs.Name: "email", diff --git a/utils.go b/utils.go index 36f84fd..d60cb80 100644 --- a/utils.go +++ b/utils.go @@ -39,10 +39,10 @@ func Show(condition bool, ifTrue, ifFalse *Element) *Element { } // TransformEach maps a slice of items to a slice of Elements using the provided function -func TransformEach[T any](items []T, fn func(T) *Element) []*Element { - var elements []*Element +func TransformEach[T any](items []T, fn func(T) Node) []Node { + var nodes []Node for _, item := range items { - elements = append(elements, fn(item)) + nodes = append(nodes, fn(item)) } - return elements + return nodes } diff --git a/utils_test.go b/utils_test.go index 6573af3..ff7a2f5 100644 --- a/utils_test.go +++ b/utils_test.go @@ -43,7 +43,7 @@ func TestShow(t *testing.T) { func TestTransformEach(t *testing.T) { items := []string{"Item 1", "Item 2", "Item 3"} - elements := TransformEach(items, func(item string) *Element { + elements := TransformEach(items, func(item string) Node { return Li(nil, Text(item)) })