Simple Form and Disabling Buttons on Submit by Default

rubyFebruary 23, 2014Dotby Justin Gordon

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)=(somebody@yahoo.com) 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.

image1

Immediately after clicking the SAVE button, the button disables and the text changes.

image2

Buttons

Example and API: button_tag

<%= button_tag "Checkout", data: { disable_with => "Please wait..." } %>

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:

  1. Opens up the FormBuilder class to add a method submit_with_override.
  2. 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.
  3. Calls alias_methodchain which makes is so that a call to submit actually calls submit_with_override and that method can call submit_without_override, which is the original submit method. The pattern of naming the methods with_override and without_override is part of the alias_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:

  1. Prevent Double Clicks in Rails Ajax Form
  2. Default Disable With for Simple Form
  3. How to Prevent Double Submit in Rails
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right