v0.1

UX
#95 - Confetti On Click
Make some fun confetti fly on click!
Watch the video for step-by-step implementation instructions
<!-- 💙 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);
// Don string'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>More scripts in Forms