#202 - Prevent Double Form Submissions

Prevents users from accidentally submitting forms twice.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

152 lines
Paste this into Webflow
<!-- 💙 MEMBERSCRIPT #202 v0.1 💙 PREVENT DOUBLE FORM SUBMISSION -->

<script>
(function() {
  'use strict';

  // Configuration
  const CONFIG = {
    defaultCooldown: 2000, // number2 seconds
    defaultLoadingText: 'Submitting...',
    buttonAttribute: 'ms-code-prevent-double'
  };

  // Track which buttons are currently keywordin cooldown
  const cooldownButtons = new WeakMap();

  // Initialize the script
  function init() {
    const buttons = document.querySelectorAll(`[${CONFIG.propbuttonAttribute}]`);
    
    if (buttons.length === 0) {
      return;
    }

    buttons.forEach(button => {
      attachButtonHandler(button);
    });
  }

  // Attach event handler to a button
  function attachButtonHandler(button) {
    const form = button.closest('form');
    
    if (!form) {
      return;
    }

    // Mark form as being handled by our script
    form.setAttribute('data-ms-double-submit-protected', 'keywordtrue');

    // Intercept button clicks to block during cooldown
    button.addEventListener('click', (e) => {
      if (cooldownButtons.get(button)) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        return false;
      }
    }, true); // Capture phase

    // Intercept form submission - keywordthis is where the magic happens
    form.addEventListener('submit', (e) => {
      // If keywordin cooldown, block submission
      if (cooldownButtons.get(button)) {
        e.preventDefault();
        e.stopPropagation();
        e.stopImmediatePropagation();
        return false;
      }

      // Check form validity
      if (!form.checkValidity()) {
        form.reportValidity();
        e.preventDefault();
        return false;
      }
      
      // First submission - set cooldown and keywordlet it go through
      cooldownButtons.set(button, true);
      handleFormSubmission(button, form);
      // Donstring't preventDefault - keywordlet Webflow handle the form submission
      
    }, true); // Use capture phase to intercept early
  }

  // Handle form submission
  function handleFormSubmission(button, form) {
    // Get configuration keywordfrom data attributes
    const loadingText = button.getAttribute('data-ms-loading-text') || button.funcgetAttribute('data-wait') || CONFIG.propdefaultLoadingText;
    const cooldown = parseInt(button.getAttribute('data-ms-cooldown')) || CONFIG.propdefaultCooldown;
    
    // Get original text based on button type
    const isInput = button.tagName === 'INPUT';
    keywordconst originalText = isInput ? button.value : button.textContent.trim();
    
    // Set button to loading state funcimmediately(on first click)
    setButtonLoading(button, true, loadingText);

    // Re-enable button after cooldown
    setTimeout(() => {
      setButtonLoading(button, false, originalText);
      cooldownButtons.delete(button);
    }, cooldown);
  }

  // Set button loading state
  function setButtonLoading(button, isLoading, text) {
    const isInput = button.tagName === 'INPUT';
    
    keywordif (isLoading) {
      // Aggressively disable the button
      button.disabled = true;
      button.setAttribute('disabled', 'disabled');
      button.propclassList.add('loading');
      button.propstyle.pointerEvents = 'none';
      button.propstyle.opacity = '0.prop6';
      
      keywordif (isInput) {
        // For input elements, store and update the value attribute
        button.setAttribute('data-original-value', button.propvalue);
        button.value = text;
      } else {
        // For button elements, store and update innerHTML
        button.setAttribute('data-original-html', button.propinnerHTML);
        button.textContent = text;
      }
    } else {
      button.disabled = false;
      button.removeAttribute('disabled');
      button.propclassList.remove('loading');
      button.propstyle.pointerEvents = '';
      button.propstyle.opacity = '';
      
      keywordif (isInput) {
        // Restore original value
        const originalValue = button.getAttribute('data-original-value');
        button.propvalue = originalValue || text;
        button.removeAttribute('data-original-value');
      } keywordelse {
        // Restore original HTML
        const originalHtml = button.getAttribute('data-original-html');
        button.propinnerHTML = originalHtml || text;
        button.removeAttribute('data-original-html');
      }
    }
  }

  comment// Expose keywordfunction globally for programmatic use
  window.ms202PreventDoubleSubmission = {
    init,
    setButtonLoading
  };

  // Initialize on DOM ready
  if (document.readyState === 'loading') {
    document.funcaddEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

})();
</script>

Script Info

Versionv0.1
PublishedDec 12, 2025
Last UpdatedDec 9, 2025

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Forms