Components
Templates
Attributes
Integrations
Site Tester
Custom Code

MemberScripts

An attribute-based solution to add features to your Webflow site.
Simply copy some code, add some attributes, and you're done.

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.
Need help with MemberScripts?

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.

UX

#138 - Anchor Link Scroll Offset

Fix the problem with anchor links & sticky/fixed navbars in Webflow.


<!-- 💙 MEMBERSCRIPT #138 v0.1 💙 - ANCHOR LINK SCROLL OFFSET -->
<script>
  // Disable Webflow's built-in smooth scrolling
  var Webflow = Webflow || [];
  Webflow.push(function() {
    $(function() { 
      $(document).off('click.wf-scroll');
    });
  });

  // Smooth scroll implementation with customizable settings
  (function() {
    // Customizable settings
    const SCROLL_SETTINGS = {
      duration: 1000, // in milliseconds
      easing: 'easeInOutCubic' // 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'
    };

    const EASING_FUNCTIONS = {
      linear: t => t,
      easeInQuad: t => t * t,
      easeOutQuad: t => t * (2 - t),
      easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
      easeInCubic: t => t * t * t,
      easeOutCubic: t => (--t) * t * t + 1,
      easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
    };

    function getOffset() {
      const navbar = document.querySelector('[ms-code-scroll-offset]');
      if (!navbar) return 0;
      const navbarHeight = navbar.offsetHeight;
      const customOffset = parseInt(navbar.getAttribute('ms-code-scroll-offset') || '0', 10);
      return navbarHeight + customOffset;
    }

    function smoothScroll(target) {
      const startPosition = window.pageYOffset;
      const offset = getOffset();
      const targetPosition = target.getBoundingClientRect().top + startPosition - offset;
      const distance = targetPosition - startPosition;
      let startTime = null;

      function animation(currentTime) {
        if (startTime === null) startTime = currentTime;
        const timeElapsed = currentTime - startTime;
        const progress = Math.min(timeElapsed / SCROLL_SETTINGS.duration, 1);
        const easeProgress = EASING_FUNCTIONS[SCROLL_SETTINGS.easing](progress);
        window.scrollTo(0, startPosition + distance * easeProgress);
        if (timeElapsed < SCROLL_SETTINGS.duration) requestAnimationFrame(animation);
      }

      requestAnimationFrame(animation);
    }

    function handleClick(e) {
      const href = e.currentTarget.getAttribute('href');
      if (href.startsWith('#')) {
        e.preventDefault();
        const target = document.getElementById(href.slice(1));
        if (target) smoothScroll(target);
      }
    }

    function handleHashChange() {
      if (window.location.hash) {
        const target = document.getElementById(window.location.hash.slice(1));
        if (target) {
          setTimeout(() => smoothScroll(target), 0);
        }
      }
    }

    function init() {
      document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', handleClick);
      });
      window.addEventListener('hashchange', handleHashChange);
      handleHashChange(); // Handle initial hash on page load
    }

    document.addEventListener('DOMContentLoaded', init);
    window.Webflow && window.Webflow.push(init);
  })();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #138 v0.1 💙 - ANCHOR LINK SCROLL OFFSET -->
<script>
  // Disable Webflow's built-in smooth scrolling
  var Webflow = Webflow || [];
  Webflow.push(function() {
    $(function() { 
      $(document).off('click.wf-scroll');
    });
  });

  // Smooth scroll implementation with customizable settings
  (function() {
    // Customizable settings
    const SCROLL_SETTINGS = {
      duration: 1000, // in milliseconds
      easing: 'easeInOutCubic' // 'linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic', 'easeOutCubic', 'easeInOutCubic'
    };

    const EASING_FUNCTIONS = {
      linear: t => t,
      easeInQuad: t => t * t,
      easeOutQuad: t => t * (2 - t),
      easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
      easeInCubic: t => t * t * t,
      easeOutCubic: t => (--t) * t * t + 1,
      easeInOutCubic: t => t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
    };

    function getOffset() {
      const navbar = document.querySelector('[ms-code-scroll-offset]');
      if (!navbar) return 0;
      const navbarHeight = navbar.offsetHeight;
      const customOffset = parseInt(navbar.getAttribute('ms-code-scroll-offset') || '0', 10);
      return navbarHeight + customOffset;
    }

    function smoothScroll(target) {
      const startPosition = window.pageYOffset;
      const offset = getOffset();
      const targetPosition = target.getBoundingClientRect().top + startPosition - offset;
      const distance = targetPosition - startPosition;
      let startTime = null;

      function animation(currentTime) {
        if (startTime === null) startTime = currentTime;
        const timeElapsed = currentTime - startTime;
        const progress = Math.min(timeElapsed / SCROLL_SETTINGS.duration, 1);
        const easeProgress = EASING_FUNCTIONS[SCROLL_SETTINGS.easing](progress);
        window.scrollTo(0, startPosition + distance * easeProgress);
        if (timeElapsed < SCROLL_SETTINGS.duration) requestAnimationFrame(animation);
      }

      requestAnimationFrame(animation);
    }

    function handleClick(e) {
      const href = e.currentTarget.getAttribute('href');
      if (href.startsWith('#')) {
        e.preventDefault();
        const target = document.getElementById(href.slice(1));
        if (target) smoothScroll(target);
      }
    }

    function handleHashChange() {
      if (window.location.hash) {
        const target = document.getElementById(window.location.hash.slice(1));
        if (target) {
          setTimeout(() => smoothScroll(target), 0);
        }
      }
    }

    function init() {
      document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', handleClick);
      });
      window.addEventListener('hashchange', handleHashChange);
      handleHashChange(); // Handle initial hash on page load
    }

    document.addEventListener('DOMContentLoaded', init);
    window.Webflow && window.Webflow.push(init);
  })();
</script>
View Memberscript
UX

#137 - Display The Visitors' Country Name

Replace text with the country a user is in based on their IP address.


<!-- 💙 MEMBERSCRIPT #137 v0.1 💙 - DISPLAY COUNTRY NAME -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    fetch('https://ipapi.co/json/')
      .then(response => response.json())
      .then(data => {
        if (data.country_name) {
          const countryElements = document.querySelectorAll('[ms-code-display-country]');
          countryElements.forEach(element => {
            element.textContent = data.country_name;
          });
        }
      })
      .catch(error => {
        console.error('Error fetching country:', error);
      });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #137 v0.1 💙 - DISPLAY COUNTRY NAME -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    fetch('https://ipapi.co/json/')
      .then(response => response.json())
      .then(data => {
        if (data.country_name) {
          const countryElements = document.querySelectorAll('[ms-code-display-country]');
          countryElements.forEach(element => {
            element.textContent = data.country_name;
          });
        }
      })
      .catch(error => {
        console.error('Error fetching country:', error);
      });
  });
</script>
View Memberscript
UX

#136 - Remove Section Path From URL

When a section is navigated to, the anchor link path will be removed.


<!-- 💙 MEMBERSCRIPT #136 💙 REMOVE SECTION PATH FROM URL -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    // Check if there's a hash in the URL
    if (window.location.hash) {
        // Get the target element
        const targetId = window.location.hash.substring(1);
        const targetElement = document.getElementById(targetId);

        if (targetElement) {
            // Scroll to the target element
            targetElement.scrollIntoView({behavior: 'smooth'});

            // Remove the hash after a short delay (to allow scrolling to complete)
            setTimeout(function() {
                history.pushState("", document.title, window.location.pathname + window.location.search);
            }, 100);
        }
    }

    // Add click event listeners to all internal links
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function(e) {
            e.preventDefault();

            const targetId = this.getAttribute('href').substring(1);
            const targetElement = document.getElementById(targetId);

            if (targetElement) {
                targetElement.scrollIntoView({behavior: 'smooth'});

                // Remove the hash after a short delay (to allow scrolling to complete)
                setTimeout(function() {
                    history.pushState("", document.title, window.location.pathname + window.location.search);
                }, 100);
            }
        });
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #136 💙 REMOVE SECTION PATH FROM URL -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    // Check if there's a hash in the URL
    if (window.location.hash) {
        // Get the target element
        const targetId = window.location.hash.substring(1);
        const targetElement = document.getElementById(targetId);

        if (targetElement) {
            // Scroll to the target element
            targetElement.scrollIntoView({behavior: 'smooth'});

            // Remove the hash after a short delay (to allow scrolling to complete)
            setTimeout(function() {
                history.pushState("", document.title, window.location.pathname + window.location.search);
            }, 100);
        }
    }

    // Add click event listeners to all internal links
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
        anchor.addEventListener('click', function(e) {
            e.preventDefault();

            const targetId = this.getAttribute('href').substring(1);
            const targetElement = document.getElementById(targetId);

            if (targetElement) {
                targetElement.scrollIntoView({behavior: 'smooth'});

                // Remove the hash after a short delay (to allow scrolling to complete)
                setTimeout(function() {
                    history.pushState("", document.title, window.location.pathname + window.location.search);
                }, 100);
            }
        });
    });
});
</script>
View Memberscript
Custom Flows

#135 - Redirect Based On Select Value

Dynamically set the form redirect based on the users' selection.


<!-- 💙 MEMBERSCRIPT #135 v0.2 💙 - REDIRECT FORM FROM SELECT VALUE -->
<script>
  (function() {
    'use strict';

    function initDropdownRedirect() {
      const dropdown = document.querySelector('select[ms-code-dropdown-redirect]');
      if (!dropdown) return;

      const form = dropdown.closest('form');
      if (!form) return;

      function updateRedirect() {
        const selectedValue = dropdown.value;
        form.setAttribute('redirect', selectedValue);
        form.setAttribute('data-redirect', selectedValue);
      }

      function handleSubmit(event) {
        event.preventDefault();
        const redirectUrl = form.getAttribute('data-redirect') || form.getAttribute('redirect');
        if (redirectUrl) {
          setTimeout(() => {
            window.location.href = redirectUrl;
          }, 500); // Delay redirect by 500 milliseconds
        } else {
          form.submit(); // Fall back to normal form submission if no redirect is set
        }
      }


      dropdown.addEventListener('change', updateRedirect);
      form.addEventListener('submit', handleSubmit);

      // Initialize redirect on page load
      updateRedirect();
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initDropdownRedirect);
    } else {
      initDropdownRedirect();
    }
  })();
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #135 v0.2 💙 - REDIRECT FORM FROM SELECT VALUE -->
<script>
  (function() {
    'use strict';

    function initDropdownRedirect() {
      const dropdown = document.querySelector('select[ms-code-dropdown-redirect]');
      if (!dropdown) return;

      const form = dropdown.closest('form');
      if (!form) return;

      function updateRedirect() {
        const selectedValue = dropdown.value;
        form.setAttribute('redirect', selectedValue);
        form.setAttribute('data-redirect', selectedValue);
      }

      function handleSubmit(event) {
        event.preventDefault();
        const redirectUrl = form.getAttribute('data-redirect') || form.getAttribute('redirect');
        if (redirectUrl) {
          setTimeout(() => {
            window.location.href = redirectUrl;
          }, 500); // Delay redirect by 500 milliseconds
        } else {
          form.submit(); // Fall back to normal form submission if no redirect is set
        }
      }


      dropdown.addEventListener('change', updateRedirect);
      form.addEventListener('submit', handleSubmit);

      // Initialize redirect on page load
      updateRedirect();
    }

    if (document.readyState === 'loading') {
      document.addEventListener('DOMContentLoaded', initDropdownRedirect);
    } else {
      initDropdownRedirect();
    }
  })();
</script>
View Memberscript
UX

#134 - Scroll To Top On Tab Change

When the tab is changed, the page will scroll to the top of the tab section.


<!-- 💙 MEMBERSCRIPT #134 💙 - SCROLL TO TOP OF TABS ON CHANGE -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Select all tab containers with the ms-code-tab-scroll-top attribute
    const tabContainers = document.querySelectorAll('.w-tabs[ms-code-tab-scroll-top]');

    tabContainers.forEach(container => {
      const tabLinks = container.querySelectorAll('.w-tab-link');
      const scrollTopValue = parseInt(container.getAttribute('ms-code-tab-scroll-top') || '0');

      tabLinks.forEach(link => {
        link.addEventListener('click', function(e) {
          // Small delay to ensure the new tab content is rendered
          setTimeout(() => {
            // Find the active tab pane within this container
            const activePane = container.querySelector('.w-tab-pane.w--tab-active');

            if (activePane) {
              // Calculate the new scroll position
              const newScrollPosition = container.getBoundingClientRect().top + window.pageYOffset + scrollTopValue;

              // Scroll to the new position
              window.scrollTo({
                top: newScrollPosition,
                behavior: 'smooth'
              });
            }
          }, 50); // 50ms delay, adjust if needed
        });
      });
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #134 💙 - SCROLL TO TOP OF TABS ON CHANGE -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Select all tab containers with the ms-code-tab-scroll-top attribute
    const tabContainers = document.querySelectorAll('.w-tabs[ms-code-tab-scroll-top]');

    tabContainers.forEach(container => {
      const tabLinks = container.querySelectorAll('.w-tab-link');
      const scrollTopValue = parseInt(container.getAttribute('ms-code-tab-scroll-top') || '0');

      tabLinks.forEach(link => {
        link.addEventListener('click', function(e) {
          // Small delay to ensure the new tab content is rendered
          setTimeout(() => {
            // Find the active tab pane within this container
            const activePane = container.querySelector('.w-tab-pane.w--tab-active');

            if (activePane) {
              // Calculate the new scroll position
              const newScrollPosition = container.getBoundingClientRect().top + window.pageYOffset + scrollTopValue;

              // Scroll to the new position
              window.scrollTo({
                top: newScrollPosition,
                behavior: 'smooth'
              });
            }
          }, 50); // 50ms delay, adjust if needed
        });
      });
    });
  });
</script>
View Memberscript
Security

#133 - Auto Watermark Images

Easily add a watermark to images on your Webflow site.


<!-- 💙 MEMBERSCRIPT #133 v0.1 💙 - AUTO IMAGE WATERMARK -->
<script>
  function addWatermarkToImages() {
    const images = document.querySelectorAll('img[ms-code-watermark]');

    images.forEach(img => {
      img.crossOrigin = "Anonymous";  // This allows us to work with images from other domains
      img.onload = function() {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        // Set canvas size to match the image
        canvas.width = img.width;
        canvas.height = img.height;

        // Draw the original image onto the canvas
        ctx.drawImage(img, 0, 0, img.width, img.height);

        // Get watermark text from attribute
        const watermarkText = img.getAttribute('ms-code-watermark') || 'Watermark';

        // Add watermark
        ctx.font = `${img.width / 20}px Arial`;  // Adjust font size based on image width
        ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';

        // Rotate and draw the watermark text
        ctx.save();
        ctx.translate(canvas.width / 2, canvas.height / 2);
        ctx.rotate(-Math.PI / 4);  // Rotate 45 degrees
        ctx.fillText(watermarkText, 0, 0);
        ctx.restore();

        // Preserve the original image's classes and other attributes
        canvas.className = img.className;
        for (let i = 0; i < img.attributes.length; i++) {
          const attr = img.attributes[i];
          if (attr.name !== 'src' && attr.name !== 'ms-code-watermark') {
            canvas.setAttribute(attr.name, attr.value);
          }
        }

        // Replace the original image with the watermarked canvas
        img.parentNode.replaceChild(canvas, img);
      };

      // Trigger onload event (in case the image is already loaded)
      if (img.complete) {
        img.onload();
      }
    });
  }

  // Run the function when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', addWatermarkToImages);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #133 v0.1 💙 - AUTO IMAGE WATERMARK -->
<script>
  function addWatermarkToImages() {
    const images = document.querySelectorAll('img[ms-code-watermark]');

    images.forEach(img => {
      img.crossOrigin = "Anonymous";  // This allows us to work with images from other domains
      img.onload = function() {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');

        // Set canvas size to match the image
        canvas.width = img.width;
        canvas.height = img.height;

        // Draw the original image onto the canvas
        ctx.drawImage(img, 0, 0, img.width, img.height);

        // Get watermark text from attribute
        const watermarkText = img.getAttribute('ms-code-watermark') || 'Watermark';

        // Add watermark
        ctx.font = `${img.width / 20}px Arial`;  // Adjust font size based on image width
        ctx.fillStyle = 'rgba(255, 255, 255, 0.5)';
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';

        // Rotate and draw the watermark text
        ctx.save();
        ctx.translate(canvas.width / 2, canvas.height / 2);
        ctx.rotate(-Math.PI / 4);  // Rotate 45 degrees
        ctx.fillText(watermarkText, 0, 0);
        ctx.restore();

        // Preserve the original image's classes and other attributes
        canvas.className = img.className;
        for (let i = 0; i < img.attributes.length; i++) {
          const attr = img.attributes[i];
          if (attr.name !== 'src' && attr.name !== 'ms-code-watermark') {
            canvas.setAttribute(attr.name, attr.value);
          }
        }

        // Replace the original image with the watermarked canvas
        img.parentNode.replaceChild(canvas, img);
      };

      // Trigger onload event (in case the image is already loaded)
      if (img.complete) {
        img.onload();
      }
    });
  }

  // Run the function when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', addWatermarkToImages);
</script>
View Memberscript
Accessibility
Modals

#132 - Hide Elements With Escape Key

Add one attribute and when the esc key is clicked, the element will be set to display none.


<!-- 💙 MEMBERSCRIPT 💙 - HIDE ELEMENTS WITH ESC KEY -->
<script>
  document.addEventListener('keydown', function(event) {
    // Check if the pressed key is ESC (key code 27)
    if (event.key === 'Escape' || event.keyCode === 27) {
      // Find all elements with the attribute ms-code-close-esc
      const elements = document.querySelectorAll('[ms-code-close-esc]');

      // Loop through the elements and set their display to 'none'
      elements.forEach(function(element) {
        element.style.display = 'none';
      });
    }
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT 💙 - HIDE ELEMENTS WITH ESC KEY -->
<script>
  document.addEventListener('keydown', function(event) {
    // Check if the pressed key is ESC (key code 27)
    if (event.key === 'Escape' || event.keyCode === 27) {
      // Find all elements with the attribute ms-code-close-esc
      const elements = document.querySelectorAll('[ms-code-close-esc]');

      // Loop through the elements and set their display to 'none'
      elements.forEach(function(element) {
        element.style.display = 'none';
      });
    }
  });
</script>
View Memberscript
Custom Fields

#131 - Add Up Number Inputs

Take the value of number inputs and show it in an input value, or in a text span.


<!-- 💙 MEMBERSCRIPT #131 v0.1 💙 - CALCULATE NUMBER INPUTS -->
<script>
  // Function to initialize the counter functionality
  function initializeCounter() {
    const counters = {};

    // Find all elements with ms-code-show-number attribute
    document.querySelectorAll('[ms-code-show-number]').forEach(el => {
      const counterName = el.getAttribute('ms-code-show-number');
      if (!counters[counterName]) {
        counters[counterName] = { total: 0, displays: [] };
      }
      counters[counterName].displays.push(el);
    });

    // Find all input elements with ms-code-add-number attribute
    document.querySelectorAll('input[ms-code-add-number]').forEach(input => {
      const counterName = input.getAttribute('ms-code-add-number');
      if (counters[counterName]) {
        input.addEventListener('input', updateCounter);
      }
    });

    // Function to update counter when input changes
    function updateCounter(event) {
      const input = event.target;
      const counterName = input.getAttribute('ms-code-add-number');
      const counter = counters[counterName];

      if (counter) {
        counter.total = 0;
        document.querySelectorAll(`input[ms-code-add-number="${counterName}"]`).forEach(input => {
          counter.total += parseInt(input.value) || 0;
        });

        counter.displays.forEach(display => {
          if (display.tagName === 'INPUT') {
            display.value = counter.total;
          } else {
            display.textContent = counter.total;
          }
        });
      }
    }

    // Initial update for all counters
    Object.keys(counters).forEach(counterName => {
      const input = document.querySelector(`input[ms-code-add-number="${counterName}"]`);
      if (input) {
        input.dispatchEvent(new Event('input'));
      }
    });
  }

  // Run the initialization when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', initializeCounter);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #131 v0.1 💙 - CALCULATE NUMBER INPUTS -->
<script>
  // Function to initialize the counter functionality
  function initializeCounter() {
    const counters = {};

    // Find all elements with ms-code-show-number attribute
    document.querySelectorAll('[ms-code-show-number]').forEach(el => {
      const counterName = el.getAttribute('ms-code-show-number');
      if (!counters[counterName]) {
        counters[counterName] = { total: 0, displays: [] };
      }
      counters[counterName].displays.push(el);
    });

    // Find all input elements with ms-code-add-number attribute
    document.querySelectorAll('input[ms-code-add-number]').forEach(input => {
      const counterName = input.getAttribute('ms-code-add-number');
      if (counters[counterName]) {
        input.addEventListener('input', updateCounter);
      }
    });

    // Function to update counter when input changes
    function updateCounter(event) {
      const input = event.target;
      const counterName = input.getAttribute('ms-code-add-number');
      const counter = counters[counterName];

      if (counter) {
        counter.total = 0;
        document.querySelectorAll(`input[ms-code-add-number="${counterName}"]`).forEach(input => {
          counter.total += parseInt(input.value) || 0;
        });

        counter.displays.forEach(display => {
          if (display.tagName === 'INPUT') {
            display.value = counter.total;
          } else {
            display.textContent = counter.total;
          }
        });
      }
    }

    // Initial update for all counters
    Object.keys(counters).forEach(counterName => {
      const input = document.querySelector(`input[ms-code-add-number="${counterName}"]`);
      if (input) {
        input.dispatchEvent(new Event('input'));
      }
    });
  }

  // Run the initialization when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', initializeCounter);
</script>
View Memberscript
UX

#130 - Auto Submit Form When All Inputs Change

Skip the need for a submit button and submit the form when all inputs change.


<!-- 💙 MEMBERSCRIPT #130 v0.1 💙 - AUTO SUBMIT FORMS FROM INPUT CHANGE -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const forms = document.querySelectorAll('form[ms-code-auto-submit]');

    forms.forEach(form => {
      const fields = form.querySelectorAll('input:not([type="submit"]):not([type="button"]):not([type="reset"]), select, textarea');
      const fieldStates = new Map(Array.from(fields).map(field => [field, false]));

      function updateFieldState(field, checkImmediately = false) {
        switch (field.type) {
          case 'checkbox':
            fieldStates.set(field, true); // Considered interacted with once changed
            break;
          case 'radio':
            const radioGroup = form.querySelectorAll(`input[type="radio"][name="${field.name}"]`);
            radioGroup.forEach(radio => fieldStates.set(radio, true));
            break;
          case 'select-one':
          case 'select-multiple':
            fieldStates.set(field, field.value !== '');
            break;
          case 'file':
            fieldStates.set(field, field.files.length > 0);
            break;
          case 'hidden':
            fieldStates.set(field, true); // Always consider hidden fields as filled
            break;
          default:
            // For text inputs, only update on blur or if checkImmediately is true
            if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
              if (checkImmediately || !field.dataset.blurred) {
                fieldStates.set(field, field.value.trim() !== '');
              }
            } else {
              fieldStates.set(field, field.value.trim() !== '');
            }
        }
        if (checkImmediately) {
          checkAndSubmit();
        }
      }

      function checkAndSubmit() {
        if (Array.from(fieldStates.values()).every(state => state)) {
          // Create and dispatch a submit event
          const submitEvent = new Event('submit', {
            bubbles: true,
            cancelable: true
          });

          const submitted = form.dispatchEvent(submitEvent);

          // If the event wasn't prevented, manually submit the form
          if (submitted) {
            form.submit();
          }
        }
      }

      fields.forEach(field => {
        // Use 'change' event for checkboxes, radios, file inputs, and selects
        if (['checkbox', 'radio', 'file', 'select-one', 'select-multiple'].includes(field.type) || field.tagName === 'SELECT') {
          field.addEventListener('change', () => updateFieldState(field, true));
        }

        // For text-like inputs, use 'blur' event
        if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
          field.addEventListener('blur', () => {
            field.dataset.blurred = 'true';
            updateFieldState(field, true);
          });
          // Also check on input, but don't submit immediately
          field.addEventListener('input', () => updateFieldState(field, false));
        }
      });

      // Initial check for pre-filled fields (e.g., browser autofill)
      fields.forEach(field => updateFieldState(field, false));
      checkAndSubmit();
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #130 v0.1 💙 - AUTO SUBMIT FORMS FROM INPUT CHANGE -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const forms = document.querySelectorAll('form[ms-code-auto-submit]');

    forms.forEach(form => {
      const fields = form.querySelectorAll('input:not([type="submit"]):not([type="button"]):not([type="reset"]), select, textarea');
      const fieldStates = new Map(Array.from(fields).map(field => [field, false]));

      function updateFieldState(field, checkImmediately = false) {
        switch (field.type) {
          case 'checkbox':
            fieldStates.set(field, true); // Considered interacted with once changed
            break;
          case 'radio':
            const radioGroup = form.querySelectorAll(`input[type="radio"][name="${field.name}"]`);
            radioGroup.forEach(radio => fieldStates.set(radio, true));
            break;
          case 'select-one':
          case 'select-multiple':
            fieldStates.set(field, field.value !== '');
            break;
          case 'file':
            fieldStates.set(field, field.files.length > 0);
            break;
          case 'hidden':
            fieldStates.set(field, true); // Always consider hidden fields as filled
            break;
          default:
            // For text inputs, only update on blur or if checkImmediately is true
            if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
              if (checkImmediately || !field.dataset.blurred) {
                fieldStates.set(field, field.value.trim() !== '');
              }
            } else {
              fieldStates.set(field, field.value.trim() !== '');
            }
        }
        if (checkImmediately) {
          checkAndSubmit();
        }
      }

      function checkAndSubmit() {
        if (Array.from(fieldStates.values()).every(state => state)) {
          // Create and dispatch a submit event
          const submitEvent = new Event('submit', {
            bubbles: true,
            cancelable: true
          });

          const submitted = form.dispatchEvent(submitEvent);

          // If the event wasn't prevented, manually submit the form
          if (submitted) {
            form.submit();
          }
        }
      }

      fields.forEach(field => {
        // Use 'change' event for checkboxes, radios, file inputs, and selects
        if (['checkbox', 'radio', 'file', 'select-one', 'select-multiple'].includes(field.type) || field.tagName === 'SELECT') {
          field.addEventListener('change', () => updateFieldState(field, true));
        }

        // For text-like inputs, use 'blur' event
        if (field.type === 'text' || field.type === 'password' || field.type === 'email' || field.type === 'tel' || field.type === 'url' || field.tagName === 'TEXTAREA') {
          field.addEventListener('blur', () => {
            field.dataset.blurred = 'true';
            updateFieldState(field, true);
          });
          // Also check on input, but don't submit immediately
          field.addEventListener('input', () => updateFieldState(field, false));
        }
      });

      // Initial check for pre-filled fields (e.g., browser autofill)
      fields.forEach(field => updateFieldState(field, false));
      checkAndSubmit();
    });
  });
</script>
View Memberscript
Conditional Visibility

#129 - Country Gating

Block visitors from viewing your site if they're in one of the disallowed countries.


<!-- 💙 MEMBERSCRIPT #129 v0.1 💙 - COUNTRY GATING -->
<script>
  // Configuration
  const ACCESS_DENIED_PAGE = '/access-denied';

  // List of disallowed countries using ISO 3166-1 alpha-2 country codes
  const DISALLOWED_COUNTRIES = [
    // "US",  // United States
    // "CN",  // China
    // "RU",  // Russia
    // "IN",  // India
    // "JP",  // Japan
    // "DE",  // Germany
    // "GB",  // United Kingdom
    // "FR",  // France
    // "BR",  // Brazil
    // "IT",  // Italy
    // Add more countries as needed
  ];

  // Function to get visitor's country and check access
  function checkCountryAccess() {
    // Check if we're already on the access denied page
    if (window.location.pathname === ACCESS_DENIED_PAGE) {
      return; // Don't redirect if already on the access denied page
    }

    fetch('https://ipapi.co/json/')
      .then(response => response.json())
      .then(data => {
        const visitorCountry = data.country_code; // This returns the ISO 3166-1 alpha-2 country code
        if (DISALLOWED_COUNTRIES.includes(visitorCountry)) {
          window.location.href = ACCESS_DENIED_PAGE;
        }
      })
      .catch(error => {
        console.error('Error fetching IP data:', error);
      });
  }

  // Run the check when the page loads
  document.addEventListener('DOMContentLoaded', checkCountryAccess);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #129 v0.1 💙 - COUNTRY GATING -->
<script>
  // Configuration
  const ACCESS_DENIED_PAGE = '/access-denied';

  // List of disallowed countries using ISO 3166-1 alpha-2 country codes
  const DISALLOWED_COUNTRIES = [
    // "US",  // United States
    // "CN",  // China
    // "RU",  // Russia
    // "IN",  // India
    // "JP",  // Japan
    // "DE",  // Germany
    // "GB",  // United Kingdom
    // "FR",  // France
    // "BR",  // Brazil
    // "IT",  // Italy
    // Add more countries as needed
  ];

  // Function to get visitor's country and check access
  function checkCountryAccess() {
    // Check if we're already on the access denied page
    if (window.location.pathname === ACCESS_DENIED_PAGE) {
      return; // Don't redirect if already on the access denied page
    }

    fetch('https://ipapi.co/json/')
      .then(response => response.json())
      .then(data => {
        const visitorCountry = data.country_code; // This returns the ISO 3166-1 alpha-2 country code
        if (DISALLOWED_COUNTRIES.includes(visitorCountry)) {
          window.location.href = ACCESS_DENIED_PAGE;
        }
      })
      .catch(error => {
        console.error('Error fetching IP data:', error);
      });
  }

  // Run the check when the page loads
  document.addEventListener('DOMContentLoaded', checkCountryAccess);
</script>
View Memberscript
Conditional Visibility
UX

#128 - Hide Elements After They've Been Seen

Add one attribute, and your visitors will only see that element one time. After refresh, it will be gone.


<!-- 💙 MEMBERSCRIPT #128 💙 - ONLY SHOW ELEMENT ONCE -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Find all elements with the ms-code-show-once attribute
    const elements = document.querySelectorAll('[ms-code-show-once]');

    elements.forEach(element => {
      const identifier = element.getAttribute('ms-code-show-once');
      const storageKey = `ms-code-shown-${identifier}`;

      // Check if the element has been seen before
      if (localStorage.getItem(storageKey) !== 'true') {
        // If not seen, show the element
        element.style.display = 'block';

        // Mark it as seen in localStorage
        localStorage.setItem(storageKey, 'true');
      } else {
        // If already seen, hide the element
        element.style.display = 'none';
      }
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #128 💙 - ONLY SHOW ELEMENT ONCE -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Find all elements with the ms-code-show-once attribute
    const elements = document.querySelectorAll('[ms-code-show-once]');

    elements.forEach(element => {
      const identifier = element.getAttribute('ms-code-show-once');
      const storageKey = `ms-code-shown-${identifier}`;

      // Check if the element has been seen before
      if (localStorage.getItem(storageKey) !== 'true') {
        // If not seen, show the element
        element.style.display = 'block';

        // Mark it as seen in localStorage
        localStorage.setItem(storageKey, 'true');
      } else {
        // If already seen, hide the element
        element.style.display = 'none';
      }
    });
  });
</script>
View Memberscript
UX

#127 - Validate Text Inputs

Validate text inputs against any list of strings, including wildcards!


<!-- 💙 MEMBERSCRIPT #127 v0.1 💙 - TEXT INPUT VALIDATION -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    // Debounce function
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // Find all fields with ms-code-require attribute
    const fields = document.querySelectorAll('[ms-code-require]');
    fields.forEach(field => {
        // Get the error element for this field
        const errorElement = document.querySelector(`[ms-code-require-error="${field.getAttribute('ms-code-require')}"]`);
        // Hide error message initially
        if (errorElement) {
            errorElement.style.display = 'none';
        }
        // Get the form containing the field
        const form = field.closest('form');
        // Get the submit button
        const submitButton = form ? form.querySelector(`[ms-code-submit-button="${field.getAttribute('ms-code-require')}"]`) : null;
        // Get the require-list attribute value
        const requireList = field.getAttribute('ms-code-require-list');
        if (requireList) {
            // Convert the require-list to an array of regex patterns
            const patterns = requireList.split(',').map(pattern => {
                return pattern.replace(/\{([^}]+)\}/g, (match, p1) => {
                    return p1.split('').map(char => {
                        switch(char) {
                            case '0': return '\\d';
                            case 'A': return '[A-Z]';
                            case 'a': return '[a-z]';
                            default: return char;
                        }
                    }).join('');
                });
            });
            // Validate function
            function validateField() {
                const value = field.value;
                const isValid = patterns.some(pattern => new RegExp(`^${pattern}$`).test(value));
                if (errorElement) {
                    errorElement.style.display = isValid ? 'none' : 'block';
                }
                if (submitButton) {
                    submitButton.style.opacity = isValid ? '1' : '0.5';
                    submitButton.style.pointerEvents = isValid ? 'auto' : 'none';
                }
                return isValid;
            }
            // Debounced validate function
            const debouncedValidate = debounce(validateField, 500);
            // Add blur event listener
            field.addEventListener('blur', validateField);
            // Add input event listener for debounced validation
            field.addEventListener('input', debouncedValidate);
            // Handle form submission
            if (form) {
                form.addEventListener('submit', function(event) {
                    if (!validateField() && submitButton) {
                        event.preventDefault();
                        field.focus();
                    }
                });
            }
        }
    });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #127 v0.1 💙 - TEXT INPUT VALIDATION -->
<script>
document.addEventListener('DOMContentLoaded', function() {
    // Debounce function
    function debounce(func, wait) {
        let timeout;
        return function executedFunction(...args) {
            const later = () => {
                clearTimeout(timeout);
                func(...args);
            };
            clearTimeout(timeout);
            timeout = setTimeout(later, wait);
        };
    }

    // Find all fields with ms-code-require attribute
    const fields = document.querySelectorAll('[ms-code-require]');
    fields.forEach(field => {
        // Get the error element for this field
        const errorElement = document.querySelector(`[ms-code-require-error="${field.getAttribute('ms-code-require')}"]`);
        // Hide error message initially
        if (errorElement) {
            errorElement.style.display = 'none';
        }
        // Get the form containing the field
        const form = field.closest('form');
        // Get the submit button
        const submitButton = form ? form.querySelector(`[ms-code-submit-button="${field.getAttribute('ms-code-require')}"]`) : null;
        // Get the require-list attribute value
        const requireList = field.getAttribute('ms-code-require-list');
        if (requireList) {
            // Convert the require-list to an array of regex patterns
            const patterns = requireList.split(',').map(pattern => {
                return pattern.replace(/\{([^}]+)\}/g, (match, p1) => {
                    return p1.split('').map(char => {
                        switch(char) {
                            case '0': return '\\d';
                            case 'A': return '[A-Z]';
                            case 'a': return '[a-z]';
                            default: return char;
                        }
                    }).join('');
                });
            });
            // Validate function
            function validateField() {
                const value = field.value;
                const isValid = patterns.some(pattern => new RegExp(`^${pattern}$`).test(value));
                if (errorElement) {
                    errorElement.style.display = isValid ? 'none' : 'block';
                }
                if (submitButton) {
                    submitButton.style.opacity = isValid ? '1' : '0.5';
                    submitButton.style.pointerEvents = isValid ? 'auto' : 'none';
                }
                return isValid;
            }
            // Debounced validate function
            const debouncedValidate = debounce(validateField, 500);
            // Add blur event listener
            field.addEventListener('blur', validateField);
            // Add input event listener for debounced validation
            field.addEventListener('input', debouncedValidate);
            // Handle form submission
            if (form) {
                form.addEventListener('submit', function(event) {
                    if (!validateField() && submitButton) {
                        event.preventDefault();
                        field.focus();
                    }
                });
            }
        }
    });
});
</script>
View Memberscript
Custom Flows

#126 - Post Form To Webhook Without Redirecting

Post data to a webhook & keep the default Webflow form behavior.


<!-- 💙 MEMBERSCRIPT #126 v0.1 💙 - POST FORM DATA TO WEBHOOK WITHOUT REDIRECTING -->
<script>
  // Wait for the DOM to be fully loaded
  document.addEventListener('DOMContentLoaded', function() {
    // Select all forms with the ms-code-form-no-redirect attribute
    const forms = document.querySelectorAll('form[ms-code-form-no-redirect]');

    forms.forEach(form => {
      // Select the success and error message elements for this form
      const formWrapper = form.closest('.w-form');
      const successMessage = formWrapper.querySelector('.w-form-done');
      const errorMessage = formWrapper.querySelector('.w-form-fail');

      // Add submit event listener to the form
      form.addEventListener('submit', function(event) {
        // Prevent the default form submission
        event.preventDefault();

        // Get the form data
        const formData = new FormData(form);

        // Get the submit button and set its text to the waiting message
        const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
        const originalButtonText = submitButton.value || submitButton.textContent;
        const waitingText = submitButton.getAttribute('data-wait') || 'Please wait...';

        if (submitButton.tagName === 'INPUT') {
          submitButton.value = waitingText;
        } else {
          submitButton.textContent = waitingText;
        }

        // Disable the submit button
        submitButton.disabled = true;

        // Send the form data to the form's action URL using fetch
        fetch(form.action, {
          method: 'POST',
          body: formData
        })
          .then(response => {
            if (response.ok) {
              // If the submission was successful, show the success message
              form.style.display = 'none';
              successMessage.style.display = 'block';
              errorMessage.style.display = 'none';
            } else {
              // If there was an error, show the error message
              throw new Error('Form submission failed');
            }
          })
          .catch(error => {
            // If there was a network error or the submission failed, show the error message
            console.error('Error:', error);
            errorMessage.style.display = 'block';
            successMessage.style.display = 'none';
          })
          .finally(() => {
            // Reset the submit button text and re-enable it
            if (submitButton.tagName === 'INPUT') {
              submitButton.value = originalButtonText;
            } else {
              submitButton.textContent = originalButtonText;
            }
            submitButton.disabled = false;
          });
      });
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #126 v0.1 💙 - POST FORM DATA TO WEBHOOK WITHOUT REDIRECTING -->
<script>
  // Wait for the DOM to be fully loaded
  document.addEventListener('DOMContentLoaded', function() {
    // Select all forms with the ms-code-form-no-redirect attribute
    const forms = document.querySelectorAll('form[ms-code-form-no-redirect]');

    forms.forEach(form => {
      // Select the success and error message elements for this form
      const formWrapper = form.closest('.w-form');
      const successMessage = formWrapper.querySelector('.w-form-done');
      const errorMessage = formWrapper.querySelector('.w-form-fail');

      // Add submit event listener to the form
      form.addEventListener('submit', function(event) {
        // Prevent the default form submission
        event.preventDefault();

        // Get the form data
        const formData = new FormData(form);

        // Get the submit button and set its text to the waiting message
        const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
        const originalButtonText = submitButton.value || submitButton.textContent;
        const waitingText = submitButton.getAttribute('data-wait') || 'Please wait...';

        if (submitButton.tagName === 'INPUT') {
          submitButton.value = waitingText;
        } else {
          submitButton.textContent = waitingText;
        }

        // Disable the submit button
        submitButton.disabled = true;

        // Send the form data to the form's action URL using fetch
        fetch(form.action, {
          method: 'POST',
          body: formData
        })
          .then(response => {
            if (response.ok) {
              // If the submission was successful, show the success message
              form.style.display = 'none';
              successMessage.style.display = 'block';
              errorMessage.style.display = 'none';
            } else {
              // If there was an error, show the error message
              throw new Error('Form submission failed');
            }
          })
          .catch(error => {
            // If there was a network error or the submission failed, show the error message
            console.error('Error:', error);
            errorMessage.style.display = 'block';
            successMessage.style.display = 'none';
          })
          .finally(() => {
            // Reset the submit button text and re-enable it
            if (submitButton.tagName === 'INPUT') {
              submitButton.value = originalButtonText;
            } else {
              submitButton.textContent = originalButtonText;
            }
            submitButton.disabled = false;
          });
      });
    });
  });
</script>
View Memberscript
Conditional Visibility
UX

#125 - Control Required State With Checkbox

Set form fields to required/optional based on the state of a checkbox.


<!-- 💙 MEMBERSCRIPT #125 v0.1 💙 - CHECKBOX TOGGLE REQUIRED ON FORM FIELDS -->
<script>
  // Function to initialize the form field requirements
  function initFormFieldRequirements() {
    // Handle checkbox changes
    document.querySelectorAll('[ms-code-req-if-checked], [ms-code-req-if-unchecked]').forEach(checkbox => {
      checkbox.addEventListener('change', updateFieldRequirements);
      // Initial update
      updateFieldRequirements.call(checkbox);
    });
  }

  // Function to update field requirements based on checkbox state
  function updateFieldRequirements() {
    const isChecked = this.checked;
    const group = this.getAttribute('ms-code-req-if-checked') || this.getAttribute('ms-code-req-if-unchecked');
    const ifChecked = this.hasAttribute('ms-code-req-if-checked');
    const shouldBeRequired = ifChecked ? isChecked : !isChecked;
    const shouldDisableIfNotReq = this.hasAttribute('ms-code-disable-if-not-req');

    // Update associated input fields
    document.querySelectorAll(`[ms-code-req-input="${group}"]`).forEach(input => {
      input.required = shouldBeRequired;
      updateInputStyle(input, shouldBeRequired, shouldDisableIfNotReq);
    });

    // Update associated labels
    document.querySelectorAll(`[ms-code-req-label="${group}"]`).forEach(label => {
      label.style.display = shouldBeRequired ? '' : 'none';
    });
  }

  // Function to update input style based on required state and disable setting
  function updateInputStyle(input, isRequired, shouldDisableIfNotReq) {
    if (shouldDisableIfNotReq) {
      input.style.opacity = isRequired ? '1' : '0.4';
      input.style.pointerEvents = isRequired ? '' : 'none';
    } else {
      input.style.opacity = '';
      input.style.pointerEvents = '';
    }
  }

  // Initialize when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', initFormFieldRequirements);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #125 v0.1 💙 - CHECKBOX TOGGLE REQUIRED ON FORM FIELDS -->
<script>
  // Function to initialize the form field requirements
  function initFormFieldRequirements() {
    // Handle checkbox changes
    document.querySelectorAll('[ms-code-req-if-checked], [ms-code-req-if-unchecked]').forEach(checkbox => {
      checkbox.addEventListener('change', updateFieldRequirements);
      // Initial update
      updateFieldRequirements.call(checkbox);
    });
  }

  // Function to update field requirements based on checkbox state
  function updateFieldRequirements() {
    const isChecked = this.checked;
    const group = this.getAttribute('ms-code-req-if-checked') || this.getAttribute('ms-code-req-if-unchecked');
    const ifChecked = this.hasAttribute('ms-code-req-if-checked');
    const shouldBeRequired = ifChecked ? isChecked : !isChecked;
    const shouldDisableIfNotReq = this.hasAttribute('ms-code-disable-if-not-req');

    // Update associated input fields
    document.querySelectorAll(`[ms-code-req-input="${group}"]`).forEach(input => {
      input.required = shouldBeRequired;
      updateInputStyle(input, shouldBeRequired, shouldDisableIfNotReq);
    });

    // Update associated labels
    document.querySelectorAll(`[ms-code-req-label="${group}"]`).forEach(label => {
      label.style.display = shouldBeRequired ? '' : 'none';
    });
  }

  // Function to update input style based on required state and disable setting
  function updateInputStyle(input, isRequired, shouldDisableIfNotReq) {
    if (shouldDisableIfNotReq) {
      input.style.opacity = isRequired ? '1' : '0.4';
      input.style.pointerEvents = isRequired ? '' : 'none';
    } else {
      input.style.opacity = '';
      input.style.pointerEvents = '';
    }
  }

  // Initialize when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', initFormFieldRequirements);
</script>
View Memberscript
UX

#124 - Toggle Item Visibility

Use custom buttons to hide and show elements, with states saved in local storage.


<!-- 💙 MEMBERSCRIPT #124 v0.1 💙 - TOGGLE ITEM VISIBILITY -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const STORAGE_KEY = 'ms-code-vis-states';

    // Load saved states from local storage
    let savedStates = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};

    // Find all unique group identifiers
    const groups = new Set([...document.querySelectorAll('[ms-code-vis-item]')].map(el => el.getAttribute('ms-code-vis-item')));

    groups.forEach(group => {
      const item = document.querySelector(`[ms-code-vis-item="${group}"]`);
      const showButton = document.querySelector(`[ms-code-vis-show="${group}"]`);
      const hideButton = document.querySelector(`[ms-code-vis-hide="${group}"]`);

      if (!item || !showButton || !hideButton) return;

      // Function to update visibility
      function updateVisibility(isVisible) {
        if (isVisible) {
          item.style.removeProperty('display');
          showButton.style.display = 'none';
          hideButton.style.removeProperty('display');
        } else {
          item.style.display = 'none';
          showButton.style.removeProperty('display');
          hideButton.style.display = 'none';
        }
        // Save state to local storage
        savedStates[group] = isVisible;
        localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
      }

      // Set initial visibility
      const defaultState = item.getAttribute('ms-code-vis-default');
      const savedState = savedStates[group];

      if (savedState !== undefined) {
        updateVisibility(savedState);
      } else if (defaultState === 'hide') {
        updateVisibility(false);
      } else {
        updateVisibility(true);
      }

      // Add click event listeners
      showButton.addEventListener('click', function(e) {
        e.preventDefault();
        updateVisibility(true);
      });

      hideButton.addEventListener('click', function(e) {
        e.preventDefault();
        updateVisibility(false);
      });
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #124 v0.1 💙 - TOGGLE ITEM VISIBILITY -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const STORAGE_KEY = 'ms-code-vis-states';

    // Load saved states from local storage
    let savedStates = JSON.parse(localStorage.getItem(STORAGE_KEY)) || {};

    // Find all unique group identifiers
    const groups = new Set([...document.querySelectorAll('[ms-code-vis-item]')].map(el => el.getAttribute('ms-code-vis-item')));

    groups.forEach(group => {
      const item = document.querySelector(`[ms-code-vis-item="${group}"]`);
      const showButton = document.querySelector(`[ms-code-vis-show="${group}"]`);
      const hideButton = document.querySelector(`[ms-code-vis-hide="${group}"]`);

      if (!item || !showButton || !hideButton) return;

      // Function to update visibility
      function updateVisibility(isVisible) {
        if (isVisible) {
          item.style.removeProperty('display');
          showButton.style.display = 'none';
          hideButton.style.removeProperty('display');
        } else {
          item.style.display = 'none';
          showButton.style.removeProperty('display');
          hideButton.style.display = 'none';
        }
        // Save state to local storage
        savedStates[group] = isVisible;
        localStorage.setItem(STORAGE_KEY, JSON.stringify(savedStates));
      }

      // Set initial visibility
      const defaultState = item.getAttribute('ms-code-vis-default');
      const savedState = savedStates[group];

      if (savedState !== undefined) {
        updateVisibility(savedState);
      } else if (defaultState === 'hide') {
        updateVisibility(false);
      } else {
        updateVisibility(true);
      }

      // Add click event listeners
      showButton.addEventListener('click', function(e) {
        e.preventDefault();
        updateVisibility(true);
      });

      hideButton.addEventListener('click', function(e) {
        e.preventDefault();
        updateVisibility(false);
      });
    });
  });
</script>
View Memberscript
UX
Accessibility

#123 - One Audio Playing At A Time

Automatically pause all other audioplayers when a user clicks to play one.


<!-- 💙 MEMBERSCRIPT #123 💙 - ONE AUDIO AT A TIME -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const pauseOthers = current => 
      document.querySelectorAll('audio, video').forEach(el => el !== current && !el.paused && el.pause());
  
    const addPlayListener = el => el.addEventListener('play', e => pauseOthers(e.target));
  
    new MutationObserver(mutations => 
      mutations.forEach(m => m.addedNodes.forEach(n => 
        (n.nodeName === 'AUDIO' || n.nodeName === 'VIDEO') && addPlayListener(n)
      ))
    ).observe(document.body, { childList: true, subtree: true });
  
    document.querySelectorAll('audio, video').forEach(addPlayListener);
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #123 💙 - ONE AUDIO AT A TIME -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const pauseOthers = current => 
      document.querySelectorAll('audio, video').forEach(el => el !== current && !el.paused && el.pause());
  
    const addPlayListener = el => el.addEventListener('play', e => pauseOthers(e.target));
  
    new MutationObserver(mutations => 
      mutations.forEach(m => m.addedNodes.forEach(n => 
        (n.nodeName === 'AUDIO' || n.nodeName === 'VIDEO') && addPlayListener(n)
      ))
    ).observe(document.body, { childList: true, subtree: true });
  
    document.querySelectorAll('audio, video').forEach(addPlayListener);
  });
</script>
View Memberscript
SEO
UX

#122 - Open External Links In New Tab

Automatically make external links open in a new tab, plus add nofollow and noreferrer attributes.


<!-- 💙 MEMBERSCRIPT #122 💙 - OPEN EXTERNAL LINKS IN NEW TAB -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const thisDomain = location.hostname;
    const externalSelector = `a:not([href*="${thisDomain}"]):not([href^="/"]):not([href^="#"]):not([ms-code-ext-link="ignore"])`;

    document.querySelectorAll(externalSelector).forEach(link => {
      link.target = '_blank';  // Open in new tab (comment out to disable)
      link.rel = (link.rel ? link.rel + ' ' : '') + 'noreferrer';  // Add noreferrer (comment out to disable)
      link.rel = (link.rel ? link.rel + ' ' : '') + 'nofollow';  // Add nofollow (comment out to disable)
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #122 💙 - OPEN EXTERNAL LINKS IN NEW TAB -->
<script>
  document.addEventListener('DOMContentLoaded', () => {
    const thisDomain = location.hostname;
    const externalSelector = `a:not([href*="${thisDomain}"]):not([href^="/"]):not([href^="#"]):not([ms-code-ext-link="ignore"])`;

    document.querySelectorAll(externalSelector).forEach(link => {
      link.target = '_blank';  // Open in new tab (comment out to disable)
      link.rel = (link.rel ? link.rel + ' ' : '') + 'noreferrer';  // Add noreferrer (comment out to disable)
      link.rel = (link.rel ? link.rel + ' ' : '') + 'nofollow';  // Add nofollow (comment out to disable)
    });
  });
</script>
View Memberscript
Custom Fields
UX

#121 - Render Array From Custom Field

Display any comma-separated list back to your members in Webflow.


<!-- 💙 MEMBERSCRIPT #121 v0.1 💙 - RENDER ARRAY FROM CUSTOM FIELD -->
<script>
  // Function to render arrays from Memberstack custom fields
  function renderMemberstackArrays() {
    // Get all elements with the ms-code-render-array attribute
    const arrayElements = document.querySelectorAll('[ms-code-render-array]');

    arrayElements.forEach(element => {
      const customFieldKey = element.getAttribute('ms-code-render-array');

      // Get Memberstack data from localStorage
      const memberData = JSON.parse(localStorage.getItem('_ms-mem'));

      if (!memberData || !memberData.customFields || !memberData.customFields[customFieldKey]) {
        // If no data found, remove the element
        element.remove();
        return;
      }

      const arrayString = memberData.customFields[customFieldKey];

      // Convert string to array, trim whitespace
      const arrayItems = arrayString.split(',').map(item => item.trim()).filter(item => item !== '');

      if (arrayItems.length === 0) {
        // If array is empty, remove the element
        element.remove();
        return;
      }

      // Store the parent element
      const parentElement = element.parentNode;

      // Clone the template
      const templateItem = element.cloneNode(true);

      // Remove the ms-code-render-array attribute from the template
      templateItem.removeAttribute('ms-code-render-array');

      // Create a document fragment to hold the new items
      const fragment = document.createDocumentFragment();

      // Render array items
      arrayItems.forEach(item => {
        const newItem = templateItem.cloneNode(true);

        // Replace the content of the new item
        replaceContent(newItem, item);

        fragment.appendChild(newItem);
      });

      // Replace the original element with the new items
      parentElement.replaceChild(fragment, element);
    });
  }

  // Helper function to replace content in the element
  function replaceContent(element, newContent) {
    if (element.childNodes.length > 0) {
      element.childNodes.forEach(child => {
        if (child.nodeType === Node.ELEMENT_NODE) {
          replaceContent(child, newContent);
        } else if (child.nodeType === Node.TEXT_NODE) {
          child.textContent = newContent;
        }
      });
    } else {
      element.textContent = newContent;
    }
  }

  // Run the function when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', renderMemberstackArrays);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #121 v0.1 💙 - RENDER ARRAY FROM CUSTOM FIELD -->
<script>
  // Function to render arrays from Memberstack custom fields
  function renderMemberstackArrays() {
    // Get all elements with the ms-code-render-array attribute
    const arrayElements = document.querySelectorAll('[ms-code-render-array]');

    arrayElements.forEach(element => {
      const customFieldKey = element.getAttribute('ms-code-render-array');

      // Get Memberstack data from localStorage
      const memberData = JSON.parse(localStorage.getItem('_ms-mem'));

      if (!memberData || !memberData.customFields || !memberData.customFields[customFieldKey]) {
        // If no data found, remove the element
        element.remove();
        return;
      }

      const arrayString = memberData.customFields[customFieldKey];

      // Convert string to array, trim whitespace
      const arrayItems = arrayString.split(',').map(item => item.trim()).filter(item => item !== '');

      if (arrayItems.length === 0) {
        // If array is empty, remove the element
        element.remove();
        return;
      }

      // Store the parent element
      const parentElement = element.parentNode;

      // Clone the template
      const templateItem = element.cloneNode(true);

      // Remove the ms-code-render-array attribute from the template
      templateItem.removeAttribute('ms-code-render-array');

      // Create a document fragment to hold the new items
      const fragment = document.createDocumentFragment();

      // Render array items
      arrayItems.forEach(item => {
        const newItem = templateItem.cloneNode(true);

        // Replace the content of the new item
        replaceContent(newItem, item);

        fragment.appendChild(newItem);
      });

      // Replace the original element with the new items
      parentElement.replaceChild(fragment, element);
    });
  }

  // Helper function to replace content in the element
  function replaceContent(element, newContent) {
    if (element.childNodes.length > 0) {
      element.childNodes.forEach(child => {
        if (child.nodeType === Node.ELEMENT_NODE) {
          replaceContent(child, newContent);
        } else if (child.nodeType === Node.TEXT_NODE) {
          child.textContent = newContent;
        }
      });
    } else {
      element.textContent = newContent;
    }
  }

  // Run the function when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', renderMemberstackArrays);
</script>
View Memberscript
UX

#120 - Show/Hide Element Based On Device, OS, or Browser

Conditional visibility based on which device, operating system, or browser the visitor is using.


<!-- 💙 MEMBERSCRIPT #120 v0.1 💙 - DEVICE/OS/BROWSER CONDITIONAL VISIBILITY -->
<script>
  // Define device types, operating systems, and browsers with their detection methods
  const detectionTypes = {
    // Device types
    mobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
    tablet: () => /iPad|Android|Silk/i.test(navigator.userAgent) && !/Mobile/i.test(navigator.userAgent),
    desktop: () => !(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)),
    touchdevice: () => 'ontouchstart' in window || navigator.maxTouchPoints > 0,
    landscape: () => window.matchMedia("(orientation: landscape)").matches,
    portrait: () => window.matchMedia("(orientation: portrait)").matches,

    // Operating Systems
    ios: () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
    android: () => /Android/.test(navigator.userAgent),
    macos: () => /Mac OS X/.test(navigator.userAgent) && !(/iPad|iPhone|iPod/.test(navigator.userAgent)),
    windows: () => /Win/.test(navigator.platform),
    linux: () => /Linux/.test(navigator.platform),

    // Browsers
    chrome: () => /Chrome/.test(navigator.userAgent) && !/Chromium/.test(navigator.userAgent),
    firefox: () => /Firefox/.test(navigator.userAgent),
    safari: () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
    edge: () => /Edg/.test(navigator.userAgent),
    opera: () => /OPR/.test(navigator.userAgent) || /Opera/.test(navigator.userAgent),
    ie: () => /Trident/.test(navigator.userAgent)
  };

  function detectTypes() {
    const detected = [];
    for (const [type, detector] of Object.entries(detectionTypes)) {
      if (detector()) {
        detected.push(type);
      }
    }
    return detected;
  }

  function evaluateCondition(condition, currentTypes) {
    const parts = condition.split('&').map(part => part.trim());
    return parts.every(part => {
      const orParts = part.split('|').map(p => p.trim());
      return orParts.some(p => {
        if (p.startsWith('!')) {
          return !currentTypes.includes(p.slice(1));
        }
        return currentTypes.includes(p);
      });
    });
  }

  function updateElementVisibility() {
    const currentTypes = detectTypes();
    const elements = document.querySelectorAll('[ms-code-device-show]');

    elements.forEach(element => {
      const showAttribute = element.getAttribute('ms-code-device-show').toLowerCase();
      const shouldShow = evaluateCondition(showAttribute, currentTypes);
      element.style.display = shouldShow ? '' : 'none';
    });
  }

  // Run on page load and window resize
  window.addEventListener('load', updateElementVisibility);
  window.addEventListener('resize', updateElementVisibility);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #120 v0.1 💙 - DEVICE/OS/BROWSER CONDITIONAL VISIBILITY -->
<script>
  // Define device types, operating systems, and browsers with their detection methods
  const detectionTypes = {
    // Device types
    mobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
    tablet: () => /iPad|Android|Silk/i.test(navigator.userAgent) && !/Mobile/i.test(navigator.userAgent),
    desktop: () => !(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)),
    touchdevice: () => 'ontouchstart' in window || navigator.maxTouchPoints > 0,
    landscape: () => window.matchMedia("(orientation: landscape)").matches,
    portrait: () => window.matchMedia("(orientation: portrait)").matches,

    // Operating Systems
    ios: () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream,
    android: () => /Android/.test(navigator.userAgent),
    macos: () => /Mac OS X/.test(navigator.userAgent) && !(/iPad|iPhone|iPod/.test(navigator.userAgent)),
    windows: () => /Win/.test(navigator.platform),
    linux: () => /Linux/.test(navigator.platform),

    // Browsers
    chrome: () => /Chrome/.test(navigator.userAgent) && !/Chromium/.test(navigator.userAgent),
    firefox: () => /Firefox/.test(navigator.userAgent),
    safari: () => /Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgent),
    edge: () => /Edg/.test(navigator.userAgent),
    opera: () => /OPR/.test(navigator.userAgent) || /Opera/.test(navigator.userAgent),
    ie: () => /Trident/.test(navigator.userAgent)
  };

  function detectTypes() {
    const detected = [];
    for (const [type, detector] of Object.entries(detectionTypes)) {
      if (detector()) {
        detected.push(type);
      }
    }
    return detected;
  }

  function evaluateCondition(condition, currentTypes) {
    const parts = condition.split('&').map(part => part.trim());
    return parts.every(part => {
      const orParts = part.split('|').map(p => p.trim());
      return orParts.some(p => {
        if (p.startsWith('!')) {
          return !currentTypes.includes(p.slice(1));
        }
        return currentTypes.includes(p);
      });
    });
  }

  function updateElementVisibility() {
    const currentTypes = detectTypes();
    const elements = document.querySelectorAll('[ms-code-device-show]');

    elements.forEach(element => {
      const showAttribute = element.getAttribute('ms-code-device-show').toLowerCase();
      const shouldShow = evaluateCondition(showAttribute, currentTypes);
      element.style.display = shouldShow ? '' : 'none';
    });
  }

  // Run on page load and window resize
  window.addEventListener('load', updateElementVisibility);
  window.addEventListener('resize', updateElementVisibility);
</script>
View Memberscript
Custom Fields

#119 - Age Calculator From Date Input

Calculate total years passed and prefill an input with the number.


<!-- 💙 MEMBERSCRIPT #119 v0.1 💙 - AGE CALCULATOR FROM DATE INPUT -->
<script>
  // Function to calculate age
  function calculateAge(birthDate) {
    const today = new Date();
    const birth = new Date(birthDate);
    let age = today.getFullYear() - birth.getFullYear();
    const monthDiff = today.getMonth() - birth.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
      age--;
    }

    return age;
  }

  // Function to update age input
  function updateAgeInput(birthdayInput) {
    const attrValue = birthdayInput.getAttribute('ms-code-bday-input');
    const ageInput = document.querySelector(`[ms-code-age-input="${attrValue}"]`);

    if (birthdayInput.value) {
      const age = calculateAge(birthdayInput.value);
      ageInput.value = age;
    } else {
      ageInput.value = '';
    }
  }

  // Function to set up event listeners for all birthday inputs
  function setupAgeCalculators() {
    const birthdayInputs = document.querySelectorAll('[ms-code-bday-input]');

    birthdayInputs.forEach(input => {
      input.addEventListener('input', () => updateAgeInput(input));
      // Initial call to set age if birthday is pre-filled
      updateAgeInput(input);
    });
  }

  // Run setup when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', setupAgeCalculators);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #119 v0.1 💙 - AGE CALCULATOR FROM DATE INPUT -->
<script>
  // Function to calculate age
  function calculateAge(birthDate) {
    const today = new Date();
    const birth = new Date(birthDate);
    let age = today.getFullYear() - birth.getFullYear();
    const monthDiff = today.getMonth() - birth.getMonth();

    if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birth.getDate())) {
      age--;
    }

    return age;
  }

  // Function to update age input
  function updateAgeInput(birthdayInput) {
    const attrValue = birthdayInput.getAttribute('ms-code-bday-input');
    const ageInput = document.querySelector(`[ms-code-age-input="${attrValue}"]`);

    if (birthdayInput.value) {
      const age = calculateAge(birthdayInput.value);
      ageInput.value = age;
    } else {
      ageInput.value = '';
    }
  }

  // Function to set up event listeners for all birthday inputs
  function setupAgeCalculators() {
    const birthdayInputs = document.querySelectorAll('[ms-code-bday-input]');

    birthdayInputs.forEach(input => {
      input.addEventListener('input', () => updateAgeInput(input));
      // Initial call to set age if birthday is pre-filled
      updateAgeInput(input);
    });
  }

  // Run setup when the DOM is fully loaded
  document.addEventListener('DOMContentLoaded', setupAgeCalculators);
</script>
View Memberscript
We couldn't find any scripts for that search... please try again.
Slack

Need help with MemberScripts? Join our 5,500+ Member Slack community! 🙌

MemberScripts are a community resource by Memberstack - if you need any help making them work with your project, please join the Memberstack 2.0 Slack and ask for help!

Join our Slack
Showcase

Explore real businesses who've succeeded with Memberstack

Don't just take our word for it - check out businesses of all sizes who rely on Memberstack for their authentication and payments.

View all success stories
Even Webflow uses Memberstack!
Start building

Start building your dreams

Memberstack is 100% free until you're ready to launch - so, what are you waiting for? Create your first app and start building today.