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)=(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:

1
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

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

Links

Example and API: link_to

1
%= 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:

1
2
3
4
5
6
7
8
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_method_chain 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.

1
2
3
4
5
6
7
.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:

1
$("#js-some-button").removeAttr("disabled").attr('value', 'ORIGINAL BUTTON TEXT');

This is a companion discussion topic for the original entry at http://www.railsonmaui.com//blog/2014/02/23/simple-form-and-disable-processing-by-default/

Hi,
disable_with doesn't work for me. It doesn't disable the button and submit's the button twice. is there anything else that I need to do apart from the below code.
<%= f.button "SAVE", class: "button", data: { disable_with: "Creating New Account..." } %>

Sounds like you're not including this in your application.js. This is what does the magic:

//= require jquery_ujs

Thanks a lot..
Do I need to download the jquery_ujs??

This doesn't work. I added the //= require jquery_ujs statement in the application.js, but still no progress. The button still submit the form_for more than once.

I'd read up on the links from this site: https://github.com/rails/jquer...
You probably want to put some breakpoints in the ujs code in the chrome debugger, and figure out why the code is not running. It could be that you've got some your own javascript that is interfering or maybe you're not including application.js in your application.html.haml.

Thank you soo much, Its Working :)
I think the trick was adding the require code in application.js file
Yesterday the same might didn't work, may be because the data was cached.. Just tried before trying other options and it works...

Just what I was looking for, but I had to rework it to be compatible with the new Ruby2+Rails5 (and possibly changed SimpleForm) conventions:

 module DisableDoubleClickOnSimpleForms
	def submit(field, options = {})
		if field.is_a?(Hash)
			field[:data] ||= {}
			field[:data][:disable_with] ||= 'Processing...'
		else
			options[:data] ||= {}
			options[:data][:disable_with] ||= 'Processing...'
		end
		super(field, options)
	end
end
SimpleForm::FormBuilder.prepend(DisableDoubleClickOnSimpleForms)

I also updated it so it didn’t override any existing data- attributes on the submit button.

Cheers!