A haiku from the htmx team:
javascript fatigue: longing for a hypertext already in hand
…and from Hypermedia Systems:
The goal of [htmx] is not less JavaScript, but less code, more readable and hypermedia-friendly code.
This gem provides native Ruby support for the htmx JavaScript library so you can build sophisticated web applications using pure Hypermedia REST APIs while avoiding unnecessary bloat and complexity common with the JavaScript ecosystem. By building upon the original foundations of Hypermedia REST APIs, you can build rich web applications with no additional JavaScript!
-
A strong understanding of Hypermedia Systems.
-
htmx.
-
Ruby.
To install with security, run:
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install htmx --trust-policy HighSecurityTo install without security, run:
gem install htmxYou can also add the gem directly to your project:
bundle add htmxOnce the gem is installed, you only need to require it:
require "htmx"One of the first tasks, when working with the htmx library, is building htmx specific HTML attributes. This can be accomplished by using the .[] method. Example:
HTMX[target: "closest .task", delete: "/tasks/1"]
# {
# "data-hx-target" => "closest .task",
# "data-hx-delete" => "/tasks/1"
# }Notice the appropriate htmx data-hx* attributes are built which are compatible with the htmx library while not having to manually prefix each one of these attributes with the hx-data- prefix. You can use symbols, strings, or a mix of both as well.
As shown above, building htmx attributes takes minimal effort but if you’d prefer to only use the HTML hx- prefix, which the htmx library supports, you can customize by using the following:
prefixer = HTMX::Prefixer.new "hx"
prefixer.call(target: "closest .task", delete: "/tasks/1")
# {
# "hx-target" => "closest .task",
# "hx-delete" => "/tasks/1"
# }As you can see, only hx-* keys are produced. By the way, the HTMX::Prefixer is called when using the htmx .[] as shown earlier. This means the following are equivalent:
HTMX[delete: "/tasks/1"]
HTMX::Prefixer.new.call delete: "/tasks/1"
HTMX::Prefixer.new("data-hx").call delete: "/tasks/1"All three of the above will produce the same output which means you’ll most likely want to use the .[] method since it has the shortest syntax. If you attempt to use an unsupported prefix, you’ll get an error:
HTMX::Prefixer.new :bogus
# Invalid prefix: :bogus. Use: "hx" or "data-hx". (HTMX::Error)Some htmx attributes use dashes. For those situations, you can use strings for keys or underscored symbols to produce the correct htmx syntax. Here’s an example using both a string and symbol for keys:
HTMX["swap-oob" => true, push_url: "/demo/123"]
# {
# "data-hx-swap-oob" => true,
# "data-hx-push-url" => "/demo/123"
# }When working with HTTP requests/responses, especially HTTP headers, there are a couple of methods that can parse and make the data easier to work with. Here’s how to use them.
The request object allows you to obtain a Data object to interact with when parsing htmx HTTP request headers. Example:
HTMX.request
# #<data HTMX::Headers::Request:0x00000a10
# boosted = nil,
# current_url = nil,
# history_restore_request = nil,
# request = nil,
# request_type = nil,
# source = nil,
# target = nil
# >Notice you get a Data instance where all members have the HX- prefix removed while each value defaults to nil. Even better — and more practical — is you can ask the request object to parse the incoming HTTP headers directly and give you exactly what you need:
HTMX.request "HTTP_HX_BOOSTED" => "true",
"HTTP_HX_CURRENT_URL" => "/demo",
"HTTP_HX_HISTORY_RESTORE_REQUEST" => "false",
"HTTP_HX_REQUEST" => "true",
"HTTP_HX_REQUEST_TYPE" => "partial",
"HTTP_HX_SOURCE" => "button",
"HTTP_HX_TRIGGER" => "demo"
# #<data HTMX::Headers::Request:0x00000a80
# boosted = "true",
# current_url = "/demo",
# history_restore_request = "false",
# request = "true",
# request_type = "partial",
# source = "button",
# target = nil
# >As you can see, this method only plucks out the htmx specific headers which may or may not have values. Extra header keys, which are not specific to htmx, are ignored.
As an added convenience, you can use predicate methods for boolean values. Example:
headers = HTMX.request "HTTP_HX_BOOSTED" => "true",
"HTTP_HX_CURRENT_URL" => "/demo",
"HTTP_HX_HISTORY_RESTORE_REQUEST" => "false",
"HTTP_HX_REQUEST" => "true",
"HTTP_HX_REQUEST_TYPE" => "partial",
"HTTP_HX_SOURCE" => "button",
"HTTP_HX_TRIGGER" => "demo"
headers.boosted? # true
headers.history_restore_request? # false
headers.request? # trueDue to the HTML.request delegating to the HTMX::Headers::Request, this means you can use the object directly. Specifically, you can obtain the record, key, or header depending on your needs. Example:
HTMX::Headers::Request.for(**headers) # Identical to `HTMX.request`.
HTMX::Headers::Request.key_for "HTTP_HX_CURRENT_URL" # :current_url
HTMX::Headers::Request.header_for :current_url # "HTTP_HX_CURRENT_URL"Should you not want to use HTMX::Headers::Request, you can use the request predicate methods instead. Example:
headers = {}
HTMX.request! headers, boosted: true, source: "button"
# {
# "HTTP_HX_BOOSTED" => true,
# "HTTP_HX_SOURCE" => "button"
# }
HTMX.request? headers, :source, "button" # true
HTMX.request? headers, :source, /but/ # true
HTMX.request? headers, :source, "input" # false
HTMX.request? headers, :source, /input/ # false💡 HTMX.request! mutates your original headers. Unknown attributes are merged as is.
The response object allows you to obtain a Data object to interact with when parsing htmx HTTP response headers. Example:
HTMX.response
# #<data HTMX::Headers::Response:0x00001830
# location = nil,
# push_url = nil,
# redirect = nil,
# refresh = nil,
# replace_url = nil,
# reselect = nil,
# reswap = nil,
# retarget = nil,
# trigger = nil
# >Notice you get a Data instance where all members have the HX- prefix removed while each value defaults to nil. Even better — and more practical — is you can ask the response object to parse the incoming HTTP headers directly and give you exactly what you need:
HTMX.response "HX-Location" => "/",
"HX-Push-Url" => "/demo",
"HX-Redirect" => "/demo",
"HX-Refresh" => "true",
"HX-Replace-Url" => "/demo",
"HX-Reselect" => "#demo",
"HX-Reswap" => "none",
"HX-Retarget" => ".demo",
"HX-Trigger" => "demo"
# #<data HTMX::Headers::Response:0x00001850
# location = "/",
# push_url = "/demo",
# redirect = "/demo",
# refresh = "true",
# replace_url = "/demo",
# reselect = "#demo",
# reswap = "none",
# retarget = ".demo",
# trigger = "demo"
# >As you can see, this method only plucks out the htmx specific headers which may or may not have values. Extra header keys, which are not specific to htmx, are ignored.
There is also a refresh predicate method that’ll answer a boolean for convenience. Example:
headers = HTMX.response "HX-Refresh" => "true"
headers.refresh? # trueDue to HTML.response delegating to the HTMX::Headers::Response, this means you can use the object directly. Specifically, you can obtain the record, key, or header depending on your needs. Example:
HTMX::Headers::Response.for(**headers) # Identical to `HTMX.response`.
HTMX::Headers::Response.key_for "HX-Location" # :location
HTMX::Headers::Response.header_for :location # "HX-Location"Should you not want to use HTMX::Headers::Response, you can use the response predicate methods instead. Example:
headers = {}
HTMX.response! headers, location: "/", push_url: "/demo"
# {
# "HX-Location" => "/",
# "HX-Push-Url" => "/demo"
# }
HTMX.response? headers, :push_url, "/demo" # true
HTMX.response? headers, :push_url, /emo/ # true
HTMX.response? headers, :push_url, "/about" # false
HTMX.response? headers, :push_url, /about/ # false💡 HTMX.response! mutates your original headers. Unknown attributes are merged as is.
To contribute, run:
git clone https://github.com/bkuhlmann/htmx
cd htmx
bin/setupYou can also use the IRB console for direct access to all objects:
bin/console-
Built with Gemsmith.
-
Engineered by Brooke Kuhlmann.