Components
Templates
Attributes
Integrations
Site Tester
Custom Code

MemberScripts

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

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

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

UX

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

#17 - Populate Link From Custom Field

Use custom fields to populate link targets with one attribute!


<!-- 💙 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>
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.

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>
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.


<!-- 💙 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>
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.


<!-- 💙 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>
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!


<!-- 💙 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>
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!


<!-- 💙 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>
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.


<!-- 💙 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>
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.


<!-- 💙 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>
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.


<!-- 💙 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>
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
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.