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.

Custom Fields

#38 - File Upload Field

Add a file uploader to any site and send the submission to Google Drive, email, or anywhere you want.


<!-- 💙 MEMBERSCRIPT #38 v0.1 💙 FORM FILE UPLOADER -->
<script>
const forms = document.querySelectorAll('form[ms-code-file-upload="form"]');

forms.forEach((form) => {
  form.setAttribute('enctype', 'multipart/form-data');
  const uploadInputs = form.querySelectorAll('[ms-code-file-upload-input]');

  uploadInputs.forEach((uploadInput) => {
    const inputName = uploadInput.getAttribute('ms-code-file-upload-input');

    const fileInput = document.createElement('input');
    fileInput.setAttribute('type', 'file');
    fileInput.setAttribute('name', inputName);
    fileInput.setAttribute('id', inputName);
    fileInput.required = true; // delete this line to make the input optional

    uploadInput.appendChild(fileInput);
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #38 v0.1 💙 FORM FILE UPLOADER -->
<script>
const forms = document.querySelectorAll('form[ms-code-file-upload="form"]');

forms.forEach((form) => {
  form.setAttribute('enctype', 'multipart/form-data');
  const uploadInputs = form.querySelectorAll('[ms-code-file-upload-input]');

  uploadInputs.forEach((uploadInput) => {
    const inputName = uploadInput.getAttribute('ms-code-file-upload-input');

    const fileInput = document.createElement('input');
    fileInput.setAttribute('type', 'file');
    fileInput.setAttribute('name', inputName);
    fileInput.setAttribute('id', inputName);
    fileInput.required = true; // delete this line to make the input optional

    uploadInput.appendChild(fileInput);
  });
});
</script>
View Memberscript
JSON
Marketing

#37 - Automatically Remove Free Plan

Automatically remove a free plan after a set amount of time!


<!-- 💙 MEMBERSCRIPT #37 v0.1 💙 MAKE FREE TRIAL EXPIRE AFTER SET TIME -->
<script>
let memberPlanId = "your_plan_ID"; // replace with your actual FREE plan ID

document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;

  // Fetch the member's data
  const member = await memberstack.getMemberJSON();

  // Fetch the member's planConnections from local storage
  const memberDataFromLocalStorage = JSON.parse(localStorage.getItem('_ms-mem'));
  const planConnections = memberDataFromLocalStorage.planConnections;

  // Check if the member has x plan
  let hasPlan = false;
  if (planConnections) {
    hasPlan = planConnections.some(planConnection => planConnection.planId === memberPlanId);
  }

  if (hasPlan) {
    // Check the members one-time-date
    let currentDate = new Date();
    let oneTimeDate = new Date(member.data['one-time-date']);

    if (currentDate > oneTimeDate) {
      // If the members' one time date has passed, remove x plan
      memberstack.removePlan({
        planId: memberPlanId
      }).then(() => {
        // Redirect to /free-trial-expired
        window.location.href = "/free-trial-expired";
      }).catch(error => {
        // Handle error
      });
    }
  }
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #37 v0.1 💙 MAKE FREE TRIAL EXPIRE AFTER SET TIME -->
<script>
let memberPlanId = "your_plan_ID"; // replace with your actual FREE plan ID

document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;

  // Fetch the member's data
  const member = await memberstack.getMemberJSON();

  // Fetch the member's planConnections from local storage
  const memberDataFromLocalStorage = JSON.parse(localStorage.getItem('_ms-mem'));
  const planConnections = memberDataFromLocalStorage.planConnections;

  // Check if the member has x plan
  let hasPlan = false;
  if (planConnections) {
    hasPlan = planConnections.some(planConnection => planConnection.planId === memberPlanId);
  }

  if (hasPlan) {
    // Check the members one-time-date
    let currentDate = new Date();
    let oneTimeDate = new Date(member.data['one-time-date']);

    if (currentDate > oneTimeDate) {
      // If the members' one time date has passed, remove x plan
      memberstack.removePlan({
        planId: memberPlanId
      }).then(() => {
        // Redirect to /free-trial-expired
        window.location.href = "/free-trial-expired";
      }).catch(error => {
        // Handle error
      });
    }
  }
});
</script>
View Memberscript
Conditional Visibility
UX

#36 - Password Validation

Use this simple method to confirm your members have entered a strong password.


<!-- 💙 MEMBERSCRIPT #36 v0.1 💙 PASSWORD VALIDATION -->
<script>
window.addEventListener('load', function() {
    const passwordInput = document.querySelector('input[data-ms-member="password"]');
    const submitButton = document.querySelector('[ms-code-submit-button]');
    
    if (!passwordInput || !submitButton) return;  // Return if essential elements are not found

    function checkAllValid() {
        const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
        return Array.from(validationPoints).every(validationPoint => {
            const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
            return validIcon && validIcon.style.display === 'flex';  // Check for validIcon existence before accessing style
        });
    }

    passwordInput.addEventListener('keyup', function() {
        const password = passwordInput.value;
        const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
        
        validationPoints.forEach(function(validationPoint) {
            const rule = validationPoint.getAttribute('ms-code-pw-validation');
            let isValid = false;
            
            // MINIMUM LENGTH VALIDATION POINT
            if (rule.startsWith('minlength-')) {
                const minLength = parseInt(rule.split('-')[1]);
                isValid = password.length >= minLength;
            }

            // SPECIAL CHARACTER VALIDATION POINT
            else if (rule === 'special-character') {
                isValid = /[!@#$%^&*(),.?":{}|<>]/g.test(password);
            }

            // UPPER AND LOWER CASE VALIDATION POINT
            else if (rule === 'upper-lower-case') {
                isValid = /[a-z]/.test(password) && /[A-Z]/.test(password);
            }

            // NUMBER VALIDATION POINT
            else if (rule === 'number') {
                isValid = /\d/.test(password);
            }
            
            const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
            const invalidIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="false"]');
            
            if (validIcon && invalidIcon) {  // Check for existence before accessing style
                if (isValid) {
                    validIcon.style.display = 'flex';
                    invalidIcon.style.display = 'none';
                } else {
                    validIcon.style.display = 'none';
                    invalidIcon.style.display = 'flex';
                }
            }
        });

        if (checkAllValid()) {
            submitButton.classList.remove('disabled');
        } else {
            submitButton.classList.add('disabled');
        }
    });

    // Trigger keyup event after adding event listener
    var event = new Event('keyup');
    passwordInput.dispatchEvent(event);
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #36 v0.1 💙 PASSWORD VALIDATION -->
<script>
window.addEventListener('load', function() {
    const passwordInput = document.querySelector('input[data-ms-member="password"]');
    const submitButton = document.querySelector('[ms-code-submit-button]');
    
    if (!passwordInput || !submitButton) return;  // Return if essential elements are not found

    function checkAllValid() {
        const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
        return Array.from(validationPoints).every(validationPoint => {
            const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
            return validIcon && validIcon.style.display === 'flex';  // Check for validIcon existence before accessing style
        });
    }

    passwordInput.addEventListener('keyup', function() {
        const password = passwordInput.value;
        const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
        
        validationPoints.forEach(function(validationPoint) {
            const rule = validationPoint.getAttribute('ms-code-pw-validation');
            let isValid = false;
            
            // MINIMUM LENGTH VALIDATION POINT
            if (rule.startsWith('minlength-')) {
                const minLength = parseInt(rule.split('-')[1]);
                isValid = password.length >= minLength;
            }

            // SPECIAL CHARACTER VALIDATION POINT
            else if (rule === 'special-character') {
                isValid = /[!@#$%^&*(),.?":{}|<>]/g.test(password);
            }

            // UPPER AND LOWER CASE VALIDATION POINT
            else if (rule === 'upper-lower-case') {
                isValid = /[a-z]/.test(password) && /[A-Z]/.test(password);
            }

            // NUMBER VALIDATION POINT
            else if (rule === 'number') {
                isValid = /\d/.test(password);
            }
            
            const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
            const invalidIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="false"]');
            
            if (validIcon && invalidIcon) {  // Check for existence before accessing style
                if (isValid) {
                    validIcon.style.display = 'flex';
                    invalidIcon.style.display = 'none';
                } else {
                    validIcon.style.display = 'none';
                    invalidIcon.style.display = 'flex';
                }
            }
        });

        if (checkAllValid()) {
            submitButton.classList.remove('disabled');
        } else {
            submitButton.classList.add('disabled');
        }
    });

    // Trigger keyup event after adding event listener
    var event = new Event('keyup');
    passwordInput.dispatchEvent(event);
});
</script>
View Memberscript
SEO

#35 - Easily Add FAQ Schema/Rich Snippets

Add one script and 2 attributes to enable constantly updating rich snippets on your page.


<!-- 💙 MEMBERSCRIPT #35 v0.1 💙 FAQ RICH SNIPPETS -->
<script>
let faqArray = [];
let questionElements = document.querySelectorAll('[ms-code-snippet-q]');
let answerElements = document.querySelectorAll('[ms-code-snippet-a]');

for (let i = 0; i < questionElements.length; i++) {
  let question = questionElements[i].innerText;
  let answer = '';

  for (let j = 0; j < answerElements.length; j++) {
    if (questionElements[i].getAttribute('ms-code-snippet-q') === answerElements[j].getAttribute('ms-code-snippet-a')) {
      answer = answerElements[j].innerText;
      break;
    }
  }

  faqArray.push({
    "@type": "Question",
    "name": question,
    "acceptedAnswer": {
      "@type": "Answer",
      "text": answer
    }
  });
}

let faqSchema = {
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": faqArray
}

let script = document.createElement('script');
script.type = "application/ld+json";
script.innerHTML = JSON.stringify(faqSchema);

document.getElementsByTagName('head')[0].appendChild(script);
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #35 v0.1 💙 FAQ RICH SNIPPETS -->
<script>
let faqArray = [];
let questionElements = document.querySelectorAll('[ms-code-snippet-q]');
let answerElements = document.querySelectorAll('[ms-code-snippet-a]');

for (let i = 0; i < questionElements.length; i++) {
  let question = questionElements[i].innerText;
  let answer = '';

  for (let j = 0; j < answerElements.length; j++) {
    if (questionElements[i].getAttribute('ms-code-snippet-q') === answerElements[j].getAttribute('ms-code-snippet-a')) {
      answer = answerElements[j].innerText;
      break;
    }
  }

  faqArray.push({
    "@type": "Question",
    "name": question,
    "acceptedAnswer": {
      "@type": "Answer",
      "text": answer
    }
  });
}

let faqSchema = {
  "@context": "https://schema.org",
  "@type": "FAQPage",
  "mainEntity": faqArray
}

let script = document.createElement('script');
script.type = "application/ld+json";
script.innerHTML = JSON.stringify(faqSchema);

document.getElementsByTagName('head')[0].appendChild(script);
</script>
View Memberscript
UX

#34 - Require Business Email For Form Submission

Block people from submitting a form if their email uses a personal email such as gmail.


<!-- 💙 MEMBERSCRIPT #34 v0.1 💙 REQUIRE BUSINESS EMAILS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/parsley.js/2.9.2/parsley.min.js"> </script>
<script>
function isPersonalEmail(email) {
  var personalDomains = [
    "gmail.com", 
    "yahoo.com", 
    "hotmail.com", 
    "aol.com", 
    "msn.com", 
    "comcast.net", 
    "live.com", 
    "outlook.com", 
    "ymail.com",
    "icloud.com"
  ];
  var emailDomain = email.split('@')[1];
  return personalDomains.includes(emailDomain);
}

window.Parsley.addValidator('businessEmail', {
  validateString: function(value) {
    return !isPersonalEmail(value);
  },
  messages: {
    en: 'Please enter a business email.'
  }
});

$(document).ready(function() {
  $('form[ms-code-validate-form]').attr('data-parsley-validate', '');
  $('input[ms-code-business-email]').attr('data-parsley-business-email', '');
  $('form').parsley();
});
  $('form').parsley().on('form:error', function() {
  $('.parsley-errors-list').addClass('ms-code-validation-error');
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #34 v0.1 💙 REQUIRE BUSINESS EMAILS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/parsley.js/2.9.2/parsley.min.js"> </script>
<script>
function isPersonalEmail(email) {
  var personalDomains = [
    "gmail.com", 
    "yahoo.com", 
    "hotmail.com", 
    "aol.com", 
    "msn.com", 
    "comcast.net", 
    "live.com", 
    "outlook.com", 
    "ymail.com",
    "icloud.com"
  ];
  var emailDomain = email.split('@')[1];
  return personalDomains.includes(emailDomain);
}

window.Parsley.addValidator('businessEmail', {
  validateString: function(value) {
    return !isPersonalEmail(value);
  },
  messages: {
    en: 'Please enter a business email.'
  }
});

$(document).ready(function() {
  $('form[ms-code-validate-form]').attr('data-parsley-validate', '');
  $('input[ms-code-business-email]').attr('data-parsley-business-email', '');
  $('form').parsley();
});
  $('form').parsley().on('form:error', function() {
  $('.parsley-errors-list').addClass('ms-code-validation-error');
});
</script>
View Memberscript
Conditional Visibility
UX

#33 - Automatically Format Form Inputs

Force form inputs to follow a set format, such as DD/MM/YYYY.


<!-- 💙 MEMBERSCRIPT #33 v0.2 💙 AUTOMATICALLY FORMAT FORM INPUTS -->
<script src="https://cdn.jsdelivr.net/npm/cleave.js@1.6.0"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cleave.js/1.6.0/addons/cleave-phone.us.js"> </script>
<script>
document.addEventListener('DOMContentLoaded', function(){
    // SELECT ALL ELEMENTS WITH THE ATTRIBUTE "ms-code-autoformat" OR "ms-code-autoformat-prefix"
    const elements = document.querySelectorAll('[ms-code-autoformat], [ms-code-autoformat-prefix]');

    for (let element of elements) {
        const formatType = element.getAttribute('ms-code-autoformat');
        const prefix = element.getAttribute('ms-code-autoformat-prefix');
        
        // SET PREFIX
        let cleaveOptions = {
            prefix: prefix || '',
            blocks: [Infinity]
        };
        
        // BASED ON THE VALUE OF "ms-code-autoformat", FORMAT THE INPUT
        if (formatType) {
            switch (formatType) {
                // FORMAT PHONE NUMBERS
                case 'phone-number':
                    cleaveOptions.phone = true;
                    cleaveOptions.phoneRegionCode = 'US';
                    break;
                    
                // FORMAT DATES IN 'YYYY-MM-DD' FORMAT
                case 'date-yyyy-mm-dd':
                    cleaveOptions.date = true;
                    cleaveOptions.datePattern = ['Y', 'm', 'd'];
                    break;
                    
                // FORMAT DATES IN 'MM-DD-YYYY' FORMAT
                case 'date-mm-dd-yyyy':
                    cleaveOptions.date = true;
                    cleaveOptions.datePattern = ['m', 'd', 'Y'];
                    break;
                    
                // FORMAT DATES IN 'DD-MM-YYYY' FORMAT
                case 'date-dd-mm-yyyy':
                    cleaveOptions.date = true;
                    cleaveOptions.datePattern = ['d', 'm', 'Y'];
                    break;
                    
                // FORMAT TIMES IN 'HH-MM-SS' FORMAT
                case 'time-hh-mm-ss':
                    cleaveOptions.time = true;
                    cleaveOptions.timePattern = ['h', 'm', 's'];
                    break;
                    
                // FORMAT TIMES IN 'HH-MM' FORMAT
                case 'time-hh-mm':
                    cleaveOptions.time = true;
                    cleaveOptions.timePattern = ['h', 'm'];
                    break;
                    
                // FORMAT NUMBERS WITH THOUSANDS SEPARATORS
                case 'number-thousand':
                    cleaveOptions.numeral = true;
                    cleaveOptions.numeralThousandsGroupStyle = 'thousand';
                    break;
            }
        }
        
        new Cleave(element, cleaveOptions);
    }
});
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #33 v0.2 💙 AUTOMATICALLY FORMAT FORM INPUTS -->
<script src="https://cdn.jsdelivr.net/npm/cleave.js@1.6.0"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cleave.js/1.6.0/addons/cleave-phone.us.js"> </script>
<script>
document.addEventListener('DOMContentLoaded', function(){
    // SELECT ALL ELEMENTS WITH THE ATTRIBUTE "ms-code-autoformat" OR "ms-code-autoformat-prefix"
    const elements = document.querySelectorAll('[ms-code-autoformat], [ms-code-autoformat-prefix]');

    for (let element of elements) {
        const formatType = element.getAttribute('ms-code-autoformat');
        const prefix = element.getAttribute('ms-code-autoformat-prefix');
        
        // SET PREFIX
        let cleaveOptions = {
            prefix: prefix || '',
            blocks: [Infinity]
        };
        
        // BASED ON THE VALUE OF "ms-code-autoformat", FORMAT THE INPUT
        if (formatType) {
            switch (formatType) {
                // FORMAT PHONE NUMBERS
                case 'phone-number':
                    cleaveOptions.phone = true;
                    cleaveOptions.phoneRegionCode = 'US';
                    break;
                    
                // FORMAT DATES IN 'YYYY-MM-DD' FORMAT
                case 'date-yyyy-mm-dd':
                    cleaveOptions.date = true;
                    cleaveOptions.datePattern = ['Y', 'm', 'd'];
                    break;
                    
                // FORMAT DATES IN 'MM-DD-YYYY' FORMAT
                case 'date-mm-dd-yyyy':
                    cleaveOptions.date = true;
                    cleaveOptions.datePattern = ['m', 'd', 'Y'];
                    break;
                    
                // FORMAT DATES IN 'DD-MM-YYYY' FORMAT
                case 'date-dd-mm-yyyy':
                    cleaveOptions.date = true;
                    cleaveOptions.datePattern = ['d', 'm', 'Y'];
                    break;
                    
                // FORMAT TIMES IN 'HH-MM-SS' FORMAT
                case 'time-hh-mm-ss':
                    cleaveOptions.time = true;
                    cleaveOptions.timePattern = ['h', 'm', 's'];
                    break;
                    
                // FORMAT TIMES IN 'HH-MM' FORMAT
                case 'time-hh-mm':
                    cleaveOptions.time = true;
                    cleaveOptions.timePattern = ['h', 'm'];
                    break;
                    
                // FORMAT NUMBERS WITH THOUSANDS SEPARATORS
                case 'number-thousand':
                    cleaveOptions.numeral = true;
                    cleaveOptions.numeralThousandsGroupStyle = 'thousand';
                    break;
            }
        }
        
        new Cleave(element, cleaveOptions);
    }
});
</script>
View Memberscript
Conditional Visibility
Custom Fields

#32 - Set Input to Required if Visible

Create conditional forms by showing and hiding required inputs.


<!-- 💙 MEMBERSCRIPT #32 v0.1 💙 REQUIRE INPUT IF VISIBLE -->
<script>
document.addEventListener("DOMContentLoaded", function() {

  // Function to check if an element is visible
  function isElementVisible(element) {
    return element.offsetParent !== null;
  }

  // Every time the user clicks on the document
  document.addEventListener('click', function() {

    // Get all inputs with the ms-code attribute
    const inputs = document.querySelectorAll('[ms-code="required-if-visible"]');

    // Loop through each input
    inputs.forEach(function(input) {

      // Check if the input or its parent is visible
      if (isElementVisible(input)) {

        // If the input is visible, add the required attribute
        input.required = true;
      } else {

        // If the input is not visible, remove the required attribute
        input.required = false;
      }
    });
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #32 v0.1 💙 REQUIRE INPUT IF VISIBLE -->
<script>
document.addEventListener("DOMContentLoaded", function() {

  // Function to check if an element is visible
  function isElementVisible(element) {
    return element.offsetParent !== null;
  }

  // Every time the user clicks on the document
  document.addEventListener('click', function() {

    // Get all inputs with the ms-code attribute
    const inputs = document.querySelectorAll('[ms-code="required-if-visible"]');

    // Loop through each input
    inputs.forEach(function(input) {

      // Check if the input or its parent is visible
      if (isElementVisible(input)) {

        // If the input is visible, add the required attribute
        input.required = true;
      } else {

        // If the input is not visible, remove the required attribute
        input.required = false;
      }
    });
  });
});
</script>
View Memberscript
UX

#31 - Open a Webflow Tab with a Link

This script automatically generates links to your Webflow tabs.


<!-- 💙 MEMBERSCRIPT #31 v0.2 💙 OPEN WEBFLOW TAB w/ LINK -->
<!-- You can link to tabs like this 👉 www.yoursite.com#tab-name-lowercase -->
<!-- And sub tabs like this 👉 www.yoursite.com#tab-name/sub-tab-name -->
<script>
  var Webflow = Webflow || [];
  Webflow.push(() => {
    function changeTab(shouldScroll = false) {
      const hashSegments = window.location.hash.substring(1).split('/');
      const offset = 90; // change this to match your fixed header height if you have one
      let lastTabTarget;

      for (const segment of hashSegments) {
        const tabTarget = document.querySelector(`[data-w-tab="${segment}"]`);

        if (tabTarget) {
          tabTarget.click();
          lastTabTarget = tabTarget;
        }
      }

      if (shouldScroll && lastTabTarget) {
        window.scrollTo({
          top: $(lastTabTarget).offset().top - offset, behavior: 'smooth'
        });
      }
    }
    
    const tabs = document.querySelectorAll('[data-w-tab]');
    
    tabs.forEach(tab => {
      const dataWTabValue = tab.dataset.wTab;
      const parsedDataTab = dataWTabValue.replace(/\s+/g,"-").toLowerCase();
      tab.dataset.wTab = parsedDataTab;
      tab.addEventListener('click', () => {
        history.pushState({}, '', `#${parsedDataTab}`);
      });
    });
 
  	if (window.location.hash) {
      requestAnimationFrame(() => { changeTab(true); });
    }

    window.addEventListener('hashchange', () => { changeTab() });
  });
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #31 v0.2 💙 OPEN WEBFLOW TAB w/ LINK -->
<!-- You can link to tabs like this 👉 www.yoursite.com#tab-name-lowercase -->
<!-- And sub tabs like this 👉 www.yoursite.com#tab-name/sub-tab-name -->
<script>
  var Webflow = Webflow || [];
  Webflow.push(() => {
    function changeTab(shouldScroll = false) {
      const hashSegments = window.location.hash.substring(1).split('/');
      const offset = 90; // change this to match your fixed header height if you have one
      let lastTabTarget;

      for (const segment of hashSegments) {
        const tabTarget = document.querySelector(`[data-w-tab="${segment}"]`);

        if (tabTarget) {
          tabTarget.click();
          lastTabTarget = tabTarget;
        }
      }

      if (shouldScroll && lastTabTarget) {
        window.scrollTo({
          top: $(lastTabTarget).offset().top - offset, behavior: 'smooth'
        });
      }
    }
    
    const tabs = document.querySelectorAll('[data-w-tab]');
    
    tabs.forEach(tab => {
      const dataWTabValue = tab.dataset.wTab;
      const parsedDataTab = dataWTabValue.replace(/\s+/g,"-").toLowerCase();
      tab.dataset.wTab = parsedDataTab;
      tab.addEventListener('click', () => {
        history.pushState({}, '', `#${parsedDataTab}`);
      });
    });
 
  	if (window.location.hash) {
      requestAnimationFrame(() => { changeTab(true); });
    }

    window.addEventListener('hashchange', () => { changeTab() });
  });
</script>
View Memberscript
UX

#30 - Count Items On Page And Update Number

Check how many items with a set attribute are on the page and apply that number to some text.


<!-- 💙 MEMBERSCRIPT #30 v0.1 💙 COUNT ITEMS AND DISPLAY COUNT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  setTimeout(function() {
    const rollupItems = document.querySelectorAll('[ms-code-rollup-item]');
    const rollupNumbers = document.querySelectorAll('[ms-code-rollup-number]');

    const updateRollupNumbers = function() {
      const rollupCountMap = new Map();

      rollupItems.forEach(item => {
        const rollupKey = item.getAttribute('ms-code-rollup-item');
        const count = rollupCountMap.get(rollupKey) || 0;
        rollupCountMap.set(rollupKey, count + 1);
      });

      rollupNumbers.forEach(number => {
        const rollupKey = number.getAttribute('ms-code-rollup-number');
        const count = rollupCountMap.get(rollupKey) || 0;
        number.textContent = count;
      });
    };

    updateRollupNumbers(); // Initial update

    // Polling function to periodically update rollup numbers
    const pollRollupNumbers = function() {
      updateRollupNumbers();
      setTimeout(pollRollupNumbers, 1000); // Adjust the polling interval as needed (in milliseconds)
    };

    pollRollupNumbers(); // Start polling
  }, 2000);
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #30 v0.1 💙 COUNT ITEMS AND DISPLAY COUNT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  setTimeout(function() {
    const rollupItems = document.querySelectorAll('[ms-code-rollup-item]');
    const rollupNumbers = document.querySelectorAll('[ms-code-rollup-number]');

    const updateRollupNumbers = function() {
      const rollupCountMap = new Map();

      rollupItems.forEach(item => {
        const rollupKey = item.getAttribute('ms-code-rollup-item');
        const count = rollupCountMap.get(rollupKey) || 0;
        rollupCountMap.set(rollupKey, count + 1);
      });

      rollupNumbers.forEach(number => {
        const rollupKey = number.getAttribute('ms-code-rollup-number');
        const count = rollupCountMap.get(rollupKey) || 0;
        number.textContent = count;
      });
    };

    updateRollupNumbers(); // Initial update

    // Polling function to periodically update rollup numbers
    const pollRollupNumbers = function() {
      updateRollupNumbers();
      setTimeout(pollRollupNumbers, 1000); // Adjust the polling interval as needed (in milliseconds)
    };

    pollRollupNumbers(); // Start polling
  }, 2000);
});
</script>
View Memberscript
UX

#29 - Temporarily Fix Element Height On Load

Force an element to be a set height for a certain duration on page load.


<!-- 💙 MEMBERSCRIPT #29 v0.1 💙 TEMPORARILY FIX ELEMENT HEIGHT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const elements = document.querySelectorAll('[ms-code-temp-height]');
  
  elements.forEach(element => {
    const attributeValue = element.getAttribute('ms-code-temp-height');
    
    if (attributeValue) {
      const [time, height] = attributeValue.split(':');
      
      if (!isNaN(time) && !isNaN(height)) {
        const defaultHeight = element.style.height;
        
        setTimeout(() => {
          element.style.height = defaultHeight;
        }, parseInt(time));
        
        element.style.height = height + 'px';
      }
    }
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #29 v0.1 💙 TEMPORARILY FIX ELEMENT HEIGHT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const elements = document.querySelectorAll('[ms-code-temp-height]');
  
  elements.forEach(element => {
    const attributeValue = element.getAttribute('ms-code-temp-height');
    
    if (attributeValue) {
      const [time, height] = attributeValue.split(':');
      
      if (!isNaN(time) && !isNaN(height)) {
        const defaultHeight = element.style.height;
        
        setTimeout(() => {
          element.style.height = defaultHeight;
        }, parseInt(time));
        
        element.style.height = height + 'px';
      }
    }
  });
});
</script>
View Memberscript
Conditional Visibility
JSON

#28 - Display Element Based On JSON Date Passing

Check the one time date from #27 and show/hide an element based on that.


<!-- 💙 MEMBERSCRIPT #28 v0.1 💙 CHECK ONE-TIME DATE AND UPDATE ELEMENT DISPLAY -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;

  const updateElementVisibility = async function() {
    const member = await memberstack.getMemberJSON();

    if (!member.data || !member.data['one-time-date']) {
      // Member data or expiration date not available, do nothing
      return;
    }

    const expirationDate = new Date(member.data['one-time-date']);
    const currentDate = new Date();

    if (currentDate < expirationDate) {
      // Expiration date has not passed, update element visibility
      const elements = document.querySelectorAll('[ms-code-element-temporary]');

      elements.forEach(element => {
        const displayValue = element.getAttribute('ms-code-element-temporary');

        // Update element visibility based on the attribute value
        element.style.display = displayValue;
      });
    }
  };

  updateElementVisibility();
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #28 v0.1 💙 CHECK ONE-TIME DATE AND UPDATE ELEMENT DISPLAY -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;

  const updateElementVisibility = async function() {
    const member = await memberstack.getMemberJSON();

    if (!member.data || !member.data['one-time-date']) {
      // Member data or expiration date not available, do nothing
      return;
    }

    const expirationDate = new Date(member.data['one-time-date']);
    const currentDate = new Date();

    if (currentDate < expirationDate) {
      // Expiration date has not passed, update element visibility
      const elements = document.querySelectorAll('[ms-code-element-temporary]');

      elements.forEach(element => {
        const displayValue = element.getAttribute('ms-code-element-temporary');

        // Update element visibility based on the attribute value
        element.style.display = displayValue;
      });
    }
  };

  updateElementVisibility();
});
</script>
View Memberscript
Conditional Visibility
JSON

#27 - Set One Time Date On Signup

Apply a date to your member JSON after signup which can be used for anything.

JSON Only

If you don't need to add the date to a custom field too, use this.


<!-- 💙 MEMBERSCRIPT #27 v0.1 💙 SET ONE TIME DATE -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;

  const updateDate = async function() {
    const member = await memberstack.getMemberJSON();

    if (!member.data) {
      member.data = {};
    }

    if (!member.data['one-time-date']) {
      const currentTime = new Date();
      const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
      member.data['one-time-date'] = expirationTime.toISOString();
      
      // Update member JSON
      await memberstack.updateMemberJSON({
        json: member.data
      });
    }
  };

  updateDate();
});
</script>

JSON + Custom Field

Use this if you need to add the date to a custom field (usually for use with automations).


<!-- 💙💙 MEMBERSCRIPT #27 v0.1.1 (CUSTOM FIELD) 💙 SET ONE TIME DATE -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;
    const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
    const member = await memberstack.getMemberJSON();

    if (!member.data) {
      member.data = {};
    }

    // Check if the user has the 'one-time-date' custom field in Memberstack
    if (!msMem.customFields || !msMem.customFields['one-time-date']) {
      const currentTime = new Date();
      const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
      const updatedCustomFields = {
        ...msMem.customFields,
        'one-time-date': expirationTime.toISOString()
      };

      member.data['one-time-date'] = expirationTime.toISOString();
      await memberstack.updateMemberJSON({
        json: member.data
      });

      await memberstack.updateMember({
        customFields: updatedCustomFields
      });
    }
  });
</script>
v0.1.1

JSON Only

If you don't need to add the date to a custom field too, use this.


<!-- 💙 MEMBERSCRIPT #27 v0.1 💙 SET ONE TIME DATE -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;

  const updateDate = async function() {
    const member = await memberstack.getMemberJSON();

    if (!member.data) {
      member.data = {};
    }

    if (!member.data['one-time-date']) {
      const currentTime = new Date();
      const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
      member.data['one-time-date'] = expirationTime.toISOString();
      
      // Update member JSON
      await memberstack.updateMemberJSON({
        json: member.data
      });
    }
  };

  updateDate();
});
</script>

JSON + Custom Field

Use this if you need to add the date to a custom field (usually for use with automations).


<!-- 💙💙 MEMBERSCRIPT #27 v0.1.1 (CUSTOM FIELD) 💙 SET ONE TIME DATE -->
<script>
  document.addEventListener('DOMContentLoaded', async function() {
    const memberstack = window.$memberstackDom;
    const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
    const member = await memberstack.getMemberJSON();

    if (!member.data) {
      member.data = {};
    }

    // Check if the user has the 'one-time-date' custom field in Memberstack
    if (!msMem.customFields || !msMem.customFields['one-time-date']) {
      const currentTime = new Date();
      const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
      const updatedCustomFields = {
        ...msMem.customFields,
        'one-time-date': expirationTime.toISOString()
      };

      member.data['one-time-date'] = expirationTime.toISOString();
      await memberstack.updateMemberJSON({
        json: member.data
      });

      await memberstack.updateMember({
        customFields: updatedCustomFields
      });
    }
  });
</script>
View Memberscript
Conditional Visibility
UX
Marketing

#26 - Gate Content With Custom Modals

Use custom modals to urge your visitors to get a paid account!


<!-- 💙 MEMBERSCRIPT #26 v0.1 💙 GATE CONTENT WITH MODALS -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
  if (!data) {
    // Member is not logged in
    const triggers = document.querySelectorAll('[ms-code-gate-modal-trigger]');
    const boxes = document.querySelectorAll('[ms-code-gate-modal-box]');
    
    triggers.forEach(trigger => {
      trigger.addEventListener('click', () => {
        const targetId = trigger.getAttribute('ms-code-gate-modal-trigger');
        const box = document.querySelector(`[ms-code-gate-modal-box="${targetId}"]`);
        if (box) {
          box.style.display = 'flex';
        }
      });
      // Remove links and attributes from trigger
      // Uncomment the lines below to enable this functionality
      // trigger.removeAttribute('href');
      // trigger.removeAttribute('target');
      // trigger.removeAttribute('rel');
      // trigger.removeAttribute('onclick');
    });
  }
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #26 v0.1 💙 GATE CONTENT WITH MODALS -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
  if (!data) {
    // Member is not logged in
    const triggers = document.querySelectorAll('[ms-code-gate-modal-trigger]');
    const boxes = document.querySelectorAll('[ms-code-gate-modal-box]');
    
    triggers.forEach(trigger => {
      trigger.addEventListener('click', () => {
        const targetId = trigger.getAttribute('ms-code-gate-modal-trigger');
        const box = document.querySelector(`[ms-code-gate-modal-box="${targetId}"]`);
        if (box) {
          box.style.display = 'flex';
        }
      });
      // Remove links and attributes from trigger
      // Uncomment the lines below to enable this functionality
      // trigger.removeAttribute('href');
      // trigger.removeAttribute('target');
      // trigger.removeAttribute('rel');
      // trigger.removeAttribute('onclick');
    });
  }
});
</script>
View Memberscript
Conditional Visibility
UX

#25 - Hide Element Based On Other Element's Children

Remove an element from the page if another defined element has no child elements.


<!-- 💙 MEMBERSCRIPT #25 v0.1 💙 HIDE ELEMENT BASED ON OTHER ELEMENT CHILDREN -->
<script>
window.addEventListener('DOMContentLoaded', function() {
  const subjectAttribute = 'ms-code-visibility-subject';
  const targetAttribute = 'ms-code-visibility-target';

  const subjectElement = document.querySelector(`[${subjectAttribute}]`);
  const targetElement = document.querySelector(`[${targetAttribute}]`);

  if (!subjectElement || !targetElement) {
    console.error('Subject or target element not found');
    return;
  }

  function checkVisibility() {
    const children = subjectElement.children;
    let allHidden = true;

    for (let i = 0; i < children.length; i++) {
      const child = children[i];
      const computedStyle = window.getComputedStyle(child);

      if (computedStyle.display !== 'none') {
        allHidden = false;
        break;
      }
    }

    if (children.length === 0 || allHidden) {
      targetElement.style.display = 'none';
    } else {
      targetElement.style.display = '';
    }
  }

  // Check visibility initially
  checkVisibility();

  // Check visibility whenever the subject element or its children change
  const observer = new MutationObserver(checkVisibility);
  observer.observe(subjectElement, { childList: true, subtree: true });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #25 v0.1 💙 HIDE ELEMENT BASED ON OTHER ELEMENT CHILDREN -->
<script>
window.addEventListener('DOMContentLoaded', function() {
  const subjectAttribute = 'ms-code-visibility-subject';
  const targetAttribute = 'ms-code-visibility-target';

  const subjectElement = document.querySelector(`[${subjectAttribute}]`);
  const targetElement = document.querySelector(`[${targetAttribute}]`);

  if (!subjectElement || !targetElement) {
    console.error('Subject or target element not found');
    return;
  }

  function checkVisibility() {
    const children = subjectElement.children;
    let allHidden = true;

    for (let i = 0; i < children.length; i++) {
      const child = children[i];
      const computedStyle = window.getComputedStyle(child);

      if (computedStyle.display !== 'none') {
        allHidden = false;
        break;
      }
    }

    if (children.length === 0 || allHidden) {
      targetElement.style.display = 'none';
    } else {
      targetElement.style.display = '';
    }
  }

  // Check visibility initially
  checkVisibility();

  // Check visibility whenever the subject element or its children change
  const observer = new MutationObserver(checkVisibility);
  observer.observe(subjectElement, { childList: true, subtree: true });
});
</script>
View Memberscript
Conditional Visibility

#24 - Filter Lists Based On Element

Filter any kind of list based on the presence of an element within it's children.

Standard option

This works in most use cases.


<!-- 💙 MEMBERSCRIPT #24 v0.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const filterListItems = function(list, filterAttribute) {
    const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);

    items.forEach(item => {
      const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);

      if (!target || window.getComputedStyle(target).display === 'none') {
        item.style.display = 'none';
      } else {
        item.style.display = '';
      }
    });
  };

  const filterLists = document.querySelectorAll('[ms-code-filter-list]');

  const updateFiltering = function() {
    filterLists.forEach(list => {
      const filterAttribute = list.getAttribute('ms-code-filter-list');
      filterListItems(list, filterAttribute);
    });
  };

  const observeListChanges = function() {
    const observer = new MutationObserver(updateFiltering);
    filterLists.forEach(list => observer.observe(list, { childList: true, subtree: true }));
  };

  updateFiltering();
  observeListChanges();
});
</script>

Polling option

If standard does not work, try this.


<!-- 💙 MEMBERSCRIPT #24 v0.1.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT (POLLING) -->
<script>
window.addEventListener("DOMContentLoaded", function() {
  const filterListItems = function(list, filterAttribute) {
    const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);

    items.forEach(item => {
      const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);

      if (!target || window.getComputedStyle(target).display === 'none') {
        item.style.display = 'none';
      } else {
        item.style.display = '';
      }
    });
  };

  const filterLists = document.querySelectorAll('[ms-code-filter-list]');

  const updateFiltering = function() {
    filterLists.forEach(list => {
      const filterAttribute = list.getAttribute('ms-code-filter-list');
      filterListItems(list, filterAttribute);
    });
  };

  const pollPage = function() {
    updateFiltering();
    setTimeout(pollPage, 1000); // Poll every 1 second
  };

  pollPage();
});
</script>
v0.1.1

Standard option

This works in most use cases.


<!-- 💙 MEMBERSCRIPT #24 v0.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const filterListItems = function(list, filterAttribute) {
    const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);

    items.forEach(item => {
      const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);

      if (!target || window.getComputedStyle(target).display === 'none') {
        item.style.display = 'none';
      } else {
        item.style.display = '';
      }
    });
  };

  const filterLists = document.querySelectorAll('[ms-code-filter-list]');

  const updateFiltering = function() {
    filterLists.forEach(list => {
      const filterAttribute = list.getAttribute('ms-code-filter-list');
      filterListItems(list, filterAttribute);
    });
  };

  const observeListChanges = function() {
    const observer = new MutationObserver(updateFiltering);
    filterLists.forEach(list => observer.observe(list, { childList: true, subtree: true }));
  };

  updateFiltering();
  observeListChanges();
});
</script>

Polling option

If standard does not work, try this.


<!-- 💙 MEMBERSCRIPT #24 v0.1.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT (POLLING) -->
<script>
window.addEventListener("DOMContentLoaded", function() {
  const filterListItems = function(list, filterAttribute) {
    const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);

    items.forEach(item => {
      const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);

      if (!target || window.getComputedStyle(target).display === 'none') {
        item.style.display = 'none';
      } else {
        item.style.display = '';
      }
    });
  };

  const filterLists = document.querySelectorAll('[ms-code-filter-list]');

  const updateFiltering = function() {
    filterLists.forEach(list => {
      const filterAttribute = list.getAttribute('ms-code-filter-list');
      filterListItems(list, filterAttribute);
    });
  };

  const pollPage = function() {
    updateFiltering();
    setTimeout(pollPage, 1000); // Poll every 1 second
  };

  pollPage();
});
</script>
View Memberscript
UX

#23 - Skeleton Screens / Content Loaders

Easily add these industry-standard loading states to your site in just a few seconds.

Light mode

Use this on a white background


<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
  const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
  
  skeletonElements.forEach(element => {
    // Create a skeleton div
    const skeletonDiv = document.createElement('div');
    skeletonDiv.classList.add('skeleton-loader');

    // Add the skeleton div to the current element
    element.style.position = 'relative';
    element.appendChild(skeletonDiv);

    // Get delay from the attribute
    let delay = element.getAttribute('ms-code-skeleton');

    // If attribute value is not a number, set default delay as 2000ms
    if (isNaN(delay)) {
      delay = 2000;
    }

    setTimeout(() => {
      // Remove the skeleton loader div after delay
      const skeletonDiv = element.querySelector('.skeleton-loader');
      element.removeChild(skeletonDiv);
    }, delay);
  });
});
</script>
<style>
.skeleton-loader {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  border-radius: inherit; /* Inherit the border-radius of the parent element */
  background: linear-gradient(to right, #f6f7f8 25%, #e0e0e0 50%, #f6f7f8 75%);
  background-size: 200% 100%;  /* Increase the size of the background image */
  z-index: 1; /* Make sure the skeleton loader is on top of the content */
  animation: skeleton 1s infinite linear;
}

@keyframes skeleton {
  0% { background-position: -100% 0; }
  100% { background-position: 100% 0; }
}

[ms-code-skeleton] {
  background-clip: padding-box;
}
</style>

Dark mode

Use this on a black background


<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
  const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
  
  skeletonElements.forEach(element => {
    // Create a skeleton div
    const skeletonDiv = document.createElement('div');
    skeletonDiv.classList.add('skeleton-loader');

    // Add the skeleton div to the current element
    element.style.position = 'relative';
    element.appendChild(skeletonDiv);

    // Get delay from the attribute
    let delay = element.getAttribute('ms-code-skeleton');

    // If attribute value is not a number, set default delay as 2000ms
    if (isNaN(delay)) {
      delay = 2000;
    }

    setTimeout(() => {
      // Remove the skeleton loader div after delay
      const skeletonDiv = element.querySelector('.skeleton-loader');
      element.removeChild(skeletonDiv);
    }, delay);
  });
});
</script>
<style>
.skeleton-loader {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  border-radius: inherit;
  background: linear-gradient(to right, #222222 25%, #333333 50%, #222222 75%); /* Updated background colors */
  background-size: 200% 100%;
  z-index: 1;
  animation: skeleton 1s infinite linear;
}

@keyframes skeleton {
  0% { background-position: -100% 0; }
  100% { background-position: 100% 0; }
}

[ms-code-skeleton] {
  background-clip: padding-box;
}
</style>
v0.1

Light mode

Use this on a white background


<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
  const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
  
  skeletonElements.forEach(element => {
    // Create a skeleton div
    const skeletonDiv = document.createElement('div');
    skeletonDiv.classList.add('skeleton-loader');

    // Add the skeleton div to the current element
    element.style.position = 'relative';
    element.appendChild(skeletonDiv);

    // Get delay from the attribute
    let delay = element.getAttribute('ms-code-skeleton');

    // If attribute value is not a number, set default delay as 2000ms
    if (isNaN(delay)) {
      delay = 2000;
    }

    setTimeout(() => {
      // Remove the skeleton loader div after delay
      const skeletonDiv = element.querySelector('.skeleton-loader');
      element.removeChild(skeletonDiv);
    }, delay);
  });
});
</script>
<style>
.skeleton-loader {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  border-radius: inherit; /* Inherit the border-radius of the parent element */
  background: linear-gradient(to right, #f6f7f8 25%, #e0e0e0 50%, #f6f7f8 75%);
  background-size: 200% 100%;  /* Increase the size of the background image */
  z-index: 1; /* Make sure the skeleton loader is on top of the content */
  animation: skeleton 1s infinite linear;
}

@keyframes skeleton {
  0% { background-position: -100% 0; }
  100% { background-position: 100% 0; }
}

[ms-code-skeleton] {
  background-clip: padding-box;
}
</style>

Dark mode

Use this on a black background


<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
  const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
  
  skeletonElements.forEach(element => {
    // Create a skeleton div
    const skeletonDiv = document.createElement('div');
    skeletonDiv.classList.add('skeleton-loader');

    // Add the skeleton div to the current element
    element.style.position = 'relative';
    element.appendChild(skeletonDiv);

    // Get delay from the attribute
    let delay = element.getAttribute('ms-code-skeleton');

    // If attribute value is not a number, set default delay as 2000ms
    if (isNaN(delay)) {
      delay = 2000;
    }

    setTimeout(() => {
      // Remove the skeleton loader div after delay
      const skeletonDiv = element.querySelector('.skeleton-loader');
      element.removeChild(skeletonDiv);
    }, delay);
  });
});
</script>
<style>
.skeleton-loader {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  border-radius: inherit;
  background: linear-gradient(to right, #222222 25%, #333333 50%, #222222 75%); /* Updated background colors */
  background-size: 200% 100%;
  z-index: 1;
  animation: skeleton 1s infinite linear;
}

@keyframes skeleton {
  0% { background-position: -100% 0; }
  100% { background-position: 100% 0; }
}

[ms-code-skeleton] {
  background-clip: padding-box;
}
</style>
View Memberscript
Conditional Visibility
UX

#22 - Disable Submit Button Until Required Fields Are Done

Gray out your submit button until all required values have something in them.


<!-- 💙 MEMBERSCRIPT #22 v0.1 💙 DISABLE SUBMIT BUTTON UNTIL REQUIRED FIELDS ARE COMPLETE -->
<script>
window.onload = function() {
  const forms = document.querySelectorAll('form[ms-code-submit-form]');

  forms.forEach(form => {
    const submitButton = form.querySelector('input[type="submit"]');
    const requiredFields = form.querySelectorAll('input[required]');

    form.addEventListener('input', function() {
      const allFilled = Array.from(requiredFields).every(field => field.value.trim() !== '');

      if (allFilled) {
        submitButton.classList.add('submit-enabled');
      } else {
        submitButton.classList.remove('submit-enabled');
      }
    });
  });
};
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #22 v0.1 💙 DISABLE SUBMIT BUTTON UNTIL REQUIRED FIELDS ARE COMPLETE -->
<script>
window.onload = function() {
  const forms = document.querySelectorAll('form[ms-code-submit-form]');

  forms.forEach(form => {
    const submitButton = form.querySelector('input[type="submit"]');
    const requiredFields = form.querySelectorAll('input[required]');

    form.addEventListener('input', function() {
      const allFilled = Array.from(requiredFields).every(field => field.value.trim() !== '');

      if (allFilled) {
        submitButton.classList.add('submit-enabled');
      } else {
        submitButton.classList.remove('submit-enabled');
      }
    });
  });
};
</script>
View Memberscript
Conditional Visibility
UX

#21 - Custom Toast Notifications

Display custom toast boxes on element click!


<!-- 💙 MEMBERSCRIPT #21 v0.1 💙 CUSTOM TOAST BOXES -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const toastTriggers = document.querySelectorAll("[ms-code-toast-trigger]");

  toastTriggers.forEach(trigger => {
    trigger.addEventListener("click", function() {
      const triggerId = trigger.getAttribute("ms-code-toast-trigger");
      const toastBox = document.querySelector(`[ms-code-toast-box="${triggerId}"]`);

      if (toastBox) {
        const fadeInDuration = 200;
        const fadeOutDuration = 200;
        const staticDuration = 2000;
        const totalDuration = fadeInDuration + staticDuration + fadeOutDuration;

        toastBox.style.opacity = "0";
        toastBox.style.display = "block";

        let currentTime = 0;

        const fade = function() {
          currentTime += 10;
          const opacity = currentTime < fadeInDuration
            ? currentTime / fadeInDuration
            : currentTime < fadeInDuration + staticDuration
              ? 1
              : 1 - (currentTime - fadeInDuration - staticDuration) / fadeOutDuration;

          toastBox.style.opacity = opacity;

          if (currentTime < totalDuration) {
            setTimeout(fade, 10);
          } else {
            toastBox.style.display = "none";
          }
        };

        fade();
      }
    });
  });
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #21 v0.1 💙 CUSTOM TOAST BOXES -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const toastTriggers = document.querySelectorAll("[ms-code-toast-trigger]");

  toastTriggers.forEach(trigger => {
    trigger.addEventListener("click", function() {
      const triggerId = trigger.getAttribute("ms-code-toast-trigger");
      const toastBox = document.querySelector(`[ms-code-toast-box="${triggerId}"]`);

      if (toastBox) {
        const fadeInDuration = 200;
        const fadeOutDuration = 200;
        const staticDuration = 2000;
        const totalDuration = fadeInDuration + staticDuration + fadeOutDuration;

        toastBox.style.opacity = "0";
        toastBox.style.display = "block";

        let currentTime = 0;

        const fade = function() {
          currentTime += 10;
          const opacity = currentTime < fadeInDuration
            ? currentTime / fadeInDuration
            : currentTime < fadeInDuration + staticDuration
              ? 1
              : 1 - (currentTime - fadeInDuration - staticDuration) / fadeOutDuration;

          toastBox.style.opacity = opacity;

          if (currentTime < totalDuration) {
            setTimeout(fade, 10);
          } else {
            toastBox.style.display = "none";
          }
        };

        fade();
      }
    });
  });
});
</script>
View Memberscript
Custom Fields

#19 - Add URL From Custom Field To IFrame SRC

Create a member-specific embed functionality with this custom field iframe solution!


<!-- 💙 MEMBERSCRIPT #19 v0.1 💙 ADD CUSTOM FIELD AS AN IFRAME SRC -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  // Parse member data from local storage
  const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');

  // Check if the user is logged in
  if(memberData && memberData.id) {
    // Get custom fields
    const customFields = memberData.customFields;

    // Select all elements with 'ms-code-field-link' attribute
    const elements = document.querySelectorAll('[ms-code-field-link]');
    
    // Iterate over all selected elements
    elements.forEach(element => {
      // Get custom field key from 'ms-code-field-link' attribute
      const fieldKey = element.getAttribute('ms-code-field-link');
      
      // If key exists in custom fields, set element src to the corresponding value
      if(customFields.hasOwnProperty(fieldKey)) {
        element.setAttribute('src', customFields[fieldKey]);
      }
    });
  }
});
</script>
v0.1

<!-- 💙 MEMBERSCRIPT #19 v0.1 💙 ADD CUSTOM FIELD AS AN IFRAME SRC -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  // Parse member data from local storage
  const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');

  // Check if the user is logged in
  if(memberData && memberData.id) {
    // Get custom fields
    const customFields = memberData.customFields;

    // Select all elements with 'ms-code-field-link' attribute
    const elements = document.querySelectorAll('[ms-code-field-link]');
    
    // Iterate over all selected elements
    elements.forEach(element => {
      // Get custom field key from 'ms-code-field-link' attribute
      const fieldKey = element.getAttribute('ms-code-field-link');
      
      // If key exists in custom fields, set element src to the corresponding value
      if(customFields.hasOwnProperty(fieldKey)) {
        element.setAttribute('src', customFields[fieldKey]);
      }
    });
  }
});
</script>
View Memberscript
Marketing

#18 - Easily Truncate Text

Add one attribute and a simple script to programatically truncate text!


<!-- 💙 MEMBERSCRIPT #18 v0.2 💙 - EASILY TRUNCATE TEXT -->
<script>
const elements = document.querySelectorAll('[ms-code-truncate]');

elements.forEach((element) => {
  const charLimit = parseInt(element.getAttribute('ms-code-truncate'));

  // Create a helper function that will recursively traverse the DOM tree
  const traverseNodes = (node, count) => {
    for (let child of node.childNodes) {
      // If the node is a text node, truncate if necessary
      if (child.nodeType === Node.TEXT_NODE) {
        if (count + child.textContent.length > charLimit) {
          child.textContent = child.textContent.slice(0, charLimit - count) + '...';
          return count + child.textContent.length;
        }
        count += child.textContent.length;
      }
      // If the node is an element, recurse through its children
      else if (child.nodeType === Node.ELEMENT_NODE) {
        count = traverseNodes(child, count);
      }
    }
    return count;
  }

  // Create a deep clone of the element to work on. This is so that we don't modify the original element
  // until we have completely finished processing.
  const clone = element.cloneNode(true);

  // Traverse and truncate the cloned node
  traverseNodes(clone, 0);

  // Replace the original element with our modified clone
  element.parentNode.replaceChild(clone, element);
});
</script>
v0.2

<!-- 💙 MEMBERSCRIPT #18 v0.2 💙 - EASILY TRUNCATE TEXT -->
<script>
const elements = document.querySelectorAll('[ms-code-truncate]');

elements.forEach((element) => {
  const charLimit = parseInt(element.getAttribute('ms-code-truncate'));

  // Create a helper function that will recursively traverse the DOM tree
  const traverseNodes = (node, count) => {
    for (let child of node.childNodes) {
      // If the node is a text node, truncate if necessary
      if (child.nodeType === Node.TEXT_NODE) {
        if (count + child.textContent.length > charLimit) {
          child.textContent = child.textContent.slice(0, charLimit - count) + '...';
          return count + child.textContent.length;
        }
        count += child.textContent.length;
      }
      // If the node is an element, recurse through its children
      else if (child.nodeType === Node.ELEMENT_NODE) {
        count = traverseNodes(child, count);
      }
    }
    return count;
  }

  // Create a deep clone of the element to work on. This is so that we don't modify the original element
  // until we have completely finished processing.
  const clone = element.cloneNode(true);

  // Traverse and truncate the cloned node
  traverseNodes(clone, 0);

  // Replace the original element with our modified clone
  element.parentNode.replaceChild(clone, element);
});
</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.