#195 - Custom Input Validation v0.1

Add real-time form validation with custom error messages.

View demo

<!-- 💙 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 if 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 for your country/region
    // ============================================================================
    // Replace these patterns with formats for your country or use 'regex' type
    // for custom patterns. Examples for different countries are provided below.
    PATTERNS: {
      // POSTAL CODE PATTERNS (choose one or use 'regex' for custom)
      // US ZIP: 12345 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]{1,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: 2000-9999
      // Uncomment to use Australian format instead:
      // zip: /^\d{4}$/,
      
      // German Postcode: 12345
      // Uncomment to use German format instead:
      // zip: /^\d{5}$/,
      
      // Flexible international postal code (allows letters, numbers, spaces, hyphens)
      // Uncomment to use flexible format:
      // zip: /^[A-Z0-9\s\-]{3,10}$/i,
      
      // PHONE NUMBER PATTERNS
      // Flexible international phone (allows digits, spaces, hyphens, parentheses, plus, dots)
      phone: /^[\d\s\-\(\)\+\.]+$/,
      
      // US Phone: (123) 456-7890 or 123-456-7890
      // Uncomment to use US format instead:
      // phone: /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/,
      
      // International with country code: +1 234 567 8900, +44 20 1234 5678
      // Uncomment to use international format:
      // phone: /^[\+]?[1-9][\d]{0,15}$/,
      
      // EMAIL PATTERN (works internationally)
      email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/
    },
    
    // ============================================================================
    // MODIFY: ERROR MESSAGES - Customize messages in your language
    // ============================================================================
    // Change these messages to match your language and validation rules
    MESSAGES: {
      // Postal code messages (update example to match your pattern)
      zip: 'Please enter a valid postal code',
      // Examples for different countries:
      // zip: 'Please enter a valid UK postcode (e.g., SW1A 1AA)',
      // zip: 'Please enter a valid Canadian postal code (e.g., A1A 1A1)',
      // zip: 'Bitte geben Sie eine gültige Postleitzahl ein', // German
      // zip: 'Por favor ingrese un código postal válido', // Spanish
      
      // Phone number messages
      phone: 'Please enter a valid phone number',
      // Examples:
      // phone: 'Please enter a valid phone number (e.g., +1 234 567 8900)',
      // phone: '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 if validation fails
    }
  };
  
  let form = null;
  let validationRules = [];
  
  // TIMING - Adjust timeout values if 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 #195: 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 #195: No validation inputs found. Add data-ms-code="validation-input-[name]" to your inputs.');
      return;
    }
    
    // Build validation rules for 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 #195: Input "${fieldName}" missing data-validation-type attribute.`);
        return;
      }
      
      if (!errorElement) {
        console.warn(`MemberScript #195: 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') === 'true'
      };
      
      // Set up validation based on type
      if (validationType === 'regex') {
        // MODIFY: Custom regex validation - use data-validation-pattern attribute
        // Example: data-validation-pattern="^[A-Z0-9]{5}$" for 5 alphanumeric characters
        const customPattern = input.getAttribute('data-validation-pattern');
        const customMessage = input.getAttribute('data-validation-message');
        
        if (!customPattern) {
          console.warn(`MemberScript #195: 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 #195: Invalid regex pattern for "${fieldName}":`, e);
          return;
        }
      } else if (CONFIG.PATTERNS[validationType]) {
        // Use predefined pattern from CONFIG.PATTERNS
        rule.pattern = CONFIG.PATTERNS[validationType];
        rule.message = CONFIG.MESSAGES[validationType];
        
        // MODIFY: Override message per field using data-validation-message attribute
        // Example: <input data-validation-message="Custom error message" ...>
        const fieldMessage = input.getAttribute('data-validation-message');
        if (fieldMessage) {
          rule.message = fieldMessage;
        }
      } else {
        console.warn(`MemberScript #195: 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 button (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's a link, prevent default navigation and submit form
            if (submitButton.tagName === 'A' && (submitButton.href === '#' || submitButton.getAttribute('href') === '#')) {
              e.preventDefault();
              e.stopPropagation();
              form.submit();
            }
          }
        });
      }
    }
  }
  
  function validateField(rule) {
    const value = rule.input.value.trim();
    let isValid = true;
    let errorMessage = '';
    
    // Check required field
    if (rule.required && !value) {
      isValid = false;
      errorMessage = CONFIG.MESSAGES.required;
    }
    // Check pattern if 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.input.classList.remove('validation-error');
      rule.input.classList.add('validation-valid');
    } else {
      rule.errorElement.style.display = 'block';
      rule.errorElement.textContent = errorMessage;
      rule.input.classList.add('validation-error');
      rule.input.classList.remove('validation-valid');
    }
    
    return 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' });
      }
    }
  }
  
  // ============================================================================
  // MODIFY: INITIALIZATION DELAY - Adjust if scripts load slowly (in milliseconds)
  // ============================================================================
  const INIT_DELAY = 100; // Increase to 200-500 if form loads slowly
  
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => setTimeout(init, INIT_DELAY));
  } else {
    setTimeout(init, INIT_DELAY);
  }
  
})();
</script>



Customer Showcase

Have you used a Memberscript in your project? We’d love to highlight your work and share it with the community!

Creating the Make.com Scenario

1. Download the JSON blueprint below to get stated.

2. Navigate to Make.com and Create a New Scenario...

3. Click the small box with 3 dots and then Import Blueprint...

4. Upload your file and voila! You're ready to link your own accounts.

Need help with this MemberScript?

All Memberstack customers can ask for assistance in the 2.0 Slack. Please note that these are not official features and support cannot be guaranteed.

Join the 2.0 Slack
Version notes
Attributes
Description
Attribute
No items found.
Guides / Tutorials
No items found.
Tutorial
What is Memberstack?

Auth & payments for Webflow sites

Add logins, subscriptions, gated content, and more to your Webflow site - easy, and fully customizable.

Learn more

"We've been using Memberstack for a long time, and it has helped us achieve things we would have never thought possible using Webflow. It's allowed us to build platforms with great depth and functionality and the team behind it has always been super helpful and receptive to feedback"

Jamie Debnam
39 Digital

"Been building a membership site with Memberstack and Jetboost for a client. Feels like magic building with these tools. As someone who’s worked in an agency where some of these apps were coded from scratch, I finally get the hype now. This is a lot faster and a lot cheaper."

Félix Meens
Webflix Studio

"One of the best products to start a membership site - I like the ease of use of Memberstack. I was able to my membership site up and running within a day. Doesn't get easier than that. Also provides the functionality I need to make the user experience more custom."

Eric McQuesten
Health Tech Nerds
Off World Depot

"My business wouldn't be what it is without Memberstack. If you think $30/month is expensive, try hiring a developer to integrate custom recommendations into your site for that price. Incredibly flexible set of tools for those willing to put in some minimal efforts to watch their well put together documentation."

Riley Brown
Off World Depot

"The Slack community is one of the most active I've seen and fellow customers are willing to jump in to answer questions and offer solutions. I've done in-depth evaluations of alternative tools and we always come back to Memberstack - save yourself the time and give it a shot."

Abbey Burtis
Health Tech Nerds
Slack

Need help with this MemberScript? Join our Slack community!

Join the Memberstack community Slack and ask away! Expect a prompt reply from a team member, a Memberstack expert, or a fellow community member.

Join our Slack