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/hxgoThen 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 theIsBoostedfunction to determine if the request is a boosted requestHX-Current-URL: Use theGetCurrentUrlfunction to get the current URL of the requestHX-History-Restore-Request: Use theIsHistoryRestoreRequestfunction to determine if the request is a history restore requestHX-Prompt: Use theGetPromptfunction to get the prompt value of the requestHX-Request: Use theIsRequestorIsHTMXfunctions to determine if the request is an HTMX requestHX-Target: Use theGetTargetfunction to get the target value of the requestHX-Trigger-Name: Use theGetTriggerNamefunction to get the trigger name of the requestHX-Trigger: Use theGetTriggerfunction 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 theLocationoption with a variable number of properties to set the location header. See the Location section for more details.HX-Push-Url: Use thePushURLoption to push a new URL into the browser historyHX-Redirect: Use theRedirectoption to redirect the browser to a new URLHX-Refresh: Use theRefreshoption to refresh the browserHX-Replace-Url: Use theReplaceUrloption to replace the current URL in the browser historyHX-Reswap: Use theReswapoption or one of theSwap*constants to specify how the response will be swapped. See the Reswap section for more details.HX-Retarget: Use theRetargetoption with a CSS selector to redirect the response to a new elementHX-Reselect: Use theReselectoption with a CSS selector to designate a different element in the response to be usedHX-Trigger: Use theTriggeroption to trigger client-side events. See the Trigger section for more details.HX-Trigger-After-Settle: Use theTriggerAfterSettleoption to trigger client-side events after the response has settled. See the Trigger section for more details.HX-Trigger-After-Swap: Use theTriggerAfterSwapoption 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: TheSourceproperty is used to set the source element of the location header.Event: TheEventNameproperty is used to set the name of the event of the location header.Note: This property is called
EventNameso that it does not conflict with theEventproperty used by theTriggeroption.Handler: TheHandlerproperty is used to set the handler of the location header.Target: TheTargetproperty is used to set the target of the location header.Swap: TheSwapproperty is used to set the swap of the location header. The value may be a string or any of theSwap*constants.Values: TheValuesproperty is used to set the values of the location header. The value may be anything, but it is recommended to use amap[string]anyor struct with JSON tags.Headers: TheHeadersproperty is used to set the headers of the location header. The value needs to be amap[string]string.Select: TheSelectproperty 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 toinnerHTMLSwapOuterHtml: Sets the HX-Reswap response header toouterHTMLSwapBeforeBegin: Sets the HX-Reswap response header tobeforebeginSwapAfterBegin: Sets the HX-Reswap response header toafterbeginSwapBeforeEnd: Sets the HX-Reswap response header tobeforeendSwapAfterEnd: Sets the HX-Reswap response header toafterendSwapDelete: Sets the HX-Reswap response header todeleteSwapNone: 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:trueto enable the use of the View Transition APISwap: Used with atime.Durationto set the swap delaySettle: Used with atime.Durationto set the settle delayIgnoreTitle: AddsignoreTitle:trueto 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.
