Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce Node interface #12

Merged
merged 1 commit into from
Oct 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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