Often, you’ll want to customize the default htmx configuration. There are a couple ways you can do this:
-
Globally.
-
Per page.
We’ll discuss each including how you can make short order of these configuration changes in your Ruby applications. For the purposes of this discussion, we’ll use Hanami as our web application but what you see here can be applied to your specific web stack.
With the above in mind, let’s get started by first setting up our htmx provider.
Provider
As documented in Hanami Containers, the best way to have access to the HTMX gem is to implement a corresponding provider. Here’s what this looks like:
# demo/config/providers/htmx.rb
Hanami.app.register_provider :htmx do
prepare { require "htmx" }
start do
register :htmx, HTMX
register :htmx_defaults,
{"allowScriptTags" => false, "defaultSwapStyle" => "outerHTML"}
end
end
This allows you to quickly inject htmx as a dependency while providing a default configuration which can be customized further if desired. There’s a lot of htmx Configuration settings you can tweak but what is shown above is a good starting point for a couple reasons:
-
allowScriptTags: Disabled as a security precaution to prevent random JavaScript being loaded and executed (especially if content is user created). -
defaultSwapStyle: The default swap style isinnerHTMLwhich means content within the body of your HTML element will be swapped. In practice, this isn’t want you want as you generally want to swap out the entire element itself (including it’s content) when making htmx requests.
Context
With the above provider in hand, we can now inject our htmx dependencies into our view context, via include Deps[:htmx, :htmx_defaults], as shown below:
# demo/app/views/context.rb
# auto_register: false
require "hanami/view"
module Demo
module Views
class Context < Hanami::View::Context
include Deps[:htmx, :htmx_defaults]
def htmx? = htmx.request? request.env, :request, "true"
def htmx_configuration
content_for(:htmx_merge).then { it ? htmx_defaults.merge(it) : htmx_defaults }
.to_json
end
end
end
end
The following illustrates how simple it is to test the implementation since you can use an instance of Hanami::Action::Request for requests and plain old hashes for configuration merging.
# demo/spec/app/views/context_spec.rb
# frozen_string_literal: true
require "hanami_helper"
RSpec.describe Demo::Views::Context do
subject(:view_context) { described_class.new }
describe "#htmx?" do
it "answers true when htmx request" do
request = Hanami::Action::Request.new env: {"HTTP_HX_REQUEST" => "true"}, params: {}
view_context.instance_variable_set :@request, request
expect(view_context.htmx?).to be(true)
end
it "answers false when not a htmx HTTP request" do
request = Hanami::Action::Request.new env: {}, params: {}
view_context.instance_variable_set :@request, request
expect(view_context.htmx?).to be(false)
end
end
describe "#htmx_configuration" do
it "answers default configuration" do
expect(view_context.htmx_configuration).to eq(
{"allowScriptTags" => false, "defaultSwapStyle" => "outerHTML"}.to_json
)
end
it "answers custom configuration" do
view_context.content_for :htmx_merge, "defaultSwapStyle" => "innerHTML"
expect(view_context.htmx_configuration).to eq(
{"allowScriptTags" => false, "defaultSwapStyle" => "innerHTML"}.to_json
)
end
end
end
The #htmx? method leverages the HTMX gem to check if the current HTTP request — especially handy within Hanami Actions — is an htmx request or not. This allows you have access to this information within your Hanami Views. Quite powerful.
The #htmx_configuration method leverages the htmx_defaults dependency by merging it with whatever is provided by the htmx_merge hash and then immediately converting this into JSON which is the format that htmx needs in order to configure itself. This allows you to use the content_for Hanami Views helper at the top of any template to alter htmx’s configuration for specific pages. For example, let’s say you need to alter the swap style for a specific page. You’d only need to add the following to the top of one of your HTML templates:
<%= content_for :htmx_merge, {defaultSwapStyle: "innerHTML"} %>
That’s a lot of power for a single line of code.
Layout
With all of the above in place, the only thing left to do is configure your application layout (i.e. app.html.erb) to load your custom htmx configuration for all pages:
<!-- demo/app/templates/layouts/app.html.erb -->
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Truncated for brevity... -->
<%= tag.meta name: "htmx-config", content: htmx_configuration %>
<!-- Truncated for brevity... -->
</head>
<!-- Truncated for brevity... -->
</html>
The above will ensure htmx is properly configured for every page using your application layout (unless overwritten by the content_for helper with the htmx_merge key). 🎉
Conclusion
You’ve learned how to configure htmx that best suites the needs of your web application either globally (default) or per page (specific). Even better, you’ve learned how you can leverage the HTMX gem to check if you’re dealing with htmx requests which allows you to provide specialized behavior for htmx and non-htmx requests. Finally, you’ve learned how you can apply all of these configuration change via your application layout for federated behavior.
Enjoy!
