Simple Form and Disabling Buttons on Submit by Default
TLDR
Here's an easy way to have all your SimpleForm submit buttons default to
setting data-disable-with
so that you don't get errors when users
double click on submit buttons. If you've gotten a few
ActiveRecord::RecordNotUnique
errors that were hard to reproduce, then
here's your solution, with our without SimpleForm. Additionally, using
data-disable-with
provides the user with nice feedback once a button
is clicked.
ActiveRecord::RecordNotUnique Error!
If you're using Devise, and you get a ActiveRecord::RecordNotUnique
error when a new user is signing up, where do you look?
An ActiveRecord::RecordNotUnique occurred in registrations#create:
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint
"index_users_on_email" DETAIL: Key (email)=([email protected]) already
exists. : INSERT INTO "users" ("address", "city", "confirmation_sent_at",
"confirmation_token", "created_at", "default_location_id", "email",
"encrypted_password", "first_name", "last_name", "mobile", "role", "state",
"updated_at", "zip_code") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,
$12, $13, $14, $15) RETURNING "id"
At first, I was concerned that my unique index on my users table is not case insensitive. I started going down the road of converting my normal unique index on users.email to this index:
CREATE UNIQUE INDEX users_email_ci_idx ON users ((lower(email)));
However, I soon figured out that Devise was already always saving email
in the database in lower case via a before_validation
hook.
So then I tried to double click the SAVE
button, and, BOOM, I got
the same error.
data-disable-with='Processing…'
A little bit of googling quickly revealed some handy rails techniques
disabling a submit button after being clicked, namely the setting of
attribute data-disable-with: "Some Message…" on both links and buttons.
This works nicely to fix the double submit RecordNotUnique error, and it
provides some sweet user feedback upon clicking a button. Here's an
example of a SAVE
button.
Immediately after clicking the SAVE
button, the button disables and
the text changes.
Buttons
Example and API: button_tag
<%= button_tag "Checkout", data: { disable_with => "Please wait..." } %>
Links
Example and API: link_to
<%= link_to "Profile", profile_path(@profile), data: { disable_with: "Processsing..." } %>
SimpleForm Submit Buttons
Even better, this can be done in one place for all SimpleForm submit buttons!
In a file like config/simple_form.rb
, place this initialization code:
SimpleForm::FormBuilder.class_eval do
def submit_with_override(field, options = {})
data_disable_with = { disable_with: 'Processing...' }
options[:data] = data_disable_with.merge(options[:data] || {})
submit_without_override(field, options)
end
alias_method_chain :submit, :override
end
What the bit of code above does is that it:
- Opens up the FormBuilder class to add a method
submit_with_override
. - Modifies options hash's :data element, setting a default value for
key
disable_with
that will not apply if there's already a value there, thus allowing the default to be overridden by any individual button. - Calls
alias_methodchain
which makes is so that a call to submit actually calls
submit_with_override
and that method can callsubmit_without_override
, which is the originalsubmit
method. The pattern of naming the methodswith_override
andwithout_override
is part of thealias_method_chain
call. Pretty darn cool!
Here's a sample sign-up form that overrides the default "Processing…"
label when the SAVE
button is clicked.
.box.clearfix.box-last
= simple_form_for resource, as: resource_name, url: registration_path(resource_name), html: { class: ""} do |f|
= f.error_notification
= f.input :first_name, required: false, autofocus: true, label_html: { class: "label-required"}, input_html: {class: ".col-md-4" }
= f.input :last_name, required: false, label_html: { class: "label-required"}, input_html: {class: ".col-md-4" }
= f.input :email, required: false, label_html: { class: "label-required"}, input_html: {class: ".col-md-4" }
= f.button :submit, "SAVE", class: "submit", data: { disable_with: "Creating New Account..." }
Now go and click on some of your submit buttons, and they will all
disable and display "Processing…". On a remote form that returned
js.erb
, I had to send back this line to reset the submit button:
$("#js-some-button").removeAttr("disabled").attr('value', 'ORIGINAL BUTTON TEXT');
References
Stack Overflow Discussions: