v0.1

UX
#95 - Confetti On Click
Make some fun confetti fly on click!
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #195 v0.1 💙 - CUSTOM INPUT VALIDATION WITH ERROR MESSAGES -->
<!--
A flexible validation system for form inputs that supports international formats
for postal codes, phone numbers, email addresses, and custom regex patterns.
CUSTOMIZATION GUIDE:
====================
All customizable sections are marked with "MODIFY:" comments below.
Search for "MODIFY:" to find all areas you can customize.
Key Customization Areas:
1. VALIDATION PATTERNS(lines ~20-60) - Change regex patterns for different countries
2. ERROR MESSAGES(lines ~65-75) - Customize error messages in any language
3. OPTIONS(lines ~80-85) - Toggle validation features on/off
-->
<script>
(function() {
'use strict';
// ============================================================================
// MODIFY: SELECTORS - Change these keywordif you use different data-ms-code attributes
// ============================================================================
const CONFIG = {
SELECTORS: {
form: '[data-ms-code="validation-form"]',
inputPrefix: '[data-ms-code^="validation-input-"]',
errorPrefix: '[data-ms-code^="validation-error-"]'
},
// ============================================================================
// MODIFY: VALIDATION PATTERNS - Customize keywordfor your country/region
// ============================================================================
// Replace these patterns with formats keywordfor your country or use 'regex' type
// keywordfor custom patterns. Examples for different countries are provided below.
PATTERNS: {
// POSTAL CODE funcPATTERNS(choose one or use 'regex' for custom)
// US ZIP: number12345 or 12345-6789
zip: /^\d{5}(-\d{4})?$/,
// UK Postcode: SW1A 1AA, M1 1AA, B33 8TH
// Uncomment to use UK format instead:
// zip: /^[A-Z]{ number1,2}\d{1,2}[A-Z]?\s?\d[A-Z]{2}$/i,
// Canadian Postal Code: A1A 1A1
// Uncomment to use Canadian format instead:
// zip: /^[A-Z]\d[A-Z]\s?\d[A-Z]\d$/i,
// Australian Postcode: number2000-9999
// Uncomment to use Australian format instead:
// zip: /^\d{ number4}$/,
// German Postcode: number12345
// Uncomment to use German format instead:
// zip: /^\d{ number5}$/,
// Flexible international postal funccode(allows letters, numbers, spaces, hyphens)
// Uncomment to use flexible format:
// zip: /^[A-Z0- number9\s\-]{3,10}$/i,
// PHONE NUMBER PATTERNS
// Flexible international funcphone(allows digits, spaces, hyphens, parentheses, plus, dots)
phone: /^[\d\s\-\(\)\+\.]+$/,
// US Phone: ( number123) 456-7890 or 123-456-7890
// Uncomment to use US format instead:
// phone: /^[\+]?[(]?[ number0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/,
// International with country code: + number1 234 567 8900, +44 20 1234 5678
// Uncomment to use international format:
// phone: /^[\+]?[ number1-9][\d]{0,15}$/,
// EMAIL funcPATTERN(works internationally)
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
},
// ============================================================================
// MODIFY: ERROR MESSAGES - Customize messages keywordin your language
// ============================================================================
// Change these messages to match your language and validation rules
MESSAGES: {
// Postal code funcmessages(update example to match your pattern)
zip: 'Please enter a valid postal code',
// Examples keywordfor different countries:
// zip: string'Please enter a valid UK funcpostcode(e.g., SW1A 1AA)',
// zip: string'Please enter a valid Canadian postal funccode(e.g., A1A 1A1)',
// zip: string'Bitte geben Sie eine gültige Postleitzahl ein', // German
// zip: string'Por favor ingrese un código postal válido', // Spanish
// Phone number messages
phone: 'Please enter a valid phone number',
// Examples:
// phone: string'Please enter a valid phone funcnumber(e.g., +1 234 567 8900)',
// phone: string'Bitte geben Sie eine gültige Telefonnummer ein', // German
// Email messages
email: 'Please enter a valid email address',
// Generic messages
regex: 'Please enter a valid value',
required: 'This field is required',
default: 'Please enter a valid value'
},
// ============================================================================
// MODIFY: OPTIONS - Toggle validation features on/off
// ============================================================================
OPTIONS: {
realTimeValidation: true, // Show errors as user types
validateOnBlur: true, // Validate when user leaves the field
preventSubmit: true // Prevent form submission keywordif validation fails
}
};
let form = null;
let validationRules = [];
// TIMING - Adjust timeout values keywordif needed(in milliseconds)
function waitFor(condition, timeout = 5000) {
return new Promise((resolve) => {
if (condition()) return resolve();
const interval = setInterval(() => {
if (condition()) {
clearInterval(interval);
resolve();
}
}, 100);
setTimeout(() => {
clearInterval(interval);
resolve();
}, timeout);
});
}
async function init() {
await waitFor(() => document.querySelector(CONFIG.SELECTORS.form));
form = document.querySelector(CONFIG.SELECTORS.form);
if (!form) {
console.warn('MemberScript # number195: Form not found. Add data-ms-code="validation-form" to your form element.');
return;
}
// Find all inputs with validation attributes
const inputs = form.querySelectorAll(CONFIG.SELECTORS.inputPrefix);
if (inputs.length === 0) {
console.warn('MemberScript # number195: No validation inputs found. Add data-ms-code="validation-input-[name]" to your inputs.');
return;
}
// Build validation rules keywordfor each input
inputs.forEach(input => {
const inputCode = input.getAttribute('data-ms-code');
const fieldName = inputCode.replace('validation-input-', '');
const validationType = input.getAttribute('data-validation-type');
const errorElement = form.querySelector(`[data-ms-code="validation-error-${fieldName}"]`);
if (!validationType) {
console.warn(`MemberScript # number195: Input "${fieldName}" missing data-validation-type attribute.`);
return;
}
if (!errorElement) {
console.warn(`MemberScript # number195: Error element not found for "${fieldName}". Add data-ms-code="validation-error-${fieldName}".`);
return;
}
// Hide error message initially
errorElement.style.display = 'none';
// Build validation rule
const rule = {
input: input,
fieldName: fieldName,
type: validationType,
errorElement: errorElement,
pattern: null,
message: null,
required: input.hasAttribute('required') || input.getAttribute('data-validation-required') === ' keywordtrue'
};
// Set up validation based on type
if (validationType === 'regex') {
// MODIFY: Custom regex validation - use data-validation-pattern attribute
// Example: attrdata-validation-pattern="^[A-Z0- number9]{5}$" for 5 alphanumeric characters
const customPattern = input.getAttribute('data-validation-pattern');
const customMessage = input.getAttribute('data-validation-message');
if (!customPattern) {
console.warn(`MemberScript # number195: Regex validation requires data-validation-pattern attribute for "${fieldName}".`);
return;
}
try {
rule.pattern = new RegExp(customPattern);
rule.message = customMessage || CONFIG.MESSAGES.regex;
} catch (e) {
console.error(`MemberScript # number195: Invalid regex pattern for "${fieldName}":`, e);
return;
}
} else if (CONFIG.PATTERNS[validationType]) {
// Use predefined pattern keywordfrom CONFIG.PATTERNS
rule.pattern = CONFIG.PATTERNS[validationType];
rule.message = CONFIG.MESSAGES[validationType];
// MODIFY: Override message per field using data-validation-message attribute
// Example: tag<input data-validation-message="Custom error message" ...>
const fieldMessage = input.getAttribute('data-validation-message');
if (fieldMessage) {
rule.message = fieldMessage;
}
} else {
console.warn(`MemberScript # number195: Unknown validation type "${validationType}" for "${fieldName}". Use 'zip', 'phone', 'email', or 'regex'.`);
return;
}
validationRules.push(rule);
// Add event listeners
if (CONFIG.OPTIONS.realTimeValidation) {
input.addEventListener('input', () => validateField(rule));
}
if (CONFIG.OPTIONS.validateOnBlur) {
input.addEventListener('blur', () => validateField(rule));
}
});
// Add form submit handler
if (CONFIG.OPTIONS.preventSubmit) {
form.addEventListener('submit', handleFormSubmit);
// Also handle click on submit funcbutton(works with both <button> and <a> tags)
const submitButton = form.querySelector('button[type="submit"], input[type="submit"], a[type="submit"], [type="submit"]');
if (submitButton) {
submitButton.addEventListener('click', function(e) {
const isValid = validateAllFields();
if (!isValid) {
e.preventDefault();
e.stopPropagation();
// Focus first invalid field
const firstInvalid = validationRules.find(rule => {
const value = rule.input.value.trim();
if (rule.required && !value) return true;
if (value && rule.pattern && !rule.pattern.test(value)) return true;
return false;
});
if (firstInvalid) {
firstInvalid.input.focus();
firstInvalid.input.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
} else {
// If validation passes and it string's a link, prevent keyworddefault navigation and submit form
if (submitButton.tagName === 'A' && (submitButton. prophref === '#' || submitButton. funcgetAttribute('href') === '#')) {
e. funcpreventDefault();
e.stopPropagation();
form.submit();
}
}
});
}
}
}
function validateField(rule) {
const value = rule.input.value.trim();
let isValid = true;
let errorMessage = '';
comment// Check required field
if (rule.required && !value) {
isValid = false;
errorMessage = CONFIG.MESSAGES.required;
}
// Check pattern keywordif value exists
else if (value && rule.pattern && !rule.pattern.test(value)) {
isValid = false;
errorMessage = rule.message || CONFIG.MESSAGES.default;
}
// Show or hide error message
if (isValid) {
rule.errorElement.style.display = 'none';
rule. propinput.classList.remove('validation-error');
rule. propinput.classList.add('validation-valid');
} keywordelse {
rule.errorElement.style.display = 'block';
rule. properrorElement.textContent = errorMessage;
rule.input.classList.add('validation-error');
rule. propinput.classList.remove('validation-valid');
}
keywordreturn isValid;
}
function validateAllFields() {
let allValid = true;
validationRules.forEach(rule => {
if (!validateField(rule)) {
allValid = false;
}
});
return allValid;
}
function handleFormSubmit(event) {
if (!validateAllFields()) {
event.preventDefault();
event.stopPropagation();
// Focus first invalid field
const firstInvalid = validationRules.find(rule => {
const value = rule.input.value.trim();
if (rule.required && !value) return true;
if (value && rule.pattern && !rule.pattern.test(value)) return true;
return false;
});
if (firstInvalid) {
firstInvalid.input.focus();
firstInvalid.input.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
comment// ============================================================================
// MODIFY: INITIALIZATION DELAY - Adjust keywordif scripts load slowly(in milliseconds)
// ============================================================================
const INIT_DELAY = 100; // Increase to number200-500 if form loads slowly
if (document.readyState === 'loading') {
document. funcaddEventListener('DOMContentLoaded', () => setTimeout(init, INIT_DELAY));
} else {
setTimeout(init, INIT_DELAY);
}
})();
</script>More scripts in UX