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

#41 - Perfect Phone Number Inputs

International phone number inputs, the way they should be.

v0.2

With IP Lookup

Use this if you want the users' IP country to automatically be prefilled. IMPORTANT: Do not use this with profile forms or it will behave erratically.


<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITH IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
  $(document).ready(function() {
    $('input[ms-code-phone-number]').each(function() {
      var input = this;
      var preferredCountries = $(input).attr('ms-code-phone-number').split(',');

      var iti = window.intlTelInput(input, {
        preferredCountries: preferredCountries,
        utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
      });

      $.get("https://ipinfo.io", function(response) {
        var countryCode = response.country;
        iti.setCountry(countryCode);
      }, "jsonp");

      input.addEventListener('change', formatPhoneNumber);
      input.addEventListener('keyup', formatPhoneNumber);

      function formatPhoneNumber() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      }

      var form = $(input).closest('form');
      form.submit(function() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      });
    });
  });
</script>

Without IP Lookup

Use this on profile forms and/or if you do not want to automatically prefill based on user IP.


<!-- 💙 MEMBERSCRIPT #41 v0.2 💙 PERFECT PHONE NUMBER INPUTS (WITHOUT IP LOOKUP) -->
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/css/intlTelInput.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/intlTelInput.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"> </script>
<script>
  $(document).ready(function() {
    $('input[ms-code-phone-number]').each(function() {
      var input = this;
      var preferredCountries = $(input).attr('ms-code-phone-number').split(',');

      var iti = window.intlTelInput(input, {
        preferredCountries: preferredCountries,
        utilsScript: "https://cdnjs.cloudflare.com/ajax/libs/intl-tel-input/17.0.8/js/utils.js"
      });

      input.addEventListener('change', formatPhoneNumber);
      input.addEventListener('keyup', formatPhoneNumber);

      function formatPhoneNumber() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      }

      var form = $(input).closest('form');
      form.submit(function() {
        var formattedNumber = iti.getNumber(intlTelInputUtils.numberFormat.INTERNATIONAL);
        input.value = formattedNumber;
      });
    });
  });
</script>
View Memberscript
Custom Fields
UX

#40 - Drag And Drop File Uploader

Easily add a drag n' drop file upload feature to your Webflow site!

v0.1

Important

If you are using MemberScript #38, make sure you put this script AFTER!


<!-- 💙 MEMBERSCRIPT #40 v0.1 💙 DRAG AND DROP FILE UPLOADER -->
<script> src="https://unpkg.com/filepond@^4/dist/filepond.js"> </script>
<script>
  document.addEventListener('DOMContentLoaded', function() {
    const inputElement = document.querySelector('input[type="file"]');
    const pond = FilePond.create(inputElement, {
      credits: false,
      name: 'fileToUpload',
      storeAsFile: true
      // for more property options, go to https://pqina.nl/filepond/docs/api/instance/properties/
    });
  });
</script>
View Memberscript
Custom Fields
UX

#39 - Better Select Fields

Add searches and a better UI to select & multi-select fields!

v0.1

Head Code

Put this in the <head> section of your page.


<!-- 💙 MEMBERSCRIPT #39 v0.1 HEAD CODE 💙 BETTER SELECT FIELDS -->
<script> src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"> </script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" />

Body Code

Put this in the </body> section of your page.


<!-- 💙 MEMBERSCRIPT #39 v0.1 BODY CODE 💙 BETTER SELECT FIELDS -->
<script> src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"> </script>
<script>
$(document).ready(function() {
    $('[ms-code-custom-select="select-with-search"]').select2();
});
</script>
View Memberscript
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.

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!

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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!

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.

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.

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.

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.

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!

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!

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!

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
Custom Fields

#17 - Populate Link From Custom Field

Use custom fields to populate link targets with one attribute!

v0.2

<!-- 💙 MEMBERSCRIPT #17 v0.3 💙 ADD CUSTOM FIELD AS A LINK -->
<script>
  document.addEventListener("DOMContentLoaded", function() {
    const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');
    if (!memberData?.id) return;

    document.querySelectorAll('[ms-code-field-link]').forEach(element => {
      const fieldKey = element.getAttribute('ms-code-field-link');
      const fieldValue = memberData.customFields?.[fieldKey]?.trim();

      if (!fieldValue) {
        element.style.display = 'none';
        return;
      }

      try {
        // Add protocol if missing and validate URL
        const url = !/^https?:\/\//i.test(fieldValue) ? 'https://' + fieldValue : fieldValue;
        new URL(url); // Will throw if invalid URL

        element.href = url;
        element.rel = 'noopener noreferrer';
        element.target = '_blank';
      } catch {
        element.style.display = 'none';
      }
    });
  });
</script>
View Memberscript
JSON

#16 - End Session After X Minutes of Inactivity

This script redirects inactive users to a "Session Expired" page after a certain period of inactivity. A countdown time is displayed on the page and resets with page activity.

v0.2

Add this code before the closing </body> tag any page which needs a countdown timer.


<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
let logoutTimer;
let countdownInterval;
let initialTime;

function startLogoutTimer() {
  // Get the logout time from the HTML element
  const timeElement = document.querySelector('[ms-code-logout-timer]');
  const timeParts = timeElement.textContent.split(':');
  const minutes = parseInt(timeParts[0], 10);
  const seconds = parseInt(timeParts[1], 10);
  const LOGOUT_TIME = (minutes * 60 + seconds) * 1000; // Convert to milliseconds

  // Store the initial time value
  if (!initialTime) {
    initialTime = LOGOUT_TIME;
  }

  // Clear the previous timer, if any
  clearTimeout(logoutTimer);
  clearInterval(countdownInterval);

  let startTime = Date.now();

  // Start a new timer to redirect the user after the specified time
  logoutTimer = setTimeout(() => {
    window.location.href = "/expired";
    startLogoutTimer(); // Start the logout timer again
  }, LOGOUT_TIME); 

  // Start a countdown timer
  countdownInterval = setInterval(() => {
    let elapsed = Date.now() - startTime;
    let remaining = LOGOUT_TIME - elapsed;
    updateCountdownDisplay(remaining);
  }, 1000); // update every second
}

function updateCountdownDisplay(remainingTimeInMs) {
  // convert ms to MM:SS format
  let minutes = Math.floor(remainingTimeInMs / 1000 / 60);
  let seconds = Math.floor((remainingTimeInMs / 1000) % 60);
  minutes = minutes < 10 ? "0" + minutes : minutes;
  seconds = seconds < 10 ? "0" + seconds : seconds;

  const timeElement = document.querySelector('[ms-code-logout-timer]');
  timeElement.textContent = `${minutes}:${seconds}`;
}

// Call this function whenever the user interacts with the page
function resetLogoutTimer() {
  const timeElement = document.querySelector('[ms-code-logout-timer]');
  timeElement.textContent = formatTime(initialTime); // Reset to the initial time
  startLogoutTimer();
}

function formatTime(timeInMs) {
  let minutes = Math.floor(timeInMs / 1000 / 60);
  let seconds = Math.floor((timeInMs / 1000) % 60);
  minutes = minutes < 10 ? "0" + minutes : minutes;
  seconds = seconds < 10 ? "0" + seconds : seconds;
  return `${minutes}:${seconds}`;
}

// Call this function when the user logs in
function cancelLogoutTimer() {
  clearTimeout(logoutTimer);
  clearInterval(countdownInterval);
}

// Start the timer when the page loads
startLogoutTimer();

// Add event listeners to reset timer on any page activity
document.addEventListener('mousemove', resetLogoutTimer);
document.addEventListener('keypress', resetLogoutTimer);
document.addEventListener('touchstart', resetLogoutTimer);

</script>

Add this code to your /expired page before the closing </body> tag.


<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
window.addEventListener('load', () => {
  window.$memberstackDom.getCurrentMember().then(async ({ data: member }) => {   
    if (member) {
      try {
        await window.$memberstackDom.logout();
        console.log('Logged out successfully');
        
        setTimeout(() => {
          location.reload();
        }, 1000); // Refresh the page 1 second after logout

      } catch (error) {
        console.error(error);
      }
    }
  });
});
</script>
View Memberscript
UX

#15 - Refresh Page After Set Duration On Click

Refresh the page after a set amount of time when an element is clicked.

v0.1

<!-- 💙 MEMBERSCRIPT #15 v0.1 💙 TRIGGER REFRESH ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  document.addEventListener("click", function(event) {
    const clickedElement = event.target.closest('[ms-code-refresh]');

    if (clickedElement) {
      const refreshDelay = parseInt(clickedElement.getAttribute('ms-code-refresh'));

      if (!isNaN(refreshDelay)) {
        setTimeout(function() {
          location.reload();
        }, refreshDelay);
      }
    }
  });
});
</script>
View Memberscript
UX

#14 - Create Loading State On Click

Simulate a custom loading state when an element is clicked.

v0.1

<!-- 💙 MEMBERSCRIPT #14 v0.1 💙 CREATE LOADING STATE ON CLICK -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous" ></script>
<script>
$(document).ready(function() {

    // Bind click event to elements with ms-code-loading-trigger attribute
    $(document).on('click', '[ms-code-loading-trigger]', function() {
        // Get the value of ms-code-loading-trigger attribute
        var triggerValue = $(this).attr("ms-code-loading-trigger");

        // Find the matching loading element
        var loadingElement = $("[ms-code-loading-element='" + triggerValue + "']");
        
        // Find the matching subject element
        var subjectElement = $("[ms-code-loading-subject='" + triggerValue + "']");

        // Show the loading element (with flex display), append it to the subject element
        loadingElement.css('display', 'flex').appendTo(subjectElement);

        // After 5 seconds (5000 milliseconds), hide the loading element
        setTimeout(function() {
            loadingElement.hide();
        }, 5000);
    });
});
</script>
View Memberscript
JSON
Conditional Visibility

#13 - Filter JSON Item Groups

Show filtered lists to your members based on a JSON property!

v0.2

<!-- 💙 MEMBERSCRIPT #13 v0.2 💙 FILTER ITEM GROUPS FROM JSON BASED ON ATTRIBUTE VALUE -->
<script>
(function() {
  const memberstack = window.$memberstackDom;
  let member;

  const fetchMemberJSON = async function() {
    member = await memberstack.getMemberJSON();
    return member;
  }

  const filterItems = async function(printList) {
    const filterAttr = printList.getAttribute('ms-code-list-filter');
    if (!filterAttr) return;

    const filters = filterAttr.split(',').map(filter => filter.trim());
    const jsonGroup = printList.getAttribute('ms-code-print-list');
    const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];

    const itemContainer = document.createElement('div');
    const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
    const itemTemplate = placeholder.outerHTML;

    items.forEach(item => {
      const newItem = document.createElement('div');
      newItem.innerHTML = itemTemplate;
      const itemElements = newItem.querySelectorAll('[ms-code-item-text]');

      let skipItem = false;
      filters.forEach(filter => {
        const exclude = filter.startsWith('!');
        const filterKey = exclude ? filter.substring(1) : filter;
        if ((exclude && item.hasOwnProperty(filterKey)) || (!exclude && !item.hasOwnProperty(filterKey))) {
          skipItem = true;
        }
      });

      if (skipItem) return; // Skip this item

      itemElements.forEach(itemElement => {
        const jsonKey = itemElement.getAttribute('ms-code-item-text');
        const value = item && item[jsonKey] ? item[jsonKey] : '';
        itemElement.textContent = value;
      });

      const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
      newItem.firstChild.setAttribute('ms-code-item-key', itemKey);

      itemContainer.appendChild(newItem.firstChild);
    });

    printList.innerHTML = itemContainer.innerHTML;
  };

  // Fetch member JSON
  let intervalId = setInterval(async () => {
    member = await fetchMemberJSON();
    if (member && member.data) {
      clearInterval(intervalId);
      const printLists = document.querySelectorAll('[ms-code-print-list]');
      printLists.forEach(filterItems);
    }
  }, 500);

  // Add click event listener to elements with ms-code-update="json"
  const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
  updateButtons.forEach(button => {
    button.addEventListener("click", async function() {
      // Fetch member JSON on each click
      let intervalIdClick = setInterval(async () => {
        member = await fetchMemberJSON();
        if (member && member.data) {
          clearInterval(intervalIdClick);
          const printLists = document.querySelectorAll('[ms-code-print-list]');
          printLists.forEach(filterItems);
        }
      }, 500);
    });
  });
})();
</script>
View Memberscript
JSON

#12 - Add Items To JSON Groups On Click

Add an item/property to previously created JSON items with one click!

v0.1

<!-- 💙 MEMBERSCRIPT #12 v0.1 💙 ADD ITEM TO JSON GROUP ON CLICK -->
<script>
  document.addEventListener("DOMContentLoaded", function() {
    const memberstack = window.$memberstackDom;

    // Add click event listener to the document
    document.addEventListener("click", async function(event) {
      const target = event.target;

      // Check if the clicked element has ms-code-update-json-item attribute
      const updateJsonItem = target.closest('[ms-code-update-json-item]');
      if (updateJsonItem) {
        const key = updateJsonItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');
        const jsonGroup = updateJsonItem.closest('[ms-code-print-list]').getAttribute('ms-code-print-list');

        // Retrieve the current member JSON data
        const member = await memberstack.getMemberJSON();

        if (member.data && member.data[jsonGroup] && member.data[jsonGroup][key]) {
          // Get the value to be added to the item in the member JSON
          const updateValue = updateJsonItem.getAttribute('ms-code-update-json-item');
          // Update the member JSON with the new value
          member.data[jsonGroup][key][updateValue] = true;

          // Update the member JSON using the Memberstack SDK
          await memberstack.updateMemberJSON({
            json: member.data
          });

          // Optional: Display a success message or perform any other desired action
          console.log('Member JSON updated successfully');
        } else {
          console.error(`Could not find item with key: ${key}`);
        }
      }

      // Check if the clicked element has ms-code-update attribute
      const updateButton = target.closest('[ms-code-update="json"]');
      if (updateButton) {
        // Add a delay
        await new Promise(resolve => setTimeout(resolve, 500));

        // Retrieve the current member JSON data
        const member = await memberstack.getMemberJSON();

        // Save the member JSON as a local storage item
        localStorage.setItem("memberJSON", JSON.stringify(member));

        // Optional: Display a success message or perform any other desired action
        console.log('Member JSON saved to local storage');
      }
    });
  });
</script>
View Memberscript
Conditional Visibility

#11 - Automatically Open Login Modal

Show all logged-out visitors a login modal immediately on page visit. Both custom and default Memberstack work.

v0.2

<!-- 💙 MEMBERSCRIPT #11 v0.1 💙 SHOW LOGIN MODAL IF MEMBER IS NOT LOGGED IN -->

<!-- KEEP THIS FOR DEFAULT MEMBERSTACK MODAL -->
<script>
function handleRedirect(redirect) {
  if (redirect && (window.location.pathname !== redirect)) return window.location.href = redirect;
  window.location.reload()
}

$memberstackDom.getCurrentMember().then(async ({
  data
}) => {
  if (!data) {
    const {
      data
    } = await $memberstackDom.openModal("login");
    handleRedirect(data.redirect)
  }
})
</script>

<!-- KEEP THIS FOR YOUR OWN CUSTOM MODAL -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
  if (!data) {
    const loginModal = document.querySelector('[ms-code-modal="login"]');
    if (loginModal) {
      loginModal.style.display = "flex";
    }
  }
});
</script>
View Memberscript
Conditional Visibility

#10 - Display/Remove Element based on Custom Field

Check if a member has filled out a custom field. If yes, set the target element to display.

v0.2

<!-- 💙 MEMBERSCRIPT #10 v0.2 💙 HIDE ELEMENTS IF CUSTOM FIELD IS BLANK -->
<script>
document.addEventListener('DOMContentLoaded', function() {
  // Get the `_ms-mem` object from the local storage
  const msMem = JSON.parse(localStorage.getItem('_ms-mem'));

  // Get all the elements that have the `ms-code-customfield` attribute
  const elements = document.querySelectorAll('[ms-code-customfield]');

  // Iterate over each element
  elements.forEach(element => {
    // Get the value of the `ms-code-customfield` attribute
    const customField = element.getAttribute('ms-code-customfield');

    // If customField starts with '!', we invert the logic
    if (customField.startsWith('!')) {
      const actualCustomField = customField.slice(1); // remove the '!' from the start

      // If the custom field is empty, remove the element from the DOM
      if (msMem.customFields && msMem.customFields[actualCustomField]) {
        element.parentNode.removeChild(element);
      }
    } else {
      // Check if the user has the corresponding custom field in Memberstack
      if (!msMem.customFields || !msMem.customFields[customField]) {
        // If the custom field is empty, remove the element from the DOM
        element.parentNode.removeChild(element);
      }
    }
  });
});
</script>
View Memberscript
Marketing

#9 - Real, User Based Countdown

Dynamically set a per-user countdown time and then hide elements when time is up.

v0.1

<!-- 💙 MEMBERSCRIPT #9 v0.1 💙 COUNTDOWN BY USER -->
<script>
// Step 1: Get the current date and time
const currentDate = new Date();

// Step 2: Calculate the new date and time based on the attribute values
const addTime = (date, unit, value) => {
  const newDate = new Date(date);
  switch (unit) {
    case 'hour':
      newDate.setHours(newDate.getHours() + value);
      break;
    case 'minute':
      newDate.setMinutes(newDate.getMinutes() + value);
      break;
    case 'second':
      newDate.setSeconds(newDate.getSeconds() + value);
      break;
    case 'millisecond':
      newDate.setMilliseconds(newDate.getMilliseconds() + value);
      break;
  }
  return newDate;
};

// Retrieve attribute values and calculate the new date and time
const hourAttr = document.querySelector('[ms-code-time-hour]');
const minuteAttr = document.querySelector('[ms-code-time-minute]');
const secondAttr = document.querySelector('[ms-code-time-second]');
const millisecondAttr = document.querySelector('[ms-code-time-millisecond]');

const countdownDateTime = localStorage.getItem('countdownDateTime');
let newDate;

if (countdownDateTime) {
  newDate = new Date(countdownDateTime);
} else {
  newDate = currentDate;
  if (hourAttr.hasAttribute('ms-code-time-hour')) {
    const hours = parseInt(hourAttr.getAttribute('ms-code-time-hour'));
    newDate = addTime(newDate, 'hour', isNaN(hours) ? 0 : hours);
  }
  if (minuteAttr.hasAttribute('ms-code-time-minute')) {
    const minutes = parseInt(minuteAttr.getAttribute('ms-code-time-minute'));
    newDate = addTime(newDate, 'minute', isNaN(minutes) ? 0 : minutes);
  }
  if (secondAttr.hasAttribute('ms-code-time-second')) {
    const seconds = parseInt(secondAttr.getAttribute('ms-code-time-second'));
    newDate = addTime(newDate, 'second', isNaN(seconds) ? 0 : seconds);
  }
  if (millisecondAttr.hasAttribute('ms-code-time-millisecond')) {
    const milliseconds = parseInt(millisecondAttr.getAttribute('ms-code-time-millisecond'));
    newDate = addTime(newDate, 'millisecond', isNaN(milliseconds) ? 0 : milliseconds);
  }

  localStorage.setItem('countdownDateTime', newDate);
}

// Step 4: Update the text of the elements to show the continuous countdown
const countdownElements = [hourAttr, minuteAttr, secondAttr, millisecondAttr];

const updateCountdown = () => {
  const currentTime = new Date();
  const timeDifference = newDate - currentTime;

  if (timeDifference > 0) {
    const timeParts = [
      Math.floor(timeDifference / (1000 * 60 * 60)) % 24, // Hours
      Math.floor(timeDifference / (1000 * 60)) % 60, // Minutes
      Math.floor(timeDifference / 1000) % 60, // Seconds
      Math.floor(timeDifference) % 1000, // Milliseconds
    ];

    // Update the text of the elements with the countdown values
    countdownElements.forEach((element, index) => {
      const timeValue = timeParts[index];
      if (index === 3) {
        element.innerText = timeValue.toString().padStart(2, '0').slice(0, 2); // Display only two digits for milliseconds
      } else {
        element.innerText = timeValue < 10 ? `0${timeValue}` : timeValue;
      }
    });

    // Update the countdown every 10 milliseconds
    setTimeout(updateCountdown, 10);
  } else {
    // Countdown has reached zero or has passed
    countdownElements.forEach((element) => {
      element.innerText = '00';
    });

    // Remove elements with ms-code-countdown="hide-on-end" attribute
    const hideOnEndElements = document.querySelectorAll('[ms-code-countdown="hide-on-end"]');
    hideOnEndElements.forEach((element) => {
      element.remove();
    });

    // Optionally, you can perform additional actions or display a message when the countdown ends
  }
};

// Start the countdown
updateCountdown();
</script>
View Memberscript
Marketing

#8 - Copy To Clipboard

Allow visitors to copy things such as coupon codes to their clipboard with just one click.

v0.1

<!-- 💙 MEMBERSCRIPT #8 v0.1 💙 SIMPLE COPY ELEMENT TO CLIPBOARD -->
<script>
// Step 1: Click the element with the attribute ms-code-copy="trigger"
const triggerElement = document.querySelector('[ms-code-copy="trigger"]');
triggerElement.addEventListener('click', () => {
  // Step 2: Copy text from the element with the attribute ms-code-copy="subject" to the clipboard
  const subjectElement = document.querySelector('[ms-code-copy="subject"]');
  const subjectText = subjectElement.innerText;

  // Create a temporary textarea element to facilitate the copying process
  const tempTextArea = document.createElement('textarea');
  tempTextArea.value = subjectText;
  document.body.appendChild(tempTextArea);

  // Select the text within the textarea and copy it to the clipboard
  tempTextArea.select();
  document.execCommand('copy');

  // Remove the temporary textarea
  document.body.removeChild(tempTextArea);
});
</script>
View Memberscript
Conditional Visibility

#7 - Delay Loading Elements

Delay elements from appearing for a set duration to give the page time to update with correct member data.

v0.1

<!-- 💙 MEMBERSCRIPT #7 v0.1 💙 DELAY LOADING ELEMENTS -->
<script>
  document.addEventListener("DOMContentLoaded", function() {
    const elementsToDelay = document.querySelectorAll('[ms-code-delay]');

    elementsToDelay.forEach(element => {
      element.style.visibility = "hidden"; // Hide the element initially
    });

    setTimeout(function() {
      elementsToDelay.forEach(element => {
        element.style.visibility = "visible"; // Make the element visible after the delay
        element.style.opacity = "0"; // Set the initial opacity to 0
        element.style.animation = "fadeIn 0.5s"; // Apply the fadeIn animation
        element.addEventListener("animationend", function() {
          element.style.opacity = "1"; // Set opacity to 1 at the end of the animation
        });
      });
    }, 1000);
  });
</script>
<style>
  @keyframes fadeIn {
    0% {
      opacity: 0;
    }
    100% {
      opacity: 1;
    }
  }
</style>
View Memberscript
JSON

#6 - Create Item Groups From Member JSON

Display JSON groups to logged in members using a placeholder element built in Webflow.

v0.2

<!-- 💙 MEMBERSCRIPT #6 v0.1 💙 CREATE ITEM GROUPS FROM JSON -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;

  // Function to display nested/sub items
  const displayNestedItems = async function(printList) {
    const jsonGroup = printList.getAttribute('ms-code-print-list');
    const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
    if (!placeholder) return;

    const itemTemplate = placeholder.outerHTML;
    const itemContainer = document.createElement('div'); // Create a new container for the items

    const member = await memberstack.getMemberJSON();
    const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
    if (items.length === 0) return; // If no items, exit the function

    items.forEach(item => {
      if (Object.keys(item).length === 0) return;

      const newItem = document.createElement('div');
      newItem.innerHTML = itemTemplate;
      const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
      itemElements.forEach(itemElement => {
        const jsonKey = itemElement.getAttribute('ms-code-item-text');
        const value = item && item[jsonKey] ? item[jsonKey] : '';
        itemElement.textContent = value;
      });

      // Add item key attribute
      const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
      newItem.firstChild.setAttribute('ms-code-item-key', itemKey);

      itemContainer.appendChild(newItem.firstChild);
    });

    // Replace the existing list with the new items
    printList.innerHTML = itemContainer.innerHTML;
  };

  // Call displayNestedItems function on initial page load for each instance
  const printLists = document.querySelectorAll('[ms-code-print-list]');
  printLists.forEach(printList => {
    displayNestedItems(printList);
  });

  // Add click event listener to elements with ms-code-update="json"
  const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
  updateButtons.forEach(button => {
    button.addEventListener("click", async function() {
      // Add a delay of 500ms
      await new Promise(resolve => setTimeout(resolve, 500));
      printLists.forEach(printList => {
        displayNestedItems(printList);
      });
    });
  });
});
</script>
View Memberscript
JSON

#5 - Fill Text Based On Simple JSON Item

Use Member JSON to update the text of any element on your page.

v0.1

<!-- 💙 MEMBERSCRIPT #5 v0.1 💙 FILL TEXT BASED ON SIMPLE ITEM IN JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const memberstack = window.$memberstackDom;

  // Function to fill text elements with attribute ms-code-fill-text
  const fillTextElements = async function() {
    // Retrieve the current member JSON data
    const member = await memberstack.getMemberJSON();

    // Fill text elements with attribute ms-code-fill-text
    const textElements = document.querySelectorAll('[ms-code-fill-text]');
    textElements.forEach(element => {
      const jsonKey = element.getAttribute('ms-code-fill-text');
      const value = member.data && member.data[jsonKey] ? member.data[jsonKey] : '';
      element.textContent = value;
    });
  };

  // Function to handle script #4 event
  const handleScript4Event = async function() {
    // Add a delay of 500ms
    await new Promise(resolve => setTimeout(resolve, 500));
    await fillTextElements();
  };

  // Add click event listener to elements with ms-code-update="json"
  const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
  updateButtons.forEach(button => {
    button.addEventListener("click", handleScript4Event);
  });

  // Call fillTextElements function on initial page load
  fillTextElements();
});
</script>

View Memberscript
JSON

#4 - Update Member JSON In Local Storage On Click

Add this attribute to any button/element which you want to trigger a JSON update after a short delay.

v0.1

<!-- 💙 MEMBERSCRIPT #4 v0.1 💙 UPDATE MEMBER JSON IN LOCAL STORAGE ON BUTTON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const updateButtons = document.querySelectorAll('[ms-code-update="json"]');

  updateButtons.forEach(button => {
    button.addEventListener("click", async function() {
      const memberstack = window.$memberstackDom;

      // Add a delay
      await new Promise(resolve => setTimeout(resolve, 500));

      // Retrieve the current member JSON data
      const member = await memberstack.getMemberJSON();

      // Save the member JSON as a local storage item
      localStorage.setItem("memberJSON", JSON.stringify(member));
    });
  });
});
</script>
View Memberscript
JSON

#3 - Save Member JSON To Local Storage On Page Load

Create a localstorage object containing the logged in Member JSON on page load

v0.1

<!-- 💙 MEMBERSCRIPT #3 v0.1 💙 SAVE JSON TO LOCALSTORAGE ON PAGE LOAD -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
  const memberstack = window.$memberstackDom;
  
  // Retrieve the current member JSON data
  const member = await memberstack.getMemberJSON();

  // Save the member JSON as a local storage item
  localStorage.setItem("memberJSON", JSON.stringify(member));
});
</script>
View Memberscript
JSON

#2 - Add Item Groups To Member JSON

Allow members to add nested items/groups to their Member JSON.

v0.1

<!-- 💙 MEMBERSCRIPT #2 v0.1 💙 ADD ITEM GROUPS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const msCodeForm2 = document.querySelector('[ms-code="form2"]');
  const jsonList = msCodeForm2.getAttribute("ms-code-json-list");

  msCodeForm2.addEventListener('submit', async function(event) {
    event.preventDefault(); // Prevent the default form submission

    // Retrieve the current member JSON data
    const memberstack = window.$memberstackDom;
    const member = await memberstack.getMemberJSON();

    // Create a new member.data object if it doesn't already exist
    if (!member.data) {
      member.data = {};
    }

    // Check if the friends group already exists
    if (!member.data[jsonList]) {
      // Create a new friends group object
      member.data[jsonList] = {};
    }

    // Retrieve the input values for creating the subgroup
    const inputs = msCodeForm2.querySelectorAll('[ms-code-json-name]');
    const subgroup = {};
    inputs.forEach(input => {
      const jsonName = input.getAttribute('ms-code-json-name');
      const newItem = input.value;
      subgroup[jsonName] = newItem;
    });

    // Generate a unique key for the new subgroup prefixed with the main group name
    const timestamp = Date.now();
    const subgroupKey = `${jsonList}-${timestamp}`;

    // Create the new subgroup within the friends group
    member.data[jsonList][subgroupKey] = subgroup;

    // Update the member JSON with the new data
    await memberstack.updateMemberJSON({
      json: member.data
    });

    // Log a message to the console
    console.log(`New subgroup with key ${subgroupKey} has been created within ${jsonList} group.`);

    // Reset the input values
    inputs.forEach(input => {
      input.value = "";
    });
  });
});
</script>
View Memberscript
JSON

#1 - Add Items To Member JSON

Allow members to save simple items to their JSON without writing any code.

v0.1

<!-- 💙 MEMBERSCRIPT #1 v0.1 💙 ADD INDIVIDUAL ITEMS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
  const forms = document.querySelectorAll('[ms-code="form1"]');
  
  forms.forEach(form => {
    const jsonType = form.getAttribute("ms-code-json-type");
    const jsonList = form.getAttribute("ms-code-json-list");

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

      // Retrieve the current member JSON data
      const memberstack = window.$memberstackDom;
      const member = await memberstack.getMemberJSON();

      // Create a new member.data object if it doesn't already exist
      if (!member.data) {
        member.data = {};
      }

      if (jsonType === "group") {
        // Check if the group already exists
        if (!member.data[jsonList]) {
          // Create a new group object
          member.data[jsonList] = {};
        }

        // Iterate over the inputs with ms-code-json-name attribute
        const inputs = form.querySelectorAll('[ms-code-json-name]');
        inputs.forEach(input => {
          const jsonName = input.getAttribute('ms-code-json-name');
          const newItem = input.value;
          member.data[jsonList][jsonName] = newItem;
        });

        // Log a message to the console for group type
        console.log(`Item(s) have been added to member JSON with group key ${jsonList}.`);
      } else if (jsonType === "array") {
        // Check if the array already exists
        if (!member.data[jsonList]) {
          // Create a new array
          member.data[jsonList] = [];
        }

        // Retrieve the input values for the array type
        const inputs = form.querySelectorAll('[ms-code-json-name]');
        inputs.forEach(input => {
          const jsonName = input.getAttribute('ms-code-json-name');
          const newItem = input.value;
          member.data[jsonList].push(newItem);
        });

        // Log a message to the console for array type
        console.log(`Item(s) have been added to member JSON with array key ${jsonList}.`);
      } else {
        // Retrieve the input value and key for the basic item type
        const input = form.querySelector('[ms-code-json-name]');
        const jsonName = input.getAttribute('ms-code-json-name');
        const newItem = input.value;
        member.data[jsonName] = newItem;

        // Log a message to the console for basic item type
        console.log(`Item ${newItem} has been added to member JSON with key ${jsonName}.`);
      }

      // Update the member JSON with the new data
      await memberstack.updateMemberJSON({
        json: member.data
      });

      // Reset the input values
      const inputs = form.querySelectorAll('[ms-code-json-name]');
      inputs.forEach(input => {
        input.value = "";
      });
    });
  });
});
</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.