diff --git a/framework/h/base.go b/framework/h/base.go index 7e30742..85bf047 100644 --- a/framework/h/base.go +++ b/framework/h/base.go @@ -8,8 +8,6 @@ import ( "strings" ) -type Headers = map[string]string - type Partial struct { Headers *Headers Root *Element @@ -51,6 +49,13 @@ func NewPartial(root *Element) *Partial { } } +func SwapManyPartialWithHeaders(ctx *RequestContext, headers *Headers, swaps ...*Element) *Partial { + return NewPartialWithHeaders( + headers, + SwapMany(ctx, swaps...), + ) +} + func SwapManyPartial(ctx *RequestContext, swaps ...*Element) *Partial { return NewPartial( SwapMany(ctx, swaps...), diff --git a/framework/h/header.go b/framework/h/header.go index b66bbe7..4c9c42b 100644 --- a/framework/h/header.go +++ b/framework/h/header.go @@ -5,10 +5,16 @@ import ( "net/url" ) +type Headers = map[string]string + func ReplaceUrlHeader(url string) *Headers { return NewHeaders(hx.ReplaceUrlHeader, url) } +func PushUrlHeader(url string) *Headers { + return NewHeaders(hx.PushUrlHeader, url) +} + func CombineHeaders(headers ...*Headers) *Headers { m := make(Headers) for _, h := range headers { diff --git a/framework/h/renderer.go b/framework/h/renderer.go index 374342f..55fce0e 100644 --- a/framework/h/renderer.go +++ b/framework/h/renderer.go @@ -90,28 +90,10 @@ func (m *AttributeMap) Render(builder *strings.Builder) { } } -func toHtmxTriggerName(event string) string { - if strings.HasPrefix(event, "htmx:") { - return event[5:] - } - if strings.HasPrefix(event, "on") { - return event[2:] - } - return event -} - -func formatEventName(event string, isDomEvent bool) string { - raw := toHtmxTriggerName(event) - if isDomEvent { - return "on" + raw - } - return event -} - func (l *LifeCycle) fromAttributeMap(event string, key string, value string, builder *strings.Builder) { if key == hx.GetAttr || key == hx.PatchAttr || key == hx.PostAttr { - TriggerString(toHtmxTriggerName(event)).Render(builder) + TriggerString(hx.ToHtmxTriggerName(event)).Render(builder) } Attribute(key, value).Render(builder) @@ -125,7 +107,7 @@ func (l *LifeCycle) Render(builder *strings.Builder) { for _, command := range commands { switch c := command.(type) { case JsCommand: - eventName := formatEventName(event, true) + eventName := hx.FormatEventName(event, true) m[eventName] += fmt.Sprintf("%s;", c.Command) case *AttributeMap: for k, v := range c.ToMap() { diff --git a/framework/hx/trigger.go b/framework/hx/trigger.go index 5dedefe..e275c7e 100644 --- a/framework/hx/trigger.go +++ b/framework/hx/trigger.go @@ -13,11 +13,32 @@ type TriggerEvent struct { modifiers []Modifier } +func ToHtmxTriggerName(event string) string { + if strings.HasPrefix(event, "htmx:") { + return event[5:] + } + if strings.HasPrefix(event, "on") { + return event[2:] + } + return event +} + +func FormatEventName(event string, isDomEvent bool) string { + raw := ToHtmxTriggerName(event) + if isDomEvent { + return "on" + raw + } + return event +} + func NewTrigger(opts ...TriggerEvent) *Trigger { t := Trigger{ events: make([]TriggerEvent, 0), } if len(opts) > 0 { + for i, opt := range opts { + opts[i].event = ToHtmxTriggerName(opt.event) + } t.events = opts } return &t @@ -31,11 +52,7 @@ func NewStringTrigger(trigger string) Trigger { split := strings.Split(trigger, ", ") for _, s := range split { parts := strings.Split(s, " ") - event := parts[0] - - if strings.HasPrefix(event, "htmx:") { - event = event[5:] - } + event := ToHtmxTriggerName(parts[0]) modifiers := make([]Modifier, 0) if len(parts) > 1 { diff --git a/todo-list/main.go b/todo-list/main.go index 040d8c9..8e40b4a 100644 --- a/todo-list/main.go +++ b/todo-list/main.go @@ -11,15 +11,6 @@ import ( "todolist/partials/load" ) -type CustomContext struct { - echo.Context - locator *service.Locator -} - -func (c *CustomContext) ServiceLocator() *service.Locator { - return c.locator -} - func main() { locator := service.NewLocator() diff --git a/todo-list/partials/task/task.go b/todo-list/partials/task/task.go index 5692d7e..c0478a5 100644 --- a/todo-list/partials/task/task.go +++ b/todo-list/partials/task/task.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/google/uuid" "github.com/maddalax/htmgo/framework/h" + "github.com/maddalax/htmgo/framework/hx" "todolist/ent" "todolist/internal/tasks" ) @@ -55,7 +56,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.HxTrigger("keyup[keyCode==13]"), + h.HxTrigger(hx.OnEvent(hx.TriggerKeyUpEnter)), ), CompleteAllIcon(list), ) @@ -70,7 +71,7 @@ func CompleteAllIcon(list []*ent.Task) *h.Element { h.ClassX("absolute top-0 left-0 p-4 rotate-90 text-2xl cursor-pointer", map[string]bool{ "text-slate-400": notCompletedCount > 0, }), h.Text("❯"), - h.PostPartialOnClickQs(CompleteAll, h.Ternary(notCompletedCount > 0, "complete=true", "complete=false")), + h.PostPartialWithQs(CompleteAll, h.NewQs("complete", h.Ternary(notCompletedCount > 0, "true", "false"))), ) } @@ -92,7 +93,7 @@ func Footer(list []*ent.Task, activeTab Tab) *h.Element { h.Class("flex items-center gap-4"), h.List(tabs, func(tab Tab, index int) *h.Element { return h.P( - h.PostOnClick(h.GetPartialPathWithQs(ChangeTab, "tab="+tab)), + h.PostOnClick(h.GetPartialPathWithQs(ChangeTab, h.NewQs("tab", tab))), h.ClassX("cursor-pointer px-2 py-1 rounded", map[string]bool{ "border border-rose-600": activeTab == tab, }), @@ -147,7 +148,7 @@ func Task(task *ent.Task, editing bool) *h.Element { ), h.Input( "text", - h.PostPartialOnTrigger(UpdateName, h.TriggerBlur, h.TriggerKeyUpEnter), + h.PostPartial(UpdateName, hx.TriggerBlur, hx.TriggerKeyUpEnter), h.Attributes(&h.AttributeMap{ "placeholder": "What needs to be done?", "autofocus": "true", @@ -162,8 +163,7 @@ func Task(task *ent.Task, editing bool) *h.Element { ), ), h.P( - h.HxTrigger("dblclick"), - h.GetPartialWithQs(EditNameForm, "id="+task.ID.String()), + h.GetPartialWithQs(EditNameForm, h.NewQs("id", task.ID.String()), hx.TriggerDblClick), h.ClassX("text-xl break-all text-wrap truncate", map[string]bool{ "line-through text-slate-400": task.CompletedAt != nil, }), @@ -174,8 +174,8 @@ func Task(task *ent.Task, editing bool) *h.Element { func CompleteIcon(task *ent.Task) *h.Element { return h.Div( - h.HxTrigger("click"), - h.Post(h.GetPartialPathWithQs(ToggleCompleted, "id="+task.ID.String())), + h.HxTrigger(hx.OnClick()), + h.Post(h.GetPartialPathWithQs(ToggleCompleted, h.NewQs("id", task.ID.String()))), h.Class("flex items-center justify-center cursor-pointer"), h.Div( h.ClassX("w-10 h-10 border rounded-full flex items-center justify-center", map[string]bool{ @@ -259,11 +259,11 @@ func ToggleCompleted(ctx *h.RequestContext) *h.Partial { list, _ := service.List() - return h.NewPartial(h.Fragment( - h.OobSwap(ctx, List(list, getActiveTab(ctx))), - h.OobSwap(ctx, Footer(list, getActiveTab(ctx))), - h.OobSwap(ctx, CompleteAllIcon(list)), - )) + return h.SwapManyPartial(ctx, + List(list, getActiveTab(ctx)), + Footer(list, getActiveTab(ctx)), + CompleteAllIcon(list), + ) } func CompleteAll(ctx *h.RequestContext) *h.Partial { @@ -302,7 +302,9 @@ func Create(ctx *h.RequestContext) *h.Partial { list, _ := service.List() - return h.NewPartial(h.Fragment(h.OobSwap(ctx, CardBody(list, getActiveTab(ctx))))) + return h.SwapManyPartial(ctx, + CardBody(list, getActiveTab(ctx)), + ) } func ChangeTab(ctx *h.RequestContext) *h.Partial { @@ -311,10 +313,9 @@ func ChangeTab(ctx *h.RequestContext) *h.Partial { tab := ctx.QueryParam("tab") - return h.NewPartialWithHeaders(&h.Headers{"hx-push-url": fmt.Sprintf("/tasks?tab=%s", tab)}, - h.Fragment( - h.OobSwap(ctx, List(list, tab)), - h.OobSwap(ctx, Footer(list, tab)), - ), + return h.SwapManyPartialWithHeaders(ctx, + h.PushUrlHeader(fmt.Sprintf("/tasks?tab=%s", tab)), + List(list, tab), + Footer(list, tab), ) }