Last active
August 11, 2021 17:48
-
-
Save sylus/00d00f202af23c35749e9178ffbe8820 to your computer and use it in GitHub Desktop.
Double click prevention on form submission
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
From 2a8962fa547e1dbc25768ccabfc8ae01f64998a4 Mon Sep 17 00:00:00 2001 | |
From: sylus <[email protected]> | |
Date: Wed, 11 Aug 2021 13:38:37 -0400 | |
Subject: [PATCH] Double click prevention on form submission | |
--- | |
includes/form.inc | 24 ++++++++++++++ | |
misc/form.js | 62 ++++++++++++++++++++++++++++++++++++ | |
modules/system/system.module | 6 ++-- | |
3 files changed, 89 insertions(+), 3 deletions(-) | |
diff --git a/includes/form.inc b/includes/form.inc | |
index fa45f9e..1c86f20 100644 | |
--- a/includes/form.inc | |
+++ b/includes/form.inc | |
@@ -3380,6 +3380,30 @@ function form_process_actions($element, &$form_state) { | |
return $element; | |
} | |
+/** | |
+ * Processes a form button element. | |
+ * | |
+ * @param $element | |
+ * An associative array containing the properties and children of the | |
+ * form button. | |
+ * @param $form_state | |
+ * The $form_state array for the form this element belongs to. | |
+ * | |
+ * @return | |
+ * The processed element. | |
+ */ | |
+function form_process_button($element, &$form_state) { | |
+ // We normally want to add drupal.form so that the double submit protection | |
+ // can be added to the site, however, with the addition of | |
+ // javascript_always_use_jquery, this would make most pages with a login | |
+ // block or a search form have jquery always added, changing what people who | |
+ // enabled javascript_always_use_jquery would have expected. | |
+ if (variable_get('javascript_always_use_jquery', TRUE)) { | |
+ $element['#attached']['library'][] = array('system', 'drupal.form'); | |
+ } | |
+ return $element; | |
+} | |
+ | |
/** | |
* Processes a container element. | |
* | |
diff --git a/misc/form.js b/misc/form.js | |
index 259b84e..f7c4c15 100644 | |
--- a/misc/form.js | |
+++ b/misc/form.js | |
@@ -57,6 +57,68 @@ Drupal.behaviors.formUpdated = { | |
} | |
}; | |
+/** | |
+ * Prevents consecutive form submissions of identical form values. | |
+ * | |
+ * Repetitive form submissions that would submit the identical form values are | |
+ * prevented, unless the form values are different from the previously | |
+ * submitted values. | |
+ * | |
+ * This is a simplified re-implementation of a user-agent behavior that should | |
+ * be natively supported by major web browsers, but at this time, only Firefox | |
+ * has a built-in protection. | |
+ * | |
+ * A form value-based approach ensures that the constraint is triggered for | |
+ * consecutive, identical form submissions only. Compared to that, a form | |
+ * button-based approach would (1) rely on [visible] buttons to exist where | |
+ * technically not required and (2) require more complex state management if | |
+ * there are multiple buttons in a form. | |
+ * | |
+ * This implementation is based on form-level submit events only and relies on | |
+ * jQuery's serialize() method to determine submitted form values. As such, the | |
+ * following limitations exist: | |
+ * | |
+ * - Event handlers on form buttons that preventDefault() do not receive a | |
+ * double-submit protection. That is deemed to be fine, since such button | |
+ * events typically trigger reversible client-side or server-side operations | |
+ * that are local to the context of a form only. | |
+ * - Changed values in advanced form controls, such as file inputs, are not part | |
+ * of the form values being compared between consecutive form submits (due to | |
+ * limitations of jQuery.serialize()). That is deemed to be acceptable, | |
+ * because if the user forgot to attach a file, then the size of HTTP payload | |
+ * will most likely be small enough to be fully passed to the server endpoint | |
+ * within (milli)seconds. If a user mistakenly attached a wrong file and is | |
+ * technically versed enough to cancel the form submission (and HTTP payload) | |
+ * in order to attach a different file, then that edge-case is not supported | |
+ * here. | |
+ * | |
+ * Lastly, all forms submitted via HTTP GET are idempotent by definition of HTTP | |
+ * standards, so excluded in this implementation. | |
+ */ | |
+Drupal.behaviors.formSingleSubmit = { | |
+ attach: function () { | |
+ function onFormSubmit (e) { | |
+ if (e.isDefaultPrevented()) { | |
+ // Don't act on form submissions that have been prevented by other JS. | |
+ return; | |
+ } | |
+ var $form = $(e.currentTarget); | |
+ var formValues = $form.serialize(); | |
+ var previousValues = $form.attr('data-drupal-form-submit-last'); | |
+ if (previousValues === formValues) { | |
+ e.preventDefault(); | |
+ } | |
+ else { | |
+ $form.attr('data-drupal-form-submit-last', formValues); | |
+ } | |
+ } | |
+ | |
+ $('body').once('form-single-submit') | |
+ .delegate('form:not([method~="GET"])', 'submit.singleSubmit', onFormSubmit); | |
+ | |
+ } | |
+}; | |
+ | |
/** | |
* Prepopulate form fields with information from the visitor cookie. | |
*/ | |
diff --git a/modules/system/system.module b/modules/system/system.module | |
index 02785ef..d07ffdc 100644 | |
--- a/modules/system/system.module | |
+++ b/modules/system/system.module | |
@@ -335,7 +335,7 @@ function system_element_info() { | |
'#button_type' => 'submit', | |
'#executes_submit_callback' => TRUE, | |
'#limit_validation_errors' => FALSE, | |
- '#process' => array('ajax_process_form'), | |
+ '#process' => array('ajax_process_form', 'form_process_button'), | |
'#theme_wrappers' => array('button'), | |
); | |
$types['button'] = array( | |
@@ -344,7 +344,7 @@ function system_element_info() { | |
'#button_type' => 'submit', | |
'#executes_submit_callback' => FALSE, | |
'#limit_validation_errors' => FALSE, | |
- '#process' => array('ajax_process_form'), | |
+ '#process' => array('ajax_process_form', 'form_process_button'), | |
'#theme_wrappers' => array('button'), | |
); | |
$types['image_button'] = array( | |
@@ -352,7 +352,7 @@ function system_element_info() { | |
'#button_type' => 'submit', | |
'#executes_submit_callback' => TRUE, | |
'#limit_validation_errors' => FALSE, | |
- '#process' => array('ajax_process_form'), | |
+ '#process' => array('ajax_process_form', 'form_process_button'), | |
'#return_value' => TRUE, | |
'#has_garbage_value' => TRUE, | |
'#src' => NULL, | |
-- | |
2.32.0 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment