From a76262652b227c95ce140f3698c46f59b79354ac Mon Sep 17 00:00:00 2001
From: Markus Wüstenberg
Date: Thu, 10 Dec 2020 13:00:23 +0100
Subject: Move elements and attributes into html package (#52)
This makes it easier to use dot-imports.
Also updated the readme and examples with new usage, and move the `Classes` helper into the `components` package.---
README.md | 116 +++++-----
attr/attributes.go | 38 ----
attr/attributes_test.go | 32 ---
attr/boolean.go | 45 ----
attr/boolean_test.go | 32 ---
attr/simple.go | 121 -----------
attr/simple_test.go | 51 -----
components/attributes.go | 37 ++++
components/attributes_test.go | 32 +++
components/documents.go | 29 ++-
components/documents_test.go | 13 +-
el/elements.go | 68 ------
el/elements_test.go | 82 -------
el/simple.go | 257 ----------------------
el/simple_test.go | 97 ---------
el/text.go | 131 -----------
el/text_test.go | 52 -----
examples/dot-import/dot-import.go | 43 ++--
examples/simple/simple.go | 30 +--
html/attributes.go | 161 ++++++++++++++
html/attributes_test.go | 73 +++++++
html/elements.go | 447 ++++++++++++++++++++++++++++++++++++++
html/elements_test.go | 212 ++++++++++++++++++
23 files changed, 1088 insertions(+), 1111 deletions(-)
delete mode 100644 attr/attributes.go
delete mode 100644 attr/attributes_test.go
delete mode 100644 attr/boolean.go
delete mode 100644 attr/boolean_test.go
delete mode 100644 attr/simple.go
delete mode 100644 attr/simple_test.go
create mode 100644 components/attributes.go
create mode 100644 components/attributes_test.go
delete mode 100644 el/elements.go
delete mode 100644 el/elements_test.go
delete mode 100644 el/simple.go
delete mode 100644 el/simple_test.go
delete mode 100644 el/text.go
delete mode 100644 el/text_test.go
create mode 100644 html/attributes.go
create mode 100644 html/attributes_test.go
create mode 100644 html/elements.go
create mode 100644 html/elements_test.go
diff --git a/README.md b/README.md
index cba1e66..cd8a887 100644
--- a/README.md
+++ b/README.md
@@ -8,19 +8,19 @@ gomponents aims to make it easy to build HTML5 pages of reusable components,
without the use of a template language. Think server-side-rendered React,
but without the virtual DOM and diffing.
-The implementation is still incomplete, but usable. The API may change until version 1 is reached.
+The implementation is very usable, but the API may change until version 1 is reached.
Check out the blog post [gomponents: declarative view components in Go](https://www.maragu.dk/blog/gomponents-declarative-view-components-in-go/)
for background.
## Features
+- Build reusable view components
- Write declarative HTML5 in Go without all the strings, so you get
- Type safety
- Auto-completion
- Nice formatting with `gofmt`
-- Simple API that's easy to learn and use
-- Build reusable view components
+- Simple API that's easy to learn and use (you know most already if you know HTML)
- No external dependencies
## Usage
@@ -31,7 +31,8 @@ Get the library using `go get`:
go get -u github.com/maragudk/gomponents
```
-Then do something like this:
+The preferred way to use gomponents is with so-called dot-imports (note the dot before the `gomponents/html` import),
+to give you that smooth, native HTML feel:
```go
package main
@@ -40,47 +41,51 @@ import (
"net/http"
g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/attr"
- "github.com/maragudk/gomponents/el"
+ c "github.com/maragudk/gomponents/components"
+ . "github.com/maragudk/gomponents/html"
)
func main() {
- _ = http.ListenAndServe("localhost:8080", handler())
+ _ = http.ListenAndServe("localhost:8080", http.HandlerFunc(handler))
}
-func handler() http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- page := Page("Hi!", r.URL.Path)
- _ = page.Render(w)
- }
+func handler(w http.ResponseWriter, r *http.Request) {
+ _ = Page("Hi!", r.URL.Path).Render(w)
}
-func Page(title, path string) g.Node {
- return el.Document(
- el.HTML(
- g.Attr("lang", "en"),
- el.Head(
- el.Title(title),
- el.Style(g.Attr("type", "text/css"), g.Raw(".is-active{font-weight: bold}")),
+func Page(title, currentPath string) g.Node {
+ return Document(
+ HTML(
+ Lang("en"),
+ Head(
+ TitleEl(title),
+ StyleEl(Type("text/css"), g.Raw(".is-active{ font-weight: bold }")),
),
- el.Body(
- Navbar(path),
- el.H1(title),
- el.P(g.Textf("Welcome to the page at %v.", path)),
+ Body(
+ Navbar(currentPath),
+ H1(title),
+ P(g.Textf("Welcome to the page at %v.", currentPath)),
),
),
)
}
-func Navbar(path string) g.Node {
- return g.El("nav",
- el.A("/", attr.Classes{"is-active": path == "/"}, g.Text("Home")),
- el.A("/about", attr.Classes{"is-active": path == "/about"}, g.Text("About")),
+func Navbar(currentPath string) g.Node {
+ return Nav(
+ NavbarLink("/", "Home", currentPath),
+ NavbarLink("/about", "About", currentPath),
)
}
+
+func NavbarLink(href, name, currentPath string) g.Node {
+ return A(href, c.Classes{"is-active": currentPath == href}, g.Text(name))
+}
```
-You could also use a page template to simplify your code a bit:
+Some people don't like dot-imports, and luckily it's completely optional.
+If you don't like dot-imports, just use regular imports.
+
+You could also use the provided HTML5 document template to simplify your code a bit:
```go
package main
@@ -89,41 +94,52 @@ import (
"net/http"
g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/attr"
- c "github.com/maragudk/gomponents/components"
- "github.com/maragudk/gomponents/el"
+ . "github.com/maragudk/gomponents/components"
+ . "github.com/maragudk/gomponents/html"
)
func main() {
- _ = http.ListenAndServe("localhost:8080", handler())
+ _ = http.ListenAndServe("localhost:8080", http.HandlerFunc(handler))
}
-func handler() http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- page := Page("Hi!", r.URL.Path)
- _ = page.Render(w)
- }
+func handler(w http.ResponseWriter, r *http.Request) {
+ _ = Page("Hi!", r.URL.Path).Render(w)
}
-func Page(title, path string) g.Node {
- return c.HTML5(c.DocumentProps{
- Title: title,
- Language: "en",
- Head: []g.Node{el.Style(g.Attr("type", "text/css"), g.Raw(".is-active{font-weight: bold}"))},
- Body: []g.Node{
- Navbar(path),
- el.H1(title),
- el.P(g.Textf("Welcome to the page at %v.", path)),
+func Page(title, currentPath string) g.Node {
+ return HTML5(HTML5Props{
+ Title: title,
+ Language: "en",
+ Head: []g.Node{
+ StyleEl(Type("text/css"), g.Raw(".is-active{ font-weight: bold }")),
+ },
+ Body: []g.Node{
+ Navbar(currentPath),
+ H1(title),
+ P(g.Textf("Welcome to the page at %v.", currentPath)),
},
})
}
-func Navbar(path string) g.Node {
- return g.El("nav",
- el.A("/", attr.Classes{"is-active": path == "/"}, g.Text("Home")),
- el.A("/about", attr.Classes{"is-active": path == "/about"}, g.Text("About")),
+func Navbar(currentPath string) g.Node {
+ return Nav(
+ NavbarLink("/", "Home", currentPath),
+ NavbarLink("/about", "About", currentPath),
)
}
+
+func NavbarLink(href, name, currentPath string) g.Node {
+ return A(href, Classes{"is-active": currentPath == href}, g.Text(name))
+}
```
For more complete examples, see [the examples directory](examples/).
+
+### What's up with the specially named elements and attributes?
+
+Unfortunately, there are three main name clashes in HTML elements and attributes, so they need an `El` or `Attr` suffix,
+respectively, to be able to co-exist in the same package in Go:
+
+- `form` (`FormEl`/`FormAttr`)
+- `style` (`StyleEl`/`StyleAttr`)
+- `title` (`TitleEl`/`TitleAttr`)
diff --git a/attr/attributes.go b/attr/attributes.go
deleted file mode 100644
index 938d762..0000000
--- a/attr/attributes.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Package attr provides shortcuts and helpers to common HTML attributes.
-// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes for a list of attributes.
-package attr
-
-import (
- "io"
- "sort"
- "strings"
-
- g "github.com/maragudk/gomponents"
-)
-
-// Classes is a map of strings to booleans, which Renders to an attribute with name "class".
-// The attribute value is a sorted, space-separated string of all the map keys,
-// for which the corresponding map value is true.
-type Classes map[string]bool
-
-func (c Classes) Render(w io.Writer) error {
- var included []string
- for c, include := range c {
- if include {
- included = append(included, c)
- }
- }
- sort.Strings(included)
- return g.Attr("class", strings.Join(included, " ")).Render(w)
-}
-
-func (c Classes) Type() g.NodeType {
- return g.AttributeType
-}
-
-// String satisfies fmt.Stringer.
-func (c Classes) String() string {
- var b strings.Builder
- _ = c.Render(&b)
- return b.String()
-}
diff --git a/attr/attributes_test.go b/attr/attributes_test.go
deleted file mode 100644
index da04a20..0000000
--- a/attr/attributes_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package attr_test
-
-import (
- "testing"
-
- g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/assert"
- "github.com/maragudk/gomponents/attr"
-)
-
-func TestClasses(t *testing.T) {
- t.Run("given a map, returns sorted keys from the map with value true", func(t *testing.T) {
- assert.Equal(t, ` class="boheme-hat hat partyhat"`, attr.Classes{
- "boheme-hat": true,
- "hat": true,
- "partyhat": true,
- "turtlehat": false,
- })
- })
-
- t.Run("renders as attribute in an element", func(t *testing.T) {
- e := g.El("div", attr.Classes{"hat": true})
- assert.Equal(t, `
`, e)
- })
-
- t.Run("also works with fmt", func(t *testing.T) {
- a := attr.Classes{"hat": true}
- if a.String() != ` class="hat"` {
- t.FailNow()
- }
- })
-}
diff --git a/attr/boolean.go b/attr/boolean.go
deleted file mode 100644
index 2a70c9d..0000000
--- a/attr/boolean.go
+++ /dev/null
@@ -1,45 +0,0 @@
-package attr
-
-import (
- g "github.com/maragudk/gomponents"
-)
-
-func Async() g.Node {
- return g.Attr("async")
-}
-
-func AutoFocus() g.Node {
- return g.Attr("autofocus")
-}
-
-func AutoPlay() g.Node {
- return g.Attr("autoplay")
-}
-
-func Controls() g.Node {
- return g.Attr("controls")
-}
-
-func Defer() g.Node {
- return g.Attr("defer")
-}
-
-func Disabled() g.Node {
- return g.Attr("disabled")
-}
-
-func Multiple() g.Node {
- return g.Attr("multiple")
-}
-
-func ReadOnly() g.Node {
- return g.Attr("readonly")
-}
-
-func Required() g.Node {
- return g.Attr("required")
-}
-
-func Selected() g.Node {
- return g.Attr("selected")
-}
diff --git a/attr/boolean_test.go b/attr/boolean_test.go
deleted file mode 100644
index 309b025..0000000
--- a/attr/boolean_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package attr_test
-
-import (
- "fmt"
- "testing"
-
- g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/assert"
- "github.com/maragudk/gomponents/attr"
-)
-
-func TestBooleanAttributes(t *testing.T) {
- cases := map[string]func() g.Node{
- "async": attr.Async,
- "autofocus": attr.AutoFocus,
- "autoplay": attr.AutoPlay,
- "controls": attr.Controls,
- "defer": attr.Defer,
- "disabled": attr.Disabled,
- "multiple": attr.Multiple,
- "readonly": attr.ReadOnly,
- "required": attr.Required,
- "selected": attr.Selected,
- }
-
- for name, fn := range cases {
- t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
- n := g.El("div", fn())
- assert.Equal(t, fmt.Sprintf(``, name), n)
- })
- }
-}
diff --git a/attr/simple.go b/attr/simple.go
deleted file mode 100644
index a96c139..0000000
--- a/attr/simple.go
+++ /dev/null
@@ -1,121 +0,0 @@
-package attr
-
-import (
- g "github.com/maragudk/gomponents"
-)
-
-func Accept(v string) g.Node {
- return g.Attr("accept", v)
-}
-
-func AutoComplete(v string) g.Node {
- return g.Attr("autocomplete", v)
-}
-
-func Charset(v string) g.Node {
- return g.Attr("charset", v)
-}
-
-func Class(v string) g.Node {
- return g.Attr("class", v)
-}
-
-func Cols(v string) g.Node {
- return g.Attr("cols", v)
-}
-
-func Content(v string) g.Node {
- return g.Attr("content", v)
-}
-
-func Form(v string) g.Node {
- return g.Attr("form", v)
-}
-
-func Height(v string) g.Node {
- return g.Attr("height", v)
-}
-
-func Href(v string) g.Node {
- return g.Attr("href", v)
-}
-
-func ID(v string) g.Node {
- return g.Attr("id", v)
-}
-
-func Lang(v string) g.Node {
- return g.Attr("lang", v)
-}
-
-func Max(v string) g.Node {
- return g.Attr("max", v)
-}
-
-func MaxLength(v string) g.Node {
- return g.Attr("maxlength", v)
-}
-
-func Min(v string) g.Node {
- return g.Attr("min", v)
-}
-
-func MinLength(v string) g.Node {
- return g.Attr("minlength", v)
-}
-
-func Name(v string) g.Node {
- return g.Attr("name", v)
-}
-
-func Pattern(v string) g.Node {
- return g.Attr("pattern", v)
-}
-
-func Preload(v string) g.Node {
- return g.Attr("preload", v)
-}
-
-func Placeholder(v string) g.Node {
- return g.Attr("placeholder", v)
-}
-
-func Rel(v string) g.Node {
- return g.Attr("rel", v)
-}
-
-func Rows(v string) g.Node {
- return g.Attr("rows", v)
-}
-
-func Src(v string) g.Node {
- return g.Attr("src", v)
-}
-
-func Style(v string) g.Node {
- return g.Attr("style", v)
-}
-
-func TabIndex(v string) g.Node {
- return g.Attr("tabindex", v)
-}
-
-func Target(v string) g.Node {
- return g.Attr("target", v)
-}
-
-func Title(v string) g.Node {
- return g.Attr("title", v)
-}
-
-func Type(v string) g.Node {
- return g.Attr("type", v)
-}
-
-func Value(v string) g.Node {
- return g.Attr("value", v)
-}
-
-func Width(v string) g.Node {
- return g.Attr("width", v)
-}
diff --git a/attr/simple_test.go b/attr/simple_test.go
deleted file mode 100644
index d006b76..0000000
--- a/attr/simple_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package attr_test
-
-import (
- "fmt"
- "testing"
-
- g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/assert"
- "github.com/maragudk/gomponents/attr"
-)
-
-func TestSimpleAttributes(t *testing.T) {
- cases := map[string]func(string) g.Node{
- "accept": attr.Accept,
- "autocomplete": attr.AutoComplete,
- "charset": attr.Charset,
- "class": attr.Class,
- "cols": attr.Cols,
- "content": attr.Content,
- "form": attr.Form,
- "height": attr.Height,
- "href": attr.Href,
- "id": attr.ID,
- "lang": attr.Lang,
- "max": attr.Max,
- "maxlength": attr.MaxLength,
- "min": attr.Min,
- "minlength": attr.MinLength,
- "name": attr.Name,
- "pattern": attr.Pattern,
- "preload": attr.Preload,
- "placeholder": attr.Placeholder,
- "rel": attr.Rel,
- "rows": attr.Rows,
- "src": attr.Src,
- "style": attr.Style,
- "tabindex": attr.TabIndex,
- "target": attr.Target,
- "title": attr.Title,
- "type": attr.Type,
- "value": attr.Value,
- "width": attr.Width,
- }
-
- for name, fn := range cases {
- t.Run(fmt.Sprintf(`should output %v="hat"`, name), func(t *testing.T) {
- n := g.El("div", fn("hat"))
- assert.Equal(t, fmt.Sprintf(``, name), n)
- })
- }
-}
diff --git a/components/attributes.go b/components/attributes.go
new file mode 100644
index 0000000..dc7ef9b
--- /dev/null
+++ b/components/attributes.go
@@ -0,0 +1,37 @@
+package components
+
+import (
+ "io"
+ "sort"
+ "strings"
+
+ g "github.com/maragudk/gomponents"
+ "github.com/maragudk/gomponents/html"
+)
+
+// Classes is a map of strings to booleans, which Renders to an attribute with name "class".
+// The attribute value is a sorted, space-separated string of all the map keys,
+// for which the corresponding map value is true.
+type Classes map[string]bool
+
+func (c Classes) Render(w io.Writer) error {
+ var included []string
+ for c, include := range c {
+ if include {
+ included = append(included, c)
+ }
+ }
+ sort.Strings(included)
+ return html.Class(strings.Join(included, " ")).Render(w)
+}
+
+func (c Classes) Type() g.NodeType {
+ return g.AttributeType
+}
+
+// String satisfies fmt.Stringer.
+func (c Classes) String() string {
+ var b strings.Builder
+ _ = c.Render(&b)
+ return b.String()
+}
diff --git a/components/attributes_test.go b/components/attributes_test.go
new file mode 100644
index 0000000..bf3e93b
--- /dev/null
+++ b/components/attributes_test.go
@@ -0,0 +1,32 @@
+package components_test
+
+import (
+ "testing"
+
+ g "github.com/maragudk/gomponents"
+ "github.com/maragudk/gomponents/assert"
+ c "github.com/maragudk/gomponents/components"
+)
+
+func TestClasses(t *testing.T) {
+ t.Run("given a map, returns sorted keys from the map with value true", func(t *testing.T) {
+ assert.Equal(t, ` class="boheme-hat hat partyhat"`, c.Classes{
+ "boheme-hat": true,
+ "hat": true,
+ "partyhat": true,
+ "turtlehat": false,
+ })
+ })
+
+ t.Run("renders as attribute in an element", func(t *testing.T) {
+ e := g.El("div", c.Classes{"hat": true})
+ assert.Equal(t, ``, e)
+ })
+
+ t.Run("also works with fmt", func(t *testing.T) {
+ a := c.Classes{"hat": true}
+ if a.String() != ` class="hat"` {
+ t.FailNow()
+ }
+ })
+}
diff --git a/components/documents.go b/components/documents.go
index 5e96466..e2d3a22 100644
--- a/components/documents.go
+++ b/components/documents.go
@@ -1,15 +1,14 @@
-// Package components provides high-level components that are composed of low-level elements and attributes.
+// Package components provides high-level components and helpers that are composed of low-level elements and attributes.
package components
import (
g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/attr"
- "github.com/maragudk/gomponents/el"
+ . "github.com/maragudk/gomponents/html"
)
-// DocumentProps for HTML5.
+// HTML5Props for HTML5.
// Title is set no matter what, Description and Language elements only if the strings are non-empty.
-type DocumentProps struct {
+type HTML5Props struct {
Title string
Description string
Language string
@@ -18,24 +17,24 @@ type DocumentProps struct {
}
// HTML5 document template.
-func HTML5(p DocumentProps) g.NodeFunc {
+func HTML5(p HTML5Props) g.NodeFunc {
var lang, description g.Node
if p.Language != "" {
- lang = attr.Lang(p.Language)
+ lang = Lang(p.Language)
}
if p.Description != "" {
- description = el.Meta(attr.Name("description"), attr.Content(p.Description))
+ description = Meta(Name("description"), Content(p.Description))
}
- return el.Document(
- el.HTML(lang,
- el.Head(
- el.Meta(attr.Charset("utf-8")),
- el.Meta(attr.Name("viewport"), attr.Content("width=device-width, initial-scale=1")),
- el.Title(p.Title),
+ return Document(
+ HTML(lang,
+ Head(
+ Meta(Charset("utf-8")),
+ Meta(Name("viewport"), Content("width=device-width, initial-scale=1")),
+ TitleEl(p.Title),
description,
g.Group(p.Head),
),
- el.Body(g.Group(p.Body)),
+ Body(g.Group(p.Body)),
),
)
}
diff --git a/components/documents_test.go b/components/documents_test.go
index 863ac96..07f8697 100644
--- a/components/documents_test.go
+++ b/components/documents_test.go
@@ -5,26 +5,25 @@ import (
g "github.com/maragudk/gomponents"
"github.com/maragudk/gomponents/assert"
- "github.com/maragudk/gomponents/attr"
- c "github.com/maragudk/gomponents/components"
- "github.com/maragudk/gomponents/el"
+ . "github.com/maragudk/gomponents/components"
+ . "github.com/maragudk/gomponents/html"
)
func TestHTML5(t *testing.T) {
t.Run("returns an html5 document template", func(t *testing.T) {
- e := c.HTML5(c.DocumentProps{
+ e := HTML5(HTML5Props{
Title: "Hat",
Description: "Love hats.",
Language: "en",
- Head: []g.Node{el.Link(attr.Rel("stylesheet"), attr.Href("/hat.css"))},
- Body: []g.Node{el.Div()},
+ Head: []g.Node{Link(Rel("stylesheet"), Href("/hat.css"))},
+ Body: []g.Node{Div()},
})
assert.Equal(t, `Hat`, e)
})
t.Run("returns no language, description, and extra head/body elements if empty", func(t *testing.T) {
- e := c.HTML5(c.DocumentProps{
+ e := HTML5(HTML5Props{
Title: "Hat",
})
diff --git a/el/elements.go b/el/elements.go
deleted file mode 100644
index 0841ff8..0000000
--- a/el/elements.go
+++ /dev/null
@@ -1,68 +0,0 @@
-// Package el provides shortcuts and helpers to common HTML elements.
-// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element for a list of elements.
-package el
-
-import (
- "fmt"
- "io"
-
- g "github.com/maragudk/gomponents"
-)
-
-func A(href string, children ...g.Node) g.NodeFunc {
- return g.El("a", g.Attr("href", href), g.Group(children))
-}
-
-// Document returns an special kind of Node that prefixes its child with the string "".
-func Document(child g.Node) g.NodeFunc {
- return func(w io.Writer) error {
- if _, err := w.Write([]byte("")); err != nil {
- return err
- }
- return child.Render(w)
- }
-}
-
-// Form returns an element with name "form", the given action and method attributes, and the given children.
-func Form(action, method string, children ...g.Node) g.NodeFunc {
- return g.El("form", g.Attr("action", action), g.Attr("method", method), g.Group(children))
-}
-
-func Img(src, alt string, children ...g.Node) g.NodeFunc {
- return g.El("img", g.Attr("src", src), g.Attr("alt", alt), g.Group(children))
-}
-
-// Input returns an element with name "input", the given type and name attributes, and the given children.
-// Note that "type" is a keyword in Go, so the parameter is called typ.
-func Input(typ, name string, children ...g.Node) g.NodeFunc {
- return g.El("input", g.Attr("type", typ), g.Attr("name", name), g.Group(children))
-}
-
-// Label returns an element with name "label", the given for attribute, and the given children.
-// Note that "for" is a keyword in Go, so the parameter is called forr.
-func Label(forr string, children ...g.Node) g.NodeFunc {
- return g.El("label", g.Attr("for", forr), g.Group(children))
-}
-
-// Option returns an element with name "option", the given text content and value attribute, and the given children.
-func Option(text, value string, children ...g.Node) g.NodeFunc {
- return g.El("option", g.Attr("value", value), g.Text(text), g.Group(children))
-}
-
-// Progress returns an element with name "progress", the given value and max attributes, and the given children.
-func Progress(value, max float64, children ...g.Node) g.NodeFunc {
- return g.El("progress",
- g.Attr("value", fmt.Sprintf("%v", value)),
- g.Attr("max", fmt.Sprintf("%v", max)),
- g.Group(children))
-}
-
-// Select returns an element with name "select", the given name attribute, and the given children.
-func Select(name string, children ...g.Node) g.NodeFunc {
- return g.El("select", g.Attr("name", name), g.Group(children))
-}
-
-// Textarea returns an element with name "textarea", the given name attribute, and the given children.
-func Textarea(name string, children ...g.Node) g.NodeFunc {
- return g.El("textarea", g.Attr("name", name), g.Group(children))
-}
diff --git a/el/elements_test.go b/el/elements_test.go
deleted file mode 100644
index adce6df..0000000
--- a/el/elements_test.go
+++ /dev/null
@@ -1,82 +0,0 @@
-package el_test
-
-import (
- "errors"
- "testing"
-
- g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/assert"
- "github.com/maragudk/gomponents/el"
-)
-
-type erroringWriter struct{}
-
-func (w *erroringWriter) Write(p []byte) (n int, err error) {
- return 0, errors.New("don't want to write")
-}
-
-func TestDocument(t *testing.T) {
- t.Run("returns doctype and children", func(t *testing.T) {
- assert.Equal(t, ``, el.Document(g.El("html")))
- })
-
- t.Run("errors on write error in Render", func(t *testing.T) {
- err := el.Document(g.El("html")).Render(&erroringWriter{})
- assert.Error(t, err)
- })
-}
-
-func TestForm(t *testing.T) {
- t.Run("returns a form element with action and method attributes", func(t *testing.T) {
- assert.Equal(t, ``, el.Form("/", "post"))
- })
-}
-
-func TestInput(t *testing.T) {
- t.Run("returns an input element with attributes type and name", func(t *testing.T) {
- assert.Equal(t, ``, el.Input("text", "hat"))
- })
-}
-
-func TestLabel(t *testing.T) {
- t.Run("returns a label element with attribute for", func(t *testing.T) {
- assert.Equal(t, ``, el.Label("hat", g.Text("Hat")))
- })
-}
-
-func TestOption(t *testing.T) {
- t.Run("returns an option element with attribute label and content", func(t *testing.T) {
- assert.Equal(t, ``, el.Option("Hat", "hat"))
- })
-}
-
-func TestProgress(t *testing.T) {
- t.Run("returns a progress element with attributes value and max", func(t *testing.T) {
- assert.Equal(t, ``, el.Progress(5.5, 10))
- })
-}
-
-func TestSelect(t *testing.T) {
- t.Run("returns a select element with attribute name", func(t *testing.T) {
- assert.Equal(t, ``,
- el.Select("hat", el.Option("Partyhat", "partyhat")))
- })
-}
-
-func TestTextarea(t *testing.T) {
- t.Run("returns a textarea element with attribute name", func(t *testing.T) {
- assert.Equal(t, ``, el.Textarea("hat"))
- })
-}
-
-func TestA(t *testing.T) {
- t.Run("returns an a element with a href attribute", func(t *testing.T) {
- assert.Equal(t, `hat`, el.A("#", g.Text("hat")))
- })
-}
-
-func TestImg(t *testing.T) {
- t.Run("returns an img element with href and alt attributes", func(t *testing.T) {
- assert.Equal(t, `
`, el.Img("hat.png", "hat", g.Attr("id", "image")))
- })
-}
diff --git a/el/simple.go b/el/simple.go
deleted file mode 100644
index 1ebf33f..0000000
--- a/el/simple.go
+++ /dev/null
@@ -1,257 +0,0 @@
-package el
-
-import (
- g "github.com/maragudk/gomponents"
-)
-
-func Address(children ...g.Node) g.NodeFunc {
- return g.El("address", children...)
-}
-
-func Area(children ...g.Node) g.NodeFunc {
- return g.El("area", children...)
-}
-
-func Article(children ...g.Node) g.NodeFunc {
- return g.El("article", children...)
-}
-
-func Aside(children ...g.Node) g.NodeFunc {
- return g.El("aside", children...)
-}
-
-func Audio(children ...g.Node) g.NodeFunc {
- return g.El("audio", children...)
-}
-
-func Base(children ...g.Node) g.NodeFunc {
- return g.El("base", children...)
-}
-
-func BlockQuote(children ...g.Node) g.NodeFunc {
- return g.El("blockquote", children...)
-}
-
-func Body(children ...g.Node) g.NodeFunc {
- return g.El("body", children...)
-}
-
-func Br(children ...g.Node) g.NodeFunc {
- return g.El("br", children...)
-}
-
-func Button(children ...g.Node) g.NodeFunc {
- return g.El("button", children...)
-}
-
-func Canvas(children ...g.Node) g.NodeFunc {
- return g.El("canvas", children...)
-}
-
-func Cite(children ...g.Node) g.NodeFunc {
- return g.El("cite", children...)
-}
-
-func Code(children ...g.Node) g.NodeFunc {
- return g.El("code", children...)
-}
-
-func Col(children ...g.Node) g.NodeFunc {
- return g.El("col", children...)
-}
-
-func ColGroup(children ...g.Node) g.NodeFunc {
- return g.El("colgroup", children...)
-}
-
-func Data(children ...g.Node) g.NodeFunc {
- return g.El("data", children...)
-}
-
-func DataList(children ...g.Node) g.NodeFunc {
- return g.El("datalist", children...)
-}
-
-func Details(children ...g.Node) g.NodeFunc {
- return g.El("details", children...)
-}
-
-func Dialog(children ...g.Node) g.NodeFunc {
- return g.El("dialog", children...)
-}
-
-func Div(children ...g.Node) g.NodeFunc {
- return g.El("div", children...)
-}
-
-func Dl(children ...g.Node) g.NodeFunc {
- return g.El("dl", children...)
-}
-
-func Embed(children ...g.Node) g.NodeFunc {
- return g.El("embed", children...)
-}
-
-func FieldSet(children ...g.Node) g.NodeFunc {
- return g.El("fieldset", children...)
-}
-
-func Figure(children ...g.Node) g.NodeFunc {
- return g.El("figure", children...)
-}
-
-func Footer(children ...g.Node) g.NodeFunc {
- return g.El("footer", children...)
-}
-
-func Head(children ...g.Node) g.NodeFunc {
- return g.El("head", children...)
-}
-
-func Header(children ...g.Node) g.NodeFunc {
- return g.El("header", children...)
-}
-
-func HGroup(children ...g.Node) g.NodeFunc {
- return g.El("hgroup", children...)
-}
-
-func Hr(children ...g.Node) g.NodeFunc {
- return g.El("hr", children...)
-}
-
-func HTML(children ...g.Node) g.NodeFunc {
- return g.El("html", children...)
-}
-
-func IFrame(children ...g.Node) g.NodeFunc {
- return g.El("iframe", children...)
-}
-
-func Legend(children ...g.Node) g.NodeFunc {
- return g.El("legend", children...)
-}
-
-func Li(children ...g.Node) g.NodeFunc {
- return g.El("li", children...)
-}
-
-func Link(children ...g.Node) g.NodeFunc {
- return g.El("link", children...)
-}
-
-func Main(children ...g.Node) g.NodeFunc {
- return g.El("main", children...)
-}
-
-func Menu(children ...g.Node) g.NodeFunc {
- return g.El("menu", children...)
-}
-
-func Meta(children ...g.Node) g.NodeFunc {
- return g.El("meta", children...)
-}
-
-func Meter(children ...g.Node) g.NodeFunc {
- return g.El("meter", children...)
-}
-
-func Nav(children ...g.Node) g.NodeFunc {
- return g.El("nav", children...)
-}
-
-func NoScript(children ...g.Node) g.NodeFunc {
- return g.El("noscript", children...)
-}
-
-func Object(children ...g.Node) g.NodeFunc {
- return g.El("object", children...)
-}
-
-func Ol(children ...g.Node) g.NodeFunc {
- return g.El("ol", children...)
-}
-
-func OptGroup(children ...g.Node) g.NodeFunc {
- return g.El("optgroup", children...)
-}
-
-func P(children ...g.Node) g.NodeFunc {
- return g.El("p", children...)
-}
-
-func Param(children ...g.Node) g.NodeFunc {
- return g.El("param", children...)
-}
-
-func Picture(children ...g.Node) g.NodeFunc {
- return g.El("picture", children...)
-}
-
-func Pre(children ...g.Node) g.NodeFunc {
- return g.El("pre", children...)
-}
-
-func Script(children ...g.Node) g.NodeFunc {
- return g.El("script", children...)
-}
-
-func Section(children ...g.Node) g.NodeFunc {
- return g.El("section", children...)
-}
-
-func Source(children ...g.Node) g.NodeFunc {
- return g.El("source", children...)
-}
-
-func Span(children ...g.Node) g.NodeFunc {
- return g.El("span", children...)
-}
-
-func Style(children ...g.Node) g.NodeFunc {
- return g.El("style", children...)
-}
-
-func Summary(children ...g.Node) g.NodeFunc {
- return g.El("summary", children...)
-}
-
-func SVG(children ...g.Node) g.NodeFunc {
- return g.El("svg", children...)
-}
-
-func Table(children ...g.Node) g.NodeFunc {
- return g.El("table", children...)
-}
-
-func TBody(children ...g.Node) g.NodeFunc {
- return g.El("tbody", children...)
-}
-
-func Td(children ...g.Node) g.NodeFunc {
- return g.El("td", children...)
-}
-
-func TFoot(children ...g.Node) g.NodeFunc {
- return g.El("tfoot", children...)
-}
-
-func Th(children ...g.Node) g.NodeFunc {
- return g.El("th", children...)
-}
-
-func THead(children ...g.Node) g.NodeFunc {
- return g.El("thead", children...)
-}
-
-func Tr(children ...g.Node) g.NodeFunc {
- return g.El("tr", children...)
-}
-
-func Ul(children ...g.Node) g.NodeFunc {
- return g.El("ul", children...)
-}
-
-func Wbr(children ...g.Node) g.NodeFunc {
- return g.El("wbr", children...)
-}
diff --git a/el/simple_test.go b/el/simple_test.go
deleted file mode 100644
index 2621982..0000000
--- a/el/simple_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-package el_test
-
-import (
- "fmt"
- "testing"
-
- g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/assert"
- "github.com/maragudk/gomponents/el"
-)
-
-func TestSimpleElements(t *testing.T) {
- cases := map[string]func(...g.Node) g.NodeFunc{
- "address": el.Address,
- "article": el.Article,
- "aside": el.Aside,
- "audio": el.Audio,
- "blockquote": el.BlockQuote,
- "body": el.Body,
- "button": el.Button,
- "canvas": el.Canvas,
- "cite": el.Cite,
- "code": el.Code,
- "colgroup": el.ColGroup,
- "data": el.Data,
- "datalist": el.DataList,
- "details": el.Details,
- "dialog": el.Dialog,
- "div": el.Div,
- "dl": el.Dl,
- "fieldset": el.FieldSet,
- "figure": el.Figure,
- "footer": el.Footer,
- "head": el.Head,
- "header": el.Header,
- "hgroup": el.HGroup,
- "html": el.HTML,
- "iframe": el.IFrame,
- "legend": el.Legend,
- "li": el.Li,
- "main": el.Main,
- "menu": el.Menu,
- "meter": el.Meter,
- "nav": el.Nav,
- "noscript": el.NoScript,
- "object": el.Object,
- "ol": el.Ol,
- "optgroup": el.OptGroup,
- "p": el.P,
- "picture": el.Picture,
- "pre": el.Pre,
- "script": el.Script,
- "section": el.Section,
- "span": el.Span,
- "style": el.Style,
- "summary": el.Summary,
- "svg": el.SVG,
- "table": el.Table,
- "tbody": el.TBody,
- "td": el.Td,
- "tfoot": el.TFoot,
- "th": el.Th,
- "thead": el.THead,
- "tr": el.Tr,
- "ul": el.Ul,
- }
-
- for name, fn := range cases {
- t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
- n := fn(g.Attr("id", "hat"))
- assert.Equal(t, fmt.Sprintf(`<%v id="hat">%v>`, name, name), n)
- })
- }
-}
-
-func TestSimpleVoidKindElements(t *testing.T) {
- cases := map[string]func(...g.Node) g.NodeFunc{
- "area": el.Area,
- "base": el.Base,
- "br": el.Br,
- "col": el.Col,
- "embed": el.Embed,
- "hr": el.Hr,
- "link": el.Link,
- "meta": el.Meta,
- "param": el.Param,
- "source": el.Source,
- "wbr": el.Wbr,
- }
-
- for name, fn := range cases {
- t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
- n := fn(g.Attr("id", "hat"))
- assert.Equal(t, fmt.Sprintf(`<%v id="hat">`, name), n)
- })
- }
-}
diff --git a/el/text.go b/el/text.go
deleted file mode 100644
index 6ab88db..0000000
--- a/el/text.go
+++ /dev/null
@@ -1,131 +0,0 @@
-package el
-
-import (
- g "github.com/maragudk/gomponents"
-)
-
-func Abbr(text string, children ...g.Node) g.NodeFunc {
- return g.El("abbr", g.Text(text), g.Group(children))
-}
-
-func B(text string, children ...g.Node) g.NodeFunc {
- return g.El("b", g.Text(text), g.Group(children))
-}
-
-func Caption(text string, children ...g.Node) g.NodeFunc {
- return g.El("caption", g.Text(text), g.Group(children))
-}
-
-func Dd(text string, children ...g.Node) g.NodeFunc {
- return g.El("dd", g.Text(text), g.Group(children))
-}
-
-func Del(text string, children ...g.Node) g.NodeFunc {
- return g.El("del", g.Text(text), g.Group(children))
-}
-
-func Dfn(text string, children ...g.Node) g.NodeFunc {
- return g.El("dfn", g.Text(text), g.Group(children))
-}
-
-func Dt(text string, children ...g.Node) g.NodeFunc {
- return g.El("dt", g.Text(text), g.Group(children))
-}
-
-func Em(text string, children ...g.Node) g.NodeFunc {
- return g.El("em", g.Text(text), g.Group(children))
-}
-
-func FigCaption(text string, children ...g.Node) g.NodeFunc {
- return g.El("figcaption", g.Text(text), g.Group(children))
-}
-
-// H1 returns an element with name "h1", the given text content, and the given children.
-func H1(text string, children ...g.Node) g.NodeFunc {
- return g.El("h1", g.Text(text), g.Group(children))
-}
-
-// H2 returns an element with name "h2", the given text content, and the given children.
-func H2(text string, children ...g.Node) g.NodeFunc {
- return g.El("h2", g.Text(text), g.Group(children))
-}
-
-// H3 returns an element with name "h3", the given text content, and the given children.
-func H3(text string, children ...g.Node) g.NodeFunc {
- return g.El("h3", g.Text(text), g.Group(children))
-}
-
-// H4 returns an element with name "h4", the given text content, and the given children.
-func H4(text string, children ...g.Node) g.NodeFunc {
- return g.El("h4", g.Text(text), g.Group(children))
-}
-
-// H5 returns an element with name "h5", the given text content, and the given children.
-func H5(text string, children ...g.Node) g.NodeFunc {
- return g.El("h5", g.Text(text), g.Group(children))
-}
-
-// H6 returns an element with name "h6", the given text content, and the given children.
-func H6(text string, children ...g.Node) g.NodeFunc {
- return g.El("h6", g.Text(text), g.Group(children))
-}
-
-func I(text string, children ...g.Node) g.NodeFunc {
- return g.El("i", g.Text(text), g.Group(children))
-}
-
-func Ins(text string, children ...g.Node) g.NodeFunc {
- return g.El("ins", g.Text(text), g.Group(children))
-}
-
-func Kbd(text string, children ...g.Node) g.NodeFunc {
- return g.El("kbd", g.Text(text), g.Group(children))
-}
-
-func Mark(text string, children ...g.Node) g.NodeFunc {
- return g.El("mark", g.Text(text), g.Group(children))
-}
-
-func Q(text string, children ...g.Node) g.NodeFunc {
- return g.El("q", g.Text(text), g.Group(children))
-}
-
-func S(text string, children ...g.Node) g.NodeFunc {
- return g.El("s", g.Text(text), g.Group(children))
-}
-
-func Samp(text string, children ...g.Node) g.NodeFunc {
- return g.El("samp", g.Text(text), g.Group(children))
-}
-
-func Small(text string, children ...g.Node) g.NodeFunc {
- return g.El("small", g.Text(text), g.Group(children))
-}
-
-func Strong(text string, children ...g.Node) g.NodeFunc {
- return g.El("strong", g.Text(text), g.Group(children))
-}
-
-func Sub(text string, children ...g.Node) g.NodeFunc {
- return g.El("sub", g.Text(text), g.Group(children))
-}
-
-func Sup(text string, children ...g.Node) g.NodeFunc {
- return g.El("sup", g.Text(text), g.Group(children))
-}
-
-func Time(text string, children ...g.Node) g.NodeFunc {
- return g.El("time", g.Text(text), g.Group(children))
-}
-
-func Title(title string, children ...g.Node) g.NodeFunc {
- return g.El("title", g.Text(title), g.Group(children))
-}
-
-func U(text string, children ...g.Node) g.NodeFunc {
- return g.El("u", g.Text(text), g.Group(children))
-}
-
-func Var(text string, children ...g.Node) g.NodeFunc {
- return g.El("var", g.Text(text), g.Group(children))
-}
diff --git a/el/text_test.go b/el/text_test.go
deleted file mode 100644
index a751e19..0000000
--- a/el/text_test.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package el_test
-
-import (
- "fmt"
- "testing"
-
- g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/assert"
- "github.com/maragudk/gomponents/el"
-)
-
-func TestTextElements(t *testing.T) {
- cases := map[string]func(string, ...g.Node) g.NodeFunc{
- "abbr": el.Abbr,
- "b": el.B,
- "caption": el.Caption,
- "dd": el.Dd,
- "del": el.Del,
- "dfn": el.Dfn,
- "dt": el.Dt,
- "em": el.Em,
- "figcaption": el.FigCaption,
- "h1": el.H1,
- "h2": el.H2,
- "h3": el.H3,
- "h4": el.H4,
- "h5": el.H5,
- "h6": el.H6,
- "i": el.I,
- "ins": el.Ins,
- "kbd": el.Kbd,
- "mark": el.Mark,
- "q": el.Q,
- "s": el.S,
- "samp": el.Samp,
- "small": el.Small,
- "strong": el.Strong,
- "sub": el.Sub,
- "sup": el.Sup,
- "time": el.Time,
- "title": el.Title,
- "u": el.U,
- "var": el.Var,
- }
-
- for name, fn := range cases {
- t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
- n := fn("hat", g.Attr("id", "hat"))
- assert.Equal(t, fmt.Sprintf(`<%v id="hat">hat%v>`, name, name), n)
- })
- }
-}
diff --git a/examples/dot-import/dot-import.go b/examples/dot-import/dot-import.go
index be3bd50..1d595b8 100644
--- a/examples/dot-import/dot-import.go
+++ b/examples/dot-import/dot-import.go
@@ -3,9 +3,9 @@ package main
import (
"net/http"
- . "github.com/maragudk/gomponents"
+ g "github.com/maragudk/gomponents"
. "github.com/maragudk/gomponents/components"
- . "github.com/maragudk/gomponents/el"
+ . "github.com/maragudk/gomponents/html"
)
func main() {
@@ -13,25 +13,32 @@ func main() {
}
func handler(w http.ResponseWriter, r *http.Request) {
- p := page(props{
- title: r.URL.Path,
- path: r.URL.Path,
- })
- _ = p.Render(w)
-}
-
-type props struct {
- title string
- path string
+ page := Page("Hi!", r.URL.Path)
+ _ = page.Render(w)
}
-func page(p props) Node {
- return HTML5(DocumentProps{
- Title: p.title,
+func Page(title, currentPath string) g.Node {
+ return HTML5(HTML5Props{
+ Title: title,
Language: "en",
- Body: []Node{
- H1(p.title),
- P(Textf("Welcome to the page at %v.", p.path)),
+ Head: []g.Node{
+ StyleEl(Type("text/css"), g.Raw(".is-active{ font-weight: bold }")),
+ },
+ Body: []g.Node{
+ Navbar(currentPath),
+ H1(title),
+ P(g.Textf("Welcome to the page at %v.", currentPath)),
},
})
}
+
+func Navbar(currentPath string) g.Node {
+ return Nav(
+ NavbarLink("/", "Home", currentPath),
+ NavbarLink("/about", "About", currentPath),
+ )
+}
+
+func NavbarLink(href, name, currentPath string) g.Node {
+ return A(href, Classes{"is-active": currentPath == href}, g.Text(name))
+}
diff --git a/examples/simple/simple.go b/examples/simple/simple.go
index dd6dd58..535fa6e 100644
--- a/examples/simple/simple.go
+++ b/examples/simple/simple.go
@@ -5,8 +5,8 @@ import (
"time"
g "github.com/maragudk/gomponents"
- "github.com/maragudk/gomponents/attr"
- "github.com/maragudk/gomponents/el"
+ c "github.com/maragudk/gomponents/components"
+ h "github.com/maragudk/gomponents/html"
)
func main() {
@@ -27,22 +27,22 @@ type props struct {
}
func page(p props) g.Node {
- return el.Document(
- el.HTML(attr.Lang("en"),
- el.Head(
- el.Title(p.title),
- el.Style(attr.Type("text/css"),
+ return h.Document(
+ h.HTML(h.Lang("en"),
+ h.Head(
+ h.TitleEl(p.title),
+ h.StyleEl(h.Type("text/css"),
g.Raw(".is-active{font-weight: bold}"),
g.Raw("ul.nav { list-style-type: none; margin: 0; padding: 0; overflow: hidden; }"),
g.Raw("ul.nav li { display: block; padding: 8px; float: left; }"),
),
),
- el.Body(
+ h.Body(
navbar(navbarProps{path: p.path}),
- el.Hr(),
- el.H1(p.title),
- el.P(g.Textf("Welcome to the page at %v.", p.path)),
- el.P(g.Textf("Rendered at %v", time.Now())),
+ h.Hr(),
+ h.H1(p.title),
+ h.P(g.Textf("Welcome to the page at %v.", p.path)),
+ h.P(g.Textf("Rendered at %v", time.Now())),
),
),
)
@@ -63,9 +63,9 @@ func navbar(props navbarProps) g.Node {
}
lis := g.Map(len(items), func(i int) g.Node {
item := items[i]
- return el.Li(
- el.A(item.path, attr.Classes(map[string]bool{"is-active": props.path == item.path}), g.Text(item.text)),
+ return h.Li(
+ h.A(item.path, c.Classes(map[string]bool{"is-active": props.path == item.path}), g.Text(item.text)),
)
})
- return el.Ul(attr.Class("nav"), g.Group(lis))
+ return h.Ul(h.Class("nav"), g.Group(lis))
}
diff --git a/html/attributes.go b/html/attributes.go
new file mode 100644
index 0000000..3db8584
--- /dev/null
+++ b/html/attributes.go
@@ -0,0 +1,161 @@
+package html
+
+import (
+ g "github.com/maragudk/gomponents"
+)
+
+func Async() g.Node {
+ return g.Attr("async")
+}
+
+func AutoFocus() g.Node {
+ return g.Attr("autofocus")
+}
+
+func AutoPlay() g.Node {
+ return g.Attr("autoplay")
+}
+
+func Controls() g.Node {
+ return g.Attr("controls")
+}
+
+func Defer() g.Node {
+ return g.Attr("defer")
+}
+
+func Disabled() g.Node {
+ return g.Attr("disabled")
+}
+
+func Multiple() g.Node {
+ return g.Attr("multiple")
+}
+
+func ReadOnly() g.Node {
+ return g.Attr("readonly")
+}
+
+func Required() g.Node {
+ return g.Attr("required")
+}
+
+func Selected() g.Node {
+ return g.Attr("selected")
+}
+
+func Accept(v string) g.Node {
+ return g.Attr("accept", v)
+}
+
+func AutoComplete(v string) g.Node {
+ return g.Attr("autocomplete", v)
+}
+
+func Charset(v string) g.Node {
+ return g.Attr("charset", v)
+}
+
+func Class(v string) g.Node {
+ return g.Attr("class", v)
+}
+
+func Cols(v string) g.Node {
+ return g.Attr("cols", v)
+}
+
+func Content(v string) g.Node {
+ return g.Attr("content", v)
+}
+
+func FormAttr(v string) g.Node {
+ return g.Attr("form", v)
+}
+
+func Height(v string) g.Node {
+ return g.Attr("height", v)
+}
+
+func Href(v string) g.Node {
+ return g.Attr("href", v)
+}
+
+func ID(v string) g.Node {
+ return g.Attr("id", v)
+}
+
+func Lang(v string) g.Node {
+ return g.Attr("lang", v)
+}
+
+func Max(v string) g.Node {
+ return g.Attr("max", v)
+}
+
+func MaxLength(v string) g.Node {
+ return g.Attr("maxlength", v)
+}
+
+func Min(v string) g.Node {
+ return g.Attr("min", v)
+}
+
+func MinLength(v string) g.Node {
+ return g.Attr("minlength", v)
+}
+
+func Name(v string) g.Node {
+ return g.Attr("name", v)
+}
+
+func Pattern(v string) g.Node {
+ return g.Attr("pattern", v)
+}
+
+func Preload(v string) g.Node {
+ return g.Attr("preload", v)
+}
+
+func Placeholder(v string) g.Node {
+ return g.Attr("placeholder", v)
+}
+
+func Rel(v string) g.Node {
+ return g.Attr("rel", v)
+}
+
+func Rows(v string) g.Node {
+ return g.Attr("rows", v)
+}
+
+func Src(v string) g.Node {
+ return g.Attr("src", v)
+}
+
+func StyleAttr(v string) g.Node {
+ return g.Attr("style", v)
+}
+
+func TabIndex(v string) g.Node {
+ return g.Attr("tabindex", v)
+}
+
+func Target(v string) g.Node {
+ return g.Attr("target", v)
+}
+
+func TitleAttr(v string) g.Node {
+ return g.Attr("title", v)
+}
+
+func Type(v string) g.Node {
+ return g.Attr("type", v)
+}
+
+func Value(v string) g.Node {
+ return g.Attr("value", v)
+}
+
+func Width(v string) g.Node {
+ return g.Attr("width", v)
+}
diff --git a/html/attributes_test.go b/html/attributes_test.go
new file mode 100644
index 0000000..7bf45bb
--- /dev/null
+++ b/html/attributes_test.go
@@ -0,0 +1,73 @@
+package html_test
+
+import (
+ "fmt"
+ "testing"
+
+ g "github.com/maragudk/gomponents"
+ "github.com/maragudk/gomponents/assert"
+ . "github.com/maragudk/gomponents/html"
+)
+
+func TestBooleanAttributes(t *testing.T) {
+ cases := map[string]func() g.Node{
+ "async": Async,
+ "autofocus": AutoFocus,
+ "autoplay": AutoPlay,
+ "controls": Controls,
+ "defer": Defer,
+ "disabled": Disabled,
+ "multiple": Multiple,
+ "readonly": ReadOnly,
+ "required": Required,
+ "selected": Selected,
+ }
+
+ for name, fn := range cases {
+ t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+ n := g.El("div", fn())
+ assert.Equal(t, fmt.Sprintf(``, name), n)
+ })
+ }
+}
+
+func TestSimpleAttributes(t *testing.T) {
+ cases := map[string]func(string) g.Node{
+ "accept": Accept,
+ "autocomplete": AutoComplete,
+ "charset": Charset,
+ "class": Class,
+ "cols": Cols,
+ "content": Content,
+ "form": FormAttr,
+ "height": Height,
+ "href": Href,
+ "id": ID,
+ "lang": Lang,
+ "max": Max,
+ "maxlength": MaxLength,
+ "min": Min,
+ "minlength": MinLength,
+ "name": Name,
+ "pattern": Pattern,
+ "preload": Preload,
+ "placeholder": Placeholder,
+ "rel": Rel,
+ "rows": Rows,
+ "src": Src,
+ "style": StyleAttr,
+ "tabindex": TabIndex,
+ "target": Target,
+ "title": TitleAttr,
+ "type": Type,
+ "value": Value,
+ "width": Width,
+ }
+
+ for name, fn := range cases {
+ t.Run(fmt.Sprintf(`should output %v="hat"`, name), func(t *testing.T) {
+ n := g.El("div", fn("hat"))
+ assert.Equal(t, fmt.Sprintf(``, name), n)
+ })
+ }
+}
diff --git a/html/elements.go b/html/elements.go
new file mode 100644
index 0000000..8045561
--- /dev/null
+++ b/html/elements.go
@@ -0,0 +1,447 @@
+// Package html provides common HTML elements and attributes.
+// See https://developer.mozilla.org/en-US/docs/Web/HTML/Element for a list of elements.
+// See https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes for a list of attributes.
+package html
+
+import (
+ "fmt"
+ "io"
+
+ g "github.com/maragudk/gomponents"
+)
+
+func A(href string, children ...g.Node) g.NodeFunc {
+ return g.El("a", g.Attr("href", href), g.Group(children))
+}
+
+// Document returns an special kind of Node that prefixes its child with the string "".
+func Document(child g.Node) g.NodeFunc {
+ return func(w io.Writer) error {
+ if _, err := w.Write([]byte("")); err != nil {
+ return err
+ }
+ return child.Render(w)
+ }
+}
+
+// FormEl returns an element with name "form", the given action and method attributes, and the given children.
+func FormEl(action, method string, children ...g.Node) g.NodeFunc {
+ return g.El("form", g.Attr("action", action), g.Attr("method", method), g.Group(children))
+}
+
+func Img(src, alt string, children ...g.Node) g.NodeFunc {
+ return g.El("img", g.Attr("src", src), g.Attr("alt", alt), g.Group(children))
+}
+
+// Input returns an element with name "input", the given type and name attributes, and the given children.
+// Note that "type" is a keyword in Go, so the parameter is called typ.
+func Input(typ, name string, children ...g.Node) g.NodeFunc {
+ return g.El("input", g.Attr("type", typ), g.Attr("name", name), g.Group(children))
+}
+
+// Label returns an element with name "label", the given for attribute, and the given children.
+// Note that "for" is a keyword in Go, so the parameter is called forr.
+func Label(forr string, children ...g.Node) g.NodeFunc {
+ return g.El("label", g.Attr("for", forr), g.Group(children))
+}
+
+// Option returns an element with name "option", the given text content and value attribute, and the given children.
+func Option(text, value string, children ...g.Node) g.NodeFunc {
+ return g.El("option", g.Attr("value", value), g.Text(text), g.Group(children))
+}
+
+// Progress returns an element with name "progress", the given value and max attributes, and the given children.
+func Progress(value, max float64, children ...g.Node) g.NodeFunc {
+ return g.El("progress",
+ g.Attr("value", fmt.Sprintf("%v", value)),
+ g.Attr("max", fmt.Sprintf("%v", max)),
+ g.Group(children))
+}
+
+// Select returns an element with name "select", the given name attribute, and the given children.
+func Select(name string, children ...g.Node) g.NodeFunc {
+ return g.El("select", g.Attr("name", name), g.Group(children))
+}
+
+// Textarea returns an element with name "textarea", the given name attribute, and the given children.
+func Textarea(name string, children ...g.Node) g.NodeFunc {
+ return g.El("textarea", g.Attr("name", name), g.Group(children))
+}
+
+func Address(children ...g.Node) g.NodeFunc {
+ return g.El("address", children...)
+}
+
+func Area(children ...g.Node) g.NodeFunc {
+ return g.El("area", children...)
+}
+
+func Article(children ...g.Node) g.NodeFunc {
+ return g.El("article", children...)
+}
+
+func Aside(children ...g.Node) g.NodeFunc {
+ return g.El("aside", children...)
+}
+
+func Audio(children ...g.Node) g.NodeFunc {
+ return g.El("audio", children...)
+}
+
+func Base(children ...g.Node) g.NodeFunc {
+ return g.El("base", children...)
+}
+
+func BlockQuote(children ...g.Node) g.NodeFunc {
+ return g.El("blockquote", children...)
+}
+
+func Body(children ...g.Node) g.NodeFunc {
+ return g.El("body", children...)
+}
+
+func Br(children ...g.Node) g.NodeFunc {
+ return g.El("br", children...)
+}
+
+func Button(children ...g.Node) g.NodeFunc {
+ return g.El("button", children...)
+}
+
+func Canvas(children ...g.Node) g.NodeFunc {
+ return g.El("canvas", children...)
+}
+
+func Cite(children ...g.Node) g.NodeFunc {
+ return g.El("cite", children...)
+}
+
+func Code(children ...g.Node) g.NodeFunc {
+ return g.El("code", children...)
+}
+
+func Col(children ...g.Node) g.NodeFunc {
+ return g.El("col", children...)
+}
+
+func ColGroup(children ...g.Node) g.NodeFunc {
+ return g.El("colgroup", children...)
+}
+
+func Data(children ...g.Node) g.NodeFunc {
+ return g.El("data", children...)
+}
+
+func DataList(children ...g.Node) g.NodeFunc {
+ return g.El("datalist", children...)
+}
+
+func Details(children ...g.Node) g.NodeFunc {
+ return g.El("details", children...)
+}
+
+func Dialog(children ...g.Node) g.NodeFunc {
+ return g.El("dialog", children...)
+}
+
+func Div(children ...g.Node) g.NodeFunc {
+ return g.El("div", children...)
+}
+
+func Dl(children ...g.Node) g.NodeFunc {
+ return g.El("dl", children...)
+}
+
+func Embed(children ...g.Node) g.NodeFunc {
+ return g.El("embed", children...)
+}
+
+func FieldSet(children ...g.Node) g.NodeFunc {
+ return g.El("fieldset", children...)
+}
+
+func Figure(children ...g.Node) g.NodeFunc {
+ return g.El("figure", children...)
+}
+
+func Footer(children ...g.Node) g.NodeFunc {
+ return g.El("footer", children...)
+}
+
+func Head(children ...g.Node) g.NodeFunc {
+ return g.El("head", children...)
+}
+
+func Header(children ...g.Node) g.NodeFunc {
+ return g.El("header", children...)
+}
+
+func HGroup(children ...g.Node) g.NodeFunc {
+ return g.El("hgroup", children...)
+}
+
+func Hr(children ...g.Node) g.NodeFunc {
+ return g.El("hr", children...)
+}
+
+func HTML(children ...g.Node) g.NodeFunc {
+ return g.El("html", children...)
+}
+
+func IFrame(children ...g.Node) g.NodeFunc {
+ return g.El("iframe", children...)
+}
+
+func Legend(children ...g.Node) g.NodeFunc {
+ return g.El("legend", children...)
+}
+
+func Li(children ...g.Node) g.NodeFunc {
+ return g.El("li", children...)
+}
+
+func Link(children ...g.Node) g.NodeFunc {
+ return g.El("link", children...)
+}
+
+func Main(children ...g.Node) g.NodeFunc {
+ return g.El("main", children...)
+}
+
+func Menu(children ...g.Node) g.NodeFunc {
+ return g.El("menu", children...)
+}
+
+func Meta(children ...g.Node) g.NodeFunc {
+ return g.El("meta", children...)
+}
+
+func Meter(children ...g.Node) g.NodeFunc {
+ return g.El("meter", children...)
+}
+
+func Nav(children ...g.Node) g.NodeFunc {
+ return g.El("nav", children...)
+}
+
+func NoScript(children ...g.Node) g.NodeFunc {
+ return g.El("noscript", children...)
+}
+
+func Object(children ...g.Node) g.NodeFunc {
+ return g.El("object", children...)
+}
+
+func Ol(children ...g.Node) g.NodeFunc {
+ return g.El("ol", children...)
+}
+
+func OptGroup(children ...g.Node) g.NodeFunc {
+ return g.El("optgroup", children...)
+}
+
+func P(children ...g.Node) g.NodeFunc {
+ return g.El("p", children...)
+}
+
+func Param(children ...g.Node) g.NodeFunc {
+ return g.El("param", children...)
+}
+
+func Picture(children ...g.Node) g.NodeFunc {
+ return g.El("picture", children...)
+}
+
+func Pre(children ...g.Node) g.NodeFunc {
+ return g.El("pre", children...)
+}
+
+func Script(children ...g.Node) g.NodeFunc {
+ return g.El("script", children...)
+}
+
+func Section(children ...g.Node) g.NodeFunc {
+ return g.El("section", children...)
+}
+
+func Source(children ...g.Node) g.NodeFunc {
+ return g.El("source", children...)
+}
+
+func Span(children ...g.Node) g.NodeFunc {
+ return g.El("span", children...)
+}
+
+func StyleEl(children ...g.Node) g.NodeFunc {
+ return g.El("style", children...)
+}
+
+func Summary(children ...g.Node) g.NodeFunc {
+ return g.El("summary", children...)
+}
+
+func SVG(children ...g.Node) g.NodeFunc {
+ return g.El("svg", children...)
+}
+
+func Table(children ...g.Node) g.NodeFunc {
+ return g.El("table", children...)
+}
+
+func TBody(children ...g.Node) g.NodeFunc {
+ return g.El("tbody", children...)
+}
+
+func Td(children ...g.Node) g.NodeFunc {
+ return g.El("td", children...)
+}
+
+func TFoot(children ...g.Node) g.NodeFunc {
+ return g.El("tfoot", children...)
+}
+
+func Th(children ...g.Node) g.NodeFunc {
+ return g.El("th", children...)
+}
+
+func THead(children ...g.Node) g.NodeFunc {
+ return g.El("thead", children...)
+}
+
+func Tr(children ...g.Node) g.NodeFunc {
+ return g.El("tr", children...)
+}
+
+func Ul(children ...g.Node) g.NodeFunc {
+ return g.El("ul", children...)
+}
+
+func Wbr(children ...g.Node) g.NodeFunc {
+ return g.El("wbr", children...)
+}
+
+func Abbr(text string, children ...g.Node) g.NodeFunc {
+ return g.El("abbr", g.Text(text), g.Group(children))
+}
+
+func B(text string, children ...g.Node) g.NodeFunc {
+ return g.El("b", g.Text(text), g.Group(children))
+}
+
+func Caption(text string, children ...g.Node) g.NodeFunc {
+ return g.El("caption", g.Text(text), g.Group(children))
+}
+
+func Dd(text string, children ...g.Node) g.NodeFunc {
+ return g.El("dd", g.Text(text), g.Group(children))
+}
+
+func Del(text string, children ...g.Node) g.NodeFunc {
+ return g.El("del", g.Text(text), g.Group(children))
+}
+
+func Dfn(text string, children ...g.Node) g.NodeFunc {
+ return g.El("dfn", g.Text(text), g.Group(children))
+}
+
+func Dt(text string, children ...g.Node) g.NodeFunc {
+ return g.El("dt", g.Text(text), g.Group(children))
+}
+
+func Em(text string, children ...g.Node) g.NodeFunc {
+ return g.El("em", g.Text(text), g.Group(children))
+}
+
+func FigCaption(text string, children ...g.Node) g.NodeFunc {
+ return g.El("figcaption", g.Text(text), g.Group(children))
+}
+
+// H1 returns an element with name "h1", the given text content, and the given children.
+func H1(text string, children ...g.Node) g.NodeFunc {
+ return g.El("h1", g.Text(text), g.Group(children))
+}
+
+// H2 returns an element with name "h2", the given text content, and the given children.
+func H2(text string, children ...g.Node) g.NodeFunc {
+ return g.El("h2", g.Text(text), g.Group(children))
+}
+
+// H3 returns an element with name "h3", the given text content, and the given children.
+func H3(text string, children ...g.Node) g.NodeFunc {
+ return g.El("h3", g.Text(text), g.Group(children))
+}
+
+// H4 returns an element with name "h4", the given text content, and the given children.
+func H4(text string, children ...g.Node) g.NodeFunc {
+ return g.El("h4", g.Text(text), g.Group(children))
+}
+
+// H5 returns an element with name "h5", the given text content, and the given children.
+func H5(text string, children ...g.Node) g.NodeFunc {
+ return g.El("h5", g.Text(text), g.Group(children))
+}
+
+// H6 returns an element with name "h6", the given text content, and the given children.
+func H6(text string, children ...g.Node) g.NodeFunc {
+ return g.El("h6", g.Text(text), g.Group(children))
+}
+
+func I(text string, children ...g.Node) g.NodeFunc {
+ return g.El("i", g.Text(text), g.Group(children))
+}
+
+func Ins(text string, children ...g.Node) g.NodeFunc {
+ return g.El("ins", g.Text(text), g.Group(children))
+}
+
+func Kbd(text string, children ...g.Node) g.NodeFunc {
+ return g.El("kbd", g.Text(text), g.Group(children))
+}
+
+func Mark(text string, children ...g.Node) g.NodeFunc {
+ return g.El("mark", g.Text(text), g.Group(children))
+}
+
+func Q(text string, children ...g.Node) g.NodeFunc {
+ return g.El("q", g.Text(text), g.Group(children))
+}
+
+func S(text string, children ...g.Node) g.NodeFunc {
+ return g.El("s", g.Text(text), g.Group(children))
+}
+
+func Samp(text string, children ...g.Node) g.NodeFunc {
+ return g.El("samp", g.Text(text), g.Group(children))
+}
+
+func Small(text string, children ...g.Node) g.NodeFunc {
+ return g.El("small", g.Text(text), g.Group(children))
+}
+
+func Strong(text string, children ...g.Node) g.NodeFunc {
+ return g.El("strong", g.Text(text), g.Group(children))
+}
+
+func Sub(text string, children ...g.Node) g.NodeFunc {
+ return g.El("sub", g.Text(text), g.Group(children))
+}
+
+func Sup(text string, children ...g.Node) g.NodeFunc {
+ return g.El("sup", g.Text(text), g.Group(children))
+}
+
+func Time(text string, children ...g.Node) g.NodeFunc {
+ return g.El("time", g.Text(text), g.Group(children))
+}
+
+func TitleEl(title string, children ...g.Node) g.NodeFunc {
+ return g.El("title", g.Text(title), g.Group(children))
+}
+
+func U(text string, children ...g.Node) g.NodeFunc {
+ return g.El("u", g.Text(text), g.Group(children))
+}
+
+func Var(text string, children ...g.Node) g.NodeFunc {
+ return g.El("var", g.Text(text), g.Group(children))
+}
diff --git a/html/elements_test.go b/html/elements_test.go
new file mode 100644
index 0000000..e550bfe
--- /dev/null
+++ b/html/elements_test.go
@@ -0,0 +1,212 @@
+package html_test
+
+import (
+ "errors"
+ "fmt"
+ "testing"
+
+ g "github.com/maragudk/gomponents"
+ "github.com/maragudk/gomponents/assert"
+ . "github.com/maragudk/gomponents/html"
+)
+
+type erroringWriter struct{}
+
+func (w *erroringWriter) Write(p []byte) (n int, err error) {
+ return 0, errors.New("don't want to write")
+}
+
+func TestDocument(t *testing.T) {
+ t.Run("returns doctype and children", func(t *testing.T) {
+ assert.Equal(t, ``, Document(g.El("html")))
+ })
+
+ t.Run("errors on write error in Render", func(t *testing.T) {
+ err := Document(g.El("html")).Render(&erroringWriter{})
+ assert.Error(t, err)
+ })
+}
+
+func TestFormEl(t *testing.T) {
+ t.Run("returns a form element with action and method attributes", func(t *testing.T) {
+ assert.Equal(t, ``, FormEl("/", "post"))
+ })
+}
+
+func TestInput(t *testing.T) {
+ t.Run("returns an input element with attributes type and name", func(t *testing.T) {
+ assert.Equal(t, ``, Input("text", "hat"))
+ })
+}
+
+func TestLabel(t *testing.T) {
+ t.Run("returns a label element with attribute for", func(t *testing.T) {
+ assert.Equal(t, ``, Label("hat", g.Text("Hat")))
+ })
+}
+
+func TestOption(t *testing.T) {
+ t.Run("returns an option element with attribute label and content", func(t *testing.T) {
+ assert.Equal(t, ``, Option("Hat", "hat"))
+ })
+}
+
+func TestProgress(t *testing.T) {
+ t.Run("returns a progress element with attributes value and max", func(t *testing.T) {
+ assert.Equal(t, ``, Progress(5.5, 10))
+ })
+}
+
+func TestSelect(t *testing.T) {
+ t.Run("returns a select element with attribute name", func(t *testing.T) {
+ assert.Equal(t, ``,
+ Select("hat", Option("Partyhat", "partyhat")))
+ })
+}
+
+func TestTextarea(t *testing.T) {
+ t.Run("returns a textarea element with attribute name", func(t *testing.T) {
+ assert.Equal(t, ``, Textarea("hat"))
+ })
+}
+
+func TestA(t *testing.T) {
+ t.Run("returns an a element with a href attribute", func(t *testing.T) {
+ assert.Equal(t, `hat`, A("#", g.Text("hat")))
+ })
+}
+
+func TestImg(t *testing.T) {
+ t.Run("returns an img element with href and alt attributes", func(t *testing.T) {
+ assert.Equal(t, `
`, Img("hat.png", "hat", g.Attr("id", "image")))
+ })
+}
+
+func TestSimpleElements(t *testing.T) {
+ cases := map[string]func(...g.Node) g.NodeFunc{
+ "address": Address,
+ "article": Article,
+ "aside": Aside,
+ "audio": Audio,
+ "blockquote": BlockQuote,
+ "body": Body,
+ "button": Button,
+ "canvas": Canvas,
+ "cite": Cite,
+ "code": Code,
+ "colgroup": ColGroup,
+ "data": Data,
+ "datalist": DataList,
+ "details": Details,
+ "dialog": Dialog,
+ "div": Div,
+ "dl": Dl,
+ "fieldset": FieldSet,
+ "figure": Figure,
+ "footer": Footer,
+ "head": Head,
+ "header": Header,
+ "hgroup": HGroup,
+ "html": HTML,
+ "iframe": IFrame,
+ "legend": Legend,
+ "li": Li,
+ "main": Main,
+ "menu": Menu,
+ "meter": Meter,
+ "nav": Nav,
+ "noscript": NoScript,
+ "object": Object,
+ "ol": Ol,
+ "optgroup": OptGroup,
+ "p": P,
+ "picture": Picture,
+ "pre": Pre,
+ "script": Script,
+ "section": Section,
+ "span": Span,
+ "style": StyleEl,
+ "summary": Summary,
+ "svg": SVG,
+ "table": Table,
+ "tbody": TBody,
+ "td": Td,
+ "tfoot": TFoot,
+ "th": Th,
+ "thead": THead,
+ "tr": Tr,
+ "ul": Ul,
+ }
+
+ for name, fn := range cases {
+ t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+ n := fn(g.Attr("id", "hat"))
+ assert.Equal(t, fmt.Sprintf(`<%v id="hat">%v>`, name, name), n)
+ })
+ }
+}
+
+func TestSimpleVoidKindElements(t *testing.T) {
+ cases := map[string]func(...g.Node) g.NodeFunc{
+ "area": Area,
+ "base": Base,
+ "br": Br,
+ "col": Col,
+ "embed": Embed,
+ "hr": Hr,
+ "link": Link,
+ "meta": Meta,
+ "param": Param,
+ "source": Source,
+ "wbr": Wbr,
+ }
+
+ for name, fn := range cases {
+ t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+ n := fn(g.Attr("id", "hat"))
+ assert.Equal(t, fmt.Sprintf(`<%v id="hat">`, name), n)
+ })
+ }
+}
+
+func TestTextElements(t *testing.T) {
+ cases := map[string]func(string, ...g.Node) g.NodeFunc{
+ "abbr": Abbr,
+ "b": B,
+ "caption": Caption,
+ "dd": Dd,
+ "del": Del,
+ "dfn": Dfn,
+ "dt": Dt,
+ "em": Em,
+ "figcaption": FigCaption,
+ "h1": H1,
+ "h2": H2,
+ "h3": H3,
+ "h4": H4,
+ "h5": H5,
+ "h6": H6,
+ "i": I,
+ "ins": Ins,
+ "kbd": Kbd,
+ "mark": Mark,
+ "q": Q,
+ "s": S,
+ "samp": Samp,
+ "small": Small,
+ "strong": Strong,
+ "sub": Sub,
+ "sup": Sup,
+ "time": Time,
+ "title": TitleEl,
+ "u": U,
+ "var": Var,
+ }
+
+ for name, fn := range cases {
+ t.Run(fmt.Sprintf("should output %v", name), func(t *testing.T) {
+ n := fn("hat", g.Attr("id", "hat"))
+ assert.Equal(t, fmt.Sprintf(`<%v id="hat">hat%v>`, name, name), n)
+ })
+ }
+}
--
cgit 1.4.1