Skip to content

Commit

Permalink
Deprecate DataAttr, StyleAttr, TitleAttr, FormEl (#174)
Browse files Browse the repository at this point in the history
This change addresses #170 by deprecating some HTML helpers in favor of
using one of the styles as a main one, selected based on what I think is
the main use case.

- For `Data`, it's the attribute. I don't see much use of the `<data>`
element in the wild.
- For `Style`, it's the attribute. The `style` attribute is everywhere,
the `<style>` element is perhaps less so (but not much). This was the
hardest one to decide.
- For `Title`, it's the attribute. The `<title>` element only shows up
once per document.
- For `Form`, it's the element. I haven't seen much use of the `form`
attribute in the wild.

I know this is arguably not a "consistent" approach, but I think it
makes for a much nicer API, simply because the most-used option will not
be a suffixed version.
  • Loading branch information
markuswustenberg committed Jun 6, 2024
2 parents d944acd + 0b85f72 commit 81b2d6a
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 188 deletions.
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ For more complete examples, see [the examples directory](examples/).
### What's up with the specially named elements and attributes?

Unfortunately, there are four 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:
to be able to co-exist in the same package in Go. I've chosen one or the other based on what I think is the common usage.
In either case, the less-used variant also exists in the codebase:

- `data` (`DataEl`/`DataAttr`)
- `form` (`FormEl`/`FormAttr`)
- `style` (`StyleEl`/`StyleAttr`)
- `title` (`TitleEl`/`TitleAttr`)
- `data` (`DataEl`/`Data`, `DataAttr` also exists)
- `form` (`Form`/`FormAttr`, `FormEl` also exists)
- `style` (`StyleEl`/`Style`, `StyleAttr` also exists)
- `title` (`TitleEl`/`Title`, `TitleAttr` also exists)
17 changes: 17 additions & 0 deletions html/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@ func Content(v string) g.Node {
return g.Attr("content", v)
}

// Data attributes automatically have their name prefixed with "data-".
func Data(name, v string) g.Node {
return g.Attr("data-"+name, v)
}

// DataAttr attributes automatically have their name prefixed with "data-".
//
// Deprecated: Use [Data] instead.
func DataAttr(name, v string) g.Node {
return g.Attr("data-"+name, v)
}
Expand Down Expand Up @@ -214,6 +221,11 @@ func Step(v string) g.Node {
return g.Attr("step", v)
}

func Style(v string) g.Node {
return g.Attr("style", v)
}

// Deprecated: Use [Style] instead.
func StyleAttr(v string) g.Node {
return g.Attr("style", v)
}
Expand All @@ -226,6 +238,11 @@ func Target(v string) g.Node {
return g.Attr("target", v)
}

func Title(v string) g.Node {
return g.Attr("title", v)
}

// Deprecated: Use [Title] instead.
func TitleAttr(v string) g.Node {
return g.Attr("title", v)
}
Expand Down
151 changes: 81 additions & 70 deletions html/attributes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,83 +10,91 @@ import (
)

func TestBooleanAttributes(t *testing.T) {
cases := map[string]func() g.Node{
"async": Async,
"autofocus": AutoFocus,
"autoplay": AutoPlay,
"checked": Checked,
"controls": Controls,
"defer": Defer,
"disabled": Disabled,
"loop": Loop,
"multiple": Multiple,
"muted": Muted,
"playsinline": PlaysInline,
"readonly": ReadOnly,
"required": Required,
"selected": Selected,
tests := []struct {
Name string
Func func() g.Node
}{
{Name: "async", Func: Async},
{Name: "autofocus", Func: AutoFocus},
{Name: "autoplay", Func: AutoPlay},
{Name: "checked", Func: Checked},
{Name: "controls", Func: Controls},
{Name: "defer", Func: Defer},
{Name: "disabled", Func: Disabled},
{Name: "loop", Func: Loop},
{Name: "multiple", Func: Multiple},
{Name: "muted", Func: Muted},
{Name: "playsinline", Func: PlaysInline},
{Name: "readonly", Func: ReadOnly},
{Name: "required", Func: Required},
{Name: "selected", Func: 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(`<div %v></div>`, name), n)
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
n := g.El("div", test.Func())
assert.Equal(t, fmt.Sprintf(`<div %v></div>`, test.Name), n)
})
}
}

func TestSimpleAttributes(t *testing.T) {
cases := map[string]func(string) g.Node{
"accept": Accept,
"action": Action,
"alt": Alt,
"as": As,
"autocomplete": AutoComplete,
"charset": Charset,
"class": Class,
"cols": Cols,
"colspan": ColSpan,
"content": Content,
"crossorigin": CrossOrigin,
"enctype": EncType,
"for": For,
"form": FormAttr,
"height": Height,
"href": Href,
"id": ID,
"integrity": Integrity,
"lang": Lang,
"loading": Loading,
"max": Max,
"maxlength": MaxLength,
"method": Method,
"min": Min,
"minlength": MinLength,
"name": Name,
"pattern": Pattern,
"placeholder": Placeholder,
"poster": Poster,
"preload": Preload,
"rel": Rel,
"role": Role,
"rows": Rows,
"rowspan": RowSpan,
"src": Src,
"srcset": SrcSet,
"step": Step,
"style": StyleAttr,
"tabindex": TabIndex,
"target": Target,
"title": TitleAttr,
"type": Type,
"value": Value,
"width": Width,
tests := []struct {
Name string
Func func(string) g.Node
}{
{Name: "accept", Func: Accept},
{Name: "action", Func: Action},
{Name: "alt", Func: Alt},
{Name: "as", Func: As},
{Name: "autocomplete", Func: AutoComplete},
{Name: "charset", Func: Charset},
{Name: "class", Func: Class},
{Name: "cols", Func: Cols},
{Name: "colspan", Func: ColSpan},
{Name: "content", Func: Content},
{Name: "crossorigin", Func: CrossOrigin},
{Name: "enctype", Func: EncType},
{Name: "for", Func: For},
{Name: "form", Func: FormAttr},
{Name: "height", Func: Height},
{Name: "href", Func: Href},
{Name: "id", Func: ID},
{Name: "integrity", Func: Integrity},
{Name: "lang", Func: Lang},
{Name: "loading", Func: Loading},
{Name: "max", Func: Max},
{Name: "maxlength", Func: MaxLength},
{Name: "method", Func: Method},
{Name: "min", Func: Min},
{Name: "minlength", Func: MinLength},
{Name: "name", Func: Name},
{Name: "pattern", Func: Pattern},
{Name: "placeholder", Func: Placeholder},
{Name: "poster", Func: Poster},
{Name: "preload", Func: Preload},
{Name: "rel", Func: Rel},
{Name: "role", Func: Role},
{Name: "rows", Func: Rows},
{Name: "rowspan", Func: RowSpan},
{Name: "src", Func: Src},
{Name: "srcset", Func: SrcSet},
{Name: "step", Func: Step},
{Name: "style", Func: Style},
{Name: "style", Func: StyleAttr},
{Name: "tabindex", Func: TabIndex},
{Name: "target", Func: Target},
{Name: "title", Func: Title},
{Name: "title", Func: TitleAttr},
{Name: "type", Func: Type},
{Name: "value", Func: Value},
{Name: "width", Func: 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(`<div %v="hat"></div>`, name), n)
for _, test := range tests {
t.Run(test.Name, func(t *testing.T) {
n := g.El("div", test.Func("hat"))
assert.Equal(t, fmt.Sprintf(`<div %v="hat"></div>`, test.Name), n)
})
}
}
Expand All @@ -98,9 +106,12 @@ func TestAria(t *testing.T) {
})
}

func TestDataAttr(t *testing.T) {
func TestData(t *testing.T) {
t.Run("returns an attribute which name is prefixed with data-", func(t *testing.T) {
n := DataAttr("id", "partyhat")
n := Data("id", "partyhat")
assert.Equal(t, ` data-id="partyhat"`, n)

n = DataAttr("id", "partyhat")
assert.Equal(t, ` data-id="partyhat"`, n)
})
}
5 changes: 5 additions & 0 deletions html/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ func Embed(children ...g.Node) g.Node {
return g.El("embed", children...)
}

func Form(children ...g.Node) g.Node {
return g.El("form", children...)
}

// Deprecated: Use [Form] instead.
func FormEl(children ...g.Node) g.Node {
return g.El("form", children...)
}
Expand Down
Loading

0 comments on commit 81b2d6a

Please sign in to comment.