Cover
htmx Configuration

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.

Table of Contents

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 is innerHTML which 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
Specification

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!