more cleanup
This commit is contained in:
parent
c555da4dc9
commit
da7e22c446
16 changed files with 444 additions and 344 deletions
|
|
@ -47,10 +47,10 @@ func Button(props ButtonProps) h.Ren {
|
|||
button := h.Button(
|
||||
h.If(props.Id != "", h.Id(props.Id)),
|
||||
h.If(props.Children != nil, h.Children(props.Children...)),
|
||||
h.If(props.Trigger != "", h.Trigger(props.Trigger)),
|
||||
h.If(props.Trigger != "", h.HxTrigger(props.Trigger)),
|
||||
h.Class("flex gap-1 items-center border p-4 rounded cursor-hover", props.Class),
|
||||
h.If(props.Get != "", h.Get(props.Get)),
|
||||
h.If(props.Target != "", h.Target(props.Target)),
|
||||
h.If(props.Target != "", h.HxTarget(props.Target)),
|
||||
h.IfElse(props.Type != "", h.Type(props.Type), h.Type("button")),
|
||||
lifecycle,
|
||||
text,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/maddalax/htmgo/framework/htmgo/service"
|
||||
"github.com/maddalax/htmgo/framework/hx"
|
||||
"github.com/maddalax/htmgo/framework/util/process"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
|
@ -11,7 +12,14 @@ import (
|
|||
|
||||
type RequestContext struct {
|
||||
echo.Context
|
||||
locator *service.Locator
|
||||
locator *service.Locator
|
||||
isBoosted bool
|
||||
currentBrowserUrl string
|
||||
hxPromptResponse string
|
||||
isHxRequest bool
|
||||
hxTargetId string
|
||||
hxTriggerName string
|
||||
hxTriggerId string
|
||||
}
|
||||
|
||||
func (c *RequestContext) ServiceLocator() *service.Locator {
|
||||
|
|
@ -47,6 +55,16 @@ func Start(opts AppOpts) {
|
|||
instance.start()
|
||||
}
|
||||
|
||||
func populateHxFields(cc *RequestContext) {
|
||||
cc.isBoosted = cc.Request().Header.Get(hx.BoostedHeader) == "true"
|
||||
cc.currentBrowserUrl = cc.Request().Header.Get(hx.CurrentUrlHeader)
|
||||
cc.hxPromptResponse = cc.Request().Header.Get(hx.PromptResponseHeader)
|
||||
cc.isHxRequest = cc.Request().Header.Get(hx.RequestHeader) == "true"
|
||||
cc.hxTargetId = cc.Request().Header.Get(hx.TargetIdHeader)
|
||||
cc.hxTriggerName = cc.Request().Header.Get(hx.TriggerNameHeader)
|
||||
cc.hxTriggerId = cc.Request().Header.Get(hx.TriggerIdHeader)
|
||||
}
|
||||
|
||||
func (a App) start() {
|
||||
|
||||
if a.Opts.Register != nil {
|
||||
|
|
@ -56,9 +74,10 @@ func (a App) start() {
|
|||
a.Echo.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
cc := &RequestContext{
|
||||
c,
|
||||
a.Opts.ServiceLocator,
|
||||
Context: c,
|
||||
locator: a.Opts.ServiceLocator,
|
||||
}
|
||||
populateHxFields(cc)
|
||||
return next(cc)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package h
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/maddalax/htmgo/framework/hx"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AttributeMap map[string]any
|
||||
|
||||
|
|
@ -21,3 +25,143 @@ func (m *AttributeMap) ToMap() map[string]string {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func Attribute(key string, value string) *AttributeMap {
|
||||
return Attributes(&AttributeMap{key: value})
|
||||
}
|
||||
|
||||
func AttributeList(children ...*AttributeMap) *AttributeMap {
|
||||
m := make(AttributeMap)
|
||||
for _, child := range children {
|
||||
for k, v := range *child {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
func Attributes(attrs *AttributeMap) *AttributeMap {
|
||||
return attrs
|
||||
}
|
||||
|
||||
func AttributePairs(pairs ...string) *AttributeMap {
|
||||
if len(pairs)%2 != 0 {
|
||||
return &AttributeMap{}
|
||||
}
|
||||
m := make(AttributeMap)
|
||||
for i := 0; i < len(pairs); i++ {
|
||||
m[pairs[i]] = pairs[i+1]
|
||||
i++
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
func Checked() Ren {
|
||||
return Attribute("checked", "true")
|
||||
}
|
||||
|
||||
func Id(value string) Ren {
|
||||
if strings.HasPrefix(value, "#") {
|
||||
value = value[1:]
|
||||
}
|
||||
return Attribute("id", value)
|
||||
}
|
||||
|
||||
func Disabled() Ren {
|
||||
return Attribute("disabled", "")
|
||||
}
|
||||
|
||||
func HxTarget(target string) Ren {
|
||||
return Attribute(hx.TargetAttr, target)
|
||||
}
|
||||
|
||||
func Name(name string) Ren {
|
||||
return Attribute("name", name)
|
||||
}
|
||||
|
||||
func HxConfirm(message string) Ren {
|
||||
return Attribute(hx.ConfirmAttr, message)
|
||||
}
|
||||
|
||||
// HxInclude https://htmx.org/attributes/hx-include/
|
||||
func HxInclude(selector string) Ren {
|
||||
return Attribute(hx.IncludeAttr, selector)
|
||||
}
|
||||
|
||||
func HxIndicator(tag string) *AttributeMap {
|
||||
return Attribute(hx.IndicatorAttr, tag)
|
||||
}
|
||||
|
||||
func TriggerChildren() Ren {
|
||||
return HxExtension("trigger-children")
|
||||
}
|
||||
|
||||
func TriggerString(triggers ...string) *AttributeMap {
|
||||
trigger := hx.NewStringTrigger(strings.Join(triggers, ", "))
|
||||
return Attribute(hx.TriggerAttr, trigger.ToString())
|
||||
}
|
||||
|
||||
func HxTrigger(opts ...hx.TriggerEvent) *AttributeMap {
|
||||
return Attribute(hx.TriggerAttr, hx.NewTrigger(opts...).ToString())
|
||||
}
|
||||
|
||||
func HxTriggerClick(opts ...hx.Modifier) *AttributeMap {
|
||||
return HxTrigger(hx.OnClick(opts...))
|
||||
}
|
||||
|
||||
func HxExtension(value string) Ren {
|
||||
return Attribute(hx.ExtAttr, value)
|
||||
}
|
||||
|
||||
func Href(path string) Ren {
|
||||
return Attribute("href", path)
|
||||
}
|
||||
|
||||
func Type(name string) Ren {
|
||||
return Attribute("type", name)
|
||||
}
|
||||
|
||||
func Placeholder(placeholder string) Ren {
|
||||
return Attribute("placeholder", placeholder)
|
||||
}
|
||||
|
||||
func Hidden() Ren {
|
||||
return Attribute("style", "display:none")
|
||||
}
|
||||
|
||||
func Class(value ...string) Ren {
|
||||
return Attribute("class", MergeClasses(value...))
|
||||
}
|
||||
|
||||
func ClassX(value string, m ClassMap) Ren {
|
||||
builder := strings.Builder{}
|
||||
builder.WriteString(value)
|
||||
builder.WriteString(" ")
|
||||
for k, v := range m {
|
||||
if v {
|
||||
builder.WriteString(k)
|
||||
builder.WriteString(" ")
|
||||
}
|
||||
}
|
||||
return Class(builder.String())
|
||||
}
|
||||
|
||||
func MergeClasses(classes ...string) string {
|
||||
if len(classes) == 1 {
|
||||
return classes[0]
|
||||
}
|
||||
builder := strings.Builder{}
|
||||
for _, s := range classes {
|
||||
builder.WriteString(s)
|
||||
builder.WriteString(" ")
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func Boost() Ren {
|
||||
return Attribute(hx.BoostAttr, "true")
|
||||
}
|
||||
|
||||
func IfQueryParam(key string, node *Element) Ren {
|
||||
return Fragment(Attribute("hx-if-qp:"+key, "true"), node)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,18 @@ func NewPartial(root *Element) *Partial {
|
|||
}
|
||||
}
|
||||
|
||||
func SwapManyPartial(ctx *RequestContext, swaps ...*Element) *Partial {
|
||||
return NewPartial(
|
||||
SwapMany(ctx, swaps...),
|
||||
)
|
||||
}
|
||||
|
||||
func SwapManyXPartial(ctx *RequestContext, swaps ...SwapArg) *Partial {
|
||||
return NewPartial(
|
||||
SwapManyX(ctx, swaps...),
|
||||
)
|
||||
}
|
||||
|
||||
func GetPartialPath(partial func(ctx *RequestContext) *Partial) string {
|
||||
return runtime.FuncForPC(reflect.ValueOf(partial).Pointer()).Name()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,13 @@ func If(condition bool, node Ren) Ren {
|
|||
}
|
||||
}
|
||||
|
||||
func Ternary[T any](value bool, a T, b T) T {
|
||||
if value {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func IfElse(condition bool, node Ren, node2 Ren) Ren {
|
||||
if condition {
|
||||
return node
|
||||
|
|
@ -30,3 +37,10 @@ func IfHtmxRequest(ctx *RequestContext, node Ren) Ren {
|
|||
}
|
||||
return Empty()
|
||||
}
|
||||
|
||||
func ClassIf(condition bool, value string) Ren {
|
||||
if condition {
|
||||
return Class(value)
|
||||
}
|
||||
return Empty()
|
||||
}
|
||||
|
|
|
|||
41
framework/h/header.go
Normal file
41
framework/h/header.go
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
package h
|
||||
|
||||
import (
|
||||
"github.com/maddalax/htmgo/framework/hx"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func ReplaceUrlHeader(url string) *Headers {
|
||||
return NewHeaders(hx.ReplaceUrlHeader, url)
|
||||
}
|
||||
|
||||
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 *RequestContext) string {
|
||||
current := ctx.Request().Header.Get(hx.CurrentUrlHeader)
|
||||
parsed, err := url.Parse(current)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsed.Path
|
||||
}
|
||||
|
||||
func NewHeaders(headers ...string) *Headers {
|
||||
if len(headers)%2 != 0 {
|
||||
return &Headers{}
|
||||
}
|
||||
m := make(Headers)
|
||||
for i := 0; i < len(headers); i++ {
|
||||
m[headers[i]] = headers[i+1]
|
||||
i++
|
||||
}
|
||||
return &m
|
||||
}
|
||||
89
framework/h/qs.go
Normal file
89
framework/h/qs.go
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
package h
|
||||
|
||||
import (
|
||||
"github.com/maddalax/htmgo/framework/hx"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Qs struct {
|
||||
m map[string]string
|
||||
}
|
||||
|
||||
func NewQs(pairs ...string) *Qs {
|
||||
q := &Qs{
|
||||
m: make(map[string]string),
|
||||
}
|
||||
if len(pairs)%2 != 0 {
|
||||
return q
|
||||
}
|
||||
for i := 0; i < len(pairs); i++ {
|
||||
q.m[pairs[i]] = pairs[i+1]
|
||||
i++
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Qs) Add(key string, value string) *Qs {
|
||||
q.m[key] = value
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Qs) Remove(key string) *Qs {
|
||||
delete(q.m, key)
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Qs) ToString() string {
|
||||
builder := strings.Builder{}
|
||||
index := 0
|
||||
for k, v := range q.m {
|
||||
builder.WriteString(k)
|
||||
builder.WriteString("=")
|
||||
builder.WriteString(v)
|
||||
if index < len(q.m)-1 {
|
||||
builder.WriteString("&")
|
||||
}
|
||||
index++
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func PushQsHeader(ctx *RequestContext, qs *Qs) *Headers {
|
||||
parsed, err := url.Parse(ctx.currentBrowserUrl)
|
||||
if err != nil {
|
||||
return NewHeaders()
|
||||
}
|
||||
return NewHeaders(hx.ReplaceUrlHeader, SetQueryParams(parsed.Path, qs))
|
||||
}
|
||||
|
||||
func GetQueryParam(ctx *RequestContext, key string) string {
|
||||
value := ctx.QueryParam(key)
|
||||
if value == "" {
|
||||
current := ctx.currentBrowserUrl
|
||||
if current != "" {
|
||||
u, err := url.Parse(current)
|
||||
if err == nil {
|
||||
return u.Query().Get(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func SetQueryParams(href string, qs *Qs) string {
|
||||
u, err := url.Parse(href)
|
||||
if err != nil {
|
||||
return href
|
||||
}
|
||||
q := u.Query()
|
||||
for key, value := range qs.m {
|
||||
if value == "" {
|
||||
q.Del(key)
|
||||
} else {
|
||||
q.Set(key, value)
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
|
@ -6,6 +6,10 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
type Ren interface {
|
||||
Render(builder *strings.Builder)
|
||||
}
|
||||
|
||||
func Render(node Ren) string {
|
||||
start := time.Now()
|
||||
builder := &strings.Builder{}
|
||||
|
|
|
|||
83
framework/h/swap.go
Normal file
83
framework/h/swap.go
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
package h
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/maddalax/htmgo/framework/hx"
|
||||
)
|
||||
|
||||
type SwapArg struct {
|
||||
Content *Element
|
||||
Option SwapOption
|
||||
}
|
||||
|
||||
type SwapOption struct {
|
||||
Selector string
|
||||
SwapType hx.SwapType
|
||||
Modifier string
|
||||
}
|
||||
|
||||
func NewSwap(content *Element, opts ...SwapOption) SwapArg {
|
||||
option := SwapOption{}
|
||||
if len(opts) > 0 {
|
||||
option = opts[0]
|
||||
}
|
||||
return SwapArg{
|
||||
Content: content,
|
||||
Option: option,
|
||||
}
|
||||
}
|
||||
|
||||
func OobSwap(ctx *RequestContext, content *Element, option ...SwapOption) *Element {
|
||||
return OobSwapWithSelector(ctx, "", content, option...)
|
||||
}
|
||||
|
||||
func OobSwapWithSelector(ctx *RequestContext, selector string, content *Element, option ...SwapOption) *Element {
|
||||
if ctx == nil || !ctx.isHxRequest {
|
||||
return Empty()
|
||||
}
|
||||
return content.AppendChild(outOfBandSwap(selector, option...))
|
||||
}
|
||||
|
||||
func outOfBandSwap(selector string, option ...SwapOption) Ren {
|
||||
swapType := hx.SwapTypeTrue
|
||||
|
||||
if len(option) > 0 {
|
||||
o := option[0]
|
||||
|
||||
if o.SwapType != "" {
|
||||
swapType = o.SwapType
|
||||
}
|
||||
|
||||
modifier := o.Modifier
|
||||
if modifier != "" {
|
||||
swapType = fmt.Sprintf("%s %s", swapType, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
return Attribute(hx.SwapOobAttr,
|
||||
Ternary(selector == "", swapType, selector))
|
||||
}
|
||||
|
||||
func SwapMany(ctx *RequestContext, elements ...*Element) *Element {
|
||||
if !ctx.isHxRequest {
|
||||
return Empty()
|
||||
}
|
||||
for _, element := range elements {
|
||||
element.AppendChild(outOfBandSwap(""))
|
||||
}
|
||||
return Template(Map(elements, func(arg *Element) Ren {
|
||||
return arg
|
||||
})...)
|
||||
}
|
||||
|
||||
func SwapManyX(ctx *RequestContext, args ...SwapArg) *Element {
|
||||
if !ctx.isHxRequest {
|
||||
return Empty()
|
||||
}
|
||||
for _, arg := range args {
|
||||
arg.Content.AppendChild(outOfBandSwap("", arg.Option))
|
||||
}
|
||||
return Template(Map(args, func(arg SwapArg) Ren {
|
||||
return arg.Content
|
||||
})...)
|
||||
}
|
||||
|
|
@ -1,50 +1,10 @@
|
|||
package h
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/maddalax/htmgo/framework/hx"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Qs struct {
|
||||
m map[string]string
|
||||
}
|
||||
|
||||
func NewQs(pairs ...string) *Qs {
|
||||
q := &Qs{
|
||||
m: make(map[string]string),
|
||||
}
|
||||
if len(pairs)%2 != 0 {
|
||||
return q
|
||||
}
|
||||
for i := 0; i < len(pairs); i++ {
|
||||
q.m[pairs[i]] = pairs[i+1]
|
||||
i++
|
||||
}
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Qs) Add(key string, value string) *Qs {
|
||||
q.m[key] = value
|
||||
return q
|
||||
}
|
||||
|
||||
func (q *Qs) ToString() string {
|
||||
builder := strings.Builder{}
|
||||
index := 0
|
||||
for k, v := range q.m {
|
||||
builder.WriteString(k)
|
||||
builder.WriteString("=")
|
||||
builder.WriteString(v)
|
||||
if index < len(q.m)-1 {
|
||||
builder.WriteString("&")
|
||||
}
|
||||
index++
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
type ClassMap map[string]bool
|
||||
|
||||
type PartialFunc = func(ctx *RequestContext) *Partial
|
||||
|
||||
|
|
@ -59,113 +19,7 @@ func (node *Element) AppendChild(child Ren) *Element {
|
|||
return node
|
||||
}
|
||||
|
||||
func Data(data map[string]any) Ren {
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return Empty()
|
||||
}
|
||||
return Attribute("x-data", string(serialized))
|
||||
}
|
||||
|
||||
func ClassIf(condition bool, value string) Ren {
|
||||
if condition {
|
||||
return Class(value)
|
||||
}
|
||||
return Empty()
|
||||
}
|
||||
|
||||
func Class(value ...string) Ren {
|
||||
return Attribute("class", MergeClasses(value...))
|
||||
}
|
||||
|
||||
func ClassX(value string, m ClassMap) Ren {
|
||||
builder := strings.Builder{}
|
||||
builder.WriteString(value)
|
||||
builder.WriteString(" ")
|
||||
for k, v := range m {
|
||||
if v {
|
||||
builder.WriteString(k)
|
||||
builder.WriteString(" ")
|
||||
}
|
||||
}
|
||||
return Class(builder.String())
|
||||
}
|
||||
|
||||
func MergeClasses(classes ...string) string {
|
||||
if len(classes) == 1 {
|
||||
return classes[0]
|
||||
}
|
||||
builder := strings.Builder{}
|
||||
for _, s := range classes {
|
||||
builder.WriteString(s)
|
||||
builder.WriteString(" ")
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
|
||||
func Id(value string) Ren {
|
||||
if strings.HasPrefix(value, "#") {
|
||||
value = value[1:]
|
||||
}
|
||||
return Attribute("id", value)
|
||||
}
|
||||
|
||||
type ClassMap map[string]bool
|
||||
|
||||
func Attributes(attrs *AttributeMap) *AttributeMap {
|
||||
return attrs
|
||||
}
|
||||
|
||||
func AttributePairs(pairs ...string) *AttributeMap {
|
||||
if len(pairs)%2 != 0 {
|
||||
return &AttributeMap{}
|
||||
}
|
||||
m := make(AttributeMap)
|
||||
for i := 0; i < len(pairs); i++ {
|
||||
m[pairs[i]] = pairs[i+1]
|
||||
i++
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
func Checked() Ren {
|
||||
return Attribute("checked", "true")
|
||||
}
|
||||
|
||||
func Boost() Ren {
|
||||
return Attribute(hx.BoostAttr, "true")
|
||||
}
|
||||
|
||||
func Attribute(key string, value string) *AttributeMap {
|
||||
return Attributes(&AttributeMap{key: value})
|
||||
}
|
||||
|
||||
func TriggerChildren() Ren {
|
||||
return HxExtension("trigger-children")
|
||||
}
|
||||
|
||||
func HxExtension(value string) Ren {
|
||||
return Attribute(hx.ExtAttr, value)
|
||||
}
|
||||
|
||||
func Disabled() Ren {
|
||||
return Attribute("disabled", "")
|
||||
}
|
||||
|
||||
func TriggerString(triggers ...string) *AttributeMap {
|
||||
trigger := hx.NewStringTrigger(strings.Join(triggers, ", "))
|
||||
return Attribute(hx.TriggerAttr, trigger.ToString())
|
||||
}
|
||||
|
||||
func Trigger(opts ...hx.TriggerEvent) *AttributeMap {
|
||||
return Attribute(hx.TriggerAttr, hx.NewTrigger(opts...).ToString())
|
||||
}
|
||||
|
||||
func TriggerClick(opts ...hx.Modifier) *AttributeMap {
|
||||
return Trigger(hx.OnClick(opts...))
|
||||
}
|
||||
|
||||
func TextF(format string, args ...interface{}) Ren {
|
||||
func TextF(format string, args ...interface{}) *TextContent {
|
||||
return Text(fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
|
|
@ -177,39 +31,6 @@ func Pf(format string, args ...interface{}) Ren {
|
|||
return P(Text(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
func Target(target string) Ren {
|
||||
return Attribute(hx.TargetAttr, target)
|
||||
}
|
||||
|
||||
func Name(name string) Ren {
|
||||
return Attribute("name", name)
|
||||
}
|
||||
|
||||
func Confirm(message string) Ren {
|
||||
return Attribute(hx.ConfirmAttr, message)
|
||||
}
|
||||
|
||||
func Href(path string) Ren {
|
||||
return Attribute("href", path)
|
||||
}
|
||||
|
||||
func Type(name string) Ren {
|
||||
return Attribute("type", name)
|
||||
}
|
||||
|
||||
func Placeholder(placeholder string) Ren {
|
||||
return Attribute("placeholder", placeholder)
|
||||
}
|
||||
|
||||
func OutOfBandSwap(selector string) Ren {
|
||||
return Attribute(hx.SwapOobAttr,
|
||||
Ternary(selector == "", "true", selector))
|
||||
}
|
||||
|
||||
func Click(value string) Ren {
|
||||
return Attribute("onclick", value)
|
||||
}
|
||||
|
||||
func Tag(tag string, children ...Ren) *Element {
|
||||
return &Element{
|
||||
tag: tag,
|
||||
|
|
@ -292,52 +113,6 @@ func Article(children ...Ren) *Element {
|
|||
return Tag("article", children...)
|
||||
}
|
||||
|
||||
func ReplaceUrlHeader(url string) *Headers {
|
||||
return NewHeaders(hx.ReplaceUrlHeader, url)
|
||||
}
|
||||
|
||||
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 *RequestContext) string {
|
||||
current := ctx.Request().Header.Get(hx.CurrentUrlHeader)
|
||||
parsed, err := url.Parse(current)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return parsed.Path
|
||||
}
|
||||
|
||||
func PushQsHeader(ctx *RequestContext, key string, value string) *Headers {
|
||||
current := ctx.Request().Header.Get(hx.CurrentUrlHeader)
|
||||
parsed, err := url.Parse(current)
|
||||
if err != nil {
|
||||
return NewHeaders()
|
||||
}
|
||||
return NewHeaders(hx.ReplaceUrlHeader, SetQueryParams(parsed.Path, map[string]string{
|
||||
key: value,
|
||||
}))
|
||||
}
|
||||
|
||||
func NewHeaders(headers ...string) *Headers {
|
||||
if len(headers)%2 != 0 {
|
||||
return &Headers{}
|
||||
}
|
||||
m := make(Headers)
|
||||
for i := 0; i < len(headers); i++ {
|
||||
m[headers[i]] = headers[i+1]
|
||||
i++
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
||||
func Checkbox(children ...Ren) Ren {
|
||||
return Input("checkbox", children...)
|
||||
}
|
||||
|
|
@ -368,14 +143,8 @@ func Fragment(children ...Ren) *ChildList {
|
|||
return Children(children...)
|
||||
}
|
||||
|
||||
func AttributeList(children ...*AttributeMap) *AttributeMap {
|
||||
m := make(AttributeMap)
|
||||
for _, child := range children {
|
||||
for k, v := range *child {
|
||||
m[k] = v
|
||||
}
|
||||
}
|
||||
return &m
|
||||
func Template(children ...Ren) *Element {
|
||||
return Tag("template", children...)
|
||||
}
|
||||
|
||||
func AppendChildren(node *Element, children ...Ren) Ren {
|
||||
|
|
@ -388,10 +157,6 @@ func Button(children ...Ren) *Element {
|
|||
return Tag("button", children...)
|
||||
}
|
||||
|
||||
func Indicator(tag string) *AttributeMap {
|
||||
return Attribute(hx.IndicatorAttr, tag)
|
||||
}
|
||||
|
||||
func P(children ...Ren) *Element {
|
||||
return Tag("p", children...)
|
||||
}
|
||||
|
|
@ -446,14 +211,6 @@ func Empty() *Element {
|
|||
}
|
||||
}
|
||||
|
||||
func IfQueryParam(key string, node *Element) Ren {
|
||||
return Fragment(Attribute("hx-if-qp:"+key, "true"), node)
|
||||
}
|
||||
|
||||
func Hidden() Ren {
|
||||
return Attribute("style", "display:none")
|
||||
}
|
||||
|
||||
func Children(children ...Ren) *ChildList {
|
||||
return NewChildList(children...)
|
||||
}
|
||||
|
|
@ -461,42 +218,3 @@ func Children(children ...Ren) *ChildList {
|
|||
func Label(text string) *Element {
|
||||
return Tag("label", Text(text))
|
||||
}
|
||||
|
||||
func GetTriggerName(ctx *RequestContext) string {
|
||||
return ctx.Request().Header.Get("HX-Trigger-Name")
|
||||
}
|
||||
|
||||
type SwapArg struct {
|
||||
Selector string
|
||||
Content *Element
|
||||
}
|
||||
|
||||
func NewSwap(selector string, content *Element) SwapArg {
|
||||
return SwapArg{
|
||||
Selector: selector,
|
||||
Content: content,
|
||||
}
|
||||
}
|
||||
|
||||
func OobSwap(ctx *RequestContext, content *Element) *Element {
|
||||
return OobSwapWithSelector(ctx, "", content)
|
||||
}
|
||||
|
||||
func OobSwapWithSelector(ctx *RequestContext, selector string, content *Element) *Element {
|
||||
if ctx == nil || ctx.Get("HX-Request") == "" {
|
||||
return Empty()
|
||||
}
|
||||
return content.AppendChild(OutOfBandSwap(selector))
|
||||
}
|
||||
|
||||
func SwapMany(ctx *RequestContext, args ...SwapArg) Ren {
|
||||
if ctx.Get("HX-Request") == "" {
|
||||
return Empty()
|
||||
}
|
||||
for _, arg := range args {
|
||||
arg.Content.AppendChild(OutOfBandSwap(arg.Selector))
|
||||
}
|
||||
return Fragment(Map(args, func(arg SwapArg) Ren {
|
||||
return arg.Content
|
||||
})...)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,22 +2,8 @@ package h
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/labstack/echo/v4"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Ren interface {
|
||||
Render(builder *strings.Builder)
|
||||
}
|
||||
|
||||
func Ternary[T any](value bool, a T, b T) T {
|
||||
if value {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func JsonSerialize(data any) string {
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
|
|
@ -25,34 +11,3 @@ func JsonSerialize(data any) string {
|
|||
}
|
||||
return string(serialized)
|
||||
}
|
||||
|
||||
func GetQueryParam(ctx echo.Context, key string) string {
|
||||
value := ctx.QueryParam(key)
|
||||
if value == "" {
|
||||
current := ctx.Request().Header.Get("Hx-Current-Url")
|
||||
if current != "" {
|
||||
u, err := url.Parse(current)
|
||||
if err == nil {
|
||||
return u.Query().Get(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func SetQueryParams(href string, qs map[string]string) string {
|
||||
u, err := url.Parse(href)
|
||||
if err != nil {
|
||||
return href
|
||||
}
|
||||
q := u.Query()
|
||||
for key, value := range qs {
|
||||
if value == "" {
|
||||
q.Del(key)
|
||||
} else {
|
||||
q.Set(key, value)
|
||||
}
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
return u.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ func GetPartialWithQs(partial PartialFunc, qs *Qs, trigger string) *AttributeMap
|
|||
return Get(GetPartialPathWithQs(partial, qs), trigger)
|
||||
}
|
||||
|
||||
func GetWithQs(path string, qs map[string]string, trigger string) *AttributeMap {
|
||||
func GetWithQs(path string, qs *Qs, trigger string) *AttributeMap {
|
||||
return Get(SetQueryParams(path, qs), trigger)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package hx
|
|||
type Attribute = string
|
||||
type Header = string
|
||||
type Event = string
|
||||
type SwapType = string
|
||||
|
||||
// https://htmx.org/reference/#events
|
||||
const (
|
||||
|
|
@ -42,6 +43,12 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
BoostedHeader Header = "HX-Boosted"
|
||||
PromptResponseHeader Header = "HX-Prompt"
|
||||
RequestHeader Header = "HX-Request"
|
||||
TargetIdHeader Header = "HX-Target"
|
||||
TriggerNameHeader Header = "HX-Trigger-Name"
|
||||
TriggerIdHeader Header = "HX-Trigger"
|
||||
LocationHeader Header = "HX-Location"
|
||||
PushUrlHeader Header = "HX-Push-Url"
|
||||
RedirectHeader Header = "HX-Redirect"
|
||||
|
|
@ -136,3 +143,16 @@ const (
|
|||
DropEvent Event = "ondrop"
|
||||
DragEndEvent Event = "ondragend"
|
||||
)
|
||||
|
||||
const (
|
||||
SwapTypeTrue SwapType = "true"
|
||||
SwapTypeInnerHtml SwapType = "innerHTML"
|
||||
SwapTypeOuterHtml SwapType = "outerHTML"
|
||||
SwapTypeTextContent SwapType = "textContent"
|
||||
SwapTypeBeforeBegin SwapType = "beforebegin"
|
||||
SwapTypeAfterBegin SwapType = "afterbegin"
|
||||
SwapTypeBeforeEnd SwapType = "beforeend"
|
||||
SwapTypeAfterEnd SwapType = "afterend"
|
||||
SwapTypeDelete SwapType = "delete"
|
||||
SwapTypeNone SwapType = "none"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ type NavItem struct {
|
|||
}
|
||||
|
||||
func ToggleNavbar(ctx *h.RequestContext) *h.Partial {
|
||||
return h.NewPartial(
|
||||
h.OobSwap(ctx, MobileNav(h.GetQueryParam(ctx, "expanded") == "true")),
|
||||
return h.SwapManyPartial(
|
||||
ctx,
|
||||
MobileNav(h.GetQueryParam(ctx, "expanded") == "true"),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ func Create(ctx echo.Context) *h.Partial {
|
|||
}
|
||||
|
||||
headers := h.CombineHeaders(h.PushQsHeader(ctx, "adding", ""), &map[string]string{
|
||||
"HX-Trigger": "patient-added",
|
||||
"HX-HxTrigger": "patient-added",
|
||||
})
|
||||
|
||||
return h.NewPartialWithHeaders(
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func Input(list []*ent.Task) *h.Element {
|
|||
h.Class("pl-12 text-xl p-4 w-full outline-none focus:outline-2 focus:outline-rose-400"),
|
||||
h.Placeholder("What needs to be done?"),
|
||||
h.Post(h.GetPartialPath(Create)),
|
||||
h.Trigger("keyup[keyCode==13]"),
|
||||
h.HxTrigger("keyup[keyCode==13]"),
|
||||
),
|
||||
CompleteAllIcon(list),
|
||||
)
|
||||
|
|
@ -162,7 +162,7 @@ func Task(task *ent.Task, editing bool) *h.Element {
|
|||
),
|
||||
),
|
||||
h.P(
|
||||
h.Trigger("dblclick"),
|
||||
h.HxTrigger("dblclick"),
|
||||
h.GetPartialWithQs(EditNameForm, "id="+task.ID.String()),
|
||||
h.ClassX("text-xl break-all text-wrap truncate", map[string]bool{
|
||||
"line-through text-slate-400": task.CompletedAt != nil,
|
||||
|
|
@ -174,7 +174,7 @@ func Task(task *ent.Task, editing bool) *h.Element {
|
|||
|
||||
func CompleteIcon(task *ent.Task) *h.Element {
|
||||
return h.Div(
|
||||
h.Trigger("click"),
|
||||
h.HxTrigger("click"),
|
||||
h.Post(h.GetPartialPathWithQs(ToggleCompleted, "id="+task.ID.String())),
|
||||
h.Class("flex items-center justify-center cursor-pointer"),
|
||||
h.Div(
|
||||
|
|
|
|||
Loading…
Reference in a new issue