Skip to content

Commit

Permalink
Merge pull request #12 from chasefleming/chasefleming/node-interface
Browse files Browse the repository at this point in the history
Introduce `Node` interface
  • Loading branch information
chasefleming authored Oct 22, 2023
2 parents e20dc02 + 2202c83 commit c2e0272
Show file tree
Hide file tree
Showing 7 changed files with 57 additions and 47 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})

Expand Down
28 changes: 19 additions & 9 deletions elem.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
54 changes: 27 additions & 27 deletions elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,121 +2,121 @@ 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...)
}

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...)
}

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...)
}

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)
}

Expand All @@ -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...)
}
6 changes: 3 additions & 3 deletions elements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,19 +152,19 @@ func TestInput(t *testing.T) {

func TestLabel(t *testing.T) {
expected := `<label for="username">Username</label>`
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 := `<select name="color"><option value="red">Red</option><option value="blue">Blue</option></select>`
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 := `<textarea name="comment" rows="5">Leave a comment...</textarea>`
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())
}

Expand Down
4 changes: 2 additions & 2 deletions examples/htmx-fiber-form/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 4 additions & 4 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})

Expand Down

0 comments on commit c2e0272

Please sign in to comment.