DEV Community

Dale Zak
Dale Zak

Posted on • Updated on • Originally published at dalezak.Medium

Lazy Load Form Fields In Rails With HTMX

HTMX is small powerful library that lets you trigger HTTP AJAX requests simply by adding attributes to your HTML tags without having to write any Javascript.

htmx_logo_800

In a previous post I wrote how to Lazy Load Form Fields In Rails Using StimulusReflex. I like Stimulus and StimulusReflex both a lot, paired together they are incredibly powerful set of libraries for Rails.

However recently HTMX caught my attention, “a library that allows you to access modern browser features directly from HTML, rather than using Javascript”. So I thought I would try implementing the same lazy loading of form fields using HTMX for comparison.

The /app/views/users/_form.html.erb looks the same as before.

<%= form_with(model: user, url: [user], local: true) do |form| -%>
<div class="card">
  <ul class="list-group list-group-flush">
    <li class="list-group-item">
      <%= form.text_field :name, label: "Name", placeholder: "Enter name", required: true %>
    </li>
    <li class="list-group-item">
      <%= form.email_field :email, label: "Email", placeholder: "Enter email", required: true %>
    </li>
  </ul>
  <div class="card-footer">
    <%= form.submit "Save", class: "btn btn-secondary float-right" %>
  </div>
</div>
<% end %>
Enter fullscreen mode Exit fullscreen mode

Now I include the placeholder with HTMX triggers.

<li class="list-group-item">
  <label class="required">Repositories</label>
  <div hx-get="/users/repositories" hx-trigger="load delay:1s" hx-swap="innerHTML" hx-headers='{"X-Requested-With": "XMLHttpRequest"}'>
    <i>Loading...</i>
  </div>
</li>
Enter fullscreen mode Exit fullscreen mode

Some important HTMX attributes here:

  • hx-get makes GET request to /users/repositories endpoint
  • hx-trigger triggered upon the element loading with 1 second delay
  • hx-swap replacements the inner html of this element
  • hx-headers pass some additional headers so Rails knows request.xhr? is true

In your routes.rb, add the new endpoint.

resources :users do
  collection do
    get "repositories", as: :repositories
  end
end
Enter fullscreen mode Exit fullscreen mode

Then add the repositories action to the UsersController.

def repositories
  user = User.new
  user.repositories.build
  @form = ActionView::Helpers::FormBuilder.new(:user, user, view_context, {})
  github = Octokit::Client.new(access_token: current_user.token)
  github.auto_paginate = true
  @repositories = github.repos({}, query: { type: "any", sort: "asc" })
  respond_to do |format|
    format.html { render layout: request.xhr? == false }
  end
end
Enter fullscreen mode Exit fullscreen mode

Now add the /app/views/users/repositories.html.erb.

<% @repositories.each do |repository| %>
  <%= @form.fields_for :repositories, repository do |ff| %>
    <div class="form-check form-check-inline">
      <%= ff.check_box :checked, class: "form-check-input" %>
      <%= ff.label :checked, repository.full_name.downcase, class: "form-check-label mr-2 mb-1" %>
    </div>
    <%= ff.hidden_field :uid, value: repository.id %>
    <%= ff.hidden_field :name, value: repository.name %>
    <%= ff.hidden_field :path, value: repository.full_name %>
    <%= ff.hidden_field :url, value: repository.html_url %>
  <% end %>
<% end %>
Enter fullscreen mode Exit fullscreen mode

That’s it! Similar to the StimulusReflex version it asynchronously loads the repositories.

Two important notes.

  1. HTMX does not set the X-Requested-With header by default, so Rails doesn’t know it’s xhr request. So setting this header lets you use request.xhr? in your controller which is helpful to know when to render layouts.
  2. If you don’t want to include hx-headers in every request, you can instead set it on a parent element (even the body tag) which will cause the header to be inherited.
<body hx-headers='{"X-Requested-With": "XMLHttpRequest"}'>
Enter fullscreen mode Exit fullscreen mode

HTMX is a very small yet incredibly powerful library! It has a lot of potential to creating reactive applications without writing any Javascript while still leveraging your existing Rails partials on the backend. 🚀

Top comments (0)