How to Create Disabled Spinner Button in Rails
How to Create Disabled Button with Spinner in Rails
To construct meaningful user experiences we want to ensure that views provide feedback related to state changes in system. This post assumes you have already added Bootstrap to your application.
Assume there is a model called Note
with a state
column of type integer
backed by a Rails enum and a NotesController
with an archive
action called by the view.
class Note < Application Record
enum state [:started, :pending, :archived]
def archive!
sleep(10)
archived!
end
end
Figure 1: Clicking the Archive button will cause the long running process to start
The NotesController#archive
action is shown in the code snippet below.
class NotesController < ApplicationController
before_action :set_note, only: [:archive]
def archive
@note.archive!
respond_to do |format|
format.html { redirect_to @note, notice: "Note was archived" }
end
end
private
def set_note
@note = Note.find(params[:id])
end
end
Calling the NotesController#archive
action will invoke the note.archive!
method beginning a long running process to transition the note from its current state to the archived
state. The desired experience to communicate this to the user by replacing the button contents with a pending spinner while the operation is ongoing.
To prevent UI blocking, we will update the notes/show.html.erb
to make a remote AJAX request and disable the appropriate components using conditional rendering logic for the spinner:
<!-- snipped note HTML -->
<% if display_in_progress?(@note) %>
<%= loading_spinner_div %> Action in progress...
<% else %>
<%= button_to 'Archive', note_archive_path(@note),
method: :post,
class: 'btn btn-info',
remote: true,
data: { confirm: "Are you sure?", disable_with: loading_spinner_div} unless @note.archived? %>
<% end %>
In order to make this work, we need to add the two helper methods we added above to our application’s NotesHelper
module.
module NotesHelper
def display_in_progress?(note)
note.pending?
end
def loading_spinner_div
content_tag(:div, class: "spinner-grow spinner-grow-sm", role: "status") do
content_tag(:span, class: "sr-only") do
"Working..."
end
end
end
end
The display_in_progress?
method captures and returns a boolean to indicate if a set of UI elements should be displayed. The second method loading_spinner_div
returns a reusable HTML element that is styled with Bootstrap styling to display an animated loading dot to indicate that something is happening.
When the user clicks the “Archive” button, they will issue a remote AJAX request to the controller and see a disabled button with a spinner now.