Converting action buttons into loading buttons is a common way for AJAX applications indicate that it’s waiting on the server to verify an action and respond.
Built with jQuery and CoffeeScript, this page reflects the web development practices of its time rather than the Hotwire-based approach I use today.
Loading buttons are a common user-experience concept used to indicate the state between starting and completing an action. They are most useful when server-side logic decides what happens next, such as displaying validation errors or a success message.
Single page applications may bypass loading buttons to appear more responsive since they use client-side logic, but loading buttons still play a large role in the standard MVC Rails application.
represents an action for the user to perform.
indicates the action in processing via a loading icon and disabled button.
Click the buttons below for a live demonstration.
This webpage is not hosted by Rails, so the above UX is just a simulation.
A "Log out" button would normally redirect you to the sign-in page.
data-remote=true and data-loading=true attributes.data-remote=true instructs jquery_ujs.js to begin an ajax request.data-loading=true instructs a jQuery listener to disable the button and insert a spinner.# config/routes.rb
resources :sessions, only: %w[new destroy]
# controllers/sessions_controller.rb
class SessionsController < ApplicationController
def destroy
reset_session
end
end
# helpers/sessions_helper.rb
module SessionsHelper
def link_to_log_out
link_to_with_icon(
"log-out",
"Log out",
url,
method: "DELETE",
remote: true,
class: "btn btn-primary",
data: { loading: true },
)
end
end
# helpers/layout_helper.rb
module LayoutHelper
def icon_tag(name, extra_class = nil)
lucide_icon(name, class: ["icon", extra_class].compact.join(" "))
end
def icon_with_spinner(name)
idle = content_tag(:span, icon_tag(name), data: { icon: "idle" })
spinner = content_tag(:span, icon_tag("loader-circle", "icon-spin"), class: "hidden", data: { icon: "loading" })
idle + spinner
end
def link_to_with_icon(icon, title, url, options = {})
title_with_icon = icon_with_spinner(icon) << " ".html_safe << h(title)
link_to(title_with_icon, url, options)
end
end
# Gemfile
gem "jquery-rails"
gem "lucide-rails"
// assets/javascript/application.js
//
// jquery_ujs allows us to use 'data-remote',
// 'data-type', and 'data-method' attributes
//
//= require jquery
//= require jquery_ujs
//= require loading
# assets/javascripts/loading.coffee
$ ->
# Insert a spinner and disable the button
$(document).on('click', 'a[data-loading]', (e) ->
$(this).attr('disabled', 'disabled')
$(this).find('[data-icon=idle]').addClass('hidden')
$(this).find('[data-icon=loading]').removeClass('hidden')
)
<%# views/layouts/application.html.erb %>
<%= link_to_log_out %>
/* views/sessions/destroy.js.erb */
Turbolinks.visit("<%= new_session_path %>");
The Rails code is rather boring, but two important user-experience concepts occur on the frontend:
For further reading, my AJAX Toggle Buttons article builds on these concepts to implement an on/off feature for buttons.