htmgo/h/tag.go

519 lines
11 KiB
Go
Raw Normal View History

2024-01-22 15:22:16 +00:00
package h
2024-09-11 00:52:18 +00:00
import (
"encoding/json"
2024-09-11 17:31:40 +00:00
"fmt"
2024-09-11 00:52:18 +00:00
"github.com/gofiber/fiber/v2"
"html"
"net/url"
"strings"
)
2024-01-22 15:22:16 +00:00
type Node struct {
2024-09-11 00:52:18 +00:00
id string
2024-01-22 15:22:16 +00:00
tag string
attributes map[string]string
children []Renderable
2024-01-22 15:22:16 +00:00
text string
value string
2024-09-11 00:52:18 +00:00
changed bool
}
func (node *Node) Render() *Node {
return node
2024-09-11 00:52:18 +00:00
}
func (node *Node) AppendChild(child Renderable) Renderable {
2024-09-11 00:52:18 +00:00
node.children = append(node.children, child)
return node
}
func (node *Node) SetChanged(changed bool) Renderable {
2024-09-11 00:52:18 +00:00
node.changed = changed
return node
}
func Data(data map[string]any) Renderable {
2024-09-11 00:52:18 +00:00
serialized, err := json.Marshal(data)
if err != nil {
return Empty()
}
return Attribute("x-data", string(serialized))
}
func ClassIf(condition bool, value string) Renderable {
2024-09-11 00:52:18 +00:00
if condition {
return Class(value)
}
return Empty()
2024-01-22 15:22:16 +00:00
}
func Class(value ...string) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
tag: "class",
2024-09-11 00:52:18 +00:00
value: MergeClasses(value...),
2024-01-22 15:22:16 +00:00
}
}
2024-09-11 00:52:18 +00:00
func MergeClasses(classes ...string) string {
builder := ""
for _, s := range classes {
builder += s + " "
}
return builder
}
func Id(value string) Renderable {
2024-09-11 17:31:40 +00:00
if strings.HasPrefix(value, "#") {
value = value[1:]
}
2024-01-22 15:22:16 +00:00
return Attribute("id", value)
}
func Attributes(attrs map[string]string) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
2024-09-11 17:31:40 +00:00
tag: "attribute",
attributes: attrs,
2024-01-22 15:22:16 +00:00
}
}
func Boost() Renderable {
return Attribute("hx-boost", "true")
}
func Attribute(key string, value string) Renderable {
2024-09-11 17:31:40 +00:00
return Attributes(map[string]string{key: value})
}
2024-09-12 02:06:34 +00:00
func TriggerChildren() Renderable {
return HxExtension("trigger-children")
}
func HxExtension(value string) Renderable {
return Attribute("hx-ext", value)
2024-09-12 02:06:34 +00:00
}
func Disabled() Renderable {
2024-09-11 17:31:40 +00:00
return Attribute("disabled", "")
}
func Get(path string) Renderable {
2024-09-11 00:52:18 +00:00
return Attribute("hx-get", path)
}
func GetPartial(partial func(ctx *fiber.Ctx) *Partial) Renderable {
2024-09-11 17:31:40 +00:00
return Get(GetPartialPath(partial))
}
func GetPartialWithQs(partial func(ctx *fiber.Ctx) *Partial, qs string) Renderable {
2024-09-11 17:31:40 +00:00
return Get(GetPartialPathWithQs(partial, qs))
}
2024-09-11 00:52:18 +00:00
func CreateTriggers(triggers ...string) []string {
return triggers
}
type ReloadParams struct {
Triggers []string
2024-09-12 02:06:34 +00:00
Target string
Children Renderable
2024-09-11 00:52:18 +00:00
}
func ViewOnLoad(partial func(ctx *fiber.Ctx) *Partial) Renderable {
2024-09-11 17:31:40 +00:00
return View(partial, ReloadParams{
Triggers: CreateTriggers("load"),
})
}
func View(partial func(ctx *fiber.Ctx) *Partial, params ReloadParams) Renderable {
2024-09-11 17:31:40 +00:00
return Div(Attributes(map[string]string{
"hx-get": GetPartialPath(partial),
"hx-trigger": strings.Join(params.Triggers, ", "),
2024-09-12 02:06:34 +00:00
"hx-target": params.Target,
}), params.Children)
2024-09-11 17:31:40 +00:00
}
2024-09-12 02:06:34 +00:00
func PartialWithTriggers(partial func(ctx *fiber.Ctx) *Partial, triggers ...string) Renderable {
2024-09-11 17:31:40 +00:00
return Div(Attributes(map[string]string{
"hx-get": GetPartialPath(partial),
"hx-trigger": strings.Join(triggers, ", "),
}))
2024-09-11 00:52:18 +00:00
}
func GetWithQs(path string, qs map[string]string) Renderable {
2024-09-12 02:06:34 +00:00
return Get(SetQueryParams(path, qs))
2024-01-22 15:22:16 +00:00
}
func Post(url string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("hx-post", url)
}
func Trigger(trigger string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("hx-trigger", trigger)
}
func Text(text string) Renderable {
2024-09-11 00:52:18 +00:00
return &Node{
tag: "text",
text: text,
}
}
func Pf(format string, args ...interface{}) Renderable {
2024-09-11 17:31:40 +00:00
return P(fmt.Sprintf(format, args...))
}
func Target(target string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("hx-target", target)
}
func Name(name string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("name", name)
}
func Confirm(message string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("hx-confirm", message)
}
func Href(path string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("href", path)
}
func Type(name string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("type", name)
}
func Placeholder(placeholder string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("placeholder", placeholder)
}
func OutOfBandSwap(selector string) Renderable {
2024-09-11 17:31:40 +00:00
return Attribute("hx-swap-oob",
Ternary(selector == "", "true", selector))
2024-01-22 15:22:16 +00:00
}
func Click(value string) Renderable {
2024-01-22 15:22:16 +00:00
return Attribute("onclick", value)
}
func Tag(tag string, children ...Renderable) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
2024-09-11 17:31:40 +00:00
tag: tag,
2024-01-22 15:22:16 +00:00
children: children,
}
}
func Html(children ...Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return Tag("html", children...)
}
func Head(children ...Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return Tag("head", children...)
2024-01-22 15:22:16 +00:00
}
func Body(children ...Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return Tag("body", children...)
2024-01-22 15:22:16 +00:00
}
func Script(url string) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
tag: "script",
attributes: map[string]string{
"src": url,
},
children: make([]Renderable, 0),
2024-01-22 15:22:16 +00:00
}
}
func Raw(text string) Renderable {
2024-09-11 00:52:18 +00:00
return &Node{
tag: "raw",
children: make([]Renderable, 0),
2024-09-11 00:52:18 +00:00
value: text,
}
}
func RawScript(text string) Renderable {
2024-09-11 00:52:18 +00:00
return Raw("<script>" + text + "</script>")
}
func Div(children ...Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return Tag("div", children...)
2024-01-22 15:22:16 +00:00
}
func ReplaceUrlHeader(url string) *Headers {
return NewHeaders("HX-Replace-Url", url)
2024-09-12 02:06:34 +00:00
}
func CombineHeaders(headers ...*Headers) *Headers {
m := make(Headers)
for _, h := range headers {
for k, v := range *h {
m[k] = v
}
}
return &m
}
func CurrentPath(ctx *fiber.Ctx) string {
current := ctx.Get("Hx-Current-Url")
parsed, err := url.Parse(current)
if err != nil {
return ""
}
return parsed.Path
}
func PushQsHeader(ctx *fiber.Ctx, key string, value string) *Headers {
current := ctx.Get("Hx-Current-Url")
parsed, err := url.Parse(current)
if err != nil {
return NewHeaders()
}
return NewHeaders("HX-Replace-Url", SetQueryParams(parsed.Path, map[string]string{
2024-09-12 02:06:34 +00:00
key: value,
}))
}
func NewHeaders(headers ...string) *Headers {
m := make(Headers)
for i := 0; i < len(headers); i++ {
m[headers[i]] = headers[i+1]
i++
}
return &m
}
func Input(inputType string, children ...Renderable) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
tag: "input",
attributes: map[string]string{
"type": inputType,
},
children: children,
}
}
func List[T any](items []T, mapper func(item T, index int) Renderable) Renderable {
2024-01-22 15:22:16 +00:00
node := &Node{
tag: "",
children: make([]Renderable, len(items)),
2024-01-22 15:22:16 +00:00
}
for index, value := range items {
2024-09-11 18:09:55 +00:00
node.children[index] = mapper(value, index)
2024-01-22 15:22:16 +00:00
}
return node
}
func Fragment(children ...Renderable) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
tag: "",
children: children,
}
}
func AttributeList(children ...Renderable) Renderable {
2024-09-11 00:52:18 +00:00
return &Node{
tag: FlagAttributeList,
children: children,
}
}
func AppendChildren(node *Node, children ...Renderable) Renderable {
2024-09-11 00:52:18 +00:00
node.children = append(node.children, children...)
return node
}
func Button(children ...Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return Tag("button", children...)
2024-01-22 15:22:16 +00:00
}
func Indicator(tag string) Renderable {
2024-09-11 00:52:18 +00:00
return Attribute("hx-indicator", tag)
}
func P(text string, children ...Renderable) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
tag: "p",
children: children,
text: text,
}
}
func Form(children ...Renderable) Renderable {
2024-09-11 18:09:55 +00:00
return Tag("form", children...)
}
func A(text string, children ...Renderable) Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
tag: "a",
children: children,
text: text,
}
}
func Nav(children ...Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return Tag("nav", children...)
}
func Empty() Renderable {
2024-01-22 15:22:16 +00:00
return &Node{
tag: "",
}
}
func BeforeRequestSetHtml(children ...Renderable) Renderable {
2024-09-11 00:52:18 +00:00
serialized := Render(Fragment(children...))
return Attribute("hx-on::before-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
}
func BeforeRequestSetAttribute(key string, value string) Renderable {
2024-09-11 17:31:40 +00:00
return Attribute("hx-on::before-request", `this.setAttribute('`+key+`', '`+value+`')`)
}
func BeforeRequestSetText(text string) Renderable {
2024-09-11 17:31:40 +00:00
return Attribute("hx-on::before-request", `this.innerText = '`+text+`'`)
}
2024-09-12 02:06:34 +00:00
func AfterRequestSetText(text string) Renderable {
return Attribute("hx-on::after-request", `this.innerText = '`+text+`'`)
}
func AfterRequestRemoveAttribute(key string, value string) Renderable {
2024-09-11 17:31:40 +00:00
return Attribute("hx-on::after-request", `this.removeAttribute('`+key+`')`)
}
func IfQueryParam(key string, node *Node) Renderable {
2024-09-11 17:31:40 +00:00
return Fragment(Attribute("hx-if-qp:"+key, "true"), node)
}
func Hidden() Renderable {
2024-09-11 17:31:40 +00:00
return Attribute("style", "display:none")
}
func MatchQueryParam(defaultValue string, active string, m map[string]*Node) Renderable {
2024-09-11 17:31:40 +00:00
rendered := make(map[string]string)
for s, node := range m {
rendered[s] = Render(node)
}
root := Tag("span",
m[active],
Trigger("url"),
Attribute("hx-match-qp", "true"),
Attribute("hx-match-qp-default", defaultValue),
)
for s, node := range rendered {
root = AppendChildren(root.Render(), Attribute("hx-match-qp-mapping:"+s, ``+html.EscapeString(node)+``))
2024-09-11 17:31:40 +00:00
}
return root
}
func AfterRequestSetHtml(children ...Renderable) Renderable {
2024-09-11 00:52:18 +00:00
serialized := Render(Fragment(children...))
return Attribute("hx-on::after-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
}
func Children(children ...Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return &Node{
tag: FlagChildrenList,
children: children,
}
}
func Label(text string) Renderable {
2024-09-11 18:09:55 +00:00
return Tag("label", Text(text))
}
func If(condition bool, node Renderable) Renderable {
2024-01-22 15:22:16 +00:00
if condition {
return node
} else {
return Empty()
}
}
func IfElse(condition bool, node Renderable, node2 Renderable) Renderable {
2024-01-22 15:22:16 +00:00
if condition {
return node
} else {
return node2
}
}
2024-09-11 17:31:40 +00:00
func IfElseLazy(condition bool, cb1 func() Renderable, cb2 func() Renderable) Renderable {
2024-09-11 17:31:40 +00:00
if condition {
return cb1()
} else {
return cb2()
}
}
func GetTriggerName(ctx *fiber.Ctx) string {
return ctx.Get("HX-Trigger-Name")
}
func IfHtmxRequest(ctx *fiber.Ctx, node Renderable) Renderable {
2024-09-11 17:31:40 +00:00
if ctx.Get("HX-Request") != "" {
return node
}
return Empty()
}
type SwapArg struct {
Selector string
Content *Node
}
func NewSwap(selector string, content *Node) SwapArg {
return SwapArg{
Selector: selector,
Content: content,
}
}
func Swap(ctx *fiber.Ctx, content Renderable) Renderable {
2024-09-11 17:31:40 +00:00
return SwapWithSelector(ctx, "", content)
}
func SwapWithSelector(ctx *fiber.Ctx, selector string, content Renderable) Renderable {
2024-09-11 17:31:40 +00:00
if ctx == nil || ctx.Get("HX-Request") == "" {
return Empty()
}
c := content.Render()
return c.AppendChild(OutOfBandSwap(selector))
2024-09-11 17:31:40 +00:00
}
func SwapMany(ctx *fiber.Ctx, args ...SwapArg) Renderable {
2024-09-11 17:31:40 +00:00
if ctx.Get("HX-Request") == "" {
return Empty()
}
for _, arg := range args {
arg.Content.AppendChild(OutOfBandSwap(arg.Selector))
}
return Fragment(Map(args, func(arg SwapArg) Renderable {
2024-09-11 17:31:40 +00:00
return arg.Content
})...)
}
type OnRequestSwapArgs struct {
Target string
Get string
Default *Node
BeforeRequest *Node
AfterRequest *Node
}
func OnRequestSwap(args OnRequestSwapArgs) Renderable {
2024-09-11 17:31:40 +00:00
return Div(args.Default,
BeforeRequestSetHtml(args.BeforeRequest),
AfterRequestSetHtml(args.AfterRequest),
Get(args.Get),
Target(args.Target),
)
}