From 44b34da66d20afce6ce173b943a94c4d7661fe60 Mon Sep 17 00:00:00 2001 From: Will Ockelmann-Wagner Date: Fri, 17 May 2024 01:52:56 -0700 Subject: [PATCH] add response-targets and event-header --- .vscode/settings.json | 2 +- README.md | 2 + htmx/ext/eventheader/eventheader.go | 24 +++++ htmx/ext/responsetargets/responsetargets.go | 102 ++++++++++++++++++ .../responsetargets/responsetargets_test.go | 35 ++++++ htmx/htmx.go | 2 + 6 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 htmx/ext/eventheader/eventheader.go create mode 100644 htmx/ext/responsetargets/responsetargets.go create mode 100644 htmx/ext/responsetargets/responsetargets_test.go diff --git a/.vscode/settings.json b/.vscode/settings.json index 2fbef1d..ff9bc79 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { "go.lintTool": "golangci-lint", "go.lintFlags": ["--fast"], - "cSpell.words": ["classtools", "gomponents"] + "cSpell.words": ["classtools", "eventheader", "templ", "gomponents"] } diff --git a/README.md b/README.md index aa962fa..438fafa 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,8 @@ See [htmx/ext](./htmx/ext) for a full list of extensions. - [`class-tools`](https://htmx.org/extensions/class-tools/) - [`preload`](https://htmx.org/extensions/preload/) +- [`response-targets`](https://htmx.org/extensions/response-targets/) +- [`event-header`](https://htmx.org/extensions/event-header/) - [`remove-me`](https://htmx.org/extensions/remove-me/) ## Examples diff --git a/htmx/ext/eventheader/eventheader.go b/htmx/ext/eventheader/eventheader.go new file mode 100644 index 0000000..da0b212 --- /dev/null +++ b/htmx/ext/eventheader/eventheader.go @@ -0,0 +1,24 @@ +// package eventheader the Triggering-Event header to requests. The value of the header is a JSON serialized version of the event that triggered the request. +// +// # Install +// +// +// +// # Usage +// +// +// +// Sends something like this: +// +// Triggering-Event: '{ "isTrusted": false, "htmx-internal-data": { "handled": true }, "screenX": 0, "screenY": 0, "clientX": 0, "clientY": 0, "ctrlKey": false, "shiftKey": false, "altKey": false, "metaKey": false, "button": 0, "buttons": 0, "relatedTarget": null, "pageX": 0, "pageY": 0, "x": 0, "y": 0, "offsetX": 0, "offsetY": 0, "movementX": 0, "movementY": 0, "fromElement": null, "toElement": "button", "layerX": 0, "layerY": 0, "view": "Window", "detail": 0, "sourceCapabilities": null, "which": 1, "NONE": 0, "CAPTURING_PHASE": 1, "AT_TARGET": 2, "BUBBLING_PHASE": 3, "type": "click", "target": "button", "currentTarget": "button", "eventPhase": 2, "bubbles": true, "cancelable": true, "defaultPrevented": true, "composed": true, "timeStamp": 188.86999995447695, "srcElement": "button", "returnValue": false, "cancelBubble": false, "path": [ "button", "div#work-area", "body", "html", "Node", "Window" ] }' +// +// Extension: [event-header] +// +// [event-header]: https://htmx.org/extensions/event-header/ +package eventheader + +import "github.com/will-wow/typed-htmx-go/htmx" + +const Extension htmx.Extension = "event-header" diff --git a/htmx/ext/responsetargets/responsetargets.go b/htmx/ext/responsetargets/responsetargets.go new file mode 100644 index 0000000..efb9461 --- /dev/null +++ b/htmx/ext/responsetargets/responsetargets.go @@ -0,0 +1,102 @@ +// package responsetargets allows you to specify different target elements to be swapped when different HTTP response codes are received. +// +// # Install +// +// +// +// Extension: [response-targets] +// +// [response-targets]: https://htmx.org/extensions/response-targets/ +package responsetargets + +import ( + "fmt" + "strconv" + "strings" + + "github.com/will-wow/typed-htmx-go/htmx" +) + +// Extension allows you to specify different target elements to be swapped when different HTTP response codes are received. +// +// # Install +// +// +// +// Extension: [response-targets] +// +// [response-targets]: https://htmx.org/extensions/response-targets/ +const Extension htmx.Extension = "response-targets" + +// A Code is a complete or partial HTTP response code. +type Code interface { + code() string +} + +// A Status is a complete HTTP response code. You can wrap the http.Status* constants with [Status]. +type Status int + +var _ Code = Status(0) + +func (s Status) code() string { + return strconv.Itoa(int(s)) +} + +// an errorCode is the string "error", used to cover all 4xx and 5xx HTTP response codes. +type errorCode string + +var _ Code = errorCode("") + +// Error is a status code that covers all 4xx and 5xx HTTP response codes. +const Error errorCode = "error" + +func (e errorCode) code() string { + return string(e) +} + +// A wildcard is a partial HTTP response code with a wildcard component. +type wildcard []int + +var _ Code = (wildcard)(nil) + +// Wildcard creates a wildcard code with the given digits. +// For example, Wildcard(4, 1) results in hx-target-41*, and matches all 41x HTTP response codes. +func Wildcard(digits ...int) wildcard { + return digits +} + +func (w wildcard) code() string { + builder := strings.Builder{} + for _, digit := range w { + _, _ = builder.WriteString(strconv.Itoa(digit)) + } + _ = builder.WriteByte('*') + return builder.String() +} + +type wildcardX []int + +// WildcardX creates a wildcard code with the given digits, and uses an 'x' instead of a '*' in the generated attribute. +// For example, WildcardX(4, 1) results in hx-target-41x, and matches all 41x HTTP response codes. +func WildcardX(digits ...int) wildcardX { + return digits +} + +func (w wildcardX) code() string { + builder := strings.Builder{} + for _, digit := range w { + _, _ = builder.WriteString(strconv.Itoa(digit)) + } + _ = builder.WriteByte('x') + return builder.String() +} + +// Target specifies a target element to be swapped when specific HTTP response codes are received. +// +// Extension: [response-targets] +// +// [response-targets]: https://htmx.org/extensions/response-targets/ +func Target[T any](hx htmx.HX[T], code Code, extendedSelector htmx.TargetSelector) T { + attr := fmt.Sprintf("hx-target-%s", code.code()) + return hx.Attr(htmx.Attribute(attr), string(extendedSelector)) +} diff --git a/htmx/ext/responsetargets/responsetargets_test.go b/htmx/ext/responsetargets/responsetargets_test.go new file mode 100644 index 0000000..cf19c01 --- /dev/null +++ b/htmx/ext/responsetargets/responsetargets_test.go @@ -0,0 +1,35 @@ +package responsetargets_test + +import ( + "fmt" + "net/http" + + "github.com/will-wow/typed-htmx-go/htmx" + "github.com/will-wow/typed-htmx-go/htmx/ext/responsetargets" +) + +var hx = htmx.NewStringAttrs() + +func ExampleTarget_code() { + attr := responsetargets.Target(hx, responsetargets.Code(http.StatusNotFound), htmx.TargetRelative(htmx.Next, "div")) + fmt.Println(attr) + // Output: hx-target-404='next div' +} + +func ExampleTarget_error() { + attr := responsetargets.Target(hx, responsetargets.Error, htmx.TargetThis) + fmt.Println(attr) + // Output: hx-target-error='this' +} + +func ExampleTarget_wildcard() { + attr := responsetargets.Target(hx, responsetargets.Wildcard(4, 0), htmx.TargetRelative(htmx.Next, "div")) + fmt.Println(attr) + // Output: hx-target-40*='next div' +} + +func ExampleTarget_wildcardX() { + attr := responsetargets.Target(hx, responsetargets.WildcardX(4, 0), htmx.TargetRelative(htmx.Next, "div")) + fmt.Println(attr) + // Output: hx-target-40x='next div' +} diff --git a/htmx/htmx.go b/htmx/htmx.go index 9e7f642..084248a 100644 --- a/htmx/htmx.go +++ b/htmx/htmx.go @@ -796,6 +796,8 @@ func (hx *HX[T]) Encoding(encoding EncodingContentType) T { return hx.attr(Encoding, string(encoding)) } +// An Extension is the name of an htmx extension, to be passed to [HX.Ext()] to initialize the extension. +// Extensions are in packages under htmx/ext. type Extension string // Ext enables an htmx [extension] for an element and all its children.