cleaning up the api a bit
This commit is contained in:
parent
e34a4fe269
commit
c555da4dc9
22 changed files with 641 additions and 218 deletions
|
|
@ -22,6 +22,8 @@ func Build() {
|
||||||
// },
|
// },
|
||||||
//)
|
//)
|
||||||
|
|
||||||
process.RunOrExit("env GOOS=linux GOARCH=amd64 go build -o ./dist .")
|
process.RunOrExit("env GOOS=linux GOARCH=amd64 go build -o ./dist/app-linux-amd64 .")
|
||||||
|
process.RunOrExit("go build -o ./dist/app .")
|
||||||
|
|
||||||
process.RunOrExit("echo \"Build successful\"")
|
process.RunOrExit("echo \"Build successful\"")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package ui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/maddalax/htmgo/framework/h"
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
|
"github.com/maddalax/htmgo/framework/hx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type InputProps struct {
|
type InputProps struct {
|
||||||
|
|
@ -16,8 +17,7 @@ type InputProps struct {
|
||||||
|
|
||||||
func Input(props InputProps) h.Ren {
|
func Input(props InputProps) h.Ren {
|
||||||
validation := h.If(props.ValidationPath != "", h.Children(
|
validation := h.If(props.ValidationPath != "", h.Children(
|
||||||
h.Post(props.ValidationPath),
|
h.Post(props.ValidationPath, hx.ChangeEvent),
|
||||||
h.Trigger("change"),
|
|
||||||
h.Attribute("hx-swap", "innerHTML transition:true"),
|
h.Attribute("hx-swap", "innerHTML transition:true"),
|
||||||
h.Attribute("hx-target", "next div"),
|
h.Attribute("hx-target", "next div"),
|
||||||
))
|
))
|
||||||
|
|
|
||||||
2
framework/assets/dist/htmgo.js
vendored
2
framework/assets/dist/htmgo.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -6,9 +6,24 @@ let lastVersion = "";
|
||||||
htmx.defineExtension("livereload", {
|
htmx.defineExtension("livereload", {
|
||||||
init: function () {
|
init: function () {
|
||||||
const host = window.location.host;
|
const host = window.location.host;
|
||||||
|
|
||||||
|
let enabled = false
|
||||||
|
for (const element of Array.from(htmx.findAll("[hx-ext]"))) {
|
||||||
|
const value = element.getAttribute("hx-ext");
|
||||||
|
if(value?.split(" ").includes("livereload")) {
|
||||||
|
enabled = true
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!enabled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
console.log('livereload extension initialized.');
|
console.log('livereload extension initialized.');
|
||||||
|
|
||||||
createWebSocketClient({
|
createWebSocketClient({
|
||||||
url: `ws://${host}/dev/livereload`,
|
url: `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${host}/dev/livereload`,
|
||||||
onOpen: () => {
|
onOpen: () => {
|
||||||
},
|
},
|
||||||
onMessage: (message) => {
|
onMessage: (message) => {
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,6 @@ func GetPartialPath(partial func(ctx *RequestContext) *Partial) string {
|
||||||
return runtime.FuncForPC(reflect.ValueOf(partial).Pointer()).Name()
|
return runtime.FuncForPC(reflect.ValueOf(partial).Pointer()).Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartialPathWithQs(partial func(ctx *RequestContext) *Partial, qs string) string {
|
func GetPartialPathWithQs(partial func(ctx *RequestContext) *Partial, qs *Qs) string {
|
||||||
return html.EscapeString(GetPartialPath(partial) + "?" + qs)
|
return html.EscapeString(GetPartialPath(partial) + "?" + qs.ToString())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
framework/h/conditionals.go
Normal file
32
framework/h/conditionals.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package h
|
||||||
|
|
||||||
|
func If(condition bool, node Ren) Ren {
|
||||||
|
if condition {
|
||||||
|
return node
|
||||||
|
} else {
|
||||||
|
return Empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IfElse(condition bool, node Ren, node2 Ren) Ren {
|
||||||
|
if condition {
|
||||||
|
return node
|
||||||
|
} else {
|
||||||
|
return node2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IfElseLazy(condition bool, cb1 func() Ren, cb2 func() Ren) Ren {
|
||||||
|
if condition {
|
||||||
|
return cb1()
|
||||||
|
} else {
|
||||||
|
return cb2()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IfHtmxRequest(ctx *RequestContext, node Ren) Ren {
|
||||||
|
if ctx.Get("HX-Request") != "" {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
return Empty()
|
||||||
|
}
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
package h
|
|
||||||
|
|
||||||
type HxEvent = string
|
|
||||||
type HxTriggerName = string
|
|
||||||
|
|
||||||
var (
|
|
||||||
HxBeforeRequest HxEvent = "hx-on::before-request"
|
|
||||||
HxAfterRequest HxEvent = "hx-on::after-request"
|
|
||||||
HxOnMutationError HxEvent = "hx-on::mutation-error"
|
|
||||||
HxOnLoad HxEvent = "hx-on::load"
|
|
||||||
HxOnLoadError HxEvent = "hx-on::load-error"
|
|
||||||
HxRequestTimeout HxEvent = "hx-on::request-timeout"
|
|
||||||
HxTrigger HxEvent = "hx-on::trigger"
|
|
||||||
HxRequestStart HxEvent = "hx-on::xhr:loadstart"
|
|
||||||
HxRequestProgress HxEvent = "hx-on::xhr:progress"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
TriggerLoad HxTriggerName = "load"
|
|
||||||
TriggerClick HxTriggerName = "click"
|
|
||||||
TriggerDblClick HxTriggerName = "dblclick"
|
|
||||||
TriggerKeyUpEnter HxTriggerName = "keyup[keyCode==13]"
|
|
||||||
TriggerBlur HxTriggerName = "blur"
|
|
||||||
)
|
|
||||||
|
|
@ -2,61 +2,104 @@ package h
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/maddalax/htmgo/framework/hx"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LifeCycle struct {
|
type LifeCycle struct {
|
||||||
handlers map[HxEvent][]JsCommand
|
handlers map[hx.Event][]Command
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLifeCycle() *LifeCycle {
|
func NewLifeCycle() *LifeCycle {
|
||||||
return &LifeCycle{
|
return &LifeCycle{
|
||||||
handlers: make(map[HxEvent][]JsCommand),
|
handlers: make(map[hx.Event][]Command),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) OnEvent(event HxEvent, cmd ...JsCommand) *LifeCycle {
|
func validateCommands(cmds []Command) {
|
||||||
if l.handlers[event] == nil {
|
for _, cmd := range cmds {
|
||||||
l.handlers[event] = []JsCommand{}
|
switch t := cmd.(type) {
|
||||||
|
case JsCommand:
|
||||||
|
break
|
||||||
|
case *AttributeMap:
|
||||||
|
break
|
||||||
|
case *Element:
|
||||||
|
panic(fmt.Sprintf("element is not allowed in lifecycle events. Got: %v", t))
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("type is not allowed in lifecycle events. Got: %v", t))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LifeCycle) OnEvent(event hx.Event, cmd ...Command) *LifeCycle {
|
||||||
|
validateCommands(cmd)
|
||||||
|
|
||||||
|
if l.handlers[event] == nil {
|
||||||
|
l.handlers[event] = []Command{}
|
||||||
|
}
|
||||||
|
|
||||||
l.handlers[event] = append(l.handlers[event], cmd...)
|
l.handlers[event] = append(l.handlers[event], cmd...)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) BeforeRequest(cmd ...JsCommand) *LifeCycle {
|
func (l *LifeCycle) BeforeRequest(cmd ...Command) *LifeCycle {
|
||||||
l.OnEvent(HxBeforeRequest, cmd...)
|
l.OnEvent(hx.BeforeRequestEvent, cmd...)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnEvent(event HxEvent, cmd ...JsCommand) *LifeCycle {
|
func OnLoad(cmd ...Command) *LifeCycle {
|
||||||
|
return NewLifeCycle().OnEvent(hx.LoadEvent, cmd...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnAfterSwap(cmd ...Command) *LifeCycle {
|
||||||
|
return NewLifeCycle().OnEvent(hx.AfterSwapEvent, cmd...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnTrigger(trigger string, cmd ...Command) *LifeCycle {
|
||||||
|
return NewLifeCycle().OnEvent(hx.NewStringTrigger(trigger).ToString(), cmd...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnClick(cmd ...Command) *LifeCycle {
|
||||||
|
return NewLifeCycle().OnEvent(hx.ClickEvent, cmd...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnEvent(event hx.Event, cmd ...Command) *LifeCycle {
|
||||||
return NewLifeCycle().OnEvent(event, cmd...)
|
return NewLifeCycle().OnEvent(event, cmd...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeforeRequest(cmd ...JsCommand) *LifeCycle {
|
func BeforeRequest(cmd ...Command) *LifeCycle {
|
||||||
return NewLifeCycle().BeforeRequest(cmd...)
|
return NewLifeCycle().BeforeRequest(cmd...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func AfterRequest(cmd ...JsCommand) *LifeCycle {
|
func AfterRequest(cmd ...Command) *LifeCycle {
|
||||||
return NewLifeCycle().AfterRequest(cmd...)
|
return NewLifeCycle().AfterRequest(cmd...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func OnMutationError(cmd ...JsCommand) *LifeCycle {
|
func OnMutationError(cmd ...Command) *LifeCycle {
|
||||||
return NewLifeCycle().OnMutationError(cmd...)
|
return NewLifeCycle().OnMutationError(cmd...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) AfterRequest(cmd ...JsCommand) *LifeCycle {
|
func (l *LifeCycle) AfterRequest(cmd ...Command) *LifeCycle {
|
||||||
l.OnEvent(HxAfterRequest, cmd...)
|
l.OnEvent(hx.AfterRequestEvent, cmd...)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) OnMutationError(cmd ...JsCommand) *LifeCycle {
|
func (l *LifeCycle) OnMutationError(cmd ...Command) *LifeCycle {
|
||||||
l.OnEvent(HxOnMutationError, cmd...)
|
l.OnEvent(hx.OnMutationErrorEvent, cmd...)
|
||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Command = Ren
|
||||||
|
|
||||||
type JsCommand struct {
|
type JsCommand struct {
|
||||||
Command string
|
Command string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j JsCommand) Render(builder *strings.Builder) {
|
||||||
|
builder.WriteString(j.Command)
|
||||||
|
}
|
||||||
|
|
||||||
func SetText(text string) JsCommand {
|
func SetText(text string) JsCommand {
|
||||||
// language=JavaScript
|
// language=JavaScript
|
||||||
return JsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)}
|
return JsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package h
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/maddalax/htmgo/framework/hx"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -89,20 +90,61 @@ 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
Attribute(key, value).Render(builder)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *LifeCycle) Render(builder *strings.Builder) {
|
func (l *LifeCycle) Render(builder *strings.Builder) {
|
||||||
m := make(map[string]string)
|
m := make(map[string]string)
|
||||||
|
|
||||||
for event, commands := range l.handlers {
|
for event, commands := range l.handlers {
|
||||||
m[event] = ""
|
m[event] = ""
|
||||||
for _, command := range commands {
|
for _, command := range commands {
|
||||||
m[event] += fmt.Sprintf("%s;", command.Command)
|
switch c := command.(type) {
|
||||||
|
case JsCommand:
|
||||||
|
eventName := formatEventName(event, true)
|
||||||
|
m[eventName] += fmt.Sprintf("%s;", c.Command)
|
||||||
|
case *AttributeMap:
|
||||||
|
for k, v := range c.ToMap() {
|
||||||
|
l.fromAttributeMap(event, k, v, builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
children := make([]Ren, 0)
|
children := make([]Ren, 0)
|
||||||
|
|
||||||
for event, js := range m {
|
for event, value := range m {
|
||||||
children = append(children, Attribute(event, js))
|
if value != "" {
|
||||||
|
children = append(children, Attribute(event, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(children) == 0 {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Children(children...).Render(builder)
|
Children(children...).Render(builder)
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,51 @@ package h
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"github.com/maddalax/htmgo/framework/hx"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"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 PartialFunc = func(ctx *RequestContext) *Partial
|
||||||
|
|
||||||
type Element struct {
|
type Element struct {
|
||||||
tag string
|
tag string
|
||||||
attributes map[string]string
|
attributes map[string]string
|
||||||
|
|
@ -76,12 +116,24 @@ func Attributes(attrs *AttributeMap) *AttributeMap {
|
||||||
return attrs
|
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 {
|
func Checked() Ren {
|
||||||
return Attribute("checked", "true")
|
return Attribute("checked", "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Boost() Ren {
|
func Boost() Ren {
|
||||||
return Attribute("hx-boost", "true")
|
return Attribute(hx.BoostAttr, "true")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Attribute(key string, value string) *AttributeMap {
|
func Attribute(key string, value string) *AttributeMap {
|
||||||
|
|
@ -93,90 +145,24 @@ func TriggerChildren() Ren {
|
||||||
}
|
}
|
||||||
|
|
||||||
func HxExtension(value string) Ren {
|
func HxExtension(value string) Ren {
|
||||||
return Attribute("hx-ext", value)
|
return Attribute(hx.ExtAttr, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Disabled() Ren {
|
func Disabled() Ren {
|
||||||
return Attribute("disabled", "")
|
return Attribute("disabled", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get(path string) Ren {
|
func TriggerString(triggers ...string) *AttributeMap {
|
||||||
return Attribute("hx-get", path)
|
trigger := hx.NewStringTrigger(strings.Join(triggers, ", "))
|
||||||
|
return Attribute(hx.TriggerAttr, trigger.ToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartial(partial func(ctx *RequestContext) *Partial) Ren {
|
func Trigger(opts ...hx.TriggerEvent) *AttributeMap {
|
||||||
return Get(GetPartialPath(partial))
|
return Attribute(hx.TriggerAttr, hx.NewTrigger(opts...).ToString())
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartialWithQs(partial func(ctx *RequestContext) *Partial, qs string) Ren {
|
func TriggerClick(opts ...hx.Modifier) *AttributeMap {
|
||||||
return Get(GetPartialPathWithQs(partial, qs))
|
return Trigger(hx.OnClick(opts...))
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTriggers(triggers ...string) []string {
|
|
||||||
return triggers
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReloadParams struct {
|
|
||||||
Triggers []string
|
|
||||||
Target string
|
|
||||||
Children Ren
|
|
||||||
}
|
|
||||||
|
|
||||||
func ViewOnLoad(partial func(ctx *RequestContext) *Partial) Ren {
|
|
||||||
return View(partial, ReloadParams{
|
|
||||||
Triggers: CreateTriggers("load"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func View(partial func(ctx *RequestContext) *Partial, params ReloadParams) Ren {
|
|
||||||
return Div(Attributes(&AttributeMap{
|
|
||||||
"hx-get": GetPartialPath(partial),
|
|
||||||
"hx-trigger": strings.Join(params.Triggers, ", "),
|
|
||||||
"hx-target": params.Target,
|
|
||||||
}), params.Children)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PartialWithTriggers(partial func(ctx *RequestContext) *Partial, triggers ...string) Ren {
|
|
||||||
return Div(Attributes(&AttributeMap{
|
|
||||||
"hx-get": GetPartialPath(partial),
|
|
||||||
"hx-trigger": strings.Join(triggers, ", "),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetWithQs(path string, qs map[string]string) Ren {
|
|
||||||
return Get(SetQueryParams(path, qs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostPartialOnTrigger(partial func(ctx *RequestContext) *Partial, triggers ...string) Ren {
|
|
||||||
return PostOnTrigger(GetPartialPath(partial), strings.Join(triggers, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostPartialWithQsOnTrigger(partial func(ctx *RequestContext) *Partial, qs string, trigger string) Ren {
|
|
||||||
return PostOnTrigger(GetPartialPathWithQs(partial, qs), trigger)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Post(url string) Ren {
|
|
||||||
return Attribute("hx-post", url)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostOnTrigger(url string, trigger string) Ren {
|
|
||||||
return AttributeList(Attribute("hx-post", url), Trigger(trigger))
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostOnClick(url string) Ren {
|
|
||||||
return PostOnTrigger(url, "click")
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostPartialOnClick(partial func(ctx *RequestContext) *Partial) Ren {
|
|
||||||
return PostOnClick(GetPartialPath(partial))
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostPartialOnClickQs(partial func(ctx *RequestContext) *Partial, qs string) Ren {
|
|
||||||
return PostOnClick(GetPartialPathWithQs(partial, qs))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Trigger(trigger string) *AttributeMap {
|
|
||||||
return Attribute("hx-trigger", trigger)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TextF(format string, args ...interface{}) Ren {
|
func TextF(format string, args ...interface{}) Ren {
|
||||||
|
|
@ -192,7 +178,7 @@ func Pf(format string, args ...interface{}) Ren {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Target(target string) Ren {
|
func Target(target string) Ren {
|
||||||
return Attribute("hx-target", target)
|
return Attribute(hx.TargetAttr, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Name(name string) Ren {
|
func Name(name string) Ren {
|
||||||
|
|
@ -200,7 +186,7 @@ func Name(name string) Ren {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Confirm(message string) Ren {
|
func Confirm(message string) Ren {
|
||||||
return Attribute("hx-confirm", message)
|
return Attribute(hx.ConfirmAttr, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Href(path string) Ren {
|
func Href(path string) Ren {
|
||||||
|
|
@ -216,7 +202,7 @@ func Placeholder(placeholder string) Ren {
|
||||||
}
|
}
|
||||||
|
|
||||||
func OutOfBandSwap(selector string) Ren {
|
func OutOfBandSwap(selector string) Ren {
|
||||||
return Attribute("hx-swap-oob",
|
return Attribute(hx.SwapOobAttr,
|
||||||
Ternary(selector == "", "true", selector))
|
Ternary(selector == "", "true", selector))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -307,7 +293,7 @@ func Article(children ...Ren) *Element {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReplaceUrlHeader(url string) *Headers {
|
func ReplaceUrlHeader(url string) *Headers {
|
||||||
return NewHeaders("HX-Replace-Url", url)
|
return NewHeaders(hx.ReplaceUrlHeader, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CombineHeaders(headers ...*Headers) *Headers {
|
func CombineHeaders(headers ...*Headers) *Headers {
|
||||||
|
|
@ -321,7 +307,7 @@ func CombineHeaders(headers ...*Headers) *Headers {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CurrentPath(ctx *RequestContext) string {
|
func CurrentPath(ctx *RequestContext) string {
|
||||||
current := ctx.Request().Header.Get("Hx-Current-Url")
|
current := ctx.Request().Header.Get(hx.CurrentUrlHeader)
|
||||||
parsed, err := url.Parse(current)
|
parsed, err := url.Parse(current)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -330,12 +316,12 @@ func CurrentPath(ctx *RequestContext) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func PushQsHeader(ctx *RequestContext, key string, value string) *Headers {
|
func PushQsHeader(ctx *RequestContext, key string, value string) *Headers {
|
||||||
current := ctx.Request().Header.Get("Hx-Current-Url")
|
current := ctx.Request().Header.Get(hx.CurrentUrlHeader)
|
||||||
parsed, err := url.Parse(current)
|
parsed, err := url.Parse(current)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewHeaders()
|
return NewHeaders()
|
||||||
}
|
}
|
||||||
return NewHeaders("HX-Replace-Url", SetQueryParams(parsed.Path, map[string]string{
|
return NewHeaders(hx.ReplaceUrlHeader, SetQueryParams(parsed.Path, map[string]string{
|
||||||
key: value,
|
key: value,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
@ -402,8 +388,8 @@ func Button(children ...Ren) *Element {
|
||||||
return Tag("button", children...)
|
return Tag("button", children...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Indicator(tag string) Ren {
|
func Indicator(tag string) *AttributeMap {
|
||||||
return Attribute("hx-indicator", tag)
|
return Attribute(hx.IndicatorAttr, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
func P(children ...Ren) *Element {
|
func P(children ...Ren) *Element {
|
||||||
|
|
@ -460,31 +446,6 @@ func Empty() *Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BeforeRequestSetHtml(children ...Ren) Ren {
|
|
||||||
serialized := Render(Fragment(children...))
|
|
||||||
return Attribute("hx-on::before-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BeforeRequestSetAttribute(key string, value string) Ren {
|
|
||||||
return Attribute("hx-on::before-request", `this.setAttribute('`+key+`', '`+value+`')`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func OnMutationErrorSetText(text string) Ren {
|
|
||||||
return Attribute("hx-on::mutation-error", `this.innerText = '`+text+`'`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BeforeRequestSetText(text string) Ren {
|
|
||||||
return Attribute("hx-on::before-request", `this.innerText = '`+text+`'`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AfterRequestSetText(text string) Ren {
|
|
||||||
return Attribute("hx-on::after-request", `this.innerText = '`+text+`'`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func AfterRequestRemoveAttribute(key string, value string) Ren {
|
|
||||||
return Attribute("hx-on::after-request", `this.removeAttribute('`+key+`')`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func IfQueryParam(key string, node *Element) Ren {
|
func IfQueryParam(key string, node *Element) Ren {
|
||||||
return Fragment(Attribute("hx-if-qp:"+key, "true"), node)
|
return Fragment(Attribute("hx-if-qp:"+key, "true"), node)
|
||||||
}
|
}
|
||||||
|
|
@ -493,11 +454,6 @@ func Hidden() Ren {
|
||||||
return Attribute("style", "display:none")
|
return Attribute("style", "display:none")
|
||||||
}
|
}
|
||||||
|
|
||||||
func AfterRequestSetHtml(children ...Ren) Ren {
|
|
||||||
serialized := Render(Fragment(children...))
|
|
||||||
return Attribute("hx-on::after-request", `this.innerHTML = '`+html.EscapeString(serialized)+`'`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func Children(children ...Ren) *ChildList {
|
func Children(children ...Ren) *ChildList {
|
||||||
return NewChildList(children...)
|
return NewChildList(children...)
|
||||||
}
|
}
|
||||||
|
|
@ -506,41 +462,10 @@ func Label(text string) *Element {
|
||||||
return Tag("label", Text(text))
|
return Tag("label", Text(text))
|
||||||
}
|
}
|
||||||
|
|
||||||
func If(condition bool, node Ren) Ren {
|
|
||||||
if condition {
|
|
||||||
return node
|
|
||||||
} else {
|
|
||||||
return Empty()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IfElse(condition bool, node Ren, node2 Ren) Ren {
|
|
||||||
if condition {
|
|
||||||
return node
|
|
||||||
} else {
|
|
||||||
return node2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IfElseLazy(condition bool, cb1 func() Ren, cb2 func() Ren) Ren {
|
|
||||||
if condition {
|
|
||||||
return cb1()
|
|
||||||
} else {
|
|
||||||
return cb2()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTriggerName(ctx *RequestContext) string {
|
func GetTriggerName(ctx *RequestContext) string {
|
||||||
return ctx.Request().Header.Get("HX-Trigger-Name")
|
return ctx.Request().Header.Get("HX-Trigger-Name")
|
||||||
}
|
}
|
||||||
|
|
||||||
func IfHtmxRequest(ctx *RequestContext, node Ren) Ren {
|
|
||||||
if ctx.Get("HX-Request") != "" {
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
return Empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
type SwapArg struct {
|
type SwapArg struct {
|
||||||
Selector string
|
Selector string
|
||||||
Content *Element
|
Content *Element
|
||||||
|
|
@ -575,20 +500,3 @@ func SwapMany(ctx *RequestContext, args ...SwapArg) Ren {
|
||||||
return arg.Content
|
return arg.Content
|
||||||
})...)
|
})...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnRequestSwapArgs struct {
|
|
||||||
Target string
|
|
||||||
Get string
|
|
||||||
Default *Element
|
|
||||||
BeforeRequest *Element
|
|
||||||
AfterRequest *Element
|
|
||||||
}
|
|
||||||
|
|
||||||
func OnRequestSwap(args OnRequestSwapArgs) Ren {
|
|
||||||
return Div(args.Default,
|
|
||||||
BeforeRequestSetHtml(args.BeforeRequest),
|
|
||||||
AfterRequestSetHtml(args.AfterRequest),
|
|
||||||
Get(args.Get),
|
|
||||||
Target(args.Target),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
43
framework/h/xhr.go
Normal file
43
framework/h/xhr.go
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
package h
|
||||||
|
|
||||||
|
import "github.com/maddalax/htmgo/framework/hx"
|
||||||
|
|
||||||
|
func Get(path string, trigger ...string) *AttributeMap {
|
||||||
|
return AttributeList(Attribute(hx.GetAttr, path), TriggerString(trigger...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetPartial(partial PartialFunc, trigger ...string) *AttributeMap {
|
||||||
|
return Get(GetPartialPath(partial), trigger...)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return Get(SetQueryParams(path, qs), trigger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostPartial(partial PartialFunc, triggers ...string) *AttributeMap {
|
||||||
|
return Post(GetPartialPath(partial), triggers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostPartialWithQs(partial PartialFunc, qs *Qs, trigger ...string) *AttributeMap {
|
||||||
|
return Post(GetPartialPathWithQs(partial, qs), trigger...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Post(url string, trigger ...string) *AttributeMap {
|
||||||
|
return AttributeList(Attribute(hx.PostAttr, url), TriggerString(trigger...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostOnClick(url string) *AttributeMap {
|
||||||
|
return Post(url, hx.ClickEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostPartialOnClick(partial PartialFunc) *AttributeMap {
|
||||||
|
return PostOnClick(GetPartialPath(partial))
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostPartialOnClickQs(partial PartialFunc, qs *Qs) *AttributeMap {
|
||||||
|
return PostOnClick(GetPartialPathWithQs(partial, qs))
|
||||||
|
}
|
||||||
26
framework/hx/event.go
Normal file
26
framework/hx/event.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package hx
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
func OnEvent(event Event, modifiers ...Modifier) TriggerEvent {
|
||||||
|
return TriggerEvent{
|
||||||
|
event: event,
|
||||||
|
modifiers: modifiers,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnClick(modifiers ...Modifier) TriggerEvent {
|
||||||
|
return OnEvent(ClickEvent, modifiers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnLoad(modifiers ...Modifier) TriggerEvent {
|
||||||
|
return OnEvent(LoadEvent, modifiers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnChange(modifiers ...Modifier) TriggerEvent {
|
||||||
|
return OnEvent(ChangeEvent, modifiers...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func OnPoll(durationSeconds int) TriggerEvent {
|
||||||
|
return OnEvent(PollingEvent, StringModifier(fmt.Sprintf("%ds", durationSeconds)))
|
||||||
|
}
|
||||||
138
framework/hx/htmx.go
Normal file
138
framework/hx/htmx.go
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
package hx
|
||||||
|
|
||||||
|
type Attribute = string
|
||||||
|
type Header = string
|
||||||
|
type Event = string
|
||||||
|
|
||||||
|
// https://htmx.org/reference/#events
|
||||||
|
const (
|
||||||
|
GetAttr Attribute = "hx-get"
|
||||||
|
PostAttr Attribute = "hx-post"
|
||||||
|
PushUrlAttr Attribute = "hx-push-url"
|
||||||
|
SelectAttr Attribute = "hx-select"
|
||||||
|
SelectOobAttr Attribute = "hx-select-oob"
|
||||||
|
SwapAttr Attribute = "hx-swap"
|
||||||
|
SwapOobAttr Attribute = "hx-swap-oob"
|
||||||
|
TargetAttr Attribute = "hx-target"
|
||||||
|
TriggerAttr Attribute = "hx-trigger"
|
||||||
|
ValsAttr Attribute = "hx-vals"
|
||||||
|
BoostAttr Attribute = "hx-boost"
|
||||||
|
ConfirmAttr Attribute = "hx-confirm"
|
||||||
|
DeleteAttr Attribute = "hx-delete"
|
||||||
|
DisableAttr Attribute = "hx-disable"
|
||||||
|
DisabledEltAttr Attribute = "hx-disabled-elt"
|
||||||
|
DisinheritAttr Attribute = "hx-disinherit"
|
||||||
|
EncodingAttr Attribute = "hx-encoding"
|
||||||
|
ExtAttr Attribute = "hx-ext"
|
||||||
|
HeadersAttr Attribute = "hx-headers"
|
||||||
|
HistoryAttr Attribute = "hx-history"
|
||||||
|
HistoryEltAttr Attribute = "hx-history-elt"
|
||||||
|
IncludeAttr Attribute = "hx-include"
|
||||||
|
IndicatorAttr Attribute = "hx-indicator"
|
||||||
|
InheritAttr Attribute = "hx-inherit"
|
||||||
|
ParamsAttr Attribute = "hx-params"
|
||||||
|
PatchAttr Attribute = "hx-patch"
|
||||||
|
PreserveAttr Attribute = "hx-preserve"
|
||||||
|
PromptAttr Attribute = "hx-prompt"
|
||||||
|
PutAttr Attribute = "hx-put"
|
||||||
|
ReplaceUrlAttr Attribute = "hx-replace-url"
|
||||||
|
RequestAttr Attribute = "hx-request"
|
||||||
|
SyncAttr Attribute = "hx-sync"
|
||||||
|
ValidateAttr Attribute = "hx-validate"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
LocationHeader Header = "HX-Location"
|
||||||
|
PushUrlHeader Header = "HX-Push-Url"
|
||||||
|
RedirectHeader Header = "HX-Redirect"
|
||||||
|
RefreshHeader Header = "HX-Refresh"
|
||||||
|
ReplaceUrlHeader Header = "HX-Replace-Url"
|
||||||
|
CurrentUrlHeader Header = "HX-Current-Url"
|
||||||
|
ReswapHeader Header = "HX-Reswap"
|
||||||
|
RetargetHeader Header = "HX-Retarget"
|
||||||
|
ReselectHeader Header = "HX-Reselect"
|
||||||
|
TriggerHeader Header = "HX-Trigger"
|
||||||
|
TriggerAfterSettleHeader Header = "HX-Trigger-After-Settle"
|
||||||
|
TriggerAfterSwapHeader Header = "HX-Trigger-After-Swap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// AbortEvent Htmx Events
|
||||||
|
AbortEvent Event = "htmx:abort"
|
||||||
|
AfterOnLoadEvent Event = "htmx:afterOnLoad"
|
||||||
|
AfterProcessNodeEvent Event = "htmx:afterProcessNode"
|
||||||
|
AfterRequestEvent Event = "htmx:afterRequest"
|
||||||
|
OnMutationErrorEvent Event = "htmx:onMutationError"
|
||||||
|
AfterSettleEvent Event = "htmx:afterSettle"
|
||||||
|
AfterSwapEvent Event = "htmx:afterSwap"
|
||||||
|
BeforeCleanupElementEvent Event = "htmx:beforeCleanupElement"
|
||||||
|
BeforeOnLoadEvent Event = "htmx:beforeOnLoad"
|
||||||
|
BeforeProcessNodeEvent Event = "htmx:beforeProcessNode"
|
||||||
|
BeforeRequestEvent Event = "htmx:beforeRequest"
|
||||||
|
BeforeSwapEvent Event = "htmx:beforeSwap"
|
||||||
|
BeforeSendEvent Event = "htmx:beforeSend"
|
||||||
|
ConfigRequestEvent Event = "htmx:configRequest"
|
||||||
|
ConfirmEvent Event = "htmx:confirm"
|
||||||
|
HistoryCacheErrorEvent Event = "htmx:historyCacheError"
|
||||||
|
HistoryCacheMissEvent Event = "htmx:historyCacheMiss"
|
||||||
|
HistoryCacheMissErrorEvent Event = "htmx:historyCacheMissError"
|
||||||
|
HistoryCacheMissLoadEvent Event = "htmx:historyCacheMissLoad"
|
||||||
|
HistoryRestoreEvent Event = "htmx:historyRestore"
|
||||||
|
BeforeHistorySaveEvent Event = "htmx:beforeHistorySave"
|
||||||
|
LoadEvent Event = "htmx:load"
|
||||||
|
NoSSESourceErrorEvent Event = "htmx:noSSESourceError"
|
||||||
|
OnLoadErrorEvent Event = "htmx:onLoadError"
|
||||||
|
OobAfterSwapEvent Event = "htmx:oobAfterSwap"
|
||||||
|
OobBeforeSwapEvent Event = "htmx:oobBeforeSwap"
|
||||||
|
OobErrorNoTargetEvent Event = "htmx:oobErrorNoTarget"
|
||||||
|
PromptEvent Event = "htmx:prompt"
|
||||||
|
PushedIntoHistoryEvent Event = "htmx:pushedIntoHistory"
|
||||||
|
ResponseErrorEvent Event = "htmx:responseError"
|
||||||
|
SendErrorEvent Event = "htmx:sendError"
|
||||||
|
SSEErrorEvent Event = "htmx:sseError"
|
||||||
|
SSEOpenEvent Event = "htmx:sseOpen"
|
||||||
|
SwapErrorEvent Event = "htmx:swapError"
|
||||||
|
TargetErrorEvent Event = "htmx:targetError"
|
||||||
|
TimeoutEvent Event = "htmx:timeout"
|
||||||
|
ValidationValidateEvent Event = "htmx:validation:validate"
|
||||||
|
ValidationFailedEvent Event = "htmx:validation:failed"
|
||||||
|
ValidationHaltedEvent Event = "htmx:validation:halted"
|
||||||
|
XhrAbortEvent Event = "htmx:xhr:abort"
|
||||||
|
XhrLoadEndEvent Event = "htmx:xhr:loadend"
|
||||||
|
XhrLoadStartEvent Event = "htmx:xhr:loadstart"
|
||||||
|
XhrProgressEvent Event = "htmx:xhr:progress"
|
||||||
|
|
||||||
|
// RevealedEvent Misc Events
|
||||||
|
RevealedEvent Event = "revealed"
|
||||||
|
InstersectEvent Event = "intersect"
|
||||||
|
PollingEvent Event = "every"
|
||||||
|
|
||||||
|
// ClickEvent Dom Events
|
||||||
|
ClickEvent Event = "onclick"
|
||||||
|
ChangeEvent Event = "onchange"
|
||||||
|
InputEvent Event = "oninput"
|
||||||
|
FocusEvent Event = "onfocus"
|
||||||
|
BlurEvent Event = "onblur"
|
||||||
|
KeyDownEvent Event = "onkeydown"
|
||||||
|
KeyUpEvent Event = "onkeyup"
|
||||||
|
KeyPressEvent Event = "onkeypress"
|
||||||
|
SubmitEvent Event = "onsubmit"
|
||||||
|
LoadDomEvent Event = "onload"
|
||||||
|
UnloadEvent Event = "onunload"
|
||||||
|
ResizeEvent Event = "onresize"
|
||||||
|
ScrollEvent Event = "onscroll"
|
||||||
|
DblClickEvent Event = "ondblclick"
|
||||||
|
MouseOverEvent Event = "onmouseover"
|
||||||
|
MouseOutEvent Event = "onmouseout"
|
||||||
|
MouseMoveEvent Event = "onmousemove"
|
||||||
|
MouseDownEvent Event = "onmousedown"
|
||||||
|
MouseUpEvent Event = "onmouseup"
|
||||||
|
ContextMenuEvent Event = "oncontextmenu"
|
||||||
|
DragStartEvent Event = "ondragstart"
|
||||||
|
DragEvent Event = "ondrag"
|
||||||
|
DragEnterEvent Event = "ondragenter"
|
||||||
|
DragLeaveEvent Event = "ondragleave"
|
||||||
|
DragOverEvent Event = "ondragover"
|
||||||
|
DropEvent Event = "ondrop"
|
||||||
|
DragEndEvent Event = "ondragend"
|
||||||
|
)
|
||||||
19
framework/hx/htmx_test.go
Normal file
19
framework/hx/htmx_test.go
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
package hx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewStringTrigger(t *testing.T) {
|
||||||
|
trigger := "click once, htmx:click throttle:5, load delay:10"
|
||||||
|
tgr := NewStringTrigger(trigger)
|
||||||
|
assert.Equal(t, len(tgr.events), 3)
|
||||||
|
assert.Equal(t, tgr.events[0].event, "click")
|
||||||
|
assert.Equal(t, tgr.events[0].modifiers[0].Modifier(), "once")
|
||||||
|
assert.Equal(t, tgr.events[1].event, "click")
|
||||||
|
assert.Equal(t, tgr.events[1].modifiers[0].Modifier(), "throttle:5")
|
||||||
|
assert.Equal(t, tgr.events[2].event, "load")
|
||||||
|
assert.Equal(t, tgr.events[2].modifiers[0].Modifier(), "delay:10")
|
||||||
|
assert.Equal(t, "click once, click throttle:5, load delay:10", tgr.ToString())
|
||||||
|
}
|
||||||
49
framework/hx/modifiers.go
Normal file
49
framework/hx/modifiers.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package hx
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type Modifier interface {
|
||||||
|
Modifier() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RawModifier struct {
|
||||||
|
modifier string
|
||||||
|
}
|
||||||
|
|
||||||
|
func StringModifier(modifier string) RawModifier {
|
||||||
|
return RawModifier{modifier}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r RawModifier) Modifier() string {
|
||||||
|
return r.modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type OnceModifier struct{}
|
||||||
|
|
||||||
|
func (o OnceModifier) Modifier() string {
|
||||||
|
return "once"
|
||||||
|
}
|
||||||
|
|
||||||
|
type ThrottleModifier struct {
|
||||||
|
durationSeconds int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ThrottleModifier) Modifier() string {
|
||||||
|
return fmt.Sprintf("throttle:%ds", t.durationSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Throttle(durationSeconds int) ThrottleModifier {
|
||||||
|
return ThrottleModifier{durationSeconds}
|
||||||
|
}
|
||||||
|
|
||||||
|
type DelayModifier struct {
|
||||||
|
durationSeconds int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t DelayModifier) Modifier() string {
|
||||||
|
return fmt.Sprintf("delay:%ds", t.durationSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Delay(durationSeconds int) DelayModifier {
|
||||||
|
return DelayModifier{durationSeconds}
|
||||||
|
}
|
||||||
80
framework/hx/trigger.go
Normal file
80
framework/hx/trigger.go
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
package hx
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Trigger struct {
|
||||||
|
events []TriggerEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
type TriggerEvent struct {
|
||||||
|
event Event
|
||||||
|
modifiers []Modifier
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrigger(opts ...TriggerEvent) *Trigger {
|
||||||
|
t := Trigger{
|
||||||
|
events: make([]TriggerEvent, 0),
|
||||||
|
}
|
||||||
|
if len(opts) > 0 {
|
||||||
|
t.events = opts
|
||||||
|
}
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStringTrigger(trigger string) Trigger {
|
||||||
|
t := Trigger{
|
||||||
|
events: make([]TriggerEvent, 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
split := strings.Split(trigger, ", ")
|
||||||
|
for _, s := range split {
|
||||||
|
parts := strings.Split(s, " ")
|
||||||
|
event := parts[0]
|
||||||
|
|
||||||
|
if strings.HasPrefix(event, "htmx:") {
|
||||||
|
event = event[5:]
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiers := make([]Modifier, 0)
|
||||||
|
if len(parts) > 1 {
|
||||||
|
for _, m := range parts[1:] {
|
||||||
|
modifiers = append(modifiers, RawModifier{modifier: m})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.events = append(t.events, TriggerEvent{
|
||||||
|
event: event,
|
||||||
|
modifiers: modifiers,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Trigger) AddEvent(event TriggerEvent) Trigger {
|
||||||
|
t.events = append(t.events, event)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Trigger) ToString() string {
|
||||||
|
builder := strings.Builder{}
|
||||||
|
for i, e := range t.events {
|
||||||
|
eventName := e.event
|
||||||
|
if strings.HasPrefix(eventName, "htmx:") {
|
||||||
|
eventName = eventName[5:]
|
||||||
|
}
|
||||||
|
builder.WriteString(eventName)
|
||||||
|
for _, m := range e.modifiers {
|
||||||
|
builder.WriteString(" ")
|
||||||
|
builder.WriteString(m.Modifier())
|
||||||
|
}
|
||||||
|
if i < len(t.events)-1 {
|
||||||
|
builder.WriteString(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Trigger) Render(builder *strings.Builder) {
|
||||||
|
builder.WriteString(t.ToString())
|
||||||
|
}
|
||||||
16
framework/hx/triggers.go
Normal file
16
framework/hx/triggers.go
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
package hx
|
||||||
|
|
||||||
|
// TriggerClick Common trigger events
|
||||||
|
const TriggerClick = ClickEvent
|
||||||
|
const TriggerClickOnce = TriggerClick + " once"
|
||||||
|
const TriggerDblClick = DblClickEvent
|
||||||
|
const TriggerKeyUpEnter = "keyup[keyCode==13]"
|
||||||
|
const TriggerEnterPressed = TriggerKeyUpEnter
|
||||||
|
const TriggerBlur = "blur"
|
||||||
|
const TriggerEvery1s = "every:1s"
|
||||||
|
const TriggerEvery2s = "every:2s"
|
||||||
|
const TriggerEvery5s = "every:5s"
|
||||||
|
const TriggerEvery10s = "every:10s"
|
||||||
|
const TriggerEvery30s = "every:30s"
|
||||||
|
const TriggerEvery1m = "every:1m"
|
||||||
|
const TriggerLoad = LoadEvent
|
||||||
|
|
@ -3,22 +3,22 @@ module htmgo-site
|
||||||
go 1.23.0
|
go 1.23.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0
|
|
||||||
github.com/labstack/echo/v4 v4.12.0
|
github.com/labstack/echo/v4 v4.12.0
|
||||||
github.com/maddalax/htmgo/framework v0.0.0-20240920021308-279a3c716342
|
github.com/maddalax/htmgo/framework v0.0.0-20240920021308-279a3c716342
|
||||||
github.com/mattn/go-sqlite3 v1.14.16
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
|
github.com/yuin/goldmark v1.7.4
|
||||||
|
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0 // indirect
|
github.com/alecthomas/chroma/v2 v2.2.0 // indirect
|
||||||
github.com/dlclark/regexp2 v1.7.0 // indirect
|
github.com/dlclark/regexp2 v1.7.0 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
github.com/labstack/gommon v0.4.2 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/yuin/goldmark v1.7.4 // indirect
|
|
||||||
github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc // indirect
|
|
||||||
golang.org/x/crypto v0.27.0 // indirect
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
golang.org/x/net v0.29.0 // indirect
|
golang.org/x/net v0.29.0 // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
|
github.com/alecthomas/chroma/v2 v2.2.0 h1:Aten8jfQwUqEdadVFFjNyjx7HTexhKP0XuqBG67mRDY=
|
||||||
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae h1:zzGwJfFlFGD94CyyYwCJeSuD32Gj9GTaSi5y9hoVzdY=
|
||||||
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
"github.com/maddalax/htmgo/framework/h"
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
"github.com/maddalax/htmgo/framework/htmgo/service"
|
"github.com/maddalax/htmgo/framework/htmgo/service"
|
||||||
|
"github.com/maddalax/htmgo/framework/hx"
|
||||||
"htmgo-site/internal/markdown"
|
"htmgo-site/internal/markdown"
|
||||||
"htmgo-site/pages/base"
|
"htmgo-site/pages/base"
|
||||||
|
"htmgo-site/partials"
|
||||||
)
|
)
|
||||||
|
|
||||||
func MarkdownHandler(ctx *h.RequestContext, path string) error {
|
func MarkdownHandler(ctx *h.RequestContext, path string) error {
|
||||||
|
|
@ -15,6 +17,9 @@ func MarkdownHandler(ctx *h.RequestContext, path string) error {
|
||||||
func MarkdownPage(ctx *h.RequestContext, path string) *h.Element {
|
func MarkdownPage(ctx *h.RequestContext, path string) *h.Element {
|
||||||
return base.RootPage(
|
return base.RootPage(
|
||||||
h.Div(
|
h.Div(
|
||||||
|
h.Div(
|
||||||
|
h.GetPartial(partials.TestPartial, hx.LoadEvent),
|
||||||
|
),
|
||||||
h.Class("w-full p-4 flex flex-col justify-center items-center"),
|
h.Class("w-full p-4 flex flex-col justify-center items-center"),
|
||||||
MarkdownContent(ctx, path),
|
MarkdownContent(ctx, path),
|
||||||
h.Div(
|
h.Div(
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,10 @@ func GetPartialFromContext(ctx echo.Context) *h.Partial {
|
||||||
cc := ctx.(*h.RequestContext)
|
cc := ctx.(*h.RequestContext)
|
||||||
return partials.ToggleNavbar(cc)
|
return partials.ToggleNavbar(cc)
|
||||||
}
|
}
|
||||||
|
if path == "TestPartial" || path == "/htmgo-site/partials.TestPartial" {
|
||||||
|
cc := ctx.(*h.RequestContext)
|
||||||
|
return partials.TestPartial(cc)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package partials
|
package partials
|
||||||
|
|
||||||
import "github.com/maddalax/htmgo/framework/h"
|
import (
|
||||||
|
"github.com/maddalax/htmgo/framework/h"
|
||||||
|
)
|
||||||
|
|
||||||
type NavItem struct {
|
type NavItem struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
@ -13,6 +15,12 @@ func ToggleNavbar(ctx *h.RequestContext) *h.Partial {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPartial(ctx *h.RequestContext) *h.Partial {
|
||||||
|
return h.NewPartial(
|
||||||
|
h.Div(h.Text("This is a test")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var navItems = []NavItem{
|
var navItems = []NavItem{
|
||||||
{Name: "Docs", Url: "/docs"},
|
{Name: "Docs", Url: "/docs"},
|
||||||
{Name: "Examples", Url: "/examples"},
|
{Name: "Examples", Url: "/examples"},
|
||||||
|
|
@ -37,6 +45,7 @@ func NavBar(expanded bool) *h.Element {
|
||||||
)
|
)
|
||||||
|
|
||||||
desktopNav := h.Nav(
|
desktopNav := h.Nav(
|
||||||
|
h.Script("https://buttons.github.io/buttons.js"),
|
||||||
h.Class("hidden sm:block bg-neutral-100 border border-b-slate-300 p-4 md:p-3"),
|
h.Class("hidden sm:block bg-neutral-100 border border-b-slate-300 p-4 md:p-3"),
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("max-w-[95%] md:max-w-prose mx-auto"),
|
h.Class("max-w-[95%] md:max-w-prose mx-auto"),
|
||||||
|
|
@ -45,6 +54,7 @@ func NavBar(expanded bool) *h.Element {
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("flex items-center"),
|
h.Class("flex items-center"),
|
||||||
h.A(
|
h.A(
|
||||||
|
h.Boost(),
|
||||||
h.Class("text-2xl"),
|
h.Class("text-2xl"),
|
||||||
h.Href("/"),
|
h.Href("/"),
|
||||||
h.Text("htmgo"),
|
h.Text("htmgo"),
|
||||||
|
|
@ -55,6 +65,7 @@ func NavBar(expanded bool) *h.Element {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("flex items-center"),
|
h.Class("flex items-center"),
|
||||||
h.A(
|
h.A(
|
||||||
|
h.Boost(),
|
||||||
h.Class(""),
|
h.Class(""),
|
||||||
h.Href(item.Url),
|
h.Href(item.Url),
|
||||||
h.Text(item.Name),
|
h.Text(item.Name),
|
||||||
|
|
@ -87,6 +98,7 @@ func MobileNav(expanded bool) *h.Element {
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("flex items-center"),
|
h.Class("flex items-center"),
|
||||||
h.A(
|
h.A(
|
||||||
|
h.Boost(),
|
||||||
h.Class("text-2xl"),
|
h.Class("text-2xl"),
|
||||||
h.Href("/"),
|
h.Href("/"),
|
||||||
h.Text("htmgo"),
|
h.Text("htmgo"),
|
||||||
|
|
@ -94,8 +106,19 @@ func MobileNav(expanded bool) *h.Element {
|
||||||
h.Div(
|
h.Div(
|
||||||
h.Class("flex items-center"),
|
h.Class("flex items-center"),
|
||||||
h.Button(
|
h.Button(
|
||||||
h.GetPartialWithQs(ToggleNavbar, h.Ternary(expanded, "expanded=false", "expanded=true")),
|
h.Boost(),
|
||||||
h.Trigger(h.TriggerClick),
|
|
||||||
|
h.GetPartialWithQs(
|
||||||
|
ToggleNavbar,
|
||||||
|
h.NewQs("expanded", h.Ternary(expanded, "false", "true"), "test", "true"),
|
||||||
|
"click",
|
||||||
|
),
|
||||||
|
|
||||||
|
h.AttributePairs(
|
||||||
|
"class", "text-2xl",
|
||||||
|
"aria-expanded", h.Ternary(expanded, "true", "false"),
|
||||||
|
),
|
||||||
|
|
||||||
h.Class("text-2xl"),
|
h.Class("text-2xl"),
|
||||||
h.Text("☰"),
|
h.Text("☰"),
|
||||||
),
|
),
|
||||||
|
|
@ -109,6 +132,7 @@ func MobileNav(expanded bool) *h.Element {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("flex items-center"),
|
h.Class("flex items-center"),
|
||||||
h.A(
|
h.A(
|
||||||
|
h.Boost(),
|
||||||
h.Class(""),
|
h.Class(""),
|
||||||
h.Href(item.Url),
|
h.Href(item.Url),
|
||||||
h.Text(item.Name),
|
h.Text(item.Name),
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue