MemberScripts
An attribute-based solution to add features to your Webflow site.
Simply copy some code, add some attributes, and you're done.
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.

#78 - Clear Inputs OnClick
Create a button which can clear the values of one or more inputs.
<!-- 💙 MEMBERSCRIPT #78 v0.1 💙 CLEAR INPUT VALUES ONCLICK -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const clearBtns = document.querySelectorAll('[ms-code-clear-value]');
clearBtns.forEach(btn => {
btn.addEventListener('click', () => {
const fieldIds = btn.getAttribute('ms-code-clear-value').split(',');
fieldIds.forEach(fieldId => {
const input = document.querySelector(`[data-ms-member="${fieldId}"]`);
if (input) {
input.value = '';
}
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #78 v0.1 💙 CLEAR INPUT VALUES ONCLICK -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const clearBtns = document.querySelectorAll('[ms-code-clear-value]');
clearBtns.forEach(btn => {
btn.addEventListener('click', () => {
const fieldIds = btn.getAttribute('ms-code-clear-value').split(',');
fieldIds.forEach(fieldId => {
const input = document.querySelector(`[data-ms-member="${fieldId}"]`);
if (input) {
input.value = '';
}
});
});
});
});
</script>

#77 - Universal Emojis
Make your on-site emojis the same on all devices/OS.
<!-- 💙 MEMBERSCRIPT #77 v0.1 💙 UNIVERSAL EMOJIS -->
<script>
document.querySelectorAll('[ms-code-emoji]').forEach(element => {
var imageUrl = element.getAttribute('ms-code-emoji');
var img = document.createElement('img');
img.src = imageUrl;
var textStyle = window.getComputedStyle(element);
var adjustedHeight = parseFloat(textStyle.fontSize) * 1.0;
img.style.height = adjustedHeight + 'px';
img.style.width = 'auto';
img.style.verticalAlign = 'text-top';
element.innerHTML = ''; // Clears the text content inside the span
element.appendChild(img);
});
</script>
<!-- 💙 MEMBERSCRIPT #77 v0.1 💙 UNIVERSAL EMOJIS -->
<script>
document.querySelectorAll('[ms-code-emoji]').forEach(element => {
var imageUrl = element.getAttribute('ms-code-emoji');
var img = document.createElement('img');
img.src = imageUrl;
var textStyle = window.getComputedStyle(element);
var adjustedHeight = parseFloat(textStyle.fontSize) * 1.0;
img.style.height = adjustedHeight + 'px';
img.style.width = 'auto';
img.style.verticalAlign = 'text-top';
element.innerHTML = ''; // Clears the text content inside the span
element.appendChild(img);
});
</script>

#76 - Time-Based Visibility
Show different elements based on the current time of day.
<!-- 💙 MEMBERSCRIPT #76 v0.1 💙 TIME-BASED VISIBILITY -->
<script>
function hideElements() {
const elements = document.querySelectorAll('[ms-code-time]');
elements.forEach(element => {
element.style.display = 'none';
});
}
function displayBasedOnTime() {
const elements = document.querySelectorAll('[ms-code-time]');
const currentTime = new Date();
elements.forEach(element => {
const timeRange = element.getAttribute('ms-code-time');
const [start, end] = timeRange.split(' - ');
const [startHour, startMinute] = start.split(':').map(Number);
const [endHour, endMinute] = end.split(':').map(Number);
let startTime = new Date(currentTime);
startTime.setHours(startHour, startMinute, 0, 0);
let endTime = new Date(currentTime);
endTime.setHours(endHour, endMinute, 0, 0);
// If the end time is earlier than the start time, add a day to the end time
if (endTime < startTime) {
endTime.setDate(endTime.getDate() + 1);
}
if (currentTime >= startTime && currentTime <= endTime) {
element.style.display = 'flex';
}
});
}
// Call the functions
hideElements();
displayBasedOnTime();
</script>
<!-- 💙 MEMBERSCRIPT #76 v0.1 💙 TIME-BASED VISIBILITY -->
<script>
function hideElements() {
const elements = document.querySelectorAll('[ms-code-time]');
elements.forEach(element => {
element.style.display = 'none';
});
}
function displayBasedOnTime() {
const elements = document.querySelectorAll('[ms-code-time]');
const currentTime = new Date();
elements.forEach(element => {
const timeRange = element.getAttribute('ms-code-time');
const [start, end] = timeRange.split(' - ');
const [startHour, startMinute] = start.split(':').map(Number);
const [endHour, endMinute] = end.split(':').map(Number);
let startTime = new Date(currentTime);
startTime.setHours(startHour, startMinute, 0, 0);
let endTime = new Date(currentTime);
endTime.setHours(endHour, endMinute, 0, 0);
// If the end time is earlier than the start time, add a day to the end time
if (endTime < startTime) {
endTime.setDate(endTime.getDate() + 1);
}
if (currentTime >= startTime && currentTime <= endTime) {
element.style.display = 'flex';
}
});
}
// Call the functions
hideElements();
displayBasedOnTime();
</script>

#75 - Disallowed Character Inputs
Show a custom error message if a user enters something you set in an input.
<!-- 💙 MEMBERSCRIPT #75 v0.1 💙 DISALOWED CHARACTER INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const inputFields = document.querySelectorAll('[ms-code-disallow]');
inputFields.forEach(inputField => {
const errorBlock = inputField.nextElementSibling;
errorBlock.innerHTML = ''; // Use innerHTML to interpret <br> tags
inputField.addEventListener('input', function() {
const rules = inputField.getAttribute('ms-code-disallow').split(')');
let errorMessage = '';
rules.forEach(rule => {
const parts = rule.trim().split('=');
const ruleType = parts[0].substring(1); // Remove the opening parenthesis
const disallowedValue = parts[1];
if (ruleType.startsWith('custom')) {
const disallowedChar = ruleType.split('-')[1]; // Extract the character after the '-'
if (inputField.value.includes(disallowedChar)) {
errorMessage += disallowedValue + '<br>'; // Add line break
}
} else if (ruleType === 'space' && inputField.value.includes(' ')) {
errorMessage += disallowedValue + '<br>'; // Add line break
} else if (ruleType === 'number' && /\d/.test(inputField.value)) {
errorMessage += disallowedValue + '<br>'; // Add line break
} else if (ruleType === 'special' && /[^a-zA-Z0-9\s]/.test(inputField.value)) { // Notice the \s here
errorMessage += disallowedValue + '<br>'; // Add line break
}
});
errorBlock.innerHTML = errorMessage || ''; // Use innerHTML to interpret <br> tags
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #75 v0.1 💙 DISALOWED CHARACTER INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const inputFields = document.querySelectorAll('[ms-code-disallow]');
inputFields.forEach(inputField => {
const errorBlock = inputField.nextElementSibling;
errorBlock.innerHTML = ''; // Use innerHTML to interpret <br> tags
inputField.addEventListener('input', function() {
const rules = inputField.getAttribute('ms-code-disallow').split(')');
let errorMessage = '';
rules.forEach(rule => {
const parts = rule.trim().split('=');
const ruleType = parts[0].substring(1); // Remove the opening parenthesis
const disallowedValue = parts[1];
if (ruleType.startsWith('custom')) {
const disallowedChar = ruleType.split('-')[1]; // Extract the character after the '-'
if (inputField.value.includes(disallowedChar)) {
errorMessage += disallowedValue + '<br>'; // Add line break
}
} else if (ruleType === 'space' && inputField.value.includes(' ')) {
errorMessage += disallowedValue + '<br>'; // Add line break
} else if (ruleType === 'number' && /\d/.test(inputField.value)) {
errorMessage += disallowedValue + '<br>'; // Add line break
} else if (ruleType === 'special' && /[^a-zA-Z0-9\s]/.test(inputField.value)) { // Notice the \s here
errorMessage += disallowedValue + '<br>'; // Add line break
}
});
errorBlock.innerHTML = errorMessage || ''; // Use innerHTML to interpret <br> tags
});
});
});
</script>

#74 - Styling with Link Parameters
Update page styling based on a link parameter. Ex. ?ms-code-target=CLASSNAME&ms-code-style=display:block
<!-- 💙 MEMBERSCRIPT #74 v0.1 💙 UPDATE STYLING WITH LINK PARAMS -->
<script>
// Function to parse URL parameters
function getURLParameter(name) {
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}
// Function to apply styles
function applyStylesFromURL() {
const targetClass = getURLParameter('ms-code-target');
const rawStyles = getURLParameter('ms-code-style');
if (targetClass && rawStyles) {
const elements = document.querySelectorAll(`.${targetClass}`);
const styles = rawStyles.split(';').filter(style => style.trim() !== ''); // filter out any empty strings
styles.forEach(style => {
const [property, value] = style.split(':');
elements.forEach(element => {
element.style[property] = value;
});
});
}
}
// Call the function once the DOM is loaded
window.addEventListener('DOMContentLoaded', (event) => {
applyStylesFromURL();
});
</script>
<!-- 💙 MEMBERSCRIPT #74 v0.1 💙 UPDATE STYLING WITH LINK PARAMS -->
<script>
// Function to parse URL parameters
function getURLParameter(name) {
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
}
// Function to apply styles
function applyStylesFromURL() {
const targetClass = getURLParameter('ms-code-target');
const rawStyles = getURLParameter('ms-code-style');
if (targetClass && rawStyles) {
const elements = document.querySelectorAll(`.${targetClass}`);
const styles = rawStyles.split(';').filter(style => style.trim() !== ''); // filter out any empty strings
styles.forEach(style => {
const [property, value] = style.split(':');
elements.forEach(element => {
element.style[property] = value;
});
});
}
}
// Call the function once the DOM is loaded
window.addEventListener('DOMContentLoaded', (event) => {
applyStylesFromURL();
});
</script>

#73 - Display Date & Time
Display the current time, time-of-day, day, month, or year to a user. Works if logged in or logged out.
<!-- 💙 MEMBERSCRIPT #73 v0.1 💙 DATES AND TIMES -->
function getCurrentDateInfo(attribute) {
const now = new Date();
const options = { hour12: true };
switch(attribute) {
case "day":
return now.toLocaleDateString('en-US', { weekday: 'long' });
case "time":
return now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).toLowerCase();
case "month":
return now.toLocaleDateString('en-US', { month: 'long' });
case "year":
return now.getFullYear().toString();
case "time-of-day":
const hour = now.getHours();
if (5 <= hour && hour < 12) return "morning";
if (12 <= hour && hour < 17) return "afternoon";
if (17 <= hour && hour < 21) return "evening";
return "night";
default:
return "Invalid attribute";
}
}
function updateDateInfoOnPage() {
const spanTags = document.querySelectorAll('span[ms-code-date]');
spanTags.forEach(tag => {
const attributeValue = tag.getAttribute('ms-code-date');
const dateInfo = getCurrentDateInfo(attributeValue);
tag.textContent = dateInfo;
});
}
// Call the function to update the content on the page
updateDateInfoOnPage();
</script>
<!-- 💙 MEMBERSCRIPT #73 v0.1 💙 DATES AND TIMES -->
function getCurrentDateInfo(attribute) {
const now = new Date();
const options = { hour12: true };
switch(attribute) {
case "day":
return now.toLocaleDateString('en-US', { weekday: 'long' });
case "time":
return now.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', hour12: true }).toLowerCase();
case "month":
return now.toLocaleDateString('en-US', { month: 'long' });
case "year":
return now.getFullYear().toString();
case "time-of-day":
const hour = now.getHours();
if (5 <= hour && hour < 12) return "morning";
if (12 <= hour && hour < 17) return "afternoon";
if (17 <= hour && hour < 21) return "evening";
return "night";
default:
return "Invalid attribute";
}
}
function updateDateInfoOnPage() {
const spanTags = document.querySelectorAll('span[ms-code-date]');
spanTags.forEach(tag => {
const attributeValue = tag.getAttribute('ms-code-date');
const dateInfo = getCurrentDateInfo(attributeValue);
tag.textContent = dateInfo;
});
}
// Call the function to update the content on the page
updateDateInfoOnPage();
</script>

#72 - Validate Original Values
Only allow a form to be submitted if the input value is original (ie. Usernames)
<!-- 💙 MEMBERSCRIPT #72 v0.1 💙 VALIDATE ORIGINAL VALUES -->
<style>
[ms-code-available="true"],
[ms-code-available="false"],
[ms-code-available="invalid"]{
display: none;
}
.disabled {
opacity: 0.5;
pointer-events: none;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
let input = document.querySelector('[ms-code-available="input"]');
let trueElement = document.querySelector('[ms-code-available="true"]');
let falseElement = document.querySelector('[ms-code-available="false"]');
let invalidElement = document.querySelector('[ms-code-available="invalid"]');
let listElements = Array.from(document.querySelectorAll('[ms-code-available="list"]'));
let submitButton = document.querySelector('[ms-code-available="submit"]');
function checkUsername() {
// Check if the input matches any of the list items
let isTaken = listElements.some(elem => elem.textContent.trim() === input.value.trim());
if (isTaken) {
trueElement.style.display = 'none';
falseElement.style.display = 'flex';
submitButton.classList.add('disabled'); // disable the button if username is taken
} else {
trueElement.style.display = 'flex';
falseElement.style.display = 'none';
submitButton.classList.remove('disabled');
}
}
input.addEventListener('input', function() {
// Display the invalid element if input length is between 1 and 3
if (input.value.length >= 1 && input.value.length <= 3) {
invalidElement.style.display = 'flex';
} else {
invalidElement.style.display = 'none';
}
// Add the .disabled class to the submit button if input is empty or less than 3 characters
if (input.value.length <= 3) {
submitButton.classList.add('disabled');
trueElement.style.display = 'none';
falseElement.style.display = 'none';
} else {
checkUsername();
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #72 v0.1 💙 VALIDATE ORIGINAL VALUES -->
<style>
[ms-code-available="true"],
[ms-code-available="false"],
[ms-code-available="invalid"]{
display: none;
}
.disabled {
opacity: 0.5;
pointer-events: none;
}
</style>
<script>
document.addEventListener('DOMContentLoaded', function() {
let input = document.querySelector('[ms-code-available="input"]');
let trueElement = document.querySelector('[ms-code-available="true"]');
let falseElement = document.querySelector('[ms-code-available="false"]');
let invalidElement = document.querySelector('[ms-code-available="invalid"]');
let listElements = Array.from(document.querySelectorAll('[ms-code-available="list"]'));
let submitButton = document.querySelector('[ms-code-available="submit"]');
function checkUsername() {
// Check if the input matches any of the list items
let isTaken = listElements.some(elem => elem.textContent.trim() === input.value.trim());
if (isTaken) {
trueElement.style.display = 'none';
falseElement.style.display = 'flex';
submitButton.classList.add('disabled'); // disable the button if username is taken
} else {
trueElement.style.display = 'flex';
falseElement.style.display = 'none';
submitButton.classList.remove('disabled');
}
}
input.addEventListener('input', function() {
// Display the invalid element if input length is between 1 and 3
if (input.value.length >= 1 && input.value.length <= 3) {
invalidElement.style.display = 'flex';
} else {
invalidElement.style.display = 'none';
}
// Add the .disabled class to the submit button if input is empty or less than 3 characters
if (input.value.length <= 3) {
submitButton.classList.add('disabled');
trueElement.style.display = 'none';
falseElement.style.display = 'none';
} else {
checkUsername();
}
});
});
</script>

#71 - Redirect if Certain Fields are Empty
Redirect a member to an onboarding page if certain custom fields are empty.
<!-- 💙 MEMBERSCRIPT #71 v0.1 💙 REDIRECT IF FIELDS ARE EMPTY -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
const onboardingPageUrl = '/onboarding'; // replace
const customFieldKeys = 'custom-field-1,custom-field-2'; // replace
// No need to edit past this line
const member = await memberstack.getCurrentMember();
if (!member) {
return;
}
// If current page slug matches the redirect slug, exit the script
const currentPageSlug = window.location.pathname;
if (currentPageSlug === onboardingPageUrl) {
return;
}
async function checkOnboardingStatus() {
try {
const memberData = await memberstack.updateMember({});
const customFields = customFieldKeys.split(',');
for (let field of customFields) {
if (!memberData.data.customFields[field.trim()]) {
// Redirect to onboarding page if the custom field is empty
window.location.href = onboardingPageUrl;
return;
}
}
} catch (error) {
console.error(`Error in checkOnboardingStatus function: ${error}`);
}
}
// Check onboarding status and potentially redirect
checkOnboardingStatus().catch(error => {
console.error(`Error in MemberScript #71 initial functions: ${error}`);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #71 v0.1 💙 REDIRECT IF FIELDS ARE EMPTY -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
const onboardingPageUrl = '/onboarding'; // replace
const customFieldKeys = 'custom-field-1,custom-field-2'; // replace
// No need to edit past this line
const member = await memberstack.getCurrentMember();
if (!member) {
return;
}
// If current page slug matches the redirect slug, exit the script
const currentPageSlug = window.location.pathname;
if (currentPageSlug === onboardingPageUrl) {
return;
}
async function checkOnboardingStatus() {
try {
const memberData = await memberstack.updateMember({});
const customFields = customFieldKeys.split(',');
for (let field of customFields) {
if (!memberData.data.customFields[field.trim()]) {
// Redirect to onboarding page if the custom field is empty
window.location.href = onboardingPageUrl;
return;
}
}
} catch (error) {
console.error(`Error in checkOnboardingStatus function: ${error}`);
}
}
// Check onboarding status and potentially redirect
checkOnboardingStatus().catch(error => {
console.error(`Error in MemberScript #71 initial functions: ${error}`);
});
});
</script>

#70 - Hide Old/Seen CMS Items
Only show CMS items which are new to a particular member. If they've seen it, hide it.
<!-- 💙 MEMBERSCRIPT #70 v0.1 💙 HIDE OLD CMS ITEMS -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
// Only proceed if a member is found
const member = await memberstack.getCurrentMember();
if (!member) {
console.log('No member found in MemberScript #70, exiting script');
return;
}
async function getCmsItemsFromJson() {
try {
const memberData = await memberstack.getMemberJSON();
return memberData?.data?.cmsItems || [];
} catch (error) {
console.error(`Error in getCmsItemsFromJson function: ${error}`);
}
}
async function updateCmsItemsInJson(newCmsItems) {
try {
const memberData = await memberstack.getMemberJSON();
memberData.data = memberData.data || {};
memberData.data.cmsItems = newCmsItems;
console.log(`CMS items in JSON after update: ${JSON.stringify(newCmsItems)}`);
await memberstack.updateMemberJSON({ json: memberData.data });
} catch (error) {
console.error(`Error in updateCmsItemsInJson function: ${error}`);
}
}
async function hideSeenCmsItems() {
try {
const cmsItemsElements = document.querySelectorAll('[ms-code-cms-item]');
const cmsItemsFromJson = await getCmsItemsFromJson();
cmsItemsElements.forEach(element => {
const cmsValue = element.getAttribute('ms-code-cms-item');
if (cmsItemsFromJson.includes(cmsValue)) {
element.style.display = 'none';
} else {
cmsItemsFromJson.push(cmsValue);
}
});
// Update the CMS items in JSON after the checks
await updateCmsItemsInJson(cmsItemsFromJson);
} catch (error) {
console.error(`Error in hideSeenCmsItems function: ${error}`);
}
}
// Hide seen CMS items when the page loads
hideSeenCmsItems().catch(error => {
console.error(`Error in MemberScript #70 initial functions: ${error}`);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #70 v0.1 💙 HIDE OLD CMS ITEMS -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
// Only proceed if a member is found
const member = await memberstack.getCurrentMember();
if (!member) {
console.log('No member found in MemberScript #70, exiting script');
return;
}
async function getCmsItemsFromJson() {
try {
const memberData = await memberstack.getMemberJSON();
return memberData?.data?.cmsItems || [];
} catch (error) {
console.error(`Error in getCmsItemsFromJson function: ${error}`);
}
}
async function updateCmsItemsInJson(newCmsItems) {
try {
const memberData = await memberstack.getMemberJSON();
memberData.data = memberData.data || {};
memberData.data.cmsItems = newCmsItems;
console.log(`CMS items in JSON after update: ${JSON.stringify(newCmsItems)}`);
await memberstack.updateMemberJSON({ json: memberData.data });
} catch (error) {
console.error(`Error in updateCmsItemsInJson function: ${error}`);
}
}
async function hideSeenCmsItems() {
try {
const cmsItemsElements = document.querySelectorAll('[ms-code-cms-item]');
const cmsItemsFromJson = await getCmsItemsFromJson();
cmsItemsElements.forEach(element => {
const cmsValue = element.getAttribute('ms-code-cms-item');
if (cmsItemsFromJson.includes(cmsValue)) {
element.style.display = 'none';
} else {
cmsItemsFromJson.push(cmsValue);
}
});
// Update the CMS items in JSON after the checks
await updateCmsItemsInJson(cmsItemsFromJson);
} catch (error) {
console.error(`Error in hideSeenCmsItems function: ${error}`);
}
}
// Hide seen CMS items when the page loads
hideSeenCmsItems().catch(error => {
console.error(`Error in MemberScript #70 initial functions: ${error}`);
});
});
</script>

#69 - Notify Members of New CMS Items
Display an element when there are new CMS items.
<!-- 💙 MEMBERSCRIPT #69 v0.1 💙 DISPLAY ELEMENT IF NEW CMS ITEMS -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
// Set this variable to 'YES' or 'NO' depending on whether you want the UI to be displayed for new users
const displayForNewUsers = 'YES';
// Only proceed if a member is found
const member = await memberstack.getCurrentMember();
if (!member) {
console.log('No member found, exiting script');
return;
}
async function getUpdatesIDFromJson() {
try {
const memberData = await memberstack.getMemberJSON();
console.log(`Member data: ${JSON.stringify(memberData)}`);
return memberData?.data?.updatesID || '';
} catch (error) {
console.error(`Error in getUpdatesIDFromJson function: ${error}`);
}
}
async function updateUpdatesIDInJson(newUpdatesID) {
try {
const memberData = await memberstack.getMemberJSON();
memberData.data = memberData.data || {};
memberData.data.updatesID = newUpdatesID;
console.log(`Updates ID in JSON after update: ${newUpdatesID}`);
await memberstack.updateMemberJSON({ json: memberData.data });
} catch (error) {
console.error(`Error in updateUpdatesIDInJson function: ${error}`);
}
}
async function checkAndUpdateUI() {
try {
const element = document.querySelector('[ms-code-update-item]');
const cmsItem = element.textContent;
console.log(`CMS item: ${cmsItem}`);
// Get the current updates ID from JSON
const updatesIDFromJson = await getUpdatesIDFromJson();
console.log(`Updates ID from JSON: ${updatesIDFromJson}`);
// Check displayForNewUsers variable to decide behavior
if (displayForNewUsers === 'NO' && !updatesIDFromJson) {
console.log('Updates ID from JSON is undefined, null, or empty, not changing UI');
return;
}
if (cmsItem !== updatesIDFromJson) {
const uiElements = document.querySelectorAll('[ms-code-update-ui]');
uiElements.forEach(uiElement => {
uiElement.style.display = 'block';
uiElement.style.opacity = '1';
});
}
// Update the updates ID in JSON after the UI has been updated
await updateUpdatesIDInJson(cmsItem);
} catch (error) {
console.error(`Error in checkAndUpdateUI function: ${error}`);
}
}
// Check and update UI when the page loads
checkAndUpdateUI().catch(error => {
console.error(`Error in initial functions: ${error}`);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #69 v0.1 💙 DISPLAY ELEMENT IF NEW CMS ITEMS -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
// Set this variable to 'YES' or 'NO' depending on whether you want the UI to be displayed for new users
const displayForNewUsers = 'YES';
// Only proceed if a member is found
const member = await memberstack.getCurrentMember();
if (!member) {
console.log('No member found, exiting script');
return;
}
async function getUpdatesIDFromJson() {
try {
const memberData = await memberstack.getMemberJSON();
console.log(`Member data: ${JSON.stringify(memberData)}`);
return memberData?.data?.updatesID || '';
} catch (error) {
console.error(`Error in getUpdatesIDFromJson function: ${error}`);
}
}
async function updateUpdatesIDInJson(newUpdatesID) {
try {
const memberData = await memberstack.getMemberJSON();
memberData.data = memberData.data || {};
memberData.data.updatesID = newUpdatesID;
console.log(`Updates ID in JSON after update: ${newUpdatesID}`);
await memberstack.updateMemberJSON({ json: memberData.data });
} catch (error) {
console.error(`Error in updateUpdatesIDInJson function: ${error}`);
}
}
async function checkAndUpdateUI() {
try {
const element = document.querySelector('[ms-code-update-item]');
const cmsItem = element.textContent;
console.log(`CMS item: ${cmsItem}`);
// Get the current updates ID from JSON
const updatesIDFromJson = await getUpdatesIDFromJson();
console.log(`Updates ID from JSON: ${updatesIDFromJson}`);
// Check displayForNewUsers variable to decide behavior
if (displayForNewUsers === 'NO' && !updatesIDFromJson) {
console.log('Updates ID from JSON is undefined, null, or empty, not changing UI');
return;
}
if (cmsItem !== updatesIDFromJson) {
const uiElements = document.querySelectorAll('[ms-code-update-ui]');
uiElements.forEach(uiElement => {
uiElement.style.display = 'block';
uiElement.style.opacity = '1';
});
}
// Update the updates ID in JSON after the UI has been updated
await updateUpdatesIDInJson(cmsItem);
} catch (error) {
console.error(`Error in checkAndUpdateUI function: ${error}`);
}
}
// Check and update UI when the page loads
checkAndUpdateUI().catch(error => {
console.error(`Error in initial functions: ${error}`);
});
});
</script>

#68 - Gift a Membership
Enable members to purchase gifts for their friends and family.

#67 - Prefill Form Based On URL Parameters
Easily fill inputs using URL parameters.
<!-- 💙 MEMBERSCRIPT #67 v0.1 💙 PREFILL INPUTS WITH URL PARAMETERS -->
<script>
// Function to get URL parameters
function getURLParams() {
const urlParams = new URLSearchParams(window.location.search);
return Object.fromEntries(urlParams.entries());
}
// Function to prefill inputs based on URL parameters
function prefillInputs() {
const urlParams = getURLParams();
const inputElements = document.querySelectorAll('[ms-code-prefill-param]');
inputElements.forEach((inputElement) => {
const paramKey = inputElement.getAttribute('ms-code-prefill-param');
if (paramKey && urlParams[paramKey]) {
inputElement.value = urlParams[paramKey];
}
});
}
// Call the function to prefill inputs when the page loads
prefillInputs();
</script>
<!-- 💙 MEMBERSCRIPT #67 v0.1 💙 PREFILL INPUTS WITH URL PARAMETERS -->
<script>
// Function to get URL parameters
function getURLParams() {
const urlParams = new URLSearchParams(window.location.search);
return Object.fromEntries(urlParams.entries());
}
// Function to prefill inputs based on URL parameters
function prefillInputs() {
const urlParams = getURLParams();
const inputElements = document.querySelectorAll('[ms-code-prefill-param]');
inputElements.forEach((inputElement) => {
const paramKey = inputElement.getAttribute('ms-code-prefill-param');
if (paramKey && urlParams[paramKey]) {
inputElement.value = urlParams[paramKey];
}
});
}
// Call the function to prefill inputs when the page loads
prefillInputs();
</script>

#66 - Member ID Invite Links
Create custom, unique invite/referral links.
<!-- 💙 MEMBERSCRIPT #66 v0.1 💙 MEMBER ID INVITE LINKS -->
<script>
// Function to get the member ID from local storage
function getMemberIDFromLocalStorage() {
// Assuming "_ms-mem" is the key that holds the member object in local storage
const memberObject = JSON.parse(localStorage.getItem("_ms-mem"));
if (memberObject && memberObject.id) {
return memberObject.id;
}
return null;
}
// Function to update the invite link with the member ID as a URL parameter
function updateInviteLink() {
const inviteLinkElement = document.querySelector('[ms-code-invite-link]');
if (inviteLinkElement) {
const inviteLinkBase = inviteLinkElement.getAttribute('ms-code-invite-link');
const memberID = getMemberIDFromLocalStorage();
if (memberID) {
const inviteLinkWithID = `${inviteLinkBase}?inviteCode=${memberID}`;
inviteLinkElement.textContent = inviteLinkWithID;
inviteLinkElement.href = inviteLinkWithID; // If it's an anchor link
}
}
}
// Call the function to update the invite link when the page loads
updateInviteLink();
</script>
<!-- 💙 MEMBERSCRIPT #66 v0.1 💙 MEMBER ID INVITE LINKS -->
<script>
// Function to get the member ID from local storage
function getMemberIDFromLocalStorage() {
// Assuming "_ms-mem" is the key that holds the member object in local storage
const memberObject = JSON.parse(localStorage.getItem("_ms-mem"));
if (memberObject && memberObject.id) {
return memberObject.id;
}
return null;
}
// Function to update the invite link with the member ID as a URL parameter
function updateInviteLink() {
const inviteLinkElement = document.querySelector('[ms-code-invite-link]');
if (inviteLinkElement) {
const inviteLinkBase = inviteLinkElement.getAttribute('ms-code-invite-link');
const memberID = getMemberIDFromLocalStorage();
if (memberID) {
const inviteLinkWithID = `${inviteLinkBase}?inviteCode=${memberID}`;
inviteLinkElement.textContent = inviteLinkWithID;
inviteLinkElement.href = inviteLinkWithID; // If it's an anchor link
}
}
}
// Call the function to update the invite link when the page loads
updateInviteLink();
</script>

#65 - Exit Intent Popup
Show visitors a popup when their mouse leaves to the top.
<!-- 💙 MEMBERSCRIPT #65 v0.1 💙 EXIT INTENT POPUP -->
<script>
const CookieService = {
setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = days ? '; expires=' + date.toUTCString() : '';
document.cookie = name + '=' + (value || '') + expires + ';';
},
getCookie(name) {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith(name))
?.split('=')[1];
return cookieValue || null;
}
};
const exitPopup = document.querySelector('[ms-code-popup="exit-intent"]');
const mouseEvent = e => {
const shouldShowExitIntent =
!e.toElement &&
!e.relatedTarget &&
e.clientY < 10;
if (shouldShowExitIntent) {
document.removeEventListener('mouseout', mouseEvent);
exitPopup.style.display = 'flex';
CookieService.setCookie('exitIntentShown', true, 30);
}
};
if (!CookieService.getCookie('exitIntentShown')) {
document.addEventListener('mouseout', mouseEvent);
document.addEventListener('keydown', exit);
exitPopup.addEventListener('click', exit);
}
</script>
<!-- 💙 MEMBERSCRIPT #65 v0.1 💙 EXIT INTENT POPUP -->
<script>
const CookieService = {
setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = days ? '; expires=' + date.toUTCString() : '';
document.cookie = name + '=' + (value || '') + expires + ';';
},
getCookie(name) {
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith(name))
?.split('=')[1];
return cookieValue || null;
}
};
const exitPopup = document.querySelector('[ms-code-popup="exit-intent"]');
const mouseEvent = e => {
const shouldShowExitIntent =
!e.toElement &&
!e.relatedTarget &&
e.clientY < 10;
if (shouldShowExitIntent) {
document.removeEventListener('mouseout', mouseEvent);
exitPopup.style.display = 'flex';
CookieService.setCookie('exitIntentShown', true, 30);
}
};
if (!CookieService.getCookie('exitIntentShown')) {
document.addEventListener('mouseout', mouseEvent);
document.addEventListener('keydown', exit);
exitPopup.addEventListener('click', exit);
}
</script>

#64 - Radio Form Logic
Show set elements depending on which radio is selected.
<!-- 💙 MEMBERSCRIPT #64 v0.1 💙 RADIO FORM LOGIC -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
// initially hide all divs with 'ms-code-more-info' attribute
$("div[ms-code-more-info]").hide();
// listen for change events on all radios with 'ms-code-radio-option' attribute
$("input[ms-code-radio-option]").change(function() {
// hide all divs again
$("div[ms-code-more-info]").hide();
// get the value of the selected radio button
var selectedValue = $(this).attr("ms-code-radio-option");
// find the div with the 'ms-code-more-info' attribute that matches the selected value and show it
$("div[ms-code-more-info=" + selectedValue + "]").show();
});
});
</script>
<!-- 💙 MEMBERSCRIPT #64 v0.1 💙 RADIO FORM LOGIC -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
$(document).ready(function() {
// initially hide all divs with 'ms-code-more-info' attribute
$("div[ms-code-more-info]").hide();
// listen for change events on all radios with 'ms-code-radio-option' attribute
$("input[ms-code-radio-option]").change(function() {
// hide all divs again
$("div[ms-code-more-info]").hide();
// get the value of the selected radio button
var selectedValue = $(this).attr("ms-code-radio-option");
// find the div with the 'ms-code-more-info' attribute that matches the selected value and show it
$("div[ms-code-more-info=" + selectedValue + "]").show();
});
});
</script>

#63 - Date Range Picker
Create a date range input in Webflow!
<!-- 💙 MEMBERSCRIPT #62 v0.1 💙 DATE RANGE PICKER -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
<style>
.daterangepicker td.active {
background-color: #006cfa !important ;
}
</style>
<script type="text/javascript">
$(function() {
$('input[ms-code-input="date-range"]').daterangepicker({
"opens": "center",
"locale": {
"format": "MM/DD/YYYY",
"separator": " - ",
"applyLabel": "Apply",
"cancelLabel": "Cancel",
"fromLabel": "From",
"toLabel": "To",
"customRangeLabel": "Custom",
"weekLabel": "W",
"daysOfWeek": [
"Su",
"Mo",
"Tu",
"We",
"Th",
"Fr",
"Sa"
],
"monthNames": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
},
});
$('input[name="datefilter"]').on('apply.daterangepicker', function(ev, picker) {
$(this).val(picker.startDate.format('MM/DD/YYYY') + ' - ' + picker.endDate.format('MM/DD/YYYY'));
});
$('input[name="datefilter"]').on('cancel.daterangepicker', function(ev, picker) {
$(this).val('');
});
});
</script>
<!-- 💙 MEMBERSCRIPT #62 v0.1 💙 DATE RANGE PICKER -->
<script type="text/javascript" src="https://cdn.jsdelivr.net/jquery/latest/jquery.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/momentjs/latest/moment.min.js"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.min.js"></script>
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/daterangepicker/daterangepicker.css" />
<style>
.daterangepicker td.active {
background-color: #006cfa !important ;
}
</style>
<script type="text/javascript">
$(function() {
$('input[ms-code-input="date-range"]').daterangepicker({
"opens": "center",
"locale": {
"format": "MM/DD/YYYY",
"separator": " - ",
"applyLabel": "Apply",
"cancelLabel": "Cancel",
"fromLabel": "From",
"toLabel": "To",
"customRangeLabel": "Custom",
"weekLabel": "W",
"daysOfWeek": [
"Su",
"Mo",
"Tu",
"We",
"Th",
"Fr",
"Sa"
],
"monthNames": [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
},
});
$('input[name="datefilter"]').on('apply.daterangepicker', function(ev, picker) {
$(this).val(picker.startDate.format('MM/DD/YYYY') + ' - ' + picker.endDate.format('MM/DD/YYYY'));
});
$('input[name="datefilter"]').on('cancel.daterangepicker', function(ev, picker) {
$(this).val('');
});
});
</script>

#62 - Upvote Button
Add upvote functionality to the Webflow CMS.
<!-- 💙 MEMBERSCRIPT #62 v0.2 💙 UPVOTE FORM -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const memberstack = window.$memberstackDom;
const upvoteButtons = document.querySelectorAll('[ms-code="upvote-button"]');
const upvoteForms = document.querySelectorAll('[ms-code="upvote-form"]');
const upvotedValues = document.querySelectorAll('[ms-code="upvoted-value"]');
const upvoteCounts = document.querySelectorAll('[ms-code="upvote-count"]');
let clickTimeout; // Variable to store the timer
let lastClickedButton = null; // Variable to store the last clicked button
// Function to handle upvote button click
function handleUpvoteButtonClick(event) {
event.preventDefault();
const button = event.currentTarget;
// Clear the timer if the same button is clicked
if (button === lastClickedButton) {
clearTimeout(clickTimeout);
}
lastClickedButton = button; // Store the reference to the currently clicked button
// Set a new timer
clickTimeout = setTimeout(function() {
const form = button.closest('form');
const cmsId = button.getAttribute('data-cms-id');
const upvotedValue = form.querySelector('[ms-code="upvoted-value"]');
const upvoteCount = form.querySelector('[ms-code="upvote-count"]');
if (button.classList.contains('is-true')) {
// Remove upvote
button.classList.remove('is-true');
upvotedValue.value = 'false';
upvoteCount.textContent = parseInt(upvoteCount.textContent) - 1;
memberstack.getMemberJSON()
.then(function(memberData) {
if (memberData.data && memberData.data.upvotes) {
const upvotes = memberData.data.upvotes;
const index = upvotes.indexOf(cmsId);
if (index !== -1) {
upvotes.splice(index, 1);
memberstack.updateMemberJSON({ json: memberData.data });
}
}
})
.catch(function(error) {
console.error('Error retrieving/updating member data:', error);
});
} else {
// Add upvote
button.classList.add('is-true');
upvotedValue.value = 'true';
upvoteCount.textContent = parseInt(upvoteCount.textContent) + 1;
memberstack.getMemberJSON()
.then(function(memberData) {
memberData.data = memberData.data || {};
memberData.data.upvotes = memberData.data.upvotes || [];
memberData.data.upvotes.push(cmsId);
memberstack.updateMemberJSON({ json: memberData.data });
})
.catch(function(error) {
console.error('Error retrieving/updating member data:', error);
});
}
// Make the API call
fetch(form.action, {
method: form.method,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(new FormData(form))
})
.then(function(response) {
if (response.ok) {
// Handle successful API response
return response.json();
} else {
// Handle API error
throw new Error('API Error');
}
})
.then(function(data) {
// Handle API response to update vote count
upvoteCount.textContent = data.upvoteCount; // Replace with the actual property holding the updated vote count
})
.catch(function(error) {
console.error('API Error:', error);
});
}, 200); // 0.2 seconds
}
// Attach event listeners to upvote buttons
upvoteButtons.forEach(function(button) {
button.addEventListener('click', handleUpvoteButtonClick);
});
// Check if member has upvotes on page load
memberstack.getMemberJSON()
.then(function(memberData) {
if (memberData.data && memberData.data.upvotes) {
const upvotes = memberData.data.upvotes;
upvoteButtons.forEach(function(button) {
const cmsId = button.getAttribute('data-cms-id');
if (upvotes.includes(cmsId)) {
button.classList.add('is-true');
const form = button.closest('form');
const upvotedValue = form.querySelector('[ms-code="upvoted-value"]');
upvotedValue.value = 'true';
}
});
}
})
.catch(function(error) {
console.error('Error retrieving member data:', error);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #62 v0.2 💙 UPVOTE FORM -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const memberstack = window.$memberstackDom;
const upvoteButtons = document.querySelectorAll('[ms-code="upvote-button"]');
const upvoteForms = document.querySelectorAll('[ms-code="upvote-form"]');
const upvotedValues = document.querySelectorAll('[ms-code="upvoted-value"]');
const upvoteCounts = document.querySelectorAll('[ms-code="upvote-count"]');
let clickTimeout; // Variable to store the timer
let lastClickedButton = null; // Variable to store the last clicked button
// Function to handle upvote button click
function handleUpvoteButtonClick(event) {
event.preventDefault();
const button = event.currentTarget;
// Clear the timer if the same button is clicked
if (button === lastClickedButton) {
clearTimeout(clickTimeout);
}
lastClickedButton = button; // Store the reference to the currently clicked button
// Set a new timer
clickTimeout = setTimeout(function() {
const form = button.closest('form');
const cmsId = button.getAttribute('data-cms-id');
const upvotedValue = form.querySelector('[ms-code="upvoted-value"]');
const upvoteCount = form.querySelector('[ms-code="upvote-count"]');
if (button.classList.contains('is-true')) {
// Remove upvote
button.classList.remove('is-true');
upvotedValue.value = 'false';
upvoteCount.textContent = parseInt(upvoteCount.textContent) - 1;
memberstack.getMemberJSON()
.then(function(memberData) {
if (memberData.data && memberData.data.upvotes) {
const upvotes = memberData.data.upvotes;
const index = upvotes.indexOf(cmsId);
if (index !== -1) {
upvotes.splice(index, 1);
memberstack.updateMemberJSON({ json: memberData.data });
}
}
})
.catch(function(error) {
console.error('Error retrieving/updating member data:', error);
});
} else {
// Add upvote
button.classList.add('is-true');
upvotedValue.value = 'true';
upvoteCount.textContent = parseInt(upvoteCount.textContent) + 1;
memberstack.getMemberJSON()
.then(function(memberData) {
memberData.data = memberData.data || {};
memberData.data.upvotes = memberData.data.upvotes || [];
memberData.data.upvotes.push(cmsId);
memberstack.updateMemberJSON({ json: memberData.data });
})
.catch(function(error) {
console.error('Error retrieving/updating member data:', error);
});
}
// Make the API call
fetch(form.action, {
method: form.method,
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: new URLSearchParams(new FormData(form))
})
.then(function(response) {
if (response.ok) {
// Handle successful API response
return response.json();
} else {
// Handle API error
throw new Error('API Error');
}
})
.then(function(data) {
// Handle API response to update vote count
upvoteCount.textContent = data.upvoteCount; // Replace with the actual property holding the updated vote count
})
.catch(function(error) {
console.error('API Error:', error);
});
}, 200); // 0.2 seconds
}
// Attach event listeners to upvote buttons
upvoteButtons.forEach(function(button) {
button.addEventListener('click', handleUpvoteButtonClick);
});
// Check if member has upvotes on page load
memberstack.getMemberJSON()
.then(function(memberData) {
if (memberData.data && memberData.data.upvotes) {
const upvotes = memberData.data.upvotes;
upvoteButtons.forEach(function(button) {
const cmsId = button.getAttribute('data-cms-id');
if (upvotes.includes(cmsId)) {
button.classList.add('is-true');
const form = button.closest('form');
const upvotedValue = form.querySelector('[ms-code="upvoted-value"]');
upvotedValue.value = 'true';
}
});
}
})
.catch(function(error) {
console.error('Error retrieving member data:', error);
});
});
</script>

#61 - Display Element If Checkbox Is Checked
Create conditional visibility based on a checkbox field.
<!-- 💙 MEMBERSCRIPT #61 v0.1 💙 SHOW ELEMENT IF CHECKBOX IS CHECKED -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script>
$(document).ready(function() {
// Initially hide all elements with the 'ms-code-checkbox-display' attribute
$("[ms-code-checkbox-display]").hide();
// When a checkbox with 'ms-code-checkbox-input' attribute is clicked, perform the following
$("[ms-code-checkbox-input]").click(function() {
// Get the value of the 'ms-code-checkbox-input' attribute
var checkboxVal = $(this).attr('ms-code-checkbox-input');
// Find the corresponding element with the 'ms-code-checkbox-display' attribute and same value
var displayElement = $("[ms-code-checkbox-display=" + checkboxVal + "]");
// If this checkbox is checked, show the corresponding element
if ($(this).is(":checked")) {
displayElement.show();
} else {
// If this checkbox is unchecked, hide the corresponding element
displayElement.hide();
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #61 v0.1 💙 SHOW ELEMENT IF CHECKBOX IS CHECKED -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"> </script>
<script>
$(document).ready(function() {
// Initially hide all elements with the 'ms-code-checkbox-display' attribute
$("[ms-code-checkbox-display]").hide();
// When a checkbox with 'ms-code-checkbox-input' attribute is clicked, perform the following
$("[ms-code-checkbox-input]").click(function() {
// Get the value of the 'ms-code-checkbox-input' attribute
var checkboxVal = $(this).attr('ms-code-checkbox-input');
// Find the corresponding element with the 'ms-code-checkbox-display' attribute and same value
var displayElement = $("[ms-code-checkbox-display=" + checkboxVal + "]");
// If this checkbox is checked, show the corresponding element
if ($(this).is(":checked")) {
displayElement.show();
} else {
// If this checkbox is unchecked, hide the corresponding element
displayElement.hide();
}
});
});
</script>

#60 - Increase/Decrease Select Value
Create previous & next buttons for a select field.
<!-- 💙 MEMBERSCRIPT #60 v0.1 💙 INCREASE/DECREASE SELECT VALUE -->
<script>
var select = document.querySelector('[ms-code-select="input"]');
var prev = document.querySelector('[ms-code-select="prev"]');
var next = document.querySelector('[ms-code-select="next"]');
function updateButtons() {
prev.style.opacity = select.selectedIndex === 0 ? '0.5' : '1';
next.style.opacity = select.selectedIndex === select.options.length - 1 ? '0.5' : '1';
}
prev.addEventListener('click', function() {
if (select.selectedIndex > 0) {
select.selectedIndex--;
}
updateButtons();
});
next.addEventListener('click', function() {
if (select.selectedIndex < select.options.length - 1) {
select.selectedIndex++;
}
updateButtons();
});
updateButtons();
</script>
<!-- 💙 MEMBERSCRIPT #60 v0.1 💙 INCREASE/DECREASE SELECT VALUE -->
<script>
var select = document.querySelector('[ms-code-select="input"]');
var prev = document.querySelector('[ms-code-select="prev"]');
var next = document.querySelector('[ms-code-select="next"]');
function updateButtons() {
prev.style.opacity = select.selectedIndex === 0 ? '0.5' : '1';
next.style.opacity = select.selectedIndex === select.options.length - 1 ? '0.5' : '1';
}
prev.addEventListener('click', function() {
if (select.selectedIndex > 0) {
select.selectedIndex--;
}
updateButtons();
});
next.addEventListener('click', function() {
if (select.selectedIndex < select.options.length - 1) {
select.selectedIndex++;
}
updateButtons();
});
updateButtons();
</script>

#59 - Restart GIF On Hover
Start a GIF from the beginning on hover.
<!-- 💙 MEMBERSCRIPT #59 v0.1 💙 RESTART GIF -->
<script>
document.addEventListener('DOMContentLoaded', (event) => {
const hoverElements = document.querySelectorAll('[data-gif-hover]');
hoverElements.forEach((element) => {
element.addEventListener('mouseover', function() {
const gifNum = this.getAttribute('data-gif-hover');
const gifElement = document.querySelector(`[data-gif="${gifNum}"]`);
if (gifElement) {
const gifSrc = gifElement.getAttribute('src');
gifElement.setAttribute('src', '');
gifElement.setAttribute('src', gifSrc);
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #59 v0.1 💙 RESTART GIF -->
<script>
document.addEventListener('DOMContentLoaded', (event) => {
const hoverElements = document.querySelectorAll('[data-gif-hover]');
hoverElements.forEach((element) => {
element.addEventListener('mouseover', function() {
const gifNum = this.getAttribute('data-gif-hover');
const gifElement = document.querySelector(`[data-gif="${gifNum}"]`);
if (gifElement) {
const gifSrc = gifElement.getAttribute('src');
gifElement.setAttribute('src', '');
gifElement.setAttribute('src', gifSrc);
}
});
});
});
</script>
MemberScripts
Instantly add custom features to your Webflow site.
Just paste a script, set attributes, and go live.
Join the Memberstack 2.0 Slack for tips, answers, and community scripts. Please note that these are not official features and support cannot be guaranteed.

#118 - Save Member's Last IP Address
Update a custom field with the most recent IP address your members log in from.
<!-- 💙 MEMBERSCRIPT #118 v0.1 💙 - SAVE LAST IP ADDRESS -->
<script>
const memberstack = window.$memberstackDom;
memberstack.getCurrentMember().then(async (response) => {
if (response && response.data) {
const member = response.data;
try {
// Fetch the current IP address from the ipify API
const ipResponse = await fetch('https://api.ipify.org?format=json');
const ipData = await ipResponse.json();
const currentIpAddress = ipData.ip;
// Retrieve the stored IP address from Memberstack's custom fields
const storedIpAddress = member.customFields["last-ip"];
// Check if the IP address has changed and update if necessary
if (currentIpAddress !== storedIpAddress) {
await memberstack.updateMember({
customFields: {
"last-ip": currentIpAddress
}
});
// Optional: Uncomment the line below to log a message when the IP is updated
// console.log('IP address updated');
} else {
// Optional: Uncomment the line below to log when the IP remains unchanged
// console.log('IP address unchanged, no update needed');
}
} catch (error) {
// Log any errors encountered during the fetch or update process
console.error('Error checking or updating member IP:', error);
}
} else {
// Optional: Uncomment the line below to log when no member is logged in
// console.log('No member is currently logged in');
}
});
</script>
<!-- 💙 MEMBERSCRIPT #118 v0.1 💙 - SAVE LAST IP ADDRESS -->
<script>
const memberstack = window.$memberstackDom;
memberstack.getCurrentMember().then(async (response) => {
if (response && response.data) {
const member = response.data;
try {
// Fetch the current IP address from the ipify API
const ipResponse = await fetch('https://api.ipify.org?format=json');
const ipData = await ipResponse.json();
const currentIpAddress = ipData.ip;
// Retrieve the stored IP address from Memberstack's custom fields
const storedIpAddress = member.customFields["last-ip"];
// Check if the IP address has changed and update if necessary
if (currentIpAddress !== storedIpAddress) {
await memberstack.updateMember({
customFields: {
"last-ip": currentIpAddress
}
});
// Optional: Uncomment the line below to log a message when the IP is updated
// console.log('IP address updated');
} else {
// Optional: Uncomment the line below to log when the IP remains unchanged
// console.log('IP address unchanged, no update needed');
}
} catch (error) {
// Log any errors encountered during the fetch or update process
console.error('Error checking or updating member IP:', error);
}
} else {
// Optional: Uncomment the line below to log when no member is logged in
// console.log('No member is currently logged in');
}
});
</script>

#117 - Page Scroll Progress Bar
A flexible & custom page scroll indicator to display page scroll progress.
<!-- 💙 MEMBERSCRIPT #117 v0.1 💙 - PAGE SCROLL PROGRESS BAR -->
<script>
// Function to update the progress bar
function updateProgressBar() {
const container = document.querySelector('[ms-code-ps="container"]');
const bar = document.querySelector('[ms-code-ps="bar"]');
const startElement = document.querySelector('[ms-code-ps="start"]');
const endElement = document.querySelector('[ms-code-ps="end"]');
if (!container || !bar) return;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
let startPosition = 0;
let endPosition = documentHeight - windowHeight;
if (startElement) {
const startRect = startElement.getBoundingClientRect();
startPosition = scrollTop + startRect.top - windowHeight;
}
if (endElement) {
const endRect = endElement.getBoundingClientRect();
endPosition = scrollTop + endRect.top - windowHeight;
}
const scrollRange = endPosition - startPosition;
const scrollProgress = scrollTop - startPosition;
const scrollPercentage = Math.max(0, Math.min(100, (scrollProgress / scrollRange) * 100));
// Use requestAnimationFrame for smooth animation
requestAnimationFrame(() => {
bar.style.width = `${scrollPercentage}%`;
bar.style.transition = 'width 0.1s linear';
});
}
// Throttle function to limit how often updateProgressBar is called
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Add scroll event listener with throttling
window.addEventListener('scroll', throttle(updateProgressBar, 10));
// Initial call to set the correct width on page load
updateProgressBar();
</script>
<!-- 💙 MEMBERSCRIPT #117 v0.1 💙 - PAGE SCROLL PROGRESS BAR -->
<script>
// Function to update the progress bar
function updateProgressBar() {
const container = document.querySelector('[ms-code-ps="container"]');
const bar = document.querySelector('[ms-code-ps="bar"]');
const startElement = document.querySelector('[ms-code-ps="start"]');
const endElement = document.querySelector('[ms-code-ps="end"]');
if (!container || !bar) return;
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
let startPosition = 0;
let endPosition = documentHeight - windowHeight;
if (startElement) {
const startRect = startElement.getBoundingClientRect();
startPosition = scrollTop + startRect.top - windowHeight;
}
if (endElement) {
const endRect = endElement.getBoundingClientRect();
endPosition = scrollTop + endRect.top - windowHeight;
}
const scrollRange = endPosition - startPosition;
const scrollProgress = scrollTop - startPosition;
const scrollPercentage = Math.max(0, Math.min(100, (scrollProgress / scrollRange) * 100));
// Use requestAnimationFrame for smooth animation
requestAnimationFrame(() => {
bar.style.width = `${scrollPercentage}%`;
bar.style.transition = 'width 0.1s linear';
});
}
// Throttle function to limit how often updateProgressBar is called
function throttle(func, limit) {
let inThrottle;
return function() {
const args = arguments;
const context = this;
if (!inThrottle) {
func.apply(context, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Add scroll event listener with throttling
window.addEventListener('scroll', throttle(updateProgressBar, 10));
// Initial call to set the correct width on page load
updateProgressBar();
</script>

#116 - Share Highlighted Text Links
Allow users to highlight text and share the link with others!
<!-- 💙 MEMBERSCRIPT #116 v0.1 💙 - SHARE HIGHLIGHTED TEXT LINKS -->
<script>
// Function to encode text and position for URL
function encodeSelection(text, nodeIndex, textOffset) {
return btoa(encodeURIComponent(JSON.stringify({ text, nodeIndex, textOffset })));
}
// Function to decode selection from URL
function decodeSelection(encoded) {
try {
return JSON.parse(decodeURIComponent(atob(encoded)));
} catch (e) {
// If parsing fails, assume it's just the text in the old format
return { text: decodeURIComponent(atob(encoded)) };
}
}
// Function to remove existing highlight
function removeExistingHighlight() {
const existingHighlight = document.querySelector('.ms-highlight');
if (existingHighlight) {
const parent = existingHighlight.parentNode;
parent.replaceChild(document.createTextNode(existingHighlight.textContent), existingHighlight);
parent.normalize(); // Merge adjacent text nodes
}
}
// Function to handle text selection
function handleSelection() {
const selection = window.getSelection();
if (selection.toString().length > 0) {
removeExistingHighlight();
const range = selection.getRangeAt(0);
const selectedText = selection.toString();
const textNodes = getAllTextNodes(document.body);
const nodeIndex = textNodes.indexOf(range.startContainer);
const textOffset = range.startOffset;
// Create a unique identifier for the selection
const selectionId = encodeSelection(selectedText, nodeIndex, textOffset);
// Update URL with the selection parameter
const url = new URL(window.location);
url.searchParams.set('highlight', selectionId);
window.history.pushState({}, '', url);
// Highlight the selected text
highlightText(selectionId, range);
}
}
// Function to highlight text
function highlightText(selectionId, range) {
const span = document.createElement('span');
span.className = 'ms-highlight';
span.id = selectionId;
range.surroundContents(span);
}
// Function to highlight and scroll to text based on URL parameter
function highlightFromURL() {
removeExistingHighlight();
const url = new URL(window.location);
const highlightId = url.searchParams.get('highlight');
if (highlightId) {
const { text, nodeIndex, textOffset } = decodeSelection(highlightId);
const textNodes = getAllTextNodes(document.body);
if (nodeIndex !== undefined && textOffset !== undefined) {
// Use precise location if available
if (nodeIndex < textNodes.length) {
const node = textNodes[nodeIndex];
if (node.textContent.substr(textOffset, text.length) === text) {
const range = document.createRange();
range.setStart(node, textOffset);
range.setEnd(node, textOffset + text.length);
highlightText(highlightId, range);
}
}
} else {
// Fall back to searching for the first occurrence of the text
for (let node of textNodes) {
const index = node.textContent.indexOf(text);
if (index !== -1) {
const range = document.createRange();
range.setStart(node, index);
range.setEnd(node, index + text.length);
highlightText(highlightId, range);
break;
}
}
}
const highlightedSpan = document.getElementById(highlightId);
if (highlightedSpan) {
highlightedSpan.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
// Helper function to get all text nodes
function getAllTextNodes(element) {
const textNodes = [];
const walk = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
let node;
while (node = walk.nextNode()) {
textNodes.push(node);
}
return textNodes;
}
// Add event listener for text selection
document.addEventListener('mouseup', handleSelection);
// Call highlightFromURL when the page loads
window.addEventListener('load', highlightFromURL);
</script>
<!-- 💙 MEMBERSCRIPT #116 v0.1 💙 - SHARE HIGHLIGHTED TEXT LINKS -->
<script>
// Function to encode text and position for URL
function encodeSelection(text, nodeIndex, textOffset) {
return btoa(encodeURIComponent(JSON.stringify({ text, nodeIndex, textOffset })));
}
// Function to decode selection from URL
function decodeSelection(encoded) {
try {
return JSON.parse(decodeURIComponent(atob(encoded)));
} catch (e) {
// If parsing fails, assume it's just the text in the old format
return { text: decodeURIComponent(atob(encoded)) };
}
}
// Function to remove existing highlight
function removeExistingHighlight() {
const existingHighlight = document.querySelector('.ms-highlight');
if (existingHighlight) {
const parent = existingHighlight.parentNode;
parent.replaceChild(document.createTextNode(existingHighlight.textContent), existingHighlight);
parent.normalize(); // Merge adjacent text nodes
}
}
// Function to handle text selection
function handleSelection() {
const selection = window.getSelection();
if (selection.toString().length > 0) {
removeExistingHighlight();
const range = selection.getRangeAt(0);
const selectedText = selection.toString();
const textNodes = getAllTextNodes(document.body);
const nodeIndex = textNodes.indexOf(range.startContainer);
const textOffset = range.startOffset;
// Create a unique identifier for the selection
const selectionId = encodeSelection(selectedText, nodeIndex, textOffset);
// Update URL with the selection parameter
const url = new URL(window.location);
url.searchParams.set('highlight', selectionId);
window.history.pushState({}, '', url);
// Highlight the selected text
highlightText(selectionId, range);
}
}
// Function to highlight text
function highlightText(selectionId, range) {
const span = document.createElement('span');
span.className = 'ms-highlight';
span.id = selectionId;
range.surroundContents(span);
}
// Function to highlight and scroll to text based on URL parameter
function highlightFromURL() {
removeExistingHighlight();
const url = new URL(window.location);
const highlightId = url.searchParams.get('highlight');
if (highlightId) {
const { text, nodeIndex, textOffset } = decodeSelection(highlightId);
const textNodes = getAllTextNodes(document.body);
if (nodeIndex !== undefined && textOffset !== undefined) {
// Use precise location if available
if (nodeIndex < textNodes.length) {
const node = textNodes[nodeIndex];
if (node.textContent.substr(textOffset, text.length) === text) {
const range = document.createRange();
range.setStart(node, textOffset);
range.setEnd(node, textOffset + text.length);
highlightText(highlightId, range);
}
}
} else {
// Fall back to searching for the first occurrence of the text
for (let node of textNodes) {
const index = node.textContent.indexOf(text);
if (index !== -1) {
const range = document.createRange();
range.setStart(node, index);
range.setEnd(node, index + text.length);
highlightText(highlightId, range);
break;
}
}
}
const highlightedSpan = document.getElementById(highlightId);
if (highlightedSpan) {
highlightedSpan.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
// Helper function to get all text nodes
function getAllTextNodes(element) {
const textNodes = [];
const walk = document.createTreeWalker(element, NodeFilter.SHOW_TEXT, null, false);
let node;
while (node = walk.nextNode()) {
textNodes.push(node);
}
return textNodes;
}
// Add event listener for text selection
document.addEventListener('mouseup', handleSelection);
// Call highlightFromURL when the page loads
window.addEventListener('load', highlightFromURL);
</script>

#115 - Generate a Random Password
Zero friction sign up. Require or allow members to set a password in the future.
<!-- 💙 MEMBERSCRIPT #115 v0.1 💙 - GENERATE PASSWORD-->
<script>
document.addEventListener('DOMContentLoaded', function() {
var passwordInput = document.querySelector('[data-ms-member="password"]');
if (passwordInput) {
// Function to generate random password
function generatePassword() {
var timestamp = Date.now().toString(36);
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+{}[]|:;<>,.?/~';
var randomChars = '';
for (var i = 0; i < 16; i++) {
randomChars += characters.charAt(Math.floor(Math.random() * characters.length));
}
return (timestamp + randomChars).slice(0, 32);
}
// Generate and set password
passwordInput.value = generatePassword();
// Block password managers and prevent editing
passwordInput.setAttribute('autocomplete', 'off');
passwordInput.setAttribute('readonly', 'readonly');
// Prevent copy and paste
passwordInput.addEventListener('copy', function(e) {
e.preventDefault();
});
passwordInput.addEventListener('paste', function(e) {
e.preventDefault();
});
// Prevent dragging
passwordInput.addEventListener('dragstart', function(e) {
e.preventDefault();
});
// Prevent context menu
passwordInput.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #115 v0.1 💙 - GENERATE PASSWORD-->
<script>
document.addEventListener('DOMContentLoaded', function() {
var passwordInput = document.querySelector('[data-ms-member="password"]');
if (passwordInput) {
// Function to generate random password
function generatePassword() {
var timestamp = Date.now().toString(36);
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+{}[]|:;<>,.?/~';
var randomChars = '';
for (var i = 0; i < 16; i++) {
randomChars += characters.charAt(Math.floor(Math.random() * characters.length));
}
return (timestamp + randomChars).slice(0, 32);
}
// Generate and set password
passwordInput.value = generatePassword();
// Block password managers and prevent editing
passwordInput.setAttribute('autocomplete', 'off');
passwordInput.setAttribute('readonly', 'readonly');
// Prevent copy and paste
passwordInput.addEventListener('copy', function(e) {
e.preventDefault();
});
passwordInput.addEventListener('paste', function(e) {
e.preventDefault();
});
// Prevent dragging
passwordInput.addEventListener('dragstart', function(e) {
e.preventDefault();
});
// Prevent context menu
passwordInput.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
}
});
</script>

#114 - Scroll To Top Button
Add a button which will scroll to the top of the page when clicked,
<!-- 💙 MEMBERSCRIPT #114 v0.1 💙 - SCROLL TO TOP BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var scrollTopButton = document.querySelector('[ms-code-scroll-top="button"]');
if (scrollTopButton) {
// Set initial styles
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
scrollTopButton.style.transition = 'opacity 0.3s, visibility 0.3s';
// Function to check scroll position and toggle button visibility
function toggleButtonVisibility() {
if (window.pageYOffset > 300) {
scrollTopButton.style.opacity = '1';
scrollTopButton.style.visibility = 'visible';
} else {
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
}
}
// Initial check on page load
toggleButtonVisibility();
// Check on scroll
window.addEventListener('scroll', toggleButtonVisibility);
// Scroll to top when button is clicked
scrollTopButton.addEventListener('click', function() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #114 v0.1 💙 - SCROLL TO TOP BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var scrollTopButton = document.querySelector('[ms-code-scroll-top="button"]');
if (scrollTopButton) {
// Set initial styles
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
scrollTopButton.style.transition = 'opacity 0.3s, visibility 0.3s';
// Function to check scroll position and toggle button visibility
function toggleButtonVisibility() {
if (window.pageYOffset > 300) {
scrollTopButton.style.opacity = '1';
scrollTopButton.style.visibility = 'visible';
} else {
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
}
}
// Initial check on page load
toggleButtonVisibility();
// Check on scroll
window.addEventListener('scroll', toggleButtonVisibility);
// Scroll to top when button is clicked
scrollTopButton.addEventListener('click', function() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
});
</script>

#113 - RSS Feeds
Use a Webflow UI to display an RSS feed directly on your Website.
<!-- 💙 MEMBERSCRIPT #113 v0.2 💙 - RSS FEEDS IN WEBFLOW -->
<script>
(function() {
// console.log('RSS Feed Script starting...');
const CORS_PROXIES = [
'https://corsproxy.io/?',
'https://api.allorigins.win/raw?url=',
'https://cors-anywhere.herokuapp.com/',
'https://thingproxy.freeboard.io/fetch/',
'https://yacdn.org/proxy/'
];
function loadScript(src, onLoad, onError) {
const script = document.createElement('script');
script.src = src;
script.onload = onLoad;
script.onerror = onError;
document.head.appendChild(script);
}
async function fetchWithFallback(url) {
for (const proxy of CORS_PROXIES) {
try {
const response = await fetch(proxy + encodeURIComponent(url));
if (response.ok) {
return await response.text();
}
} catch (error) {
console.warn(`Failed to fetch with proxy ${proxy}:`, error);
}
}
throw new Error('All CORS proxies failed');
}
function initRSSFeed() {
if (typeof RSSParser === 'undefined') {
console.error('RSSParser is not defined.');
return;
}
const parser = new RSSParser({
customFields: {
item: [
['media:content', 'mediaContent', {keepArray: true}],
['media:thumbnail', 'mediaThumbnail', {keepArray: true}],
['enclosure', 'enclosure', {keepArray: true}],
]
}
});
document.querySelectorAll('[ms-code-rss-feed]').forEach(element => {
const url = element.getAttribute('ms-code-rss-url');
const limit = parseInt(element.getAttribute('ms-code-rss-limit')) || 5;
fetchWithFallback(url)
.then(str => parser.parseString(str))
.then(feed => {
renderRSSItems(element, feed.items.slice(0, limit), {
showImage: element.getAttribute('ms-code-rss-show-image') !== 'false',
showDate: element.getAttribute('ms-code-rss-show-date') !== 'false',
dateFormat: element.getAttribute('ms-code-rss-date-format') || 'short',
target: element.getAttribute('ms-code-rss-target') || '_self'
});
})
.catch(err => {
console.error('Error fetching or parsing RSS feed:', err);
element.textContent = `Failed to load RSS feed from ${url}. Error: ${err.message}`;
});
});
}
function renderRSSItems(element, items, options) {
const templateItem = element.querySelector('[ms-code-rss-item]');
if (!templateItem) return;
element.innerHTML = ''; // Clear existing items
items.forEach(item => {
const itemElement = templateItem.cloneNode(true);
const title = itemElement.querySelector('[ms-code-rss-title]');
if (title) {
const titleLength = parseInt(title.getAttribute('ms-code-rss-title-length')) || Infinity;
title.textContent = truncate(item.title, titleLength);
}
const description = itemElement.querySelector('[ms-code-rss-description]');
if (description) {
const descriptionLength = parseInt(description.getAttribute('ms-code-rss-description-length')) || Infinity;
description.textContent = truncate(stripHtml(item.content || item.description), descriptionLength);
}
const date = itemElement.querySelector('[ms-code-rss-date]');
if (date && options.showDate && item.pubDate) {
date.textContent = formatDate(new Date(item.pubDate), options.dateFormat);
}
const img = itemElement.querySelector('[ms-code-rss-image]');
if (img && options.showImage) {
const imgUrl = getImageUrl(item);
if (imgUrl) {
img.src = imgUrl;
img.alt = item.title;
img.removeAttribute('srcset');
}
}
const linkElement = itemElement.querySelector('[ms-code-rss-link]');
if (linkElement) {
linkElement.setAttribute('href', item.link);
linkElement.setAttribute('target', options.target);
}
element.appendChild(itemElement);
});
}
function getImageUrl(item) {
const sources = ['mediaContent', 'mediaThumbnail', 'enclosure'];
for (let source of sources) {
if (item[source] && item[source][0]) {
return item[source][0].$ ? item[source][0].$.url : item[source][0].url;
}
}
return null;
}
function truncate(str, length) {
if (!str) return '';
if (length === Infinity) return str;
return str.length > length ? str.slice(0, length) + '...' : str;
}
function stripHtml(html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html || '';
return tmp.textContent || tmp.innerText || '';
}
function formatDate(date, format) {
if (!(date instanceof Date) || isNaN(date)) return '';
const options = format === 'long' ?
{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } :
undefined;
return format === 'relative' ? getRelativeTimeString(date) : date.toLocaleDateString(undefined, options);
}
function getRelativeTimeString(date, lang = navigator.language) {
const timeMs = date.getTime();
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
const units = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
}
loadScript('https://cdn.jsdelivr.net/npm/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
console.error('Error loading RSS Parser script');
loadScript('https://unpkg.com/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
console.error('Error loading RSS Parser script from backup CDN');
});
});
})();
</script>
<!-- 💙 MEMBERSCRIPT #113 v0.2 💙 - RSS FEEDS IN WEBFLOW -->
<script>
(function() {
// console.log('RSS Feed Script starting...');
const CORS_PROXIES = [
'https://corsproxy.io/?',
'https://api.allorigins.win/raw?url=',
'https://cors-anywhere.herokuapp.com/',
'https://thingproxy.freeboard.io/fetch/',
'https://yacdn.org/proxy/'
];
function loadScript(src, onLoad, onError) {
const script = document.createElement('script');
script.src = src;
script.onload = onLoad;
script.onerror = onError;
document.head.appendChild(script);
}
async function fetchWithFallback(url) {
for (const proxy of CORS_PROXIES) {
try {
const response = await fetch(proxy + encodeURIComponent(url));
if (response.ok) {
return await response.text();
}
} catch (error) {
console.warn(`Failed to fetch with proxy ${proxy}:`, error);
}
}
throw new Error('All CORS proxies failed');
}
function initRSSFeed() {
if (typeof RSSParser === 'undefined') {
console.error('RSSParser is not defined.');
return;
}
const parser = new RSSParser({
customFields: {
item: [
['media:content', 'mediaContent', {keepArray: true}],
['media:thumbnail', 'mediaThumbnail', {keepArray: true}],
['enclosure', 'enclosure', {keepArray: true}],
]
}
});
document.querySelectorAll('[ms-code-rss-feed]').forEach(element => {
const url = element.getAttribute('ms-code-rss-url');
const limit = parseInt(element.getAttribute('ms-code-rss-limit')) || 5;
fetchWithFallback(url)
.then(str => parser.parseString(str))
.then(feed => {
renderRSSItems(element, feed.items.slice(0, limit), {
showImage: element.getAttribute('ms-code-rss-show-image') !== 'false',
showDate: element.getAttribute('ms-code-rss-show-date') !== 'false',
dateFormat: element.getAttribute('ms-code-rss-date-format') || 'short',
target: element.getAttribute('ms-code-rss-target') || '_self'
});
})
.catch(err => {
console.error('Error fetching or parsing RSS feed:', err);
element.textContent = `Failed to load RSS feed from ${url}. Error: ${err.message}`;
});
});
}
function renderRSSItems(element, items, options) {
const templateItem = element.querySelector('[ms-code-rss-item]');
if (!templateItem) return;
element.innerHTML = ''; // Clear existing items
items.forEach(item => {
const itemElement = templateItem.cloneNode(true);
const title = itemElement.querySelector('[ms-code-rss-title]');
if (title) {
const titleLength = parseInt(title.getAttribute('ms-code-rss-title-length')) || Infinity;
title.textContent = truncate(item.title, titleLength);
}
const description = itemElement.querySelector('[ms-code-rss-description]');
if (description) {
const descriptionLength = parseInt(description.getAttribute('ms-code-rss-description-length')) || Infinity;
description.textContent = truncate(stripHtml(item.content || item.description), descriptionLength);
}
const date = itemElement.querySelector('[ms-code-rss-date]');
if (date && options.showDate && item.pubDate) {
date.textContent = formatDate(new Date(item.pubDate), options.dateFormat);
}
const img = itemElement.querySelector('[ms-code-rss-image]');
if (img && options.showImage) {
const imgUrl = getImageUrl(item);
if (imgUrl) {
img.src = imgUrl;
img.alt = item.title;
img.removeAttribute('srcset');
}
}
const linkElement = itemElement.querySelector('[ms-code-rss-link]');
if (linkElement) {
linkElement.setAttribute('href', item.link);
linkElement.setAttribute('target', options.target);
}
element.appendChild(itemElement);
});
}
function getImageUrl(item) {
const sources = ['mediaContent', 'mediaThumbnail', 'enclosure'];
for (let source of sources) {
if (item[source] && item[source][0]) {
return item[source][0].$ ? item[source][0].$.url : item[source][0].url;
}
}
return null;
}
function truncate(str, length) {
if (!str) return '';
if (length === Infinity) return str;
return str.length > length ? str.slice(0, length) + '...' : str;
}
function stripHtml(html) {
const tmp = document.createElement('DIV');
tmp.innerHTML = html || '';
return tmp.textContent || tmp.innerText || '';
}
function formatDate(date, format) {
if (!(date instanceof Date) || isNaN(date)) return '';
const options = format === 'long' ?
{ weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' } :
undefined;
return format === 'relative' ? getRelativeTimeString(date) : date.toLocaleDateString(undefined, options);
}
function getRelativeTimeString(date, lang = navigator.language) {
const timeMs = date.getTime();
const deltaSeconds = Math.round((timeMs - Date.now()) / 1000);
const cutoffs = [60, 3600, 86400, 86400 * 7, 86400 * 30, 86400 * 365, Infinity];
const units = ['second', 'minute', 'hour', 'day', 'week', 'month', 'year'];
const unitIndex = cutoffs.findIndex(cutoff => cutoff > Math.abs(deltaSeconds));
const divisor = unitIndex ? cutoffs[unitIndex - 1] : 1;
const rtf = new Intl.RelativeTimeFormat(lang, { numeric: 'auto' });
return rtf.format(Math.floor(deltaSeconds / divisor), units[unitIndex]);
}
loadScript('https://cdn.jsdelivr.net/npm/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
console.error('Error loading RSS Parser script');
loadScript('https://unpkg.com/rss-parser@3.12.0/dist/rss-parser.min.js', initRSSFeed, () => {
console.error('Error loading RSS Parser script from backup CDN');
});
});
})();
</script>

#112 - Before & After Sliders
Easily add a before/after photo slider to your Webflow site.
<!-- 💙 MEMBERSCRIPT #112 v0.1 💙 - BEFORE & AFTER SLIDERS -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const wraps = document.querySelectorAll('[ms-code-ba-wrap]');
wraps.forEach(wrap => {
const before = wrap.querySelector('[ms-code-ba-before]');
const after = wrap.querySelector('[ms-code-ba-after]');
// Create slider element
const slider = document.createElement('div');
slider.setAttribute('ms-code-ba-slider', wrap.getAttribute('ms-code-ba-wrap'));
wrap.appendChild(slider);
let isDown = false;
// Ensure proper positioning
wrap.style.position = 'relative';
wrap.style.overflow = 'hidden';
before.style.width = '100%';
before.style.display = 'block';
after.style.position = 'absolute';
after.style.top = '0';
after.style.left = '0';
after.style.width = '100%';
after.style.height = '100%';
slider.style.position = 'absolute';
slider.style.top = '0';
slider.style.bottom = '0';
slider.style.width = '4px';
slider.style.background = 'white';
slider.style.cursor = 'ew-resize';
slider.style.zIndex = '3';
const setPosition = (position) => {
const clampedPosition = Math.max(0, Math.min(1, position));
slider.style.left = `${clampedPosition * 100}%`;
after.style.clipPath = `inset(0 0 0 ${clampedPosition * 100}%)`;
};
const move = (e) => {
if (!isDown && e.type !== 'mousemove') return;
e.preventDefault();
const x = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
const rect = wrap.getBoundingClientRect();
const position = (x - rect.left) / rect.width;
setPosition(position);
};
const easeBack = () => {
setPosition(0.5); // Move back to center
};
wrap.addEventListener('mousedown', () => isDown = true);
wrap.addEventListener('mouseup', () => isDown = false);
wrap.addEventListener('mouseleave', () => {
isDown = false;
easeBack();
});
wrap.addEventListener('mousemove', move);
wrap.addEventListener('touchstart', (e) => {
isDown = true;
move(e);
});
wrap.addEventListener('touchmove', move);
wrap.addEventListener('touchend', () => {
isDown = false;
easeBack();
});
// Initialize position
setPosition(0.5);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #112 v0.1 💙 - BEFORE & AFTER SLIDERS -->
<script>
document.addEventListener('DOMContentLoaded', () => {
const wraps = document.querySelectorAll('[ms-code-ba-wrap]');
wraps.forEach(wrap => {
const before = wrap.querySelector('[ms-code-ba-before]');
const after = wrap.querySelector('[ms-code-ba-after]');
// Create slider element
const slider = document.createElement('div');
slider.setAttribute('ms-code-ba-slider', wrap.getAttribute('ms-code-ba-wrap'));
wrap.appendChild(slider);
let isDown = false;
// Ensure proper positioning
wrap.style.position = 'relative';
wrap.style.overflow = 'hidden';
before.style.width = '100%';
before.style.display = 'block';
after.style.position = 'absolute';
after.style.top = '0';
after.style.left = '0';
after.style.width = '100%';
after.style.height = '100%';
slider.style.position = 'absolute';
slider.style.top = '0';
slider.style.bottom = '0';
slider.style.width = '4px';
slider.style.background = 'white';
slider.style.cursor = 'ew-resize';
slider.style.zIndex = '3';
const setPosition = (position) => {
const clampedPosition = Math.max(0, Math.min(1, position));
slider.style.left = `${clampedPosition * 100}%`;
after.style.clipPath = `inset(0 0 0 ${clampedPosition * 100}%)`;
};
const move = (e) => {
if (!isDown && e.type !== 'mousemove') return;
e.preventDefault();
const x = e.type.includes('touch') ? e.touches[0].clientX : e.clientX;
const rect = wrap.getBoundingClientRect();
const position = (x - rect.left) / rect.width;
setPosition(position);
};
const easeBack = () => {
setPosition(0.5); // Move back to center
};
wrap.addEventListener('mousedown', () => isDown = true);
wrap.addEventListener('mouseup', () => isDown = false);
wrap.addEventListener('mouseleave', () => {
isDown = false;
easeBack();
});
wrap.addEventListener('mousemove', move);
wrap.addEventListener('touchstart', (e) => {
isDown = true;
move(e);
});
wrap.addEventListener('touchmove', move);
wrap.addEventListener('touchend', () => {
isDown = false;
easeBack();
});
// Initialize position
setPosition(0.5);
});
});
</script>

#111 - Auto Rotating Tabs
The easiest way to make your tabs auto-rotate on a timer.
<!-- 💙 MEMBERSCRIPT #111 v0.1 💙 - AUTO-ROTATING TABS -->
<script>
// Function to rotate tabs
function initializeTabRotator() {
// Find all tab containers with the ms-code-rotate-tabs attribute
const tabContainers = document.querySelectorAll('[ms-code-rotate-tabs]');
tabContainers.forEach(container => {
const interval = parseInt(container.getAttribute('ms-code-rotate-tabs'), 10);
const tabLinks = container.querySelectorAll('.w-tab-link');
const tabContent = container.closest('.w-tabs').querySelector('.w-tab-content');
const tabPanes = tabContent.querySelectorAll('.w-tab-pane');
let currentIndex = Array.from(tabLinks).findIndex(link => link.classList.contains('w--current'));
let rotationTimer;
// ANIMATION CONFIGURATION
// Modify these values to adjust the animation behavior
const FADE_OUT_DURATION = 300; // Duration for fading out the current tab (in milliseconds)
const FADE_IN_DURATION = 100; // Duration for fading in the new tab (in milliseconds)
const EASING_FUNCTION = 'ease'; // Choose from: 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'
// or use a cubic-bezier function like 'cubic-bezier(0.1, 0.7, 1.0, 0.1)'
// Additional easing options (uncomment to use):
// const EASING_FUNCTION = 'ease-in-quad';
// const EASING_FUNCTION = 'ease-out-quad';
// const EASING_FUNCTION = 'ease-in-out-quad';
// const EASING_FUNCTION = 'ease-in-cubic';
// const EASING_FUNCTION = 'ease-out-cubic';
// const EASING_FUNCTION = 'ease-in-out-cubic';
// const EASING_FUNCTION = 'ease-in-quart';
// const EASING_FUNCTION = 'ease-out-quart';
// const EASING_FUNCTION = 'ease-in-out-quart';
// const EASING_FUNCTION = 'ease-in-quint';
// const EASING_FUNCTION = 'ease-out-quint';
// const EASING_FUNCTION = 'ease-in-out-quint';
// const EASING_FUNCTION = 'ease-in-sine';
// const EASING_FUNCTION = 'ease-out-sine';
// const EASING_FUNCTION = 'ease-in-out-sine';
// const EASING_FUNCTION = 'ease-in-expo';
// const EASING_FUNCTION = 'ease-out-expo';
// const EASING_FUNCTION = 'ease-in-out-expo';
// const EASING_FUNCTION = 'ease-in-circ';
// const EASING_FUNCTION = 'ease-out-circ';
// const EASING_FUNCTION = 'ease-in-out-circ';
// const EASING_FUNCTION = 'ease-in-back';
// const EASING_FUNCTION = 'ease-out-back';
// const EASING_FUNCTION = 'ease-in-out-back';
// END OF ANIMATION CONFIGURATION
function switchToTab(index) {
// Fade out current tab
tabPanes[currentIndex].style.transition = `opacity ${FADE_OUT_DURATION}ms ${EASING_FUNCTION}`;
tabPanes[currentIndex].style.opacity = '0';
setTimeout(() => {
// Remove active classes and update ARIA attributes for current tab and pane
tabLinks[currentIndex].classList.remove('w--current');
tabLinks[currentIndex].setAttribute('aria-selected', 'false');
tabLinks[currentIndex].setAttribute('tabindex', '-1');
tabPanes[currentIndex].classList.remove('w--tab-active');
// Update current index
currentIndex = index;
// Add active classes and update ARIA attributes for new current tab and pane
tabLinks[currentIndex].classList.add('w--current');
tabLinks[currentIndex].setAttribute('aria-selected', 'true');
tabLinks[currentIndex].setAttribute('tabindex', '0');
tabPanes[currentIndex].classList.add('w--tab-active');
// Fade in new tab
tabPanes[currentIndex].style.transition = `opacity ${FADE_IN_DURATION}ms ${EASING_FUNCTION}`;
tabPanes[currentIndex].style.opacity = '1';
// Update the data-current attribute on the parent w-tabs element
const wTabsElement = container.closest('.w-tabs');
if (wTabsElement) {
wTabsElement.setAttribute('data-current', tabLinks[currentIndex].getAttribute('data-w-tab'));
}
}, FADE_OUT_DURATION);
}
function rotateToNextTab() {
const nextIndex = (currentIndex + 1) % tabLinks.length;
switchToTab(nextIndex);
}
function startRotation() {
clearInterval(rotationTimer);
rotationTimer = setInterval(rotateToNextTab, interval);
}
// Add click event listeners to tab links
tabLinks.forEach((link, index) => {
link.addEventListener('click', (e) => {
e.preventDefault();
switchToTab(index);
startRotation(); // Restart rotation from this tab
});
});
// Start the initial rotation
startRotation();
});
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initializeTabRotator);
</script>
<!-- 💙 MEMBERSCRIPT #111 v0.1 💙 - AUTO-ROTATING TABS -->
<script>
// Function to rotate tabs
function initializeTabRotator() {
// Find all tab containers with the ms-code-rotate-tabs attribute
const tabContainers = document.querySelectorAll('[ms-code-rotate-tabs]');
tabContainers.forEach(container => {
const interval = parseInt(container.getAttribute('ms-code-rotate-tabs'), 10);
const tabLinks = container.querySelectorAll('.w-tab-link');
const tabContent = container.closest('.w-tabs').querySelector('.w-tab-content');
const tabPanes = tabContent.querySelectorAll('.w-tab-pane');
let currentIndex = Array.from(tabLinks).findIndex(link => link.classList.contains('w--current'));
let rotationTimer;
// ANIMATION CONFIGURATION
// Modify these values to adjust the animation behavior
const FADE_OUT_DURATION = 300; // Duration for fading out the current tab (in milliseconds)
const FADE_IN_DURATION = 100; // Duration for fading in the new tab (in milliseconds)
const EASING_FUNCTION = 'ease'; // Choose from: 'linear', 'ease', 'ease-in', 'ease-out', 'ease-in-out'
// or use a cubic-bezier function like 'cubic-bezier(0.1, 0.7, 1.0, 0.1)'
// Additional easing options (uncomment to use):
// const EASING_FUNCTION = 'ease-in-quad';
// const EASING_FUNCTION = 'ease-out-quad';
// const EASING_FUNCTION = 'ease-in-out-quad';
// const EASING_FUNCTION = 'ease-in-cubic';
// const EASING_FUNCTION = 'ease-out-cubic';
// const EASING_FUNCTION = 'ease-in-out-cubic';
// const EASING_FUNCTION = 'ease-in-quart';
// const EASING_FUNCTION = 'ease-out-quart';
// const EASING_FUNCTION = 'ease-in-out-quart';
// const EASING_FUNCTION = 'ease-in-quint';
// const EASING_FUNCTION = 'ease-out-quint';
// const EASING_FUNCTION = 'ease-in-out-quint';
// const EASING_FUNCTION = 'ease-in-sine';
// const EASING_FUNCTION = 'ease-out-sine';
// const EASING_FUNCTION = 'ease-in-out-sine';
// const EASING_FUNCTION = 'ease-in-expo';
// const EASING_FUNCTION = 'ease-out-expo';
// const EASING_FUNCTION = 'ease-in-out-expo';
// const EASING_FUNCTION = 'ease-in-circ';
// const EASING_FUNCTION = 'ease-out-circ';
// const EASING_FUNCTION = 'ease-in-out-circ';
// const EASING_FUNCTION = 'ease-in-back';
// const EASING_FUNCTION = 'ease-out-back';
// const EASING_FUNCTION = 'ease-in-out-back';
// END OF ANIMATION CONFIGURATION
function switchToTab(index) {
// Fade out current tab
tabPanes[currentIndex].style.transition = `opacity ${FADE_OUT_DURATION}ms ${EASING_FUNCTION}`;
tabPanes[currentIndex].style.opacity = '0';
setTimeout(() => {
// Remove active classes and update ARIA attributes for current tab and pane
tabLinks[currentIndex].classList.remove('w--current');
tabLinks[currentIndex].setAttribute('aria-selected', 'false');
tabLinks[currentIndex].setAttribute('tabindex', '-1');
tabPanes[currentIndex].classList.remove('w--tab-active');
// Update current index
currentIndex = index;
// Add active classes and update ARIA attributes for new current tab and pane
tabLinks[currentIndex].classList.add('w--current');
tabLinks[currentIndex].setAttribute('aria-selected', 'true');
tabLinks[currentIndex].setAttribute('tabindex', '0');
tabPanes[currentIndex].classList.add('w--tab-active');
// Fade in new tab
tabPanes[currentIndex].style.transition = `opacity ${FADE_IN_DURATION}ms ${EASING_FUNCTION}`;
tabPanes[currentIndex].style.opacity = '1';
// Update the data-current attribute on the parent w-tabs element
const wTabsElement = container.closest('.w-tabs');
if (wTabsElement) {
wTabsElement.setAttribute('data-current', tabLinks[currentIndex].getAttribute('data-w-tab'));
}
}, FADE_OUT_DURATION);
}
function rotateToNextTab() {
const nextIndex = (currentIndex + 1) % tabLinks.length;
switchToTab(nextIndex);
}
function startRotation() {
clearInterval(rotationTimer);
rotationTimer = setInterval(rotateToNextTab, interval);
}
// Add click event listeners to tab links
tabLinks.forEach((link, index) => {
link.addEventListener('click', (e) => {
e.preventDefault();
switchToTab(index);
startRotation(); // Restart rotation from this tab
});
});
// Start the initial rotation
startRotation();
});
}
// Run the function when the DOM is fully loaded
document.addEventListener('DOMContentLoaded', initializeTabRotator);
</script>

#110 - Tooltips for Webflow
Easily add Tippy.js tooltips to your Webflow site with attributes.
<!-- 💙 MEMBERSCRIPT #110 v0.1 💙 - TOOLTIPS FOR WEBFLOW -->
<script>
// Function to load Tippy.js, its CSS, and additional theme/animation CSS
function loadTippy(callback) {
// Load Tippy.js script
const script = document.createElement('script');
script.src = 'https://unpkg.com/@popperjs/core@2';
script.onload = function() {
const tippyScript = document.createElement('script');
tippyScript.src = 'https://unpkg.com/tippy.js@6';
tippyScript.onload = function() {
// Load Tippy.js CSS
const cssFiles = [
'https://unpkg.com/tippy.js@6/dist/tippy.css',
'https://unpkg.com/tippy.js@6/themes/light.css',
'https://unpkg.com/tippy.js@6/themes/light-border.css',
'https://unpkg.com/tippy.js@6/animations/shift-away.css',
'https://unpkg.com/tippy.js@6/animations/shift-toward.css',
'https://unpkg.com/tippy.js@6/animations/scale.css',
'https://unpkg.com/tippy.js@6/animations/perspective.css'
];
let loadedCount = 0;
cssFiles.forEach(file => {
const link = document.createElement('link');
link.href = file;
link.rel = 'stylesheet';
link.onload = function() {
loadedCount++;
if (loadedCount === cssFiles.length) {
// Call the callback function when everything is loaded
callback();
}
};
document.head.appendChild(link);
});
};
document.head.appendChild(tippyScript);
};
document.head.appendChild(script);
}
// Function to initialize Tippy tooltips
function initializeTippyTooltips() {
// Select all elements with any ms-code-tooltip-* attribute
const elements = document.querySelectorAll('[ms-code-tooltip-top], [ms-code-tooltip-bottom], [ms-code-tooltip-left], [ms-code-tooltip-right], [ms-code-tooltip-content]');
elements.forEach(element => {
const tippyOptions = {};
// Content and Placement
if (element.hasAttribute('ms-code-tooltip-top')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-top');
tippyOptions.placement = 'top';
} else if (element.hasAttribute('ms-code-tooltip-bottom')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-bottom');
tippyOptions.placement = 'bottom';
} else if (element.hasAttribute('ms-code-tooltip-left')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-left');
tippyOptions.placement = 'left';
} else if (element.hasAttribute('ms-code-tooltip-right')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-right');
tippyOptions.placement = 'right';
} else if (element.hasAttribute('ms-code-tooltip-content')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-content');
}
if (element.hasAttribute('ms-code-tooltip-placement')) {
tippyOptions.placement = element.getAttribute('ms-code-tooltip-placement');
}
// Theme
if (element.hasAttribute('ms-code-tooltip-theme')) {
tippyOptions.theme = element.getAttribute('ms-code-tooltip-theme');
}
// Animation
if (element.hasAttribute('ms-code-tooltip-animation')) {
tippyOptions.animation = element.getAttribute('ms-code-tooltip-animation');
}
// Max Width
if (element.hasAttribute('ms-code-tooltip-maxwidth')) {
tippyOptions.maxWidth = parseInt(element.getAttribute('ms-code-tooltip-maxwidth'));
}
// Delay
if (element.hasAttribute('ms-code-tooltip-delay')) {
tippyOptions.delay = JSON.parse(element.getAttribute('ms-code-tooltip-delay'));
}
// Duration
if (element.hasAttribute('ms-code-tooltip-duration')) {
tippyOptions.duration = JSON.parse(element.getAttribute('ms-code-tooltip-duration'));
}
// Interactive
if (element.hasAttribute('ms-code-tooltip-interactive')) {
tippyOptions.interactive = element.getAttribute('ms-code-tooltip-interactive') === 'true';
}
// Arrow
if (element.hasAttribute('ms-code-tooltip-arrow')) {
tippyOptions.arrow = element.getAttribute('ms-code-tooltip-arrow') === 'true';
}
// Trigger
if (element.hasAttribute('ms-code-tooltip-trigger')) {
tippyOptions.trigger = element.getAttribute('ms-code-tooltip-trigger');
}
// Hide On Click
if (element.hasAttribute('ms-code-tooltip-hideOnClick')) {
tippyOptions.hideOnClick = element.getAttribute('ms-code-tooltip-hideOnClick') === 'true';
}
// Follow Cursor
if (element.hasAttribute('ms-code-tooltip-followCursor')) {
tippyOptions.followCursor = element.getAttribute('ms-code-tooltip-followCursor');
}
// Offset
if (element.hasAttribute('ms-code-tooltip-offset')) {
tippyOptions.offset = JSON.parse(element.getAttribute('ms-code-tooltip-offset'));
}
// Z-Index
if (element.hasAttribute('ms-code-tooltip-zIndex')) {
tippyOptions.zIndex = parseInt(element.getAttribute('ms-code-tooltip-zIndex'));
}
// Allow HTML
if (element.hasAttribute('ms-code-tooltip-allowHTML')) {
tippyOptions.allowHTML = element.getAttribute('ms-code-tooltip-allowHTML') === 'true';
}
// Touch
if (element.hasAttribute('ms-code-tooltip-touch')) {
const touchValue = element.getAttribute('ms-code-tooltip-touch');
tippyOptions.touch = touchValue === 'true' || touchValue === 'false' ? (touchValue === 'true') : JSON.parse(touchValue);
}
// Initialize Tippy instance
tippy(element, tippyOptions);
});
}
// Wait for the DOM to be fully loaded, then load Tippy and initialize tooltips
document.addEventListener('DOMContentLoaded', function() {
loadTippy(initializeTippyTooltips);
});
</script>
<!-- 💙 MEMBERSCRIPT #110 v0.1 💙 - TOOLTIPS FOR WEBFLOW -->
<script>
// Function to load Tippy.js, its CSS, and additional theme/animation CSS
function loadTippy(callback) {
// Load Tippy.js script
const script = document.createElement('script');
script.src = 'https://unpkg.com/@popperjs/core@2';
script.onload = function() {
const tippyScript = document.createElement('script');
tippyScript.src = 'https://unpkg.com/tippy.js@6';
tippyScript.onload = function() {
// Load Tippy.js CSS
const cssFiles = [
'https://unpkg.com/tippy.js@6/dist/tippy.css',
'https://unpkg.com/tippy.js@6/themes/light.css',
'https://unpkg.com/tippy.js@6/themes/light-border.css',
'https://unpkg.com/tippy.js@6/animations/shift-away.css',
'https://unpkg.com/tippy.js@6/animations/shift-toward.css',
'https://unpkg.com/tippy.js@6/animations/scale.css',
'https://unpkg.com/tippy.js@6/animations/perspective.css'
];
let loadedCount = 0;
cssFiles.forEach(file => {
const link = document.createElement('link');
link.href = file;
link.rel = 'stylesheet';
link.onload = function() {
loadedCount++;
if (loadedCount === cssFiles.length) {
// Call the callback function when everything is loaded
callback();
}
};
document.head.appendChild(link);
});
};
document.head.appendChild(tippyScript);
};
document.head.appendChild(script);
}
// Function to initialize Tippy tooltips
function initializeTippyTooltips() {
// Select all elements with any ms-code-tooltip-* attribute
const elements = document.querySelectorAll('[ms-code-tooltip-top], [ms-code-tooltip-bottom], [ms-code-tooltip-left], [ms-code-tooltip-right], [ms-code-tooltip-content]');
elements.forEach(element => {
const tippyOptions = {};
// Content and Placement
if (element.hasAttribute('ms-code-tooltip-top')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-top');
tippyOptions.placement = 'top';
} else if (element.hasAttribute('ms-code-tooltip-bottom')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-bottom');
tippyOptions.placement = 'bottom';
} else if (element.hasAttribute('ms-code-tooltip-left')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-left');
tippyOptions.placement = 'left';
} else if (element.hasAttribute('ms-code-tooltip-right')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-right');
tippyOptions.placement = 'right';
} else if (element.hasAttribute('ms-code-tooltip-content')) {
tippyOptions.content = element.getAttribute('ms-code-tooltip-content');
}
if (element.hasAttribute('ms-code-tooltip-placement')) {
tippyOptions.placement = element.getAttribute('ms-code-tooltip-placement');
}
// Theme
if (element.hasAttribute('ms-code-tooltip-theme')) {
tippyOptions.theme = element.getAttribute('ms-code-tooltip-theme');
}
// Animation
if (element.hasAttribute('ms-code-tooltip-animation')) {
tippyOptions.animation = element.getAttribute('ms-code-tooltip-animation');
}
// Max Width
if (element.hasAttribute('ms-code-tooltip-maxwidth')) {
tippyOptions.maxWidth = parseInt(element.getAttribute('ms-code-tooltip-maxwidth'));
}
// Delay
if (element.hasAttribute('ms-code-tooltip-delay')) {
tippyOptions.delay = JSON.parse(element.getAttribute('ms-code-tooltip-delay'));
}
// Duration
if (element.hasAttribute('ms-code-tooltip-duration')) {
tippyOptions.duration = JSON.parse(element.getAttribute('ms-code-tooltip-duration'));
}
// Interactive
if (element.hasAttribute('ms-code-tooltip-interactive')) {
tippyOptions.interactive = element.getAttribute('ms-code-tooltip-interactive') === 'true';
}
// Arrow
if (element.hasAttribute('ms-code-tooltip-arrow')) {
tippyOptions.arrow = element.getAttribute('ms-code-tooltip-arrow') === 'true';
}
// Trigger
if (element.hasAttribute('ms-code-tooltip-trigger')) {
tippyOptions.trigger = element.getAttribute('ms-code-tooltip-trigger');
}
// Hide On Click
if (element.hasAttribute('ms-code-tooltip-hideOnClick')) {
tippyOptions.hideOnClick = element.getAttribute('ms-code-tooltip-hideOnClick') === 'true';
}
// Follow Cursor
if (element.hasAttribute('ms-code-tooltip-followCursor')) {
tippyOptions.followCursor = element.getAttribute('ms-code-tooltip-followCursor');
}
// Offset
if (element.hasAttribute('ms-code-tooltip-offset')) {
tippyOptions.offset = JSON.parse(element.getAttribute('ms-code-tooltip-offset'));
}
// Z-Index
if (element.hasAttribute('ms-code-tooltip-zIndex')) {
tippyOptions.zIndex = parseInt(element.getAttribute('ms-code-tooltip-zIndex'));
}
// Allow HTML
if (element.hasAttribute('ms-code-tooltip-allowHTML')) {
tippyOptions.allowHTML = element.getAttribute('ms-code-tooltip-allowHTML') === 'true';
}
// Touch
if (element.hasAttribute('ms-code-tooltip-touch')) {
const touchValue = element.getAttribute('ms-code-tooltip-touch');
tippyOptions.touch = touchValue === 'true' || touchValue === 'false' ? (touchValue === 'true') : JSON.parse(touchValue);
}
// Initialize Tippy instance
tippy(element, tippyOptions);
});
}
// Wait for the DOM to be fully loaded, then load Tippy and initialize tooltips
document.addEventListener('DOMContentLoaded', function() {
loadTippy(initializeTippyTooltips);
});
</script>

#109 - Custom Multi Selects
Custom-styled multi selects with search, keyboard selection, and more.
<!-- 💙 MEMBERSCRIPT #109 v0.1 💙 - CUSTOM MULTI SELECT -->
<script>
$(document).ready(function() {
$('[ms-code-select-wrapper]').each(function() {
const $wrapper = $(this);
const isMulti = $wrapper.attr('ms-code-select-wrapper') === 'multi';
const $input = $wrapper.find('[ms-code-select="input"]');
const $list = $wrapper.find('[ms-code-select="list"]');
const $selectedWrapper = $wrapper.find('[ms-code-select="selected-wrapper"]');
const $emptyState = $wrapper.find('[ms-code-select="empty-state"]');
const options = $input.attr('ms-code-select-options').split(',').map(opt => opt.trim());
let selectedOptions = [];
let highlightedIndex = -1;
const $templateSelectedTag = $selectedWrapper.find('[ms-code-select="tag"]');
const templateSelectedTagHTML = $templateSelectedTag.prop('outerHTML');
$templateSelectedTag.remove();
const $templateNewTag = $list.find('[ms-code-select="tag-name-new"]');
const templateNewTagHTML = $templateNewTag.prop('outerHTML');
$templateNewTag.remove();
function createSelectedTag(value) {
const $newTag = $(templateSelectedTagHTML);
$newTag.find('[ms-code-select="tag-name-selected"]').text(value);
$newTag.find('[ms-code-select="tag-close"]').on('click', function(e) {
e.stopPropagation();
removeTag(value);
});
return $newTag;
}
function addTag(value) {
if (!selectedOptions.includes(value) && options.includes(value)) {
selectedOptions.push(value);
$selectedWrapper.append(createSelectedTag(value));
updateInput();
filterOptions();
}
}
function removeTag(value) {
selectedOptions = selectedOptions.filter(option => option !== value);
$selectedWrapper.find(`[ms-code-select="tag-name-selected"]:contains("${value}")`).closest('[ms-code-select="tag"]').remove();
updateInput();
if (isMulti && selectedOptions.length > 0) {
$input.val($input.val() + ', ');
}
filterOptions();
}
function updateInput() {
$input.val(selectedOptions.join(', '));
}
function toggleList(show) {
$list.toggle(show);
}
function createOptionElement(value) {
const $option = $(templateNewTagHTML);
$option.text(value);
$option.on('click', function() {
selectOption(value);
});
return $option;
}
function selectOption(value) {
if (isMulti) {
addTag(value);
$input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
$input.focus();
} else {
selectedOptions = [value];
$selectedWrapper.empty().append(createSelectedTag(value));
updateInput();
toggleList(false);
}
filterOptions();
}
function filterOptions() {
const inputValue = $input.val();
const searchTerm = isMulti ? inputValue.split(',').pop().trim() : inputValue.trim();
let visibleOptionsCount = 0;
$list.find('[ms-code-select="tag-name-new"]').each(function() {
const $option = $(this);
const optionText = $option.text().toLowerCase();
const matches = optionText.includes(searchTerm.toLowerCase());
const isSelected = selectedOptions.includes($option.text());
$option.toggle(matches && !isSelected);
if (matches && !isSelected) visibleOptionsCount++;
});
$emptyState.toggle(visibleOptionsCount === 0 && searchTerm !== '');
highlightedIndex = -1;
updateHighlight();
}
function cleanInput() {
const inputValues = $input.val().split(',').map(v => v.trim()).filter(v => v);
const validValues = inputValues.filter(v => options.includes(v));
selectedOptions = validValues;
$selectedWrapper.empty();
selectedOptions.forEach(value => $selectedWrapper.append(createSelectedTag(value)));
updateInput();
filterOptions();
}
function handleInputChange() {
const inputValue = $input.val();
const inputValues = inputValue.split(',').map(v => v.trim());
const lastValue = inputValues[inputValues.length - 1];
if (inputValue.endsWith(',') || inputValue.endsWith(', ')) {
inputValues.pop();
const newValidValues = inputValues.filter(v => options.includes(v) && !selectedOptions.includes(v));
newValidValues.forEach(addTag);
$input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
} else if (options.includes(lastValue) && !selectedOptions.includes(lastValue)) {
addTag(lastValue);
$input.val(selectedOptions.join(', ') + ', ');
}
filterOptions();
}
function initializeWithValue() {
const initialValue = $input.val();
if (initialValue) {
const initialValues = initialValue.split(',').map(v => v.trim());
initialValues.forEach(value => {
if (options.includes(value)) {
addTag(value);
}
});
updateInput();
filterOptions();
}
}
function updateHighlight() {
$list.find('[ms-code-select="tag-name-new"]').removeClass('highlighted').css('background-color', '');
if (highlightedIndex >= 0) {
$list.find('[ms-code-select="tag-name-new"]:visible').eq(highlightedIndex)
.addClass('highlighted')
.css('background-color', '#e0e0e0');
}
}
function handleKeyDown(e) {
const visibleOptions = $list.find('[ms-code-select="tag-name-new"]:visible');
const optionCount = visibleOptions.length;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
highlightedIndex = (highlightedIndex + 1) % optionCount;
updateHighlight();
break;
case 'ArrowUp':
e.preventDefault();
highlightedIndex = (highlightedIndex - 1 + optionCount) % optionCount;
updateHighlight();
break;
case 'Enter':
e.preventDefault();
if (highlightedIndex >= 0) {
const selectedValue = visibleOptions.eq(highlightedIndex).text();
selectOption(selectedValue);
}
break;
}
}
$.each(options, function(i, option) {
$list.append(createOptionElement(option));
});
$input.on('focus', function() {
toggleList(true);
if (isMulti) {
const currentVal = $input.val().trim();
if (currentVal !== '' && !currentVal.endsWith(',')) {
$input.val(currentVal + ', ');
}
this.selectionStart = this.selectionEnd = this.value.length;
}
filterOptions();
});
$input.on('click', function(e) {
e.preventDefault();
this.selectionStart = this.selectionEnd = this.value.length;
});
$input.on('blur', function() {
setTimeout(function() {
if (!$list.is(':hover')) {
toggleList(false);
cleanInput();
}
}, 100);
});
$input.on('input', handleInputChange);
$input.on('keydown', handleKeyDown);
$list.on('mouseenter', '[ms-code-select="tag-name-new"]', function() {
$(this).css('background-color', '#e0e0e0');
});
$list.on('mouseleave', '[ms-code-select="tag-name-new"]', function() {
if (!$(this).hasClass('highlighted')) {
$(this).css('background-color', '');
}
});
initializeWithValue();
toggleList(false);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #109 v0.1 💙 - CUSTOM MULTI SELECT -->
<script>
$(document).ready(function() {
$('[ms-code-select-wrapper]').each(function() {
const $wrapper = $(this);
const isMulti = $wrapper.attr('ms-code-select-wrapper') === 'multi';
const $input = $wrapper.find('[ms-code-select="input"]');
const $list = $wrapper.find('[ms-code-select="list"]');
const $selectedWrapper = $wrapper.find('[ms-code-select="selected-wrapper"]');
const $emptyState = $wrapper.find('[ms-code-select="empty-state"]');
const options = $input.attr('ms-code-select-options').split(',').map(opt => opt.trim());
let selectedOptions = [];
let highlightedIndex = -1;
const $templateSelectedTag = $selectedWrapper.find('[ms-code-select="tag"]');
const templateSelectedTagHTML = $templateSelectedTag.prop('outerHTML');
$templateSelectedTag.remove();
const $templateNewTag = $list.find('[ms-code-select="tag-name-new"]');
const templateNewTagHTML = $templateNewTag.prop('outerHTML');
$templateNewTag.remove();
function createSelectedTag(value) {
const $newTag = $(templateSelectedTagHTML);
$newTag.find('[ms-code-select="tag-name-selected"]').text(value);
$newTag.find('[ms-code-select="tag-close"]').on('click', function(e) {
e.stopPropagation();
removeTag(value);
});
return $newTag;
}
function addTag(value) {
if (!selectedOptions.includes(value) && options.includes(value)) {
selectedOptions.push(value);
$selectedWrapper.append(createSelectedTag(value));
updateInput();
filterOptions();
}
}
function removeTag(value) {
selectedOptions = selectedOptions.filter(option => option !== value);
$selectedWrapper.find(`[ms-code-select="tag-name-selected"]:contains("${value}")`).closest('[ms-code-select="tag"]').remove();
updateInput();
if (isMulti && selectedOptions.length > 0) {
$input.val($input.val() + ', ');
}
filterOptions();
}
function updateInput() {
$input.val(selectedOptions.join(', '));
}
function toggleList(show) {
$list.toggle(show);
}
function createOptionElement(value) {
const $option = $(templateNewTagHTML);
$option.text(value);
$option.on('click', function() {
selectOption(value);
});
return $option;
}
function selectOption(value) {
if (isMulti) {
addTag(value);
$input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
$input.focus();
} else {
selectedOptions = [value];
$selectedWrapper.empty().append(createSelectedTag(value));
updateInput();
toggleList(false);
}
filterOptions();
}
function filterOptions() {
const inputValue = $input.val();
const searchTerm = isMulti ? inputValue.split(',').pop().trim() : inputValue.trim();
let visibleOptionsCount = 0;
$list.find('[ms-code-select="tag-name-new"]').each(function() {
const $option = $(this);
const optionText = $option.text().toLowerCase();
const matches = optionText.includes(searchTerm.toLowerCase());
const isSelected = selectedOptions.includes($option.text());
$option.toggle(matches && !isSelected);
if (matches && !isSelected) visibleOptionsCount++;
});
$emptyState.toggle(visibleOptionsCount === 0 && searchTerm !== '');
highlightedIndex = -1;
updateHighlight();
}
function cleanInput() {
const inputValues = $input.val().split(',').map(v => v.trim()).filter(v => v);
const validValues = inputValues.filter(v => options.includes(v));
selectedOptions = validValues;
$selectedWrapper.empty();
selectedOptions.forEach(value => $selectedWrapper.append(createSelectedTag(value)));
updateInput();
filterOptions();
}
function handleInputChange() {
const inputValue = $input.val();
const inputValues = inputValue.split(',').map(v => v.trim());
const lastValue = inputValues[inputValues.length - 1];
if (inputValue.endsWith(',') || inputValue.endsWith(', ')) {
inputValues.pop();
const newValidValues = inputValues.filter(v => options.includes(v) && !selectedOptions.includes(v));
newValidValues.forEach(addTag);
$input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
} else if (options.includes(lastValue) && !selectedOptions.includes(lastValue)) {
addTag(lastValue);
$input.val(selectedOptions.join(', ') + ', ');
}
filterOptions();
}
function initializeWithValue() {
const initialValue = $input.val();
if (initialValue) {
const initialValues = initialValue.split(',').map(v => v.trim());
initialValues.forEach(value => {
if (options.includes(value)) {
addTag(value);
}
});
updateInput();
filterOptions();
}
}
function updateHighlight() {
$list.find('[ms-code-select="tag-name-new"]').removeClass('highlighted').css('background-color', '');
if (highlightedIndex >= 0) {
$list.find('[ms-code-select="tag-name-new"]:visible').eq(highlightedIndex)
.addClass('highlighted')
.css('background-color', '#e0e0e0');
}
}
function handleKeyDown(e) {
const visibleOptions = $list.find('[ms-code-select="tag-name-new"]:visible');
const optionCount = visibleOptions.length;
switch (e.key) {
case 'ArrowDown':
e.preventDefault();
highlightedIndex = (highlightedIndex + 1) % optionCount;
updateHighlight();
break;
case 'ArrowUp':
e.preventDefault();
highlightedIndex = (highlightedIndex - 1 + optionCount) % optionCount;
updateHighlight();
break;
case 'Enter':
e.preventDefault();
if (highlightedIndex >= 0) {
const selectedValue = visibleOptions.eq(highlightedIndex).text();
selectOption(selectedValue);
}
break;
}
}
$.each(options, function(i, option) {
$list.append(createOptionElement(option));
});
$input.on('focus', function() {
toggleList(true);
if (isMulti) {
const currentVal = $input.val().trim();
if (currentVal !== '' && !currentVal.endsWith(',')) {
$input.val(currentVal + ', ');
}
this.selectionStart = this.selectionEnd = this.value.length;
}
filterOptions();
});
$input.on('click', function(e) {
e.preventDefault();
this.selectionStart = this.selectionEnd = this.value.length;
});
$input.on('blur', function() {
setTimeout(function() {
if (!$list.is(':hover')) {
toggleList(false);
cleanInput();
}
}, 100);
});
$input.on('input', handleInputChange);
$input.on('keydown', handleKeyDown);
$list.on('mouseenter', '[ms-code-select="tag-name-new"]', function() {
$(this).css('background-color', '#e0e0e0');
});
$list.on('mouseleave', '[ms-code-select="tag-name-new"]', function() {
if (!$(this).hasClass('highlighted')) {
$(this).css('background-color', '');
}
});
initializeWithValue();
toggleList(false);
});
});
</script>

#108 - Custom Form Submit Buttons
Create any element in Webflow and use it to submit any kind of form.
<!-- 💙 MEMBERSCRIPT #108 v0.1 💙 CUSTOM FORM SUBMIT BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-submit-new attribute
const newSubmitButtons = document.querySelectorAll('[ms-code-submit-new]');
// Add click event listeners to each new submit button
newSubmitButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default action if it's a link
// Get the value of the ms-code-submit-new attribute
const submitId = this.getAttribute('ms-code-submit-new');
// Find the corresponding old submit button
const oldSubmitButton = document.querySelector(`[ms-code-submit-old="${submitId}"]`);
// If found, trigger a click on the old submit button
if (oldSubmitButton) {
oldSubmitButton.click();
} else {
console.error(`No matching old submit button found for ID: ${submitId}`);
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #108 v0.1 💙 CUSTOM FORM SUBMIT BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-submit-new attribute
const newSubmitButtons = document.querySelectorAll('[ms-code-submit-new]');
// Add click event listeners to each new submit button
newSubmitButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default action if it's a link
// Get the value of the ms-code-submit-new attribute
const submitId = this.getAttribute('ms-code-submit-new');
// Find the corresponding old submit button
const oldSubmitButton = document.querySelector(`[ms-code-submit-old="${submitId}"]`);
// If found, trigger a click on the old submit button
if (oldSubmitButton) {
oldSubmitButton.click();
} else {
console.error(`No matching old submit button found for ID: ${submitId}`);
}
});
});
});
</script>

#107 - Select Plan With Radios
Add a plan selector radio to sign up forms & plan update forms.
<!-- 💙 MEMBERSCRIPT #107 v0.1 💙 SELECT PLAN WITH RADIO BUTTONS -->
<script>
(function() {
const PRICE_ATTRIBUTES = [
'data-ms-plan:add',
'data-ms-plan:update',
'data-ms-price:add',
'data-ms-price:update'
];
function findElementWithAttribute(form) {
// First, check if the form itself has one of the attributes
for (let attr of PRICE_ATTRIBUTES) {
if (form.hasAttribute(attr)) {
return { element: form, attribute: attr };
}
}
// If not found on form, search child elements
for (let attr of PRICE_ATTRIBUTES) {
let element = Array.from(form.querySelectorAll('*')).find(el => el.hasAttribute(attr));
if (element) {
return { element, attribute: attr };
}
}
return null;
}
function updateAttribute(radio) {
const form = radio.closest('form');
if (!form) return;
const result = findElementWithAttribute(form);
if (result) {
result.element.setAttribute(result.attribute, radio.value);
}
}
function handleRadioChange(e) {
updateAttribute(e.target);
}
function initializeRadioButtons() {
const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
forms.forEach(form => {
const radios = form.querySelectorAll('input[type="radio"]');
radios.forEach(radio => {
radio.addEventListener('change', handleRadioChange);
if (radio.checked) {
updateAttribute(radio);
}
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeRadioButtons);
} else {
initializeRadioButtons();
}
})();
// Only keep this section if you have an update plan form on the page
(function() {
function updateRadioButtonState(radio) {
radio.checked = true;
radio.dispatchEvent(new Event('change'));
// Update custom radio button UI if present
const customRadio = radio.parentElement.querySelector('.w-radio-input');
if (customRadio) {
customRadio.classList.add('w--redirected-checked');
}
}
function checkAndSelectPlan() {
const msMemData = localStorage.getItem('_ms-mem');
if (!msMemData) return;
try {
const memberData = JSON.parse(msMemData);
const activePlanConnections = memberData.planConnections?.filter(conn => conn.active) || [];
if (activePlanConnections.length === 0) return;
const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
forms.forEach(form => {
const radios = form.querySelectorAll('input[type="radio"]');
radios.forEach(radio => {
const matchingPlan = activePlanConnections.find(conn => conn.payment.priceId === radio.value);
if (matchingPlan) {
updateRadioButtonState(radio);
}
});
});
} catch (error) {
console.error('Error processing _ms-mem data:', error);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkAndSelectPlan);
} else {
checkAndSelectPlan();
}
})();
</script>
<!-- 💙 MEMBERSCRIPT #107 v0.1 💙 SELECT PLAN WITH RADIO BUTTONS -->
<script>
(function() {
const PRICE_ATTRIBUTES = [
'data-ms-plan:add',
'data-ms-plan:update',
'data-ms-price:add',
'data-ms-price:update'
];
function findElementWithAttribute(form) {
// First, check if the form itself has one of the attributes
for (let attr of PRICE_ATTRIBUTES) {
if (form.hasAttribute(attr)) {
return { element: form, attribute: attr };
}
}
// If not found on form, search child elements
for (let attr of PRICE_ATTRIBUTES) {
let element = Array.from(form.querySelectorAll('*')).find(el => el.hasAttribute(attr));
if (element) {
return { element, attribute: attr };
}
}
return null;
}
function updateAttribute(radio) {
const form = radio.closest('form');
if (!form) return;
const result = findElementWithAttribute(form);
if (result) {
result.element.setAttribute(result.attribute, radio.value);
}
}
function handleRadioChange(e) {
updateAttribute(e.target);
}
function initializeRadioButtons() {
const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
forms.forEach(form => {
const radios = form.querySelectorAll('input[type="radio"]');
radios.forEach(radio => {
radio.addEventListener('change', handleRadioChange);
if (radio.checked) {
updateAttribute(radio);
}
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializeRadioButtons);
} else {
initializeRadioButtons();
}
})();
// Only keep this section if you have an update plan form on the page
(function() {
function updateRadioButtonState(radio) {
radio.checked = true;
radio.dispatchEvent(new Event('change'));
// Update custom radio button UI if present
const customRadio = radio.parentElement.querySelector('.w-radio-input');
if (customRadio) {
customRadio.classList.add('w--redirected-checked');
}
}
function checkAndSelectPlan() {
const msMemData = localStorage.getItem('_ms-mem');
if (!msMemData) return;
try {
const memberData = JSON.parse(msMemData);
const activePlanConnections = memberData.planConnections?.filter(conn => conn.active) || [];
if (activePlanConnections.length === 0) return;
const forms = document.querySelectorAll('[ms-code-radio-plan="form"]');
forms.forEach(form => {
const radios = form.querySelectorAll('input[type="radio"]');
radios.forEach(radio => {
const matchingPlan = activePlanConnections.find(conn => conn.payment.priceId === radio.value);
if (matchingPlan) {
updateRadioButtonState(radio);
}
});
});
} catch (error) {
console.error('Error processing _ms-mem data:', error);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', checkAndSelectPlan);
} else {
checkAndSelectPlan();
}
})();
</script>

#106 - Liking & Saving CMS Items
Allow your members to save CMS items to their profile.
<!-- 💙 MEMBERSCRIPT #106 v0.2 💙 SAVING & UNSAVING CMS ITEMS -->
<style>
[ms-code-save], [ms-code-unsave] {
display: none;
}
[ms-code-save-item] {
display: none;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
let isLoggedIn = false;
let savedItems = [];
async function checkMemberLogin() {
try {
const member = await memberstack.getCurrentMember();
return !!member;
} catch (error) {
return false;
}
}
function getSavedItems(memberData) {
return memberData.savedItems || [];
}
function updateButtonVisibility() {
const saveButtons = document.querySelectorAll('[ms-code-save]');
const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');
saveButtons.forEach(button => {
const itemId = button.getAttribute('ms-code-save');
button.style.display = !savedItems.includes(itemId) ? 'block' : 'none';
});
unsaveButtons.forEach(button => {
const itemId = button.getAttribute('ms-code-unsave');
button.style.display = savedItems.includes(itemId) ? 'block' : 'none';
});
}
function updateItemVisibility() {
const saveLists = document.querySelectorAll('[ms-code-save-list]');
saveLists.forEach(list => {
const filter = list.getAttribute('ms-code-save-list');
const items = list.querySelectorAll('[ms-code-save-item]');
items.forEach(item => {
const saveButton = item.querySelector('[ms-code-save]');
if (!saveButton) {
item.style.display = 'block';
return;
}
const itemId = saveButton.getAttribute('ms-code-save');
if (!isLoggedIn || filter === 'all') {
item.style.display = 'block';
} else if (filter === 'saved' & savedItems.includes(itemId)) {
item.style.display = 'block';
} else if (filter === 'unsaved' & !savedItems.includes(itemId)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
}
async function handleButtonClick(event) {
if (!isLoggedIn) return;
const button = event.currentTarget;
const action = button.getAttribute('ms-code-save') ? 'save' : 'unsave';
const itemId = button.getAttribute(action === 'save' ? 'ms-code-save' : 'ms-code-unsave');
if (action === 'save' && !savedItems.includes(itemId)) {
savedItems.push(itemId);
} else if (action === 'unsave') {
savedItems = savedItems.filter(id => id !== itemId);
}
try {
await memberstack.updateMemberJSON({ json: { savedItems } });
} catch (error) {
// Silently handle the error
}
updateButtonVisibility();
updateItemVisibility();
}
function addClickListeners() {
const saveButtons = document.querySelectorAll('[ms-code-save]');
const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');
saveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
unsaveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
}
async function initializeScript() {
isLoggedIn = await checkMemberLogin();
if (isLoggedIn) {
try {
const result = await memberstack.getMemberJSON();
const memberData = result.data || {};
savedItems = getSavedItems(memberData);
} catch (error) {
// Silently handle the error
}
}
updateButtonVisibility();
updateItemVisibility();
addClickListeners();
// Set up a MutationObserver to watch for changes in the DOM
const observer = new MutationObserver((mutations) => {
let shouldUpdate = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
shouldUpdate = true;
}
});
if (shouldUpdate) {
updateButtonVisibility();
updateItemVisibility();
addClickListeners();
}
});
// Start observing the document with the configured parameters
observer.observe(document.body, { childList: true, subtree: true });
}
initializeScript();
});
</script>
<!-- 💙 MEMBERSCRIPT #106 v0.2 💙 SAVING & UNSAVING CMS ITEMS -->
<style>
[ms-code-save], [ms-code-unsave] {
display: none;
}
[ms-code-save-item] {
display: none;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
let isLoggedIn = false;
let savedItems = [];
async function checkMemberLogin() {
try {
const member = await memberstack.getCurrentMember();
return !!member;
} catch (error) {
return false;
}
}
function getSavedItems(memberData) {
return memberData.savedItems || [];
}
function updateButtonVisibility() {
const saveButtons = document.querySelectorAll('[ms-code-save]');
const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');
saveButtons.forEach(button => {
const itemId = button.getAttribute('ms-code-save');
button.style.display = !savedItems.includes(itemId) ? 'block' : 'none';
});
unsaveButtons.forEach(button => {
const itemId = button.getAttribute('ms-code-unsave');
button.style.display = savedItems.includes(itemId) ? 'block' : 'none';
});
}
function updateItemVisibility() {
const saveLists = document.querySelectorAll('[ms-code-save-list]');
saveLists.forEach(list => {
const filter = list.getAttribute('ms-code-save-list');
const items = list.querySelectorAll('[ms-code-save-item]');
items.forEach(item => {
const saveButton = item.querySelector('[ms-code-save]');
if (!saveButton) {
item.style.display = 'block';
return;
}
const itemId = saveButton.getAttribute('ms-code-save');
if (!isLoggedIn || filter === 'all') {
item.style.display = 'block';
} else if (filter === 'saved' & savedItems.includes(itemId)) {
item.style.display = 'block';
} else if (filter === 'unsaved' & !savedItems.includes(itemId)) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
});
}
async function handleButtonClick(event) {
if (!isLoggedIn) return;
const button = event.currentTarget;
const action = button.getAttribute('ms-code-save') ? 'save' : 'unsave';
const itemId = button.getAttribute(action === 'save' ? 'ms-code-save' : 'ms-code-unsave');
if (action === 'save' && !savedItems.includes(itemId)) {
savedItems.push(itemId);
} else if (action === 'unsave') {
savedItems = savedItems.filter(id => id !== itemId);
}
try {
await memberstack.updateMemberJSON({ json: { savedItems } });
} catch (error) {
// Silently handle the error
}
updateButtonVisibility();
updateItemVisibility();
}
function addClickListeners() {
const saveButtons = document.querySelectorAll('[ms-code-save]');
const unsaveButtons = document.querySelectorAll('[ms-code-unsave]');
saveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
unsaveButtons.forEach(button => button.addEventListener('click', handleButtonClick));
}
async function initializeScript() {
isLoggedIn = await checkMemberLogin();
if (isLoggedIn) {
try {
const result = await memberstack.getMemberJSON();
const memberData = result.data || {};
savedItems = getSavedItems(memberData);
} catch (error) {
// Silently handle the error
}
}
updateButtonVisibility();
updateItemVisibility();
addClickListeners();
// Set up a MutationObserver to watch for changes in the DOM
const observer = new MutationObserver((mutations) => {
let shouldUpdate = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
shouldUpdate = true;
}
});
if (shouldUpdate) {
updateButtonVisibility();
updateItemVisibility();
addClickListeners();
}
});
// Start observing the document with the configured parameters
observer.observe(document.body, { childList: true, subtree: true });
}
initializeScript();
});
</script>

#105 - Checkout After Login
Automatically launch the checkout if a member selects a price before logging in.
<!-- 💙 MEMBERSCRIPT #105 v0.1 💙 CHECKOUT AFTER LOGIN -->
<script>
/* Checks if the current URL matches the configured redirect URL, or if no specific URL is required */
function isCorrectPage() {
return redirectOnLoginURL === '' || window.location.pathname === redirectOnLoginURL;
}
/* Checks if Memberstack is fully loaded before running any Memberstack-specific code.*/
function memberstackReady(callback) {
function checkAndExecute() {
if (window.$memberstackDom) {
callback(); // Memberstack is ready, run the callback function.
} else {
setTimeout(checkAndExecute, 100); // Wait for 100ms and check again.
}
}
checkAndExecute(); // Start checking if Memberstack is ready.
}
/* Initiates the Stripe checkout process with a specified price ID.*/
async function initiateCheckout(priceId) {
try {
// Set a flag in session storage to indicate that the checkout page was accessed.
sessionStorage.setItem('ms_checkout_viewed', 'true');
await window.$memberstackDom.purchasePlansWithCheckout({
priceId, // The price ID for the product being purchased.
returnUrl: window.location.href, // Redirect the user back here after completing the checkout.
});
} catch (error) {
console.error('Failed to initiate payment:', error); // Provide error details in the console.
}
}
/* Main execution flow that starts once Memberstack is confirmed to be ready */
memberstackReady(() => {
window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
if (member && sessionStorage.getItem('ms_price') && !sessionStorage.getItem('ms_checkout_viewed')) {
initiateCheckout(sessionStorage.getItem('ms_price')); // Start the checkout process if conditions are met.
}
}).catch(error => {
console.error('Failed to retrieve user data:', error); // Log an error if fetching member data fails.
});
});
</script>
<!-- 💙 MEMBERSCRIPT #105 v0.1 💙 CHECKOUT AFTER LOGIN -->
<script>
/* Checks if the current URL matches the configured redirect URL, or if no specific URL is required */
function isCorrectPage() {
return redirectOnLoginURL === '' || window.location.pathname === redirectOnLoginURL;
}
/* Checks if Memberstack is fully loaded before running any Memberstack-specific code.*/
function memberstackReady(callback) {
function checkAndExecute() {
if (window.$memberstackDom) {
callback(); // Memberstack is ready, run the callback function.
} else {
setTimeout(checkAndExecute, 100); // Wait for 100ms and check again.
}
}
checkAndExecute(); // Start checking if Memberstack is ready.
}
/* Initiates the Stripe checkout process with a specified price ID.*/
async function initiateCheckout(priceId) {
try {
// Set a flag in session storage to indicate that the checkout page was accessed.
sessionStorage.setItem('ms_checkout_viewed', 'true');
await window.$memberstackDom.purchasePlansWithCheckout({
priceId, // The price ID for the product being purchased.
returnUrl: window.location.href, // Redirect the user back here after completing the checkout.
});
} catch (error) {
console.error('Failed to initiate payment:', error); // Provide error details in the console.
}
}
/* Main execution flow that starts once Memberstack is confirmed to be ready */
memberstackReady(() => {
window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
if (member && sessionStorage.getItem('ms_price') && !sessionStorage.getItem('ms_checkout_viewed')) {
initiateCheckout(sessionStorage.getItem('ms_price')); // Start the checkout process if conditions are met.
}
}).catch(error => {
console.error('Failed to retrieve user data:', error); // Log an error if fetching member data fails.
});
});
</script>

#104 - Online Indicator
Show your site visitors your online status based on time zones.
<!-- 💙 MEMBERSCRIPT #104 v0.1 💙 ONLINE INDICATOR -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const businessHours = {
start: 9, // Business hours start at 9 AM
end: 17, // Business hours end at 5 PM
days: [1, 2, 3, 4, 5] // Monday to Friday
};
const colors = {
businessHours: '#34b426',
outsideBusinessHours: '#F25022'
};
const wrappers = document.querySelectorAll('[ms-code-online-wrapper]');
wrappers.forEach(wrapper => {
const timeZone = wrapper.getAttribute('ms-code-online-wrapper');
const dot = wrapper.querySelector('[ms-code-online="dot"]');
const timeSpan = wrapper.querySelector('[ms-code-online="time"]');
const now = new Date();
const formatter = new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
timeZone: timeZone
});
const formattedTime = formatter.format(now);
if (timeSpan) timeSpan.textContent = formattedTime;
const currentDay = now.getDay();
const currentHour = new Date().toLocaleTimeString('en-US', {
hour: '2-digit',
hour12: false,
timeZone: timeZone
});
const isBusinessDay = businessHours.days.includes(currentDay);
const isBusinessHour = currentHour >= businessHours.start && currentHour < businessHours.end;
if (dot) {
dot.style.backgroundColor = (isBusinessDay && isBusinessHour) ? colors.businessHours : colors.outsideBusinessHours;
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #104 v0.1 💙 ONLINE INDICATOR -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const businessHours = {
start: 9, // Business hours start at 9 AM
end: 17, // Business hours end at 5 PM
days: [1, 2, 3, 4, 5] // Monday to Friday
};
const colors = {
businessHours: '#34b426',
outsideBusinessHours: '#F25022'
};
const wrappers = document.querySelectorAll('[ms-code-online-wrapper]');
wrappers.forEach(wrapper => {
const timeZone = wrapper.getAttribute('ms-code-online-wrapper');
const dot = wrapper.querySelector('[ms-code-online="dot"]');
const timeSpan = wrapper.querySelector('[ms-code-online="time"]');
const now = new Date();
const formatter = new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
timeZone: timeZone
});
const formattedTime = formatter.format(now);
if (timeSpan) timeSpan.textContent = formattedTime;
const currentDay = now.getDay();
const currentHour = new Date().toLocaleTimeString('en-US', {
hour: '2-digit',
hour12: false,
timeZone: timeZone
});
const isBusinessDay = businessHours.days.includes(currentDay);
const isBusinessHour = currentHour >= businessHours.start && currentHour < businessHours.end;
if (dot) {
dot.style.backgroundColor = (isBusinessDay && isBusinessHour) ? colors.businessHours : colors.outsideBusinessHours;
}
});
});
</script>

#103 - Custom Booking Form
Add a custom booking form to your website which creates a Google calendar event.
<!-- 💙 MEMBERSCRIPT #103 v0.1 💙 CUSTOM BOOKING FORM -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.33/moment-timezone-with-data.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
function getNextBusinessDays() {
let businessDays = [];
let currentDate = moment();
currentDate.add(1, 'days');
while (businessDays.length < 14) {
if (currentDate.day() !== 0 && currentDate.day() !== 6) {
let formattedDay = currentDate.format('dddd, MMMM D');
let rawDay = currentDate.format('YYYY-MM-DD');
businessDays.push({formattedDay, rawDay});
}
currentDate.add(1, 'days');
}
return businessDays;
}
function generateTimeSlots() {
let slots = [];
let startHour = 9;
let endHour = 16.5;
let currentTime = moment().startOf('day').add(startHour, 'hours');
while (currentTime.hour() + (currentTime.minute() / 60) <= endHour) {
let formattedTime = currentTime.format('h:mm A');
let timeValue = currentTime.format('HH:mm');
slots.push({formattedTime, timeValue});
currentTime.add(30, 'minutes');
}
return slots;
}
function updateTimestamp(day, time, timezone) {
let timestampInput = document.getElementById('timestamp');
if (!timestampInput) {
timestampInput = document.createElement('input');
timestampInput.type = 'hidden';
timestampInput.id = 'timestamp';
timestampInput.name = 'timestamp';
document.querySelector('form').appendChild(timestampInput);
}
let datetime = moment.tz(`${day} ${time}`, "YYYY-MM-DD HH:mm", timezone);
timestampInput.value = datetime.valueOf();
}
function populateFields() {
const days = getNextBusinessDays();
const times = generateTimeSlots();
const daySelect = document.querySelector('[ms-code-booking="day"]');
const timeSelect = document.querySelector('[ms-code-booking="time"]');
const form = daySelect.closest('form');
const timezone = form.getAttribute('ms-code-booking-timezone') || moment.tz.guess();
days.forEach(({formattedDay, rawDay}) => {
let option = new Option(formattedDay, rawDay);
daySelect.appendChild(option);
});
times.forEach(({formattedTime, timeValue}) => {
let option = new Option(formattedTime, timeValue);
timeSelect.appendChild(option);
});
function handleSelectChange() {
if (daySelect.value && timeSelect.value) {
updateTimestamp(daySelect.value, timeSelect.value, timezone);
}
}
daySelect.addEventListener('change', handleSelectChange);
timeSelect.addEventListener('change', handleSelectChange);
}
populateFields();
});
</script>
<!-- 💙 MEMBERSCRIPT #103 v0.1 💙 CUSTOM BOOKING FORM -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.33/moment-timezone-with-data.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
function getNextBusinessDays() {
let businessDays = [];
let currentDate = moment();
currentDate.add(1, 'days');
while (businessDays.length < 14) {
if (currentDate.day() !== 0 && currentDate.day() !== 6) {
let formattedDay = currentDate.format('dddd, MMMM D');
let rawDay = currentDate.format('YYYY-MM-DD');
businessDays.push({formattedDay, rawDay});
}
currentDate.add(1, 'days');
}
return businessDays;
}
function generateTimeSlots() {
let slots = [];
let startHour = 9;
let endHour = 16.5;
let currentTime = moment().startOf('day').add(startHour, 'hours');
while (currentTime.hour() + (currentTime.minute() / 60) <= endHour) {
let formattedTime = currentTime.format('h:mm A');
let timeValue = currentTime.format('HH:mm');
slots.push({formattedTime, timeValue});
currentTime.add(30, 'minutes');
}
return slots;
}
function updateTimestamp(day, time, timezone) {
let timestampInput = document.getElementById('timestamp');
if (!timestampInput) {
timestampInput = document.createElement('input');
timestampInput.type = 'hidden';
timestampInput.id = 'timestamp';
timestampInput.name = 'timestamp';
document.querySelector('form').appendChild(timestampInput);
}
let datetime = moment.tz(`${day} ${time}`, "YYYY-MM-DD HH:mm", timezone);
timestampInput.value = datetime.valueOf();
}
function populateFields() {
const days = getNextBusinessDays();
const times = generateTimeSlots();
const daySelect = document.querySelector('[ms-code-booking="day"]');
const timeSelect = document.querySelector('[ms-code-booking="time"]');
const form = daySelect.closest('form');
const timezone = form.getAttribute('ms-code-booking-timezone') || moment.tz.guess();
days.forEach(({formattedDay, rawDay}) => {
let option = new Option(formattedDay, rawDay);
daySelect.appendChild(option);
});
times.forEach(({formattedTime, timeValue}) => {
let option = new Option(formattedTime, timeValue);
timeSelect.appendChild(option);
});
function handleSelectChange() {
if (daySelect.value && timeSelect.value) {
updateTimestamp(daySelect.value, timeSelect.value, timezone);
}
}
daySelect.addEventListener('change', handleSelectChange);
timeSelect.addEventListener('change', handleSelectChange);
}
populateFields();
});
</script>

#102 - Automatically Resize Textarea Height
Increase or decrease a textarea's height based on its content.
<!-- 💙 MEMBERSCRIPT #102 v0.1 💙 RESIZE TEXTAREA VERTICALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[data-ms-post="content"], [ms-code-resize-input="height"]');
elements.forEach(element => {
if (element.tagName.toLowerCase() === 'textarea') {
element.addEventListener('input', function() {
autoResize(this);
}, false);
}
});
function autoResize(element) {
const maxHeight = parseInt(getComputedStyle(element).maxHeight, 10);
element.style.height = 'auto';
element.style.overflow = 'hidden'; // Prevents scrollbar appearance during height adjustment
if (element.scrollHeight > maxHeight) {
element.style.height = `${maxHeight}px`;
element.style.overflow = 'auto'; // Adds scrollbar when content exceeds max height
} else {
element.style.height = `${element.scrollHeight}px`;
}
}
});
</script>
<!-- 💙 MEMBERSCRIPT #102 v0.1 💙 RESIZE TEXTAREA VERTICALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[data-ms-post="content"], [ms-code-resize-input="height"]');
elements.forEach(element => {
if (element.tagName.toLowerCase() === 'textarea') {
element.addEventListener('input', function() {
autoResize(this);
}, false);
}
});
function autoResize(element) {
const maxHeight = parseInt(getComputedStyle(element).maxHeight, 10);
element.style.height = 'auto';
element.style.overflow = 'hidden'; // Prevents scrollbar appearance during height adjustment
if (element.scrollHeight > maxHeight) {
element.style.height = `${maxHeight}px`;
element.style.overflow = 'auto'; // Adds scrollbar when content exceeds max height
} else {
element.style.height = `${element.scrollHeight}px`;
}
}
});
</script>

#101 - Automatically Resize Input Width
Increase or decrease an input's width based on content.
<!-- 💙 MEMBERSCRIPT #101 v0.1 💙 RESIZE INPUT HORIZONTALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[ms-code-resize-input="width"]');
// Store the initial widths
const initialWidths = new Map();
elements.forEach(element => {
initialWidths.set(element, element.offsetWidth);
});
elements.forEach(element => {
element.addEventListener('input', function() {
autoResizeWidth(this);
});
});
function autoResizeWidth(element) {
// Find the nearest hidden measure element
const measurer = element.nextElementSibling.getAttribute('ms-code-resize-input') === 'hidden-measure'
? element.nextElementSibling
: null;
if (!measurer) return; // Exit if no measurer is found
measurer.textContent = element.value;
const maxWidth = parseInt(getComputedStyle(element).maxWidth, 10);
const minWidth = initialWidths.get(element);
const contentWidth = measurer.offsetWidth;
if (contentWidth > minWidth && contentWidth < maxWidth) {
element.style.width = `${contentWidth}px`;
} else if (contentWidth >= maxWidth) {
element.style.width = `${maxWidth}px`;
} else {
element.style.width = `${minWidth}px`;
}
}
});
</script>
<!-- 💙 MEMBERSCRIPT #101 v0.1 💙 RESIZE INPUT HORIZONTALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[ms-code-resize-input="width"]');
// Store the initial widths
const initialWidths = new Map();
elements.forEach(element => {
initialWidths.set(element, element.offsetWidth);
});
elements.forEach(element => {
element.addEventListener('input', function() {
autoResizeWidth(this);
});
});
function autoResizeWidth(element) {
// Find the nearest hidden measure element
const measurer = element.nextElementSibling.getAttribute('ms-code-resize-input') === 'hidden-measure'
? element.nextElementSibling
: null;
if (!measurer) return; // Exit if no measurer is found
measurer.textContent = element.value;
const maxWidth = parseInt(getComputedStyle(element).maxWidth, 10);
const minWidth = initialWidths.get(element);
const contentWidth = measurer.offsetWidth;
if (contentWidth > minWidth && contentWidth < maxWidth) {
element.style.width = `${contentWidth}px`;
} else if (contentWidth >= maxWidth) {
element.style.width = `${maxWidth}px`;
} else {
element.style.width = `${minWidth}px`;
}
}
});
</script>

#100 - Auto-Compress Image Uploads
Compress image uploads, including profile images.
<!-- 💙 MEMBERSCRIPT #100 v0.1 💙 AUTO-COMPRESSED IMAGE UPLOADS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/compressorjs/1.2.1/compressor.min.js" integrity="sha512-MgYeYFj8R3S6rvZHiJ1xA9cM/VDGcT4eRRFQwGA7qDP7NHbnWKNmAm28z0LVjOuUqjD0T9JxpDMdVqsZOSHaSA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const compressibleInputs = document.querySelectorAll('input[type="file"][ms-code-file_compress]');
compressibleInputs.forEach(fileInput => {
let isCompressing = false;
fileInput.addEventListener('change', function (event) {
if (isCompressing) {
isCompressing = false;
return;
}
if (fileInput.files.length === 0) {
return;
}
const originalFile = fileInput.files[0];
const compressionLevel = parseFloat(fileInput.getAttribute('ms-code-file_compress'));
new Compressor(originalFile, {
quality: compressionLevel,
maxWidth: 2000,
maxHeight: 2000,
success(compressedResult) {
const compressedFile = new File([compressedResult], originalFile.name, {
type: compressedResult.type,
lastModified: Date.now(),
});
const dataTransfer = new DataTransfer();
dataTransfer.items.add(compressedFile);
fileInput.files = dataTransfer.files;
isCompressing = true;
fileInput.dispatchEvent(new Event('change', { bubbles: true }));
},
error(err) {
console.error('Compression Error: ', err.message);
},
});
event.stopPropagation();
}, true);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #100 v0.1 💙 AUTO-COMPRESSED IMAGE UPLOADS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/compressorjs/1.2.1/compressor.min.js" integrity="sha512-MgYeYFj8R3S6rvZHiJ1xA9cM/VDGcT4eRRFQwGA7qDP7NHbnWKNmAm28z0LVjOuUqjD0T9JxpDMdVqsZOSHaSA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
const compressibleInputs = document.querySelectorAll('input[type="file"][ms-code-file_compress]');
compressibleInputs.forEach(fileInput => {
let isCompressing = false;
fileInput.addEventListener('change', function (event) {
if (isCompressing) {
isCompressing = false;
return;
}
if (fileInput.files.length === 0) {
return;
}
const originalFile = fileInput.files[0];
const compressionLevel = parseFloat(fileInput.getAttribute('ms-code-file_compress'));
new Compressor(originalFile, {
quality: compressionLevel,
maxWidth: 2000,
maxHeight: 2000,
success(compressedResult) {
const compressedFile = new File([compressedResult], originalFile.name, {
type: compressedResult.type,
lastModified: Date.now(),
});
const dataTransfer = new DataTransfer();
dataTransfer.items.add(compressedFile);
fileInput.files = dataTransfer.files;
isCompressing = true;
fileInput.dispatchEvent(new Event('change', { bubbles: true }));
},
error(err) {
console.error('Compression Error: ', err.message);
},
});
event.stopPropagation();
}, true);
});
});
</script>

#99 - Custom File Inputs
Turn anything into a file input!
<!-- 💙 MEMBERSCRIPT #99 v0.1 💙 CUSTOM FILE UPLOAD INPUT -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const uploadButtons = document.querySelectorAll('[ms-code-file_uploader]');
uploadButtons.forEach(button => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.name = button.getAttribute('ms-code-file_uploader');
fileInput.accept = button.getAttribute('ms-code-file_types');
document.body.appendChild(fileInput);
button.addEventListener('click', function (e) {
e.preventDefault();
fileInput.click();
});
fileInput.addEventListener('change', function () {
const fileName = fileInput.files[0].name;
button.querySelector('div').textContent = fileName;
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #99 v0.1 💙 CUSTOM FILE UPLOAD INPUT -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const uploadButtons = document.querySelectorAll('[ms-code-file_uploader]');
uploadButtons.forEach(button => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.name = button.getAttribute('ms-code-file_uploader');
fileInput.accept = button.getAttribute('ms-code-file_types');
document.body.appendChild(fileInput);
button.addEventListener('click', function (e) {
e.preventDefault();
fileInput.click();
});
fileInput.addEventListener('change', function () {
const fileName = fileInput.files[0].name;
button.querySelector('div').textContent = fileName;
});
});
});
</script>
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 SlackExplore 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.
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.