This comprehensive library offers an array of functions and types specifically designed to streamline the handling of HTMX requests and the construction of responses in the Go applications.
README.md logo image courtesy of ChatGPT.
- Request and Response header helpers
- Easy APIs to build complex HTMX responses for Locations, Reswaps, and Triggers
import (
"net/http"
"github.com/stackus/hxgo"
)
func myHandler(w http.ResponseWriter, r *http.Request) {
if hx.IsHtmx(r) {
// do something
// load up on HTMX headers and set the status code to send back to the client
err := hx.Response(w,
hx.Location("/new-location",
hx.Target("#my-target"),
hx.Swap(hx.SwapInnerHtml.IgnoreTitle()),
hx.Values(map[string]string{"key": "value"}),
),
hx.StatusStopPolling,
hx.Trigger(
hx.Event("my-event"),
hx.Event("my-other-event", "my-other-event-value"),
hx.Event("my-complex-event", map[string]any{
"foo": "bar",
"baz": 123,
}
),
)
if err != nil {
// handle error
}
}
}
The minimum version of Go required is 1.18. Generics have been used to make some types and options easier to work with.
Install using go get
:
go get github.com/stackus/hxgo
Then import the package into your project:
import "github.com/stackus/hxgo"
You'll then use hx.*
to access the functions and types.
To determine if a request is an HTMX request, use the IsHtmx
function:
func MyHandler(w http.ResponseWriter, r *http.Request) {
if hx.IsHtmx(r) {
// do something
}
}
Helpers exist for each of the HTMX request headers:
HX-Boosted
: Use theIsBoosted
function to determine if the request is a boosted requestHX-Current-URL
: Use theGetCurrentUrl
function to get the current URL of the requestHX-History-Restore-Request
: Use theIsHistoryRestoreRequest
function to determine if the request is a history restore requestHX-Prompt
: Use theGetPrompt
function to get the prompt value of the requestHX-Request
: Use theIsRequest
orIsHTMX
functions to determine if the request is an HTMX requestHX-Target
: Use theGetTarget
function to get the target value of the requestHX-Trigger-Name
: Use theGetTriggerName
function to get the trigger name of the requestHX-Trigger
: Use theGetTrigger
function to get the trigger value of the request
Is*
functions return a boolean while Get*
functions return a string. The absence of the corresponding HTMX header will return false or an empty string respectively.
Use the Response
function to modify the http.ResponseWriter
to return an HTMX response:
func MyHandler(w http.ResponseWriter, r *http.Request) {
err := hx.Response(w, hx.Retarget("/new-location"))
if err != nil {
// handle error
}
}
Each of the HTMX response headers has a corresponding option to set the header:
HX-Location
: Use theLocation
option with a variable number of properties to set the location header. See the Location section for more details.HX-Push-Url
: Use thePushURL
option to push a new URL into the browser historyHX-Redirect
: Use theRedirect
option to redirect the browser to a new URLHX-Refresh
: Use theRefresh
option to refresh the browserHX-Replace-Url
: Use theReplaceUrl
option to replace the current URL in the browser historyHX-Reswap
: Use theReswap
option or one of theSwap*
constants to specify how the response will be swapped. See the Reswap section for more details.HX-Retarget
: Use theRetarget
option with a CSS selector to redirect the response to a new elementHX-Reselect
: Use theReselect
option with a CSS selector to designate a different element in the response to be usedHX-Trigger
: Use theTrigger
option to trigger client-side events. See the Trigger section for more details.HX-Trigger-After-Settle
: Use theTriggerAfterSettle
option to trigger client-side events after the response has settled. See the Trigger section for more details.HX-Trigger-After-Swap
: Use theTriggerAfterSwap
option to trigger client-side events after the response has been swapped. See the Trigger section for more details.
The Location
option is used to set the HX-Location Response Header. It takes a path string and then an optional number of properties. The following properties are supported:
Source
: TheSource
property is used to set the source element of the location header.Event
: TheEventName
property is used to set the name of the event of the location header.Note: This property is called
EventName
so that it does not conflict with theEvent
property used by theTrigger
option.Handler
: TheHandler
property is used to set the handler of the location header.Target
: TheTarget
property is used to set the target of the location header.Swap
: TheSwap
property is used to set the swap of the location header. The value may be a string or any of theSwap*
constants.Values
: TheValues
property is used to set the values of the location header. The value may be anything, but it is recommended to use amap[string]any
or struct with JSON tags.Headers
: TheHeaders
property is used to set the headers of the location header. The value needs to be amap[string]string
.Select
: TheSelect
property is used to set the select of the location header.
Setting just the path:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.Location("/new-location"))
// Hx-Location: /new-location
}
Setting multiple properties:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.Location("/new-location",
hx.Target("#my-target"),
hx.Swap(hx.SwapInnerHtml.IgnoreTitle()),
hx.Values(map[string]string{"key": "value"}),
))
// Hx-Location: {"path":"/new-location","target":"#my-target","swap":"innerHTML ignoreTitle:true","values":{"key":"value"}}
}
The Reswap
option is used to set the HX-Reswap response header. Using the Reswap
option directly is possible, but it is recommended to use one of the Swap*
constants instead. The following constants are supported:
SwapInnerHtml
: Sets the HX-Reswap response header toinnerHTML
SwapOuterHtml
: Sets the HX-Reswap response header toouterHTML
SwapBeforeBegin
: Sets the HX-Reswap response header tobeforebegin
SwapAfterBegin
: Sets the HX-Reswap response header toafterbegin
SwapBeforeEnd
: Sets the HX-Reswap response header tobeforeend
SwapAfterEnd
: Sets the HX-Reswap response header toafterend
SwapDelete
: Sets the HX-Reswap response header todelete
SwapNone
: Sets the HX-Reswap response header tonone
The result from Reswap
and each constant can be chained with modifiers to configure the header even further. The following modifiers are supported:
Transition
: Addstransition:true
to enable the use of the View Transition APISwap
: Used with atime.Duration
to set the swap delaySettle
: Used with atime.Duration
to set the settle delayIgnoreTitle
: AddsignoreTitle:true
to ignore the title of the responseScroll
: Used with a CSS selector to scroll to the element after swappingShow
: Used with a CSS selector to show the element after swappingFocusScroll
: Used with a boolean to set the focus scroll behavior
Setting just the reswap header two ways:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.Reswap("innerHTML"))
// Hx-Reswap: innerHTML
hx.Response(w, hx.SwapInnerHtml)
// Hx-Reswap: innerHTML
}
Setting the reswap header with modifiers:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.SwapInnerHtml.IgnoreTitle().Transition())
// Hx-Reswap: innerHTML ignoreTitle:true transition:true
}
The Trigger
option is used to set the HX-Trigger Response Header. It takes a variable number of events to trigger on the client.
Events are created using hx.Event
and can be either simple names or complex objects. The supported events include:
Setting a simple event:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.Trigger(hx.Event("my-event")))
// Hx-Trigger: {"my-event":null}
}
Setting a complex event:
func MyHandler(w http.ResponseWriter, r *http.Request) {
myEvent := map[string]any{
"foo": "bar",
"baz": 123,
}
hx.Response(w, hx.Trigger(hx.Event("my-event", myEvent)))
// Hx-Trigger: {"my-event":{"foo":"bar","baz":123}}
}
Setting multiple events:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.Trigger(
hx.Event("my-event"),
hx.Event("my-other-event", "my-other-event-value"),
))
// Hx-Trigger: {"my-event":null,"my-other-event":"my-other-event-value"}
}
The data
, which is the second parameter of the Event
, is variadic. If more than one data value is passed, the event is set to an array of those values. The following events demonstrate this equivalence:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.Trigger(
hx.Event("my-event-1", "foo", "bar"),
hx.Event("my-event-2", []string{"foo", "bar"}),
))
// Hx-Trigger: {"my-event-1":["foo","bar"], "my-event-2":["foo","bar"]}
}
Both TriggerAfterSettle
and TriggerAfterSwap
are available to trigger events after the response has settled or been swapped respectively. They take the same event arguments as Trigger
.
The Status
option is used to set the HTTP status code of the response. There is only one status constant available:
StatusStopPolling
: Sets the HTTP status code to 286 which is used by HTMX to halt polling requests
Setting the status code:
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.StatusStopPolling)
// HTTP/1.1 286
}
The Status
option can be used to set any HTTP status code and is not limited to the constants provided by this library.
func MyHandler(w http.ResponseWriter, r *http.Request) {
hx.Response(w, hx.Status(http.StatusGone))
// HTTP/1.1 410
}
With the standard library, and other frameworks that adhere to its http.ResponseWriter
interface, the Response
function can be used directly to modify the response.
package main
import (
"fmt"
"log"
"net/http"
"github.com/stackus/hxgo"
)
func helloWorldHandler(w http.ResponseWriter, r *http.Request) {
// Add HTMX headers and a status code to the response
err := hx.Response(w,
hx.Location("/foo"),
hx.StatusStopPolling,
)
if err != nil {
log.Fatal(err)
}
// Write the response body
_, _ = fmt.Fprintf(w, "Hello World")
}
func main() {
http.HandleFunc("/", helloWorldHandler)
fmt.Println("Server starting on port 8080...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
For frameworks that do not use the standard library's http.ResponseWriter
interface, there are request and response helpers available to make it easier to work with HTMX.
For example, with Echo:
package main
import (
"github.com/labstack/echo/v4"
"github.com/stackus/hxgo"
"github.com/stackus/hxgo/hxecho"
)
func main() {
// Create a new instance of Echo
e := echo.New()
// Define a route for "/"
e.GET("/", func(c echo.Context) error {
// use hxecho.IsHtmx to determine if the request is an HTMX request
if hxecho.IsHtmx(c) {
// do something
// Adds HTMX headers but does not set the Status Code
r, err := hxecho.Response(c,
// Continue to use the base htmx types and options
hx.Location("/foo"),
hx.StatusStopPolling,
)
if err != nil {
return err
}
// Set the HTMX status code here and response body
return c.String(r.StatusCode(), "Hello Echo")
}
})
// Start the server on port 8080
e.Logger.Fatal(e.Start(":8080"))
}
You will find request and response helpers for the following frameworks:
The Response
function for each library will return a default status of 200 if no status is set.
If you need to set a status code, you can use the Status
option.
Contributions are welcome! Please open an issue or submit a pull request. If at all possible, please provide an example with your bug reports and tests with your pull requests.
- If you find a bug, please open an issue.
- Include a clear description of the bug, steps to reproduce it, and any relevant logs or screenshots.
- Before creating a new issue, please check if it has already been reported to avoid duplicates.
- We're always looking to improve our library. If you have ideas for new features or enhancements, feel free to open an issue to discuss it.
- Clearly explain your suggestion and its potential benefits.
This project is licensed under the MIT License. See the LICENSE file for details.