Componentes
Plantillas
Atributos
Integraciones
Comprobador del sitio
Código personalizado

MemberScripts

Una solución basada en atributos para añadir funciones a su sitio Webflow.
Simplemente copie algo de código, añada algunos atributos y listo.

Muchas gracias. Hemos recibido su envío.
¡Uy! Algo ha ido mal al enviar el formulario.
¿Necesita ayuda con MemberScripts?

Todos los clientes de Memberstack pueden solicitar asistencia en el Slack 2.0. Tenga en cuenta que no se trata de funciones oficiales y que no se puede garantizar la asistencia.

UX
Accesibilidad

#145 - Automatically Save & Prefill Forms

Automatically save and prefill forms in a browsers localStorage upon form submission.


<!-- 💙 MEMBERSCRIPT #145 v0.1 💙 - HOW TO PRE-FILL FORM INPUT FIELDS AT PAGE LOAD -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Function to store form data in localStorage
    function storeFormData() {
      const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
      fields.forEach(function(field) {
        const fieldId = field.getAttribute('ms-code-field-id');
        const value = field.value.trim();
        if (value) {
          localStorage.setItem(fieldId, value);
        }
      });
    }

    // Function to pre-fill form fields with stored data
    function preFillForm() {
      const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
      fields.forEach(function(field) {
        const fieldId = field.getAttribute('ms-code-field-id');
        const storedValue = localStorage.getItem(fieldId);
        if (storedValue) {
          field.value = storedValue;
        }
      });
    }

    // Handle form submission
    const form = document.querySelector('#my-form, form[ms-code-form-id]');
    if (form) {
      form.addEventListener('submit', function(event) {
        event.preventDefault(); // Prevent default form submission
        storeFormData();

        // Refresh the page after storing data
        setTimeout(function() {
          location.reload();
        }, 500); // Short delay to simulate form submission
      });
    }

    // Pre-fill form fields when the page loads
    preFillForm();
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #145 v0.1 💙 - HOW TO PRE-FILL FORM INPUT FIELDS AT PAGE LOAD -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Function to store form data in localStorage
    function storeFormData() {
      const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
      fields.forEach(function(field) {
        const fieldId = field.getAttribute('ms-code-field-id');
        const value = field.value.trim();
        if (value) {
          localStorage.setItem(fieldId, value);
        }
      });
    }

    // Function to pre-fill form fields with stored data
    function preFillForm() {
      const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
      fields.forEach(function(field) {
        const fieldId = field.getAttribute('ms-code-field-id');
        const storedValue = localStorage.getItem(fieldId);
        if (storedValue) {
          field.value = storedValue;
        }
      });
    }

    // Handle form submission
    const form = document.querySelector('#my-form, form[ms-code-form-id]');
    if (form) {
      form.addEventListener('submit', function(event) {
        event.preventDefault(); // Prevent default form submission
        storeFormData();

        // Refresh the page after storing data
        setTimeout(function() {
          location.reload();
        }, 500); // Short delay to simulate form submission
      });
    }

    // Pre-fill form fields when the page loads
    preFillForm();
  });
</script>
Ver Memberscript
Marketing
JSON
SEO

#144 - Track Users Login History & Active Users

Automatically track a members login history, keep a login streak and total visits


<!-- 💙 MEMBERSCRIPT #144 v0.1 💙 - TRACK A USERS LOGIN HISTORY & ACTIVE USERS -->
<script>
  (function(){
    const memberstack = window.$memberstackDom;

    // Helper: Execute callback when Memberstack is ready
    function onMemberstackReady(cb) {
      if (window.$memberstackReady) {
        cb();
      } else {
        document.addEventListener("memberstack.ready", cb);
      }
    }

    async function initTracking() {
      // Check if a member is logged in (via localStorage)
      const currentUser = JSON.parse(localStorage.getItem("_ms-mem") || "null");
      if (!currentUser) {
        console.warn("No logged-in member found. Tracking not applied.");
        return;
      }

      // Retrieve member metadata
      const memberJson = await memberstack.getMemberJSON();
      let metadata = memberJson.data || {};

      // Ensure userVisits exists as an array
      metadata.userVisits = Array.isArray(metadata.userVisits) ? metadata.userVisits : [];

      // Use ISO date (YYYY-MM-DD) to record one visit per day
      const today = new Date().toISOString().split("T")[0];
      if (!metadata.userVisits.includes(today)) {
        metadata.userVisits.push(today);
      }

      // Helper: Compute consecutive login streak from userVisits
      function computeStreak(visits) {
        if (!visits.length) return 0;
        // Ensure dates are unique and sorted ascending
        const uniqueVisits = [...new Set(visits)].sort();
        let streak = 1;
        let currentDate = new Date(uniqueVisits[uniqueVisits.length - 1]);
        for (let i = uniqueVisits.length - 2; i >= 0; i--) {
          const prevDate = new Date(uniqueVisits[i]);
          const diffDays = Math.floor((currentDate - prevDate) / (1000 * 60 * 60 * 24));
          if (diffDays === 1) {
            streak++;
            currentDate = prevDate;
          } else {
            break;
          }
        }
        return streak;
      }

      // Calculate the login streak and total visits
      metadata.loginStreak = computeStreak(metadata.userVisits);
      metadata.totalVisits = metadata.userVisits.length;

      // Update Memberstack metadata
      await memberstack.updateMemberJSON({ json: metadata });

      console.log("User visits:", metadata.userVisits);
      console.log("Login streak:", metadata.loginStreak);
      console.log("Total visits:", metadata.totalVisits);
    }

    onMemberstackReady(initTracking);
  })();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #144 v0.1 💙 - TRACK A USERS LOGIN HISTORY & ACTIVE USERS -->
<script>
  (function(){
    const memberstack = window.$memberstackDom;

    // Helper: Execute callback when Memberstack is ready
    function onMemberstackReady(cb) {
      if (window.$memberstackReady) {
        cb();
      } else {
        document.addEventListener("memberstack.ready", cb);
      }
    }

    async function initTracking() {
      // Check if a member is logged in (via localStorage)
      const currentUser = JSON.parse(localStorage.getItem("_ms-mem") || "null");
      if (!currentUser) {
        console.warn("No logged-in member found. Tracking not applied.");
        return;
      }

      // Retrieve member metadata
      const memberJson = await memberstack.getMemberJSON();
      let metadata = memberJson.data || {};

      // Ensure userVisits exists as an array
      metadata.userVisits = Array.isArray(metadata.userVisits) ? metadata.userVisits : [];

      // Use ISO date (YYYY-MM-DD) to record one visit per day
      const today = new Date().toISOString().split("T")[0];
      if (!metadata.userVisits.includes(today)) {
        metadata.userVisits.push(today);
      }

      // Helper: Compute consecutive login streak from userVisits
      function computeStreak(visits) {
        if (!visits.length) return 0;
        // Ensure dates are unique and sorted ascending
        const uniqueVisits = [...new Set(visits)].sort();
        let streak = 1;
        let currentDate = new Date(uniqueVisits[uniqueVisits.length - 1]);
        for (let i = uniqueVisits.length - 2; i >= 0; i--) {
          const prevDate = new Date(uniqueVisits[i]);
          const diffDays = Math.floor((currentDate - prevDate) / (1000 * 60 * 60 * 24));
          if (diffDays === 1) {
            streak++;
            currentDate = prevDate;
          } else {
            break;
          }
        }
        return streak;
      }

      // Calculate the login streak and total visits
      metadata.loginStreak = computeStreak(metadata.userVisits);
      metadata.totalVisits = metadata.userVisits.length;

      // Update Memberstack metadata
      await memberstack.updateMemberJSON({ json: metadata });

      console.log("User visits:", metadata.userVisits);
      console.log("Login streak:", metadata.loginStreak);
      console.log("Total visits:", metadata.totalVisits);
    }

    onMemberstackReady(initTracking);
  })();
</script>
Ver Memberscript
UX
Marketing

#143 - Initial Based Profile Avatar

Generate a custom avatar with initials when a member has no profile picture.


<!-- 💙 MEMBERSCRIPT #143 v0.1 💙 - GENERATE INITIALS BASED AVATAR -->
<script>
  document.addEventListener("DOMContentLoaded", function () {
    const checkMemberstack = setInterval(() => {
      if (window.$memberstackDom) {
        clearInterval(checkMemberstack);

        window.$memberstackDom.getCurrentMember().then(({ data }) => {
          if (!data) return console.log("No member data (logged out)");

          const profileImage = document.querySelector('[data-ms-member="profile-image"]');
          const avatarWrapper = document.querySelector('[data-ms-code="avatar"]');
          const initialsDiv = avatarWrapper?.querySelector('.ms-avatar-initial');

          if (data.profileImage) {
            profileImage?.style.setProperty("display", "block");
            avatarWrapper?.style.setProperty("display", "none");
          } else {
            profileImage?.style.setProperty("display", "none");
            avatarWrapper?.style.setProperty("display", "flex");

            // Get initials from available fields
            const first = data.customFields["first-name"]?.trim().charAt(0).toUpperCase() || "";
            const last = data.customFields["last-name"]?.trim().charAt(0).toUpperCase() || "";
            let initials = first + last;

            if (!initials) {
              const fullName = data.customFields["name"]?.trim().split(" ") || [];
              initials = fullName.length > 1
                ? (fullName[0].charAt(0) + fullName[1].charAt(0)).toUpperCase()
                : fullName[0]?.charAt(0).toUpperCase() || "?";
            }

            if (initialsDiv) {
              initialsDiv.textContent = initials;
            } else {
              avatarWrapper.innerHTML = `
${initials}
`; } } }).catch(console.error); } }, 100); }); </script>
v0.1

<!-- 💙 MEMBERSCRIPT #143 v0.1 💙 - GENERATE INITIALS BASED AVATAR -->
<script>
  document.addEventListener("DOMContentLoaded", function () {
    const checkMemberstack = setInterval(() => {
      if (window.$memberstackDom) {
        clearInterval(checkMemberstack);

        window.$memberstackDom.getCurrentMember().then(({ data }) => {
          if (!data) return console.log("No member data (logged out)");

          const profileImage = document.querySelector('[data-ms-member="profile-image"]');
          const avatarWrapper = document.querySelector('[data-ms-code="avatar"]');
          const initialsDiv = avatarWrapper?.querySelector('.ms-avatar-initial');

          if (data.profileImage) {
            profileImage?.style.setProperty("display", "block");
            avatarWrapper?.style.setProperty("display", "none");
          } else {
            profileImage?.style.setProperty("display", "none");
            avatarWrapper?.style.setProperty("display", "flex");

            // Get initials from available fields
            const first = data.customFields["first-name"]?.trim().charAt(0).toUpperCase() || "";
            const last = data.customFields["last-name"]?.trim().charAt(0).toUpperCase() || "";
            let initials = first + last;

            if (!initials) {
              const fullName = data.customFields["name"]?.trim().split(" ") || [];
              initials = fullName.length > 1
                ? (fullName[0].charAt(0) + fullName[1].charAt(0)).toUpperCase()
                : fullName[0]?.charAt(0).toUpperCase() || "?";
            }

            if (initialsDiv) {
              initialsDiv.textContent = initials;
            } else {
              avatarWrapper.innerHTML = `
${initials}
`; } } }).catch(console.error); } }, 100); }); </script>
Ver Memberscript
UX
Marketing

#142 - Embed PDFs For Webflow

Easily embed a PDF on your Webflow site - for free, without any custom code.


<!-- 💙 MEMBERSCRIPT #142 v0.1 💙 - EMBED PDFS IN WEBFLOW -->
<script>
  document.addEventListener("DOMContentLoaded", function () {
    const pdfElements = document.querySelectorAll('[ms-code-pdf-src]');

    pdfElements.forEach(function (element) {
      const src = element.getAttribute('ms-code-pdf-src');
      const height = element.getAttribute('ms-code-pdf-height') || '500px';

      const iframe = document.createElement('iframe');
      iframe.src = src;
      iframe.style.width = '100%';
      iframe.style.height = height;
      iframe.style.border = 'none';
      // Set the iframe to block to remove any inline element gaps
      iframe.style.display = 'block';
      iframe.setAttribute('scrolling', 'auto');

      element.innerHTML = '';
      element.appendChild(iframe);
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #142 v0.1 💙 - EMBED PDFS IN WEBFLOW -->
<script>
  document.addEventListener("DOMContentLoaded", function () {
    const pdfElements = document.querySelectorAll('[ms-code-pdf-src]');

    pdfElements.forEach(function (element) {
      const src = element.getAttribute('ms-code-pdf-src');
      const height = element.getAttribute('ms-code-pdf-height') || '500px';

      const iframe = document.createElement('iframe');
      iframe.src = src;
      iframe.style.width = '100%';
      iframe.style.height = height;
      iframe.style.border = 'none';
      // Set the iframe to block to remove any inline element gaps
      iframe.style.display = 'block';
      iframe.setAttribute('scrolling', 'auto');

      element.innerHTML = '';
      element.appendChild(iframe);
    });
  });
</script>
Ver Memberscript
UX
Marketing

#141 - Iniciar la inserción en YouTube a una hora específica

Habilita enlaces compartibles y comienza a reproducir vídeos a una hora determinada.


<!-- 💙 MEMBERSCRIPT #141 v0.1 💙 - START YOUTUBE VIDEO AT SPECIFIC TIME -->
<script>
  (function() {
    // Function to get URL parameters
    function getUrlParameter(name) {
      name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
      var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
      var results = regex.exec(location.search);
      return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
    }

    // Function to update YouTube embed src within Embedly iframe
    function updateYouTubeEmbed(embedly_iframe, startTime) {
      var embedly_src = embedly_iframe.src;
      var youtube_src_match = embedly_src.match(/src=([^&]+)/);
      if (youtube_src_match) {
        var youtube_src = decodeURIComponent(youtube_src_match[1]);
        var new_youtube_src = youtube_src.replace(/(\?|&)start=\d+/, '');
        new_youtube_src += (new_youtube_src.includes('?') ? '&' : '?') + 'start=' + startTime;
        var new_embedly_src = embedly_src.replace(/src=([^&]+)/, 'src=' + encodeURIComponent(new_youtube_src));
        embedly_iframe.src = new_embedly_src;
      }
    }

    // Get all elements with ms-code-yt-start attribute
    var elements = document.querySelectorAll('[ms-code-yt-start]');

    elements.forEach(function(element) {
      var paramName = element.getAttribute('ms-code-yt-start');
      var startTime = getUrlParameter(paramName);
      var defaultStartTime = element.getAttribute('ms-code-yt-start-default');

      // If no URL parameter, use the default start time (if specified)
      if (!startTime && defaultStartTime) {
        startTime = defaultStartTime;
      }

      // If we have a start time (either from URL or default), update the embed
      if (startTime) {
        var iframe = element.querySelector('iframe.embedly-embed');
        if (iframe) {
          updateYouTubeEmbed(iframe, startTime);
        }
      }
    });
  })();
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #141 v0.1 💙 - START YOUTUBE VIDEO AT SPECIFIC TIME -->
<script>
  (function() {
    // Function to get URL parameters
    function getUrlParameter(name) {
      name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
      var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
      var results = regex.exec(location.search);
      return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
    }

    // Function to update YouTube embed src within Embedly iframe
    function updateYouTubeEmbed(embedly_iframe, startTime) {
      var embedly_src = embedly_iframe.src;
      var youtube_src_match = embedly_src.match(/src=([^&]+)/);
      if (youtube_src_match) {
        var youtube_src = decodeURIComponent(youtube_src_match[1]);
        var new_youtube_src = youtube_src.replace(/(\?|&)start=\d+/, '');
        new_youtube_src += (new_youtube_src.includes('?') ? '&' : '?') + 'start=' + startTime;
        var new_embedly_src = embedly_src.replace(/src=([^&]+)/, 'src=' + encodeURIComponent(new_youtube_src));
        embedly_iframe.src = new_embedly_src;
      }
    }

    // Get all elements with ms-code-yt-start attribute
    var elements = document.querySelectorAll('[ms-code-yt-start]');

    elements.forEach(function(element) {
      var paramName = element.getAttribute('ms-code-yt-start');
      var startTime = getUrlParameter(paramName);
      var defaultStartTime = element.getAttribute('ms-code-yt-start-default');

      // If no URL parameter, use the default start time (if specified)
      if (!startTime && defaultStartTime) {
        startTime = defaultStartTime;
      }

      // If we have a start time (either from URL or default), update the embed
      if (startTime) {
        var iframe = element.querySelector('iframe.embedly-embed');
        if (iframe) {
          updateYouTubeEmbed(iframe, startTime);
        }
      }
    });
  })();
</script>
Ver Memberscript
Flujos personalizados
UX

#140 - Confirmar la coincidencia de entradas

Verificar una entrada antes de permitir su envío: ¡genial para evitar información incorrecta!


<!-- 💙 MEMBERSCRIPT #140 v0.1 💙 - CONFIRM MATCHING INPUTS -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const forms = document.querySelectorAll('form');

    forms.forEach(form => {
      const inputPairs = form.querySelectorAll('[ms-code-conf-input]');
      const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');

      if (!submitButton) {
        console.error('Submit button not found in the form');
        return;
      }

      function validateForm() {
        let fieldsMatch = true;

        inputPairs.forEach(input => {
          const confType = input.getAttribute('ms-code-conf-input');
          const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
          const errorElement = form.querySelector(`[ms-code-conf-error="${confType}"]`);

          if (confirmInput && errorElement) {
            if (input.value && confirmInput.value) {
              if (input.value !== confirmInput.value) {
                errorElement.style.removeProperty('display');
                fieldsMatch = false;
              } else {
                errorElement.style.display = 'none';
              }
            } else {
              errorElement.style.display = 'none';
            }
          }
        });

        if (fieldsMatch) {
          submitButton.style.removeProperty('pointer-events');
          submitButton.disabled = false;
        } else {
          submitButton.style.pointerEvents = 'none';
          submitButton.disabled = true;
        }
      }

      inputPairs.forEach(input => {
        const confType = input.getAttribute('ms-code-conf-input');
        const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);

        if (confirmInput) {
          input.addEventListener('input', validateForm);
          confirmInput.addEventListener('input', validateForm);
        }
      });

      // Initial validation
      validateForm();

      // Extra precaution: prevent form submission if fields don't match
      form.addEventListener('submit', function(event) {
        if (submitButton.disabled) {
          event.preventDefault();
          console.log('Form submission blocked: Fields do not match');
        }
      });
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #140 v0.1 💙 - CONFIRM MATCHING INPUTS -->
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const forms = document.querySelectorAll('form');

    forms.forEach(form => {
      const inputPairs = form.querySelectorAll('[ms-code-conf-input]');
      const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');

      if (!submitButton) {
        console.error('Submit button not found in the form');
        return;
      }

      function validateForm() {
        let fieldsMatch = true;

        inputPairs.forEach(input => {
          const confType = input.getAttribute('ms-code-conf-input');
          const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
          const errorElement = form.querySelector(`[ms-code-conf-error="${confType}"]`);

          if (confirmInput && errorElement) {
            if (input.value && confirmInput.value) {
              if (input.value !== confirmInput.value) {
                errorElement.style.removeProperty('display');
                fieldsMatch = false;
              } else {
                errorElement.style.display = 'none';
              }
            } else {
              errorElement.style.display = 'none';
            }
          }
        });

        if (fieldsMatch) {
          submitButton.style.removeProperty('pointer-events');
          submitButton.disabled = false;
        } else {
          submitButton.style.pointerEvents = 'none';
          submitButton.disabled = true;
        }
      }

      inputPairs.forEach(input => {
        const confType = input.getAttribute('ms-code-conf-input');
        const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);

        if (confirmInput) {
          input.addEventListener('input', validateForm);
          confirmInput.addEventListener('input', validateForm);
        }
      });

      // Initial validation
      validateForm();

      // Extra precaution: prevent form submission if fields don't match
      form.addEventListener('submit', function(event) {
        if (submitButton.disabled) {
          event.preventDefault();
          console.log('Form submission blocked: Fields do not match');
        }
      });
    });
  });
</script>
Ver Memberscript
UX

#139 - Restablecer formulario tras envío

Cree un botón en el estado de éxito del formulario que permita enviarlo de nuevo.


<!-- 💙 MEMBERSCRIPT #139 v0.1 💙 - RESET FORM BUTTON -->
<script>
  // Wait for the DOM to be fully loaded
  document.addEventListener('DOMContentLoaded', function() {
    // Find all "Add another" buttons
    const resetButtons = document.querySelectorAll('[ms-code-reset-form]');

    // Add click event listener to each button
    resetButtons.forEach(function(resetButton) {
      resetButton.addEventListener('click', function(e) {
        e.preventDefault(); // Prevent default link behavior

        // Find the closest form and success message elements
        const formWrapper = this.closest('.w-form');
        const form = formWrapper.querySelector('form');
        const successMessage = formWrapper.querySelector('.w-form-done');

        // Reset the form
        form.reset();

        // Hide the success message
        successMessage.style.display = 'none';

        // Show the form
        form.style.display = 'block';
      });
    });
  });
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #139 v0.1 💙 - RESET FORM BUTTON -->
<script>
  // Wait for the DOM to be fully loaded
  document.addEventListener('DOMContentLoaded', function() {
    // Find all "Add another" buttons
    const resetButtons = document.querySelectorAll('[ms-code-reset-form]');

    // Add click event listener to each button
    resetButtons.forEach(function(resetButton) {
      resetButton.addEventListener('click', function(e) {
        e.preventDefault(); // Prevent default link behavior

        // Find the closest form and success message elements
        const formWrapper = this.closest('.w-form');
        const form = formWrapper.querySelector('form');
        const successMessage = formWrapper.querySelector('.w-form-done');

        // Reset the form
        form.reset();

        // Hide the success message
        successMessage.style.display = 'none';

        // Show the form
        form.style.display = 'block';
      });
    });
  });
</script>
Ver Memberscript
UX

#138 - Desplazamiento del enlace de anclaje

Solucionar el problema con los enlaces de anclaje y las barras de navegación pegajosas/fijas en 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>
Ver Memberscript
UX

#137 - Mostrar el nombre del país del visitante

Sustituye el texto por el país en el que se encuentra un usuario en función de su dirección IP.


<!-- 💙 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>
Ver Memberscript
UX

#136 - Eliminar la ruta de la sección de la URL

Cuando se navegue a una sección, se eliminará la ruta del enlace de anclaje.


<!-- 💙 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>
Ver Memberscript
Flujos personalizados

#135 - Redirección basada en el valor seleccionado

Establezca dinámicamente la redirección del formulario en función de la selección de los usuarios.


<!-- 💙 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>
Ver Memberscript
UX

#134 - Desplazarse al principio al cambiar de pestaña

Al cambiar de pestaña, la página se desplazará a la parte superior de la sección de pestañas.


<!-- 💙 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>
Ver Memberscript
Seguridad

#133 - Imágenes con marca de agua automática

Añada fácilmente una marca de agua a las imágenes de su sitio Webflow.


<!-- 💙 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>
Ver Memberscript
Accesibilidad
Modales

#132 - Ocultar elementos con la tecla Escape

Añade un atributo y cuando se haga clic en la tecla esc, el elemento se establecerá en mostrar ninguno.


<!-- 💙 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>
Ver Memberscript
Campos personalizados

#131 - Sumar entradas de números

Toma el valor de las entradas numéricas y lo muestra en un valor de entrada, o en un span de texto.


<!-- 💙 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>
Ver Memberscript
UX

#130 - Enviar formulario automáticamente cuando cambian todas las entradas

Omita la necesidad de un botón de envío y envíe el formulario cuando cambien todas las entradas.


<!-- 💙 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>
Ver Memberscript
Visibilidad condicional

#129 - El paso del país

Impida que los visitantes vean su sitio si se encuentran en uno de los países no permitidos.


<!-- 💙 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>
Ver Memberscript
Visibilidad condicional
UX

#128 - Ocultar elementos una vez vistos

Añada un atributo, y sus visitantes sólo verán ese elemento una vez. Después de actualizar, desaparecerá.


<!-- 💙 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>
Ver Memberscript
UX

#127 - Validar entradas de texto

Valide las entradas de texto con cualquier lista de cadenas, incluidos los comodines.


<!-- 💙 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>
Ver Memberscript
Flujos personalizados

#126 - Enviar formulario a Webhook sin redireccionar

Enviar datos a un webhook y mantener el comportamiento predeterminado del formulario Webflow.


<!-- 💙 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>
Ver Memberscript
No hemos podido encontrar ningún script para esa búsqueda... por favor, inténtalo de nuevo.
Slack

¿Necesitas ayuda con MemberScripts? ¡Únete a nuestra comunidad Slack de más de 5.500 miembros! 🙌

Los MemberScripts son un recurso comunitario de Memberstack - si necesitas ayuda para que funcionen con tu proyecto, ¡únete al Slack de Memberstack 2.0 y pide ayuda!

Únete a nuestro Slack
Escaparate

Explore empresas reales que han tenido éxito con Memberstack

No se fíe sólo de nuestra palabra: eche un vistazo a las empresas de todos los tamaños que confían en Memberstack para su autenticación y sus pagos.

Ver todas las historias de éxito
Incluso Webflow utiliza Memberstack.
Empezar a construir

Empieza a construir tus sueños

Memberstack es 100% gratis hasta que estés listo para lanzarla - así que, ¿a qué estás esperando? Crea tu primera aplicación y empieza a construir hoy mismo.