What is happening?
- Form is submitted
- rails-ujs disables the button (the
data-disable-with
behaviour)
- The form request succeeds
- rails-ujs re-enables the button
- turbolinks-rails makes a request to the redirect location (which might be a slow one, leaving the button in an enabled state)
Solution
We'll need to re-disable the button after step 4. To do this, we'll listen out for the ajax:success
event, and disable it using setTimeout
. This ensures that it will be disabled after Rails has done its thing. (You could use requestAnimationFrame
instead of setTimeout
, but it is not as widely supported.)
To prevent the button from being cached in a disabled state, we'll re-enable it before it is cached. (Note the use of one
rather than on
to prevent the before-cache handler executing more than once.)
I noticed you were using jQuery and jquery-ujs, so I will use functions from those libraries in the code below. Include this somewhere in your main JavaScript file.
jquery-ujs
;(function () {
var $doc = $(document)
$doc.on('submit', 'form[data-remote=true]', function () {
var $form = $(this)
var $button = $form.find('[data-disable-with]')
if (!$button.length) return
$form.on('ajax:complete', function () {
// Use setTimeout to prevent race-condition when Rails re-enables the button
setTimeout(function () {
$.rails.disableFormElement($button)
}, 0)
})
// Prevent button from being cached in disabled state
$doc.one('turbolinks:before-cache', function () {
$.rails.enableFormElement($button)
})
})
})()
rails-ujs / jQuery
;(function () {
var $doc = $(document)
$doc.on('ajax:send', 'form[data-remote=true]', function () {
var $form = $(this)
var $button = $form.find('[data-disable-with]')
if (!$button.length) return
$form.on('ajax:complete', function () {
// Use setTimeout to prevent race-condition when Rails re-enables the button
setTimeout(function () {
$button.each(function () { Rails.disableElement(this) })
}, 0)
})
// Prevent button from being cached in disabled state
$doc.one('turbolinks:before-cache', function () {
$button.each(function () { Rails.enableElement(this) })
})
})
})()
rails-ujs / vanilla JS
Rails.delegate(document, 'form[data-remote=true]', 'ajax:send', function (event) {
var form = event.target
var buttons = form.querySelectorAll('[data-disable-with]')
if (!buttons.length) return
function disableButtons () {
buttons.forEach(function (button) { Rails.disableElement(button) })
}
function enableButtons () {
buttons.forEach(function (button) { Rails.enableElement(button) })
}
function beforeCache () {
enableButtons()
document.removeEventListener('turbolinks:before-cache', beforeCache)
}
form.addEventListener('ajax:complete', function () {
// Use setTimeout to prevent race-condition when Rails re-enables the button
setTimeout(disableButtons, 0)
})
// Prevent button from being cached in disabled state
document.addEventListener('turbolinks:before-cache', beforeCache)
})
Note that this will disable buttons until the next page load on all data-remote
forms with a data-disable-with
button. You may want to change the jQuery selector to only add this behaviour to selected forms.
Hope that helps!
data-remote=true
attribute on the form, as well as thedata-disable-with
attribute on the button, which I feel is sufficient from a code perspective. It was missing a clear question, which I hope I have helped to clarify. – Wolfson