Simple Form and Disabling Buttons on Submit by Default

railsFebruary 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)=([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.

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

Closing Remark

Could your team use some help with topics like this and others covered by ShakaCode's blog and open source? We specialize in optimizing Rails applications, especially those with advanced JavaScript frontends, like React. We can also help you optimize your CI processes with lower costs and faster, more reliable tests. Scraping web data and lowering infrastructure costs are two other areas of specialization. Feel free to reach out to ShakaCode's CEO, Justin Gordon, at [email protected] or schedule an appointment to discuss how ShakaCode can help your project!
Are you looking for a software development partner who can
develop modern, high-performance web apps and sites?
See what we've doneArrow right