Project

rails-htmx

0.02
No release in over a year
htmx integration for Rails
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 5.0
~> 2.1
~> 13.0
~> 1.3

Runtime

>= 5.2
 Project Readme

htmx for Rails

Add support for partial responses for htmx in Ruby on Rails controllers.

fullstack-1

Installation

Install the gem and add it to the application's Gemfile by executing:

$ bundle add rails-htmx

Installing htmx

Providing the assets for htmx is out of the scope of this library and the assets gems have been deprecated (along with Sprockets) by Rails.

Note: You might want to disable Turbo (and turbo-rails) if your application has it already installed.

Instead is recommended to install htmx from the official sources:

Via CDN

Add the script to your application.html.erb layout file:

<script src="https://unpkg.com/htmx.org@1.9.5" integrity="sha384-xcuj3WpfgjlKF+FXhSQFQ0ZNr39ln+hwjN3npfM9VBnUskLolQAcN80McRIVOPuO" crossorigin="anonymous"></script>

Check the htmx docs to make sure that you're using the latest version of the package

Using a JS Bundler (e.g jsbundling-rails or webpacker)

Add the htmx.org package using Yarn:

yarn add htmx.org

and then import it on your app/javascript/application.js:

import "htmx.org"

Using Import Maps (e.g importmap-rails)

Pin the dependency to the into the importmap:

bin/importmap pin htmx.org

and then import it on your app/javascript/application.js:

import "htmx.org"

Usage

By default, the rails-htmx prevents the render of the application layout in your controllers and instead returns only the yielded view for the requests that have the HX-Request header present.

Additionally, rails-htmx modify redirects in non-GET and non-POST requests to return 303 (See Other) as status code instead of 302 to handle XHR redirects correctly.

For more information about how to use htmx please consult the htmx docs.

Preventing htmx requests

In certain cases might want to return a full view (e.g in a login page) instead of a partial yielded view, you can do this calling the prevent_htmx! method in your controller actions or in an callback:

  before_action :prevent_htmx!, if: -> { some_condition? }

  def login
    prevent_htmx!
    # ...
  end

Custom Layouts

If you have additional content you want to always be returned with your htmx requests, you can override htmx_layout in your controller and specify a layout to render (by default, it's false)

class ApplicationController < ActionController::Base
  def htmx_layout
    'htmx'
  end
end

The hx helper for views.

You can use the hx helper to simplify the generation of the attributes in your views, e.g:

<%= button_to "Update post", @post, method: :delete, data: { "hx-patch": url_for(@post), "hx-swap": "outerHTML", "hx-target": "body" } %>

can be rewritten using the hx helper:

<%= button_to "Update post", @post, method: :delete, data: hx(patch: url_for(@post), swap: "outerHTML", target: "body") %>

Tips

Those are some tips for using htmx with Rails.

Add the CSRF Token to the htmx requests

Add the X-CSRF-Token to the hx-headers attributes in your <body> tag so it's added by default in XHR requests done by htmx:

<body hx-headers='{"X-CSRF-Token": "<%= form_authenticity_token %>"}'>

Using the data prefix

You can use the data- prefix to make easier adding the htmx attributes with the Rails helpers:

<%= link_to "Home", root_path, data: { "hx-swap": "outerHTML" } %>

Boosting Rails helpers

You can use the default Rails helpers without modifications in your markup with the htmx Boosting feature:

<div hx-boost="true">
  <%= link_to "New post", new_post_path %>
</div>

Redirect after PUT/PATCH/DELETE (30x status)

Add the hx-target="body", hx-swap="outerHTML", and hx-push-url="true" to update the body with the content of the document retrieved after redirection and push the new URL into the browser location history.

<%= button_to "Destroy post", @post, method: :delete, data: { "hx-delete": url_for(@post), "hx-swap": "outerHTML", "hx-target": "body" "hx-push-url": "true" } %>

XHR errors handling

by default, non-200 responses are not swapped into the DOM, this behavior can be changed using one of the next options:

  1. Using the response-targets extension and setting the target for the status code, e.g:

    <button hx-post="/register"
            hx-target="#response-div"
            hx-target-5*="#serious-errors"
            hx-target-422="#response-div"
            hx-target-404="#not-found">
        Register!
    </button>

    or using Rails helpers:

    <%= button_to "Register", register_path, data:
         hx(post: register_path, target: "#response-div", "target-5*": "#serious-errors", "target-422": "#response-div" , "target-404": "#not-found") %>

    For default Rails forms your might want to set hx-target-422 to the same value as hx-target in that way the form will be swapped with the new form with validation error messages. Check the extension for more details and examples.

  2. Handle the htmx:beforeSwap event in order to modify the swap behavior:

    document.body.addEventListener('htmx:beforeSwap', function(evt) {
     if(evt.detail.xhr.status === 404){
         alert("Error: Could Not Find Resource");
     } else if(evt.detail.xhr.status === 422){
         // allow 422 responses to swap as we are using this as a signal that
         // a form was submitted with bad data and want to rerender with the
         // errors
         evt.detail.shouldSwap = true;
    
         // set isError to false to avoid error logging in console
         evt.detail.isError = false;
     }
    });

Check the htmx docs for details

Using htmx extensions in module environments.

importmap-rails

In order to get the extensions working you need to pin the extensions in your config/importmap.rb:

# config/importmap.rb
# ...
pin "htmx.org", to: "https://unpkg.com/htmx.org@1.9.5"
pin "htmx.org/dist/ext/", to: "https://unpkg.com/htmx.org@1.9.5/dist/ext/"

Then you can load extensions in your application.js:

// app/javascript/application.js
import "htmx.org"
import "htmx.org/dist/ext/method-override"
import "htmx.org/dist/ext/ajax-header"

jsbundling-rails

Using rollup does not require any changes.

For esbuild and webpack you need to load htmx globally before of importing the extensions, this can be done creating a custom file and injecting htmx to the window scope on it:

// app/javascript/application.js
import "./htmx.js"
import "htmx.org/dist/ext/method-override"
import "htmx.org/dist/ext/ajax-header"
// app/javascript/htmx.js
import htmx from "htmx.org"
window.htmx = htmx

License

rails-htmx is released under the MIT License.