Scripts des membres
Une solution basée sur les attributs pour ajouter des fonctionnalités à votre site Webflow.
Il suffit de copier un peu de code, d'ajouter quelques attributs et le tour est joué.
Tous les clients de Memberstack peuvent demander de l'aide dans le Slack 2.0. Veuillez noter qu'il ne s'agit pas de fonctionnalités officielles et que le support ne peut être garanti.

#38 - Champ de téléchargement de fichiers
Ajoutez un outil de téléchargement de fichiers à n'importe quel site et envoyez la soumission à Google Drive, à un courriel ou à n'importe quel endroit de votre choix.
<!-- 💙 MEMBERSCRIPT #38 v0.1 💙 FORM FILE UPLOADER -->
<script>
const forms = document.querySelectorAll('form[ms-code-file-upload="form"]');
forms.forEach((form) => {
form.setAttribute('enctype', 'multipart/form-data');
const uploadInputs = form.querySelectorAll('[ms-code-file-upload-input]');
uploadInputs.forEach((uploadInput) => {
const inputName = uploadInput.getAttribute('ms-code-file-upload-input');
const fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.setAttribute('name', inputName);
fileInput.setAttribute('id', inputName);
fileInput.required = true; // delete this line to make the input optional
uploadInput.appendChild(fileInput);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #38 v0.1 💙 FORM FILE UPLOADER -->
<script>
const forms = document.querySelectorAll('form[ms-code-file-upload="form"]');
forms.forEach((form) => {
form.setAttribute('enctype', 'multipart/form-data');
const uploadInputs = form.querySelectorAll('[ms-code-file-upload-input]');
uploadInputs.forEach((uploadInput) => {
const inputName = uploadInput.getAttribute('ms-code-file-upload-input');
const fileInput = document.createElement('input');
fileInput.setAttribute('type', 'file');
fileInput.setAttribute('name', inputName);
fileInput.setAttribute('id', inputName);
fileInput.required = true; // delete this line to make the input optional
uploadInput.appendChild(fileInput);
});
});
</script>

#37 - Suppression automatique du plan gratuit
Supprimez automatiquement un plan gratuit après une durée déterminée !
<!-- 💙 MEMBERSCRIPT #37 v0.1 💙 MAKE FREE TRIAL EXPIRE AFTER SET TIME -->
<script>
let memberPlanId = "your_plan_ID"; // replace with your actual FREE plan ID
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Fetch the member's data
const member = await memberstack.getMemberJSON();
// Fetch the member's planConnections from local storage
const memberDataFromLocalStorage = JSON.parse(localStorage.getItem('_ms-mem'));
const planConnections = memberDataFromLocalStorage.planConnections;
// Check if the member has x plan
let hasPlan = false;
if (planConnections) {
hasPlan = planConnections.some(planConnection => planConnection.planId === memberPlanId);
}
if (hasPlan) {
// Check the members one-time-date
let currentDate = new Date();
let oneTimeDate = new Date(member.data['one-time-date']);
if (currentDate > oneTimeDate) {
// If the members' one time date has passed, remove x plan
memberstack.removePlan({
planId: memberPlanId
}).then(() => {
// Redirect to /free-trial-expired
window.location.href = "/free-trial-expired";
}).catch(error => {
// Handle error
});
}
}
});
</script>
<!-- 💙 MEMBERSCRIPT #37 v0.1 💙 MAKE FREE TRIAL EXPIRE AFTER SET TIME -->
<script>
let memberPlanId = "your_plan_ID"; // replace with your actual FREE plan ID
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Fetch the member's data
const member = await memberstack.getMemberJSON();
// Fetch the member's planConnections from local storage
const memberDataFromLocalStorage = JSON.parse(localStorage.getItem('_ms-mem'));
const planConnections = memberDataFromLocalStorage.planConnections;
// Check if the member has x plan
let hasPlan = false;
if (planConnections) {
hasPlan = planConnections.some(planConnection => planConnection.planId === memberPlanId);
}
if (hasPlan) {
// Check the members one-time-date
let currentDate = new Date();
let oneTimeDate = new Date(member.data['one-time-date']);
if (currentDate > oneTimeDate) {
// If the members' one time date has passed, remove x plan
memberstack.removePlan({
planId: memberPlanId
}).then(() => {
// Redirect to /free-trial-expired
window.location.href = "/free-trial-expired";
}).catch(error => {
// Handle error
});
}
}
});
</script>

#36 - Validation du mot de passe
Utilisez cette méthode simple pour confirmer que vos membres ont saisi un mot de passe fort.
<!-- 💙 MEMBERSCRIPT #36 v0.1 💙 PASSWORD VALIDATION -->
<script>
window.addEventListener('load', function() {
const passwordInput = document.querySelector('input[data-ms-member="password"]');
const submitButton = document.querySelector('[ms-code-submit-button]');
if (!passwordInput || !submitButton) return; // Return if essential elements are not found
function checkAllValid() {
const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
return Array.from(validationPoints).every(validationPoint => {
const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
return validIcon && validIcon.style.display === 'flex'; // Check for validIcon existence before accessing style
});
}
passwordInput.addEventListener('keyup', function() {
const password = passwordInput.value;
const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
validationPoints.forEach(function(validationPoint) {
const rule = validationPoint.getAttribute('ms-code-pw-validation');
let isValid = false;
// MINIMUM LENGTH VALIDATION POINT
if (rule.startsWith('minlength-')) {
const minLength = parseInt(rule.split('-')[1]);
isValid = password.length >= minLength;
}
// SPECIAL CHARACTER VALIDATION POINT
else if (rule === 'special-character') {
isValid = /[!@#$%^&*(),.?":{}|<>]/g.test(password);
}
// UPPER AND LOWER CASE VALIDATION POINT
else if (rule === 'upper-lower-case') {
isValid = /[a-z]/.test(password) && /[A-Z]/.test(password);
}
// NUMBER VALIDATION POINT
else if (rule === 'number') {
isValid = /\d/.test(password);
}
const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
const invalidIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="false"]');
if (validIcon && invalidIcon) { // Check for existence before accessing style
if (isValid) {
validIcon.style.display = 'flex';
invalidIcon.style.display = 'none';
} else {
validIcon.style.display = 'none';
invalidIcon.style.display = 'flex';
}
}
});
if (checkAllValid()) {
submitButton.classList.remove('disabled');
} else {
submitButton.classList.add('disabled');
}
});
// Trigger keyup event after adding event listener
var event = new Event('keyup');
passwordInput.dispatchEvent(event);
});
</script>
<!-- 💙 MEMBERSCRIPT #36 v0.1 💙 PASSWORD VALIDATION -->
<script>
window.addEventListener('load', function() {
const passwordInput = document.querySelector('input[data-ms-member="password"]');
const submitButton = document.querySelector('[ms-code-submit-button]');
if (!passwordInput || !submitButton) return; // Return if essential elements are not found
function checkAllValid() {
const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
return Array.from(validationPoints).every(validationPoint => {
const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
return validIcon && validIcon.style.display === 'flex'; // Check for validIcon existence before accessing style
});
}
passwordInput.addEventListener('keyup', function() {
const password = passwordInput.value;
const validationPoints = document.querySelectorAll('[ms-code-pw-validation]');
validationPoints.forEach(function(validationPoint) {
const rule = validationPoint.getAttribute('ms-code-pw-validation');
let isValid = false;
// MINIMUM LENGTH VALIDATION POINT
if (rule.startsWith('minlength-')) {
const minLength = parseInt(rule.split('-')[1]);
isValid = password.length >= minLength;
}
// SPECIAL CHARACTER VALIDATION POINT
else if (rule === 'special-character') {
isValid = /[!@#$%^&*(),.?":{}|<>]/g.test(password);
}
// UPPER AND LOWER CASE VALIDATION POINT
else if (rule === 'upper-lower-case') {
isValid = /[a-z]/.test(password) && /[A-Z]/.test(password);
}
// NUMBER VALIDATION POINT
else if (rule === 'number') {
isValid = /\d/.test(password);
}
const validIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="true"]');
const invalidIcon = validationPoint.querySelector('[ms-code-pw-validation-icon="false"]');
if (validIcon && invalidIcon) { // Check for existence before accessing style
if (isValid) {
validIcon.style.display = 'flex';
invalidIcon.style.display = 'none';
} else {
validIcon.style.display = 'none';
invalidIcon.style.display = 'flex';
}
}
});
if (checkAllValid()) {
submitButton.classList.remove('disabled');
} else {
submitButton.classList.add('disabled');
}
});
// Trigger keyup event after adding event listener
var event = new Event('keyup');
passwordInput.dispatchEvent(event);
});
</script>

#35 - Ajouter facilement des FAQ Schema/Rich Snippets
Ajoutez un script et 2 attributs pour permettre la mise à jour constante des rich snippets sur votre page.
<!-- 💙 MEMBERSCRIPT #35 v0.1 💙 FAQ RICH SNIPPETS -->
<script>
let faqArray = [];
let questionElements = document.querySelectorAll('[ms-code-snippet-q]');
let answerElements = document.querySelectorAll('[ms-code-snippet-a]');
for (let i = 0; i < questionElements.length; i++) {
let question = questionElements[i].innerText;
let answer = '';
for (let j = 0; j < answerElements.length; j++) {
if (questionElements[i].getAttribute('ms-code-snippet-q') === answerElements[j].getAttribute('ms-code-snippet-a')) {
answer = answerElements[j].innerText;
break;
}
}
faqArray.push({
"@type": "Question",
"name": question,
"acceptedAnswer": {
"@type": "Answer",
"text": answer
}
});
}
let faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqArray
}
let script = document.createElement('script');
script.type = "application/ld+json";
script.innerHTML = JSON.stringify(faqSchema);
document.getElementsByTagName('head')[0].appendChild(script);
</script>
<!-- 💙 MEMBERSCRIPT #35 v0.1 💙 FAQ RICH SNIPPETS -->
<script>
let faqArray = [];
let questionElements = document.querySelectorAll('[ms-code-snippet-q]');
let answerElements = document.querySelectorAll('[ms-code-snippet-a]');
for (let i = 0; i < questionElements.length; i++) {
let question = questionElements[i].innerText;
let answer = '';
for (let j = 0; j < answerElements.length; j++) {
if (questionElements[i].getAttribute('ms-code-snippet-q') === answerElements[j].getAttribute('ms-code-snippet-a')) {
answer = answerElements[j].innerText;
break;
}
}
faqArray.push({
"@type": "Question",
"name": question,
"acceptedAnswer": {
"@type": "Answer",
"text": answer
}
});
}
let faqSchema = {
"@context": "https://schema.org",
"@type": "FAQPage",
"mainEntity": faqArray
}
let script = document.createElement('script');
script.type = "application/ld+json";
script.innerHTML = JSON.stringify(faqSchema);
document.getElementsByTagName('head')[0].appendChild(script);
</script>

#34 - Exiger l'email professionnel pour la soumission de formulaires
Empêcher les personnes de soumettre un formulaire si leur adresse électronique est une adresse personnelle telle que gmail.
<!-- 💙 MEMBERSCRIPT #34 v0.1 💙 REQUIRE BUSINESS EMAILS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/parsley.js/2.9.2/parsley.min.js"> </script>
<script>
function isPersonalEmail(email) {
var personalDomains = [
"gmail.com",
"yahoo.com",
"hotmail.com",
"aol.com",
"msn.com",
"comcast.net",
"live.com",
"outlook.com",
"ymail.com",
"icloud.com"
];
var emailDomain = email.split('@')[1];
return personalDomains.includes(emailDomain);
}
window.Parsley.addValidator('businessEmail', {
validateString: function(value) {
return !isPersonalEmail(value);
},
messages: {
en: 'Please enter a business email.'
}
});
$(document).ready(function() {
$('form[ms-code-validate-form]').attr('data-parsley-validate', '');
$('input[ms-code-business-email]').attr('data-parsley-business-email', '');
$('form').parsley();
});
$('form').parsley().on('form:error', function() {
$('.parsley-errors-list').addClass('ms-code-validation-error');
});
</script>
<!-- 💙 MEMBERSCRIPT #34 v0.1 💙 REQUIRE BUSINESS EMAILS -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/parsley.js/2.9.2/parsley.min.js"> </script>
<script>
function isPersonalEmail(email) {
var personalDomains = [
"gmail.com",
"yahoo.com",
"hotmail.com",
"aol.com",
"msn.com",
"comcast.net",
"live.com",
"outlook.com",
"ymail.com",
"icloud.com"
];
var emailDomain = email.split('@')[1];
return personalDomains.includes(emailDomain);
}
window.Parsley.addValidator('businessEmail', {
validateString: function(value) {
return !isPersonalEmail(value);
},
messages: {
en: 'Please enter a business email.'
}
});
$(document).ready(function() {
$('form[ms-code-validate-form]').attr('data-parsley-validate', '');
$('input[ms-code-business-email]').attr('data-parsley-business-email', '');
$('form').parsley();
});
$('form').parsley().on('form:error', function() {
$('.parsley-errors-list').addClass('ms-code-validation-error');
});
</script>

#33 - Formater automatiquement les entrées de formulaire
Obliger les entrées de formulaire à suivre un format défini, tel que JJ/MM/AAAA.
<!-- 💙 MEMBERSCRIPT #33 v0.2 💙 AUTOMATICALLY FORMAT FORM INPUTS -->
<script src="https://cdn.jsdelivr.net/npm/cleave.js@1.6.0"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cleave.js/1.6.0/addons/cleave-phone.us.js"> </script>
<script>
document.addEventListener('DOMContentLoaded', function(){
// SELECT ALL ELEMENTS WITH THE ATTRIBUTE "ms-code-autoformat" OR "ms-code-autoformat-prefix"
const elements = document.querySelectorAll('[ms-code-autoformat], [ms-code-autoformat-prefix]');
for (let element of elements) {
const formatType = element.getAttribute('ms-code-autoformat');
const prefix = element.getAttribute('ms-code-autoformat-prefix');
// SET PREFIX
let cleaveOptions = {
prefix: prefix || '',
blocks: [Infinity]
};
// BASED ON THE VALUE OF "ms-code-autoformat", FORMAT THE INPUT
if (formatType) {
switch (formatType) {
// FORMAT PHONE NUMBERS
case 'phone-number':
cleaveOptions.phone = true;
cleaveOptions.phoneRegionCode = 'US';
break;
// FORMAT DATES IN 'YYYY-MM-DD' FORMAT
case 'date-yyyy-mm-dd':
cleaveOptions.date = true;
cleaveOptions.datePattern = ['Y', 'm', 'd'];
break;
// FORMAT DATES IN 'MM-DD-YYYY' FORMAT
case 'date-mm-dd-yyyy':
cleaveOptions.date = true;
cleaveOptions.datePattern = ['m', 'd', 'Y'];
break;
// FORMAT DATES IN 'DD-MM-YYYY' FORMAT
case 'date-dd-mm-yyyy':
cleaveOptions.date = true;
cleaveOptions.datePattern = ['d', 'm', 'Y'];
break;
// FORMAT TIMES IN 'HH-MM-SS' FORMAT
case 'time-hh-mm-ss':
cleaveOptions.time = true;
cleaveOptions.timePattern = ['h', 'm', 's'];
break;
// FORMAT TIMES IN 'HH-MM' FORMAT
case 'time-hh-mm':
cleaveOptions.time = true;
cleaveOptions.timePattern = ['h', 'm'];
break;
// FORMAT NUMBERS WITH THOUSANDS SEPARATORS
case 'number-thousand':
cleaveOptions.numeral = true;
cleaveOptions.numeralThousandsGroupStyle = 'thousand';
break;
}
}
new Cleave(element, cleaveOptions);
}
});
</script>
<!-- 💙 MEMBERSCRIPT #33 v0.2 💙 AUTOMATICALLY FORMAT FORM INPUTS -->
<script src="https://cdn.jsdelivr.net/npm/cleave.js@1.6.0"> </script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cleave.js/1.6.0/addons/cleave-phone.us.js"> </script>
<script>
document.addEventListener('DOMContentLoaded', function(){
// SELECT ALL ELEMENTS WITH THE ATTRIBUTE "ms-code-autoformat" OR "ms-code-autoformat-prefix"
const elements = document.querySelectorAll('[ms-code-autoformat], [ms-code-autoformat-prefix]');
for (let element of elements) {
const formatType = element.getAttribute('ms-code-autoformat');
const prefix = element.getAttribute('ms-code-autoformat-prefix');
// SET PREFIX
let cleaveOptions = {
prefix: prefix || '',
blocks: [Infinity]
};
// BASED ON THE VALUE OF "ms-code-autoformat", FORMAT THE INPUT
if (formatType) {
switch (formatType) {
// FORMAT PHONE NUMBERS
case 'phone-number':
cleaveOptions.phone = true;
cleaveOptions.phoneRegionCode = 'US';
break;
// FORMAT DATES IN 'YYYY-MM-DD' FORMAT
case 'date-yyyy-mm-dd':
cleaveOptions.date = true;
cleaveOptions.datePattern = ['Y', 'm', 'd'];
break;
// FORMAT DATES IN 'MM-DD-YYYY' FORMAT
case 'date-mm-dd-yyyy':
cleaveOptions.date = true;
cleaveOptions.datePattern = ['m', 'd', 'Y'];
break;
// FORMAT DATES IN 'DD-MM-YYYY' FORMAT
case 'date-dd-mm-yyyy':
cleaveOptions.date = true;
cleaveOptions.datePattern = ['d', 'm', 'Y'];
break;
// FORMAT TIMES IN 'HH-MM-SS' FORMAT
case 'time-hh-mm-ss':
cleaveOptions.time = true;
cleaveOptions.timePattern = ['h', 'm', 's'];
break;
// FORMAT TIMES IN 'HH-MM' FORMAT
case 'time-hh-mm':
cleaveOptions.time = true;
cleaveOptions.timePattern = ['h', 'm'];
break;
// FORMAT NUMBERS WITH THOUSANDS SEPARATORS
case 'number-thousand':
cleaveOptions.numeral = true;
cleaveOptions.numeralThousandsGroupStyle = 'thousand';
break;
}
}
new Cleave(element, cleaveOptions);
}
});
</script>

#32 - Définir l'entrée comme obligatoire si elle est visible
Créez des formulaires conditionnels en affichant ou en masquant les données requises.
<!-- 💙 MEMBERSCRIPT #32 v0.1 💙 REQUIRE INPUT IF VISIBLE -->
<script>
document.addEventListener("DOMContentLoaded", function() {
// Function to check if an element is visible
function isElementVisible(element) {
return element.offsetParent !== null;
}
// Every time the user clicks on the document
document.addEventListener('click', function() {
// Get all inputs with the ms-code attribute
const inputs = document.querySelectorAll('[ms-code="required-if-visible"]');
// Loop through each input
inputs.forEach(function(input) {
// Check if the input or its parent is visible
if (isElementVisible(input)) {
// If the input is visible, add the required attribute
input.required = true;
} else {
// If the input is not visible, remove the required attribute
input.required = false;
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #32 v0.1 💙 REQUIRE INPUT IF VISIBLE -->
<script>
document.addEventListener("DOMContentLoaded", function() {
// Function to check if an element is visible
function isElementVisible(element) {
return element.offsetParent !== null;
}
// Every time the user clicks on the document
document.addEventListener('click', function() {
// Get all inputs with the ms-code attribute
const inputs = document.querySelectorAll('[ms-code="required-if-visible"]');
// Loop through each input
inputs.forEach(function(input) {
// Check if the input or its parent is visible
if (isElementVisible(input)) {
// If the input is visible, add the required attribute
input.required = true;
} else {
// If the input is not visible, remove the required attribute
input.required = false;
}
});
});
});
</script>

#31 - Ouvrir un onglet du Webflow avec un lien
Ce script génère automatiquement des liens vers vos onglets Webflow.
<!-- 💙 MEMBERSCRIPT #31 v0.2 💙 OPEN WEBFLOW TAB w/ LINK -->
<!-- You can link to tabs like this 👉 www.yoursite.com#tab-name-lowercase -->
<!-- And sub tabs like this 👉 www.yoursite.com#tab-name/sub-tab-name -->
<script>
var Webflow = Webflow || [];
Webflow.push(() => {
function changeTab(shouldScroll = false) {
const hashSegments = window.location.hash.substring(1).split('/');
const offset = 90; // change this to match your fixed header height if you have one
let lastTabTarget;
for (const segment of hashSegments) {
const tabTarget = document.querySelector(`[data-w-tab="${segment}"]`);
if (tabTarget) {
tabTarget.click();
lastTabTarget = tabTarget;
}
}
if (shouldScroll && lastTabTarget) {
window.scrollTo({
top: $(lastTabTarget).offset().top - offset, behavior: 'smooth'
});
}
}
const tabs = document.querySelectorAll('[data-w-tab]');
tabs.forEach(tab => {
const dataWTabValue = tab.dataset.wTab;
const parsedDataTab = dataWTabValue.replace(/\s+/g,"-").toLowerCase();
tab.dataset.wTab = parsedDataTab;
tab.addEventListener('click', () => {
history.pushState({}, '', `#${parsedDataTab}`);
});
});
if (window.location.hash) {
requestAnimationFrame(() => { changeTab(true); });
}
window.addEventListener('hashchange', () => { changeTab() });
});
</script>
<!-- 💙 MEMBERSCRIPT #31 v0.2 💙 OPEN WEBFLOW TAB w/ LINK -->
<!-- You can link to tabs like this 👉 www.yoursite.com#tab-name-lowercase -->
<!-- And sub tabs like this 👉 www.yoursite.com#tab-name/sub-tab-name -->
<script>
var Webflow = Webflow || [];
Webflow.push(() => {
function changeTab(shouldScroll = false) {
const hashSegments = window.location.hash.substring(1).split('/');
const offset = 90; // change this to match your fixed header height if you have one
let lastTabTarget;
for (const segment of hashSegments) {
const tabTarget = document.querySelector(`[data-w-tab="${segment}"]`);
if (tabTarget) {
tabTarget.click();
lastTabTarget = tabTarget;
}
}
if (shouldScroll && lastTabTarget) {
window.scrollTo({
top: $(lastTabTarget).offset().top - offset, behavior: 'smooth'
});
}
}
const tabs = document.querySelectorAll('[data-w-tab]');
tabs.forEach(tab => {
const dataWTabValue = tab.dataset.wTab;
const parsedDataTab = dataWTabValue.replace(/\s+/g,"-").toLowerCase();
tab.dataset.wTab = parsedDataTab;
tab.addEventListener('click', () => {
history.pushState({}, '', `#${parsedDataTab}`);
});
});
if (window.location.hash) {
requestAnimationFrame(() => { changeTab(true); });
}
window.addEventListener('hashchange', () => { changeTab() });
});
</script>

#30 - Compter les éléments sur la page et mettre à jour le nombre
Vérifier combien d'éléments dotés d'un attribut donné se trouvent sur la page et appliquer ce nombre à un texte.
<!-- 💙 MEMBERSCRIPT #30 v0.1 💙 COUNT ITEMS AND DISPLAY COUNT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
setTimeout(function() {
const rollupItems = document.querySelectorAll('[ms-code-rollup-item]');
const rollupNumbers = document.querySelectorAll('[ms-code-rollup-number]');
const updateRollupNumbers = function() {
const rollupCountMap = new Map();
rollupItems.forEach(item => {
const rollupKey = item.getAttribute('ms-code-rollup-item');
const count = rollupCountMap.get(rollupKey) || 0;
rollupCountMap.set(rollupKey, count + 1);
});
rollupNumbers.forEach(number => {
const rollupKey = number.getAttribute('ms-code-rollup-number');
const count = rollupCountMap.get(rollupKey) || 0;
number.textContent = count;
});
};
updateRollupNumbers(); // Initial update
// Polling function to periodically update rollup numbers
const pollRollupNumbers = function() {
updateRollupNumbers();
setTimeout(pollRollupNumbers, 1000); // Adjust the polling interval as needed (in milliseconds)
};
pollRollupNumbers(); // Start polling
}, 2000);
});
</script>
<!-- 💙 MEMBERSCRIPT #30 v0.1 💙 COUNT ITEMS AND DISPLAY COUNT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
setTimeout(function() {
const rollupItems = document.querySelectorAll('[ms-code-rollup-item]');
const rollupNumbers = document.querySelectorAll('[ms-code-rollup-number]');
const updateRollupNumbers = function() {
const rollupCountMap = new Map();
rollupItems.forEach(item => {
const rollupKey = item.getAttribute('ms-code-rollup-item');
const count = rollupCountMap.get(rollupKey) || 0;
rollupCountMap.set(rollupKey, count + 1);
});
rollupNumbers.forEach(number => {
const rollupKey = number.getAttribute('ms-code-rollup-number');
const count = rollupCountMap.get(rollupKey) || 0;
number.textContent = count;
});
};
updateRollupNumbers(); // Initial update
// Polling function to periodically update rollup numbers
const pollRollupNumbers = function() {
updateRollupNumbers();
setTimeout(pollRollupNumbers, 1000); // Adjust the polling interval as needed (in milliseconds)
};
pollRollupNumbers(); // Start polling
}, 2000);
});
</script>

#29 - Fixer temporairement la hauteur des éléments au chargement
Forcer un élément à avoir une hauteur déterminée pendant une certaine durée lors du chargement de la page.
<!-- 💙 MEMBERSCRIPT #29 v0.1 💙 TEMPORARILY FIX ELEMENT HEIGHT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const elements = document.querySelectorAll('[ms-code-temp-height]');
elements.forEach(element => {
const attributeValue = element.getAttribute('ms-code-temp-height');
if (attributeValue) {
const [time, height] = attributeValue.split(':');
if (!isNaN(time) && !isNaN(height)) {
const defaultHeight = element.style.height;
setTimeout(() => {
element.style.height = defaultHeight;
}, parseInt(time));
element.style.height = height + 'px';
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #29 v0.1 💙 TEMPORARILY FIX ELEMENT HEIGHT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const elements = document.querySelectorAll('[ms-code-temp-height]');
elements.forEach(element => {
const attributeValue = element.getAttribute('ms-code-temp-height');
if (attributeValue) {
const [time, height] = attributeValue.split(':');
if (!isNaN(time) && !isNaN(height)) {
const defaultHeight = element.style.height;
setTimeout(() => {
element.style.height = defaultHeight;
}, parseInt(time));
element.style.height = height + 'px';
}
}
});
});
</script>

#28 - Affichage d'un élément basé sur la transmission d'une date JSON
Vérifiez la date unique du point 27 et affichez/masquez un élément en fonction de cette date.
<!-- 💙 MEMBERSCRIPT #28 v0.1 💙 CHECK ONE-TIME DATE AND UPDATE ELEMENT DISPLAY -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
const updateElementVisibility = async function() {
const member = await memberstack.getMemberJSON();
if (!member.data || !member.data['one-time-date']) {
// Member data or expiration date not available, do nothing
return;
}
const expirationDate = new Date(member.data['one-time-date']);
const currentDate = new Date();
if (currentDate < expirationDate) {
// Expiration date has not passed, update element visibility
const elements = document.querySelectorAll('[ms-code-element-temporary]');
elements.forEach(element => {
const displayValue = element.getAttribute('ms-code-element-temporary');
// Update element visibility based on the attribute value
element.style.display = displayValue;
});
}
};
updateElementVisibility();
});
</script>
<!-- 💙 MEMBERSCRIPT #28 v0.1 💙 CHECK ONE-TIME DATE AND UPDATE ELEMENT DISPLAY -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
const updateElementVisibility = async function() {
const member = await memberstack.getMemberJSON();
if (!member.data || !member.data['one-time-date']) {
// Member data or expiration date not available, do nothing
return;
}
const expirationDate = new Date(member.data['one-time-date']);
const currentDate = new Date();
if (currentDate < expirationDate) {
// Expiration date has not passed, update element visibility
const elements = document.querySelectorAll('[ms-code-element-temporary]');
elements.forEach(element => {
const displayValue = element.getAttribute('ms-code-element-temporary');
// Update element visibility based on the attribute value
element.style.display = displayValue;
});
}
};
updateElementVisibility();
});
</script>

#27 - Fixer une date unique pour l'inscription
Appliquez une date à votre JSON de membre après l'inscription, qui peut être utilisée pour n'importe quoi.
JSON uniquement
Si vous n'avez pas besoin d'ajouter la date à un champ personnalisé, utilisez ceci.
<!-- 💙 MEMBERSCRIPT #27 v0.1 💙 SET ONE TIME DATE -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
const updateDate = async function() {
const member = await memberstack.getMemberJSON();
if (!member.data) {
member.data = {};
}
if (!member.data['one-time-date']) {
const currentTime = new Date();
const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
member.data['one-time-date'] = expirationTime.toISOString();
// Update member JSON
await memberstack.updateMemberJSON({
json: member.data
});
}
};
updateDate();
});
</script>
JSON + Champ personnalisé
Utilisez cette option si vous devez ajouter la date à un champ personnalisé (généralement dans le cadre d'automatismes).
<!-- 💙💙 MEMBERSCRIPT #27 v0.1.1 (CUSTOM FIELD) 💙 SET ONE TIME DATE -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
const member = await memberstack.getMemberJSON();
if (!member.data) {
member.data = {};
}
// Check if the user has the 'one-time-date' custom field in Memberstack
if (!msMem.customFields || !msMem.customFields['one-time-date']) {
const currentTime = new Date();
const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
const updatedCustomFields = {
...msMem.customFields,
'one-time-date': expirationTime.toISOString()
};
member.data['one-time-date'] = expirationTime.toISOString();
await memberstack.updateMemberJSON({
json: member.data
});
await memberstack.updateMember({
customFields: updatedCustomFields
});
}
});
</script>
JSON uniquement
Si vous n'avez pas besoin d'ajouter la date à un champ personnalisé, utilisez ceci.
<!-- 💙 MEMBERSCRIPT #27 v0.1 💙 SET ONE TIME DATE -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
const updateDate = async function() {
const member = await memberstack.getMemberJSON();
if (!member.data) {
member.data = {};
}
if (!member.data['one-time-date']) {
const currentTime = new Date();
const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
member.data['one-time-date'] = expirationTime.toISOString();
// Update member JSON
await memberstack.updateMemberJSON({
json: member.data
});
}
};
updateDate();
});
</script>
JSON + Champ personnalisé
Utilisez cette option si vous devez ajouter la date à un champ personnalisé (généralement dans le cadre d'automatismes).
<!-- 💙💙 MEMBERSCRIPT #27 v0.1.1 (CUSTOM FIELD) 💙 SET ONE TIME DATE -->
<script>
document.addEventListener('DOMContentLoaded', async function() {
const memberstack = window.$memberstackDom;
const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
const member = await memberstack.getMemberJSON();
if (!member.data) {
member.data = {};
}
// Check if the user has the 'one-time-date' custom field in Memberstack
if (!msMem.customFields || !msMem.customFields['one-time-date']) {
const currentTime = new Date();
const expirationTime = new Date(currentTime.getTime() + (3 * 60 * 60 * 1000)); // Set the expiration time to 3 hours (adjust as needed)
const updatedCustomFields = {
...msMem.customFields,
'one-time-date': expirationTime.toISOString()
};
member.data['one-time-date'] = expirationTime.toISOString();
await memberstack.updateMemberJSON({
json: member.data
});
await memberstack.updateMember({
customFields: updatedCustomFields
});
}
});
</script>

#26 - Gate Content With Custom Modals
Utilisez des modèles personnalisés pour inciter vos visiteurs à obtenir un compte payant !
<!-- 💙 MEMBERSCRIPT #26 v0.1 💙 GATE CONTENT WITH MODALS -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) {
// Member is not logged in
const triggers = document.querySelectorAll('[ms-code-gate-modal-trigger]');
const boxes = document.querySelectorAll('[ms-code-gate-modal-box]');
triggers.forEach(trigger => {
trigger.addEventListener('click', () => {
const targetId = trigger.getAttribute('ms-code-gate-modal-trigger');
const box = document.querySelector(`[ms-code-gate-modal-box="${targetId}"]`);
if (box) {
box.style.display = 'flex';
}
});
// Remove links and attributes from trigger
// Uncomment the lines below to enable this functionality
// trigger.removeAttribute('href');
// trigger.removeAttribute('target');
// trigger.removeAttribute('rel');
// trigger.removeAttribute('onclick');
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #26 v0.1 💙 GATE CONTENT WITH MODALS -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) {
// Member is not logged in
const triggers = document.querySelectorAll('[ms-code-gate-modal-trigger]');
const boxes = document.querySelectorAll('[ms-code-gate-modal-box]');
triggers.forEach(trigger => {
trigger.addEventListener('click', () => {
const targetId = trigger.getAttribute('ms-code-gate-modal-trigger');
const box = document.querySelector(`[ms-code-gate-modal-box="${targetId}"]`);
if (box) {
box.style.display = 'flex';
}
});
// Remove links and attributes from trigger
// Uncomment the lines below to enable this functionality
// trigger.removeAttribute('href');
// trigger.removeAttribute('target');
// trigger.removeAttribute('rel');
// trigger.removeAttribute('onclick');
});
}
});
</script>

#25 - Masquer un élément en fonction des enfants d'un autre élément
Supprime un élément de la page si un autre élément défini n'a pas d'éléments enfants.
<!-- 💙 MEMBERSCRIPT #25 v0.1 💙 HIDE ELEMENT BASED ON OTHER ELEMENT CHILDREN -->
<script>
window.addEventListener('DOMContentLoaded', function() {
const subjectAttribute = 'ms-code-visibility-subject';
const targetAttribute = 'ms-code-visibility-target';
const subjectElement = document.querySelector(`[${subjectAttribute}]`);
const targetElement = document.querySelector(`[${targetAttribute}]`);
if (!subjectElement || !targetElement) {
console.error('Subject or target element not found');
return;
}
function checkVisibility() {
const children = subjectElement.children;
let allHidden = true;
for (let i = 0; i < children.length; i++) {
const child = children[i];
const computedStyle = window.getComputedStyle(child);
if (computedStyle.display !== 'none') {
allHidden = false;
break;
}
}
if (children.length === 0 || allHidden) {
targetElement.style.display = 'none';
} else {
targetElement.style.display = '';
}
}
// Check visibility initially
checkVisibility();
// Check visibility whenever the subject element or its children change
const observer = new MutationObserver(checkVisibility);
observer.observe(subjectElement, { childList: true, subtree: true });
});
</script>
<!-- 💙 MEMBERSCRIPT #25 v0.1 💙 HIDE ELEMENT BASED ON OTHER ELEMENT CHILDREN -->
<script>
window.addEventListener('DOMContentLoaded', function() {
const subjectAttribute = 'ms-code-visibility-subject';
const targetAttribute = 'ms-code-visibility-target';
const subjectElement = document.querySelector(`[${subjectAttribute}]`);
const targetElement = document.querySelector(`[${targetAttribute}]`);
if (!subjectElement || !targetElement) {
console.error('Subject or target element not found');
return;
}
function checkVisibility() {
const children = subjectElement.children;
let allHidden = true;
for (let i = 0; i < children.length; i++) {
const child = children[i];
const computedStyle = window.getComputedStyle(child);
if (computedStyle.display !== 'none') {
allHidden = false;
break;
}
}
if (children.length === 0 || allHidden) {
targetElement.style.display = 'none';
} else {
targetElement.style.display = '';
}
}
// Check visibility initially
checkVisibility();
// Check visibility whenever the subject element or its children change
const observer = new MutationObserver(checkVisibility);
observer.observe(subjectElement, { childList: true, subtree: true });
});
</script>

#24 - Filtrer les listes en fonction de l'élément
Filtrer n'importe quel type de liste en fonction de la présence d'un élément parmi ses enfants.
Option standard
Cela fonctionne dans la plupart des cas.
<!-- 💙 MEMBERSCRIPT #24 v0.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const filterListItems = function(list, filterAttribute) {
const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);
items.forEach(item => {
const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);
if (!target || window.getComputedStyle(target).display === 'none') {
item.style.display = 'none';
} else {
item.style.display = '';
}
});
};
const filterLists = document.querySelectorAll('[ms-code-filter-list]');
const updateFiltering = function() {
filterLists.forEach(list => {
const filterAttribute = list.getAttribute('ms-code-filter-list');
filterListItems(list, filterAttribute);
});
};
const observeListChanges = function() {
const observer = new MutationObserver(updateFiltering);
filterLists.forEach(list => observer.observe(list, { childList: true, subtree: true }));
};
updateFiltering();
observeListChanges();
});
</script>
Option d'interrogation
Si la norme ne fonctionne pas, essayez ceci.
<!-- 💙 MEMBERSCRIPT #24 v0.1.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT (POLLING) -->
<script>
window.addEventListener("DOMContentLoaded", function() {
const filterListItems = function(list, filterAttribute) {
const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);
items.forEach(item => {
const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);
if (!target || window.getComputedStyle(target).display === 'none') {
item.style.display = 'none';
} else {
item.style.display = '';
}
});
};
const filterLists = document.querySelectorAll('[ms-code-filter-list]');
const updateFiltering = function() {
filterLists.forEach(list => {
const filterAttribute = list.getAttribute('ms-code-filter-list');
filterListItems(list, filterAttribute);
});
};
const pollPage = function() {
updateFiltering();
setTimeout(pollPage, 1000); // Poll every 1 second
};
pollPage();
});
</script>
Option standard
Cela fonctionne dans la plupart des cas.
<!-- 💙 MEMBERSCRIPT #24 v0.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const filterListItems = function(list, filterAttribute) {
const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);
items.forEach(item => {
const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);
if (!target || window.getComputedStyle(target).display === 'none') {
item.style.display = 'none';
} else {
item.style.display = '';
}
});
};
const filterLists = document.querySelectorAll('[ms-code-filter-list]');
const updateFiltering = function() {
filterLists.forEach(list => {
const filterAttribute = list.getAttribute('ms-code-filter-list');
filterListItems(list, filterAttribute);
});
};
const observeListChanges = function() {
const observer = new MutationObserver(updateFiltering);
filterLists.forEach(list => observer.observe(list, { childList: true, subtree: true }));
};
updateFiltering();
observeListChanges();
});
</script>
Option d'interrogation
Si la norme ne fonctionne pas, essayez ceci.
<!-- 💙 MEMBERSCRIPT #24 v0.1.1 💙 FILTER ITEMS WITHIN LIST BASED ON ELEMENT (POLLING) -->
<script>
window.addEventListener("DOMContentLoaded", function() {
const filterListItems = function(list, filterAttribute) {
const items = list.querySelectorAll(`[ms-code-filter-item="${filterAttribute}"]`);
items.forEach(item => {
const target = item.querySelector(`[ms-code-filter-target="${filterAttribute}"]`);
if (!target || window.getComputedStyle(target).display === 'none') {
item.style.display = 'none';
} else {
item.style.display = '';
}
});
};
const filterLists = document.querySelectorAll('[ms-code-filter-list]');
const updateFiltering = function() {
filterLists.forEach(list => {
const filterAttribute = list.getAttribute('ms-code-filter-list');
filterListItems(list, filterAttribute);
});
};
const pollPage = function() {
updateFiltering();
setTimeout(pollPage, 1000); // Poll every 1 second
};
pollPage();
});
</script>

#23 - Écrans squelettes / chargeurs de contenu
Ajoutez facilement ces états de chargement standard à votre site en quelques secondes.
Mode lumineux
A utiliser sur fond blanc
<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
skeletonElements.forEach(element => {
// Create a skeleton div
const skeletonDiv = document.createElement('div');
skeletonDiv.classList.add('skeleton-loader');
// Add the skeleton div to the current element
element.style.position = 'relative';
element.appendChild(skeletonDiv);
// Get delay from the attribute
let delay = element.getAttribute('ms-code-skeleton');
// If attribute value is not a number, set default delay as 2000ms
if (isNaN(delay)) {
delay = 2000;
}
setTimeout(() => {
// Remove the skeleton loader div after delay
const skeletonDiv = element.querySelector('.skeleton-loader');
element.removeChild(skeletonDiv);
}, delay);
});
});
</script>
<style>
.skeleton-loader {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: inherit; /* Inherit the border-radius of the parent element */
background: linear-gradient(to right, #f6f7f8 25%, #e0e0e0 50%, #f6f7f8 75%);
background-size: 200% 100%; /* Increase the size of the background image */
z-index: 1; /* Make sure the skeleton loader is on top of the content */
animation: skeleton 1s infinite linear;
}
@keyframes skeleton {
0% { background-position: -100% 0; }
100% { background-position: 100% 0; }
}
[ms-code-skeleton] {
background-clip: padding-box;
}
</style>
Mode sombre
A utiliser sur fond noir
<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
skeletonElements.forEach(element => {
// Create a skeleton div
const skeletonDiv = document.createElement('div');
skeletonDiv.classList.add('skeleton-loader');
// Add the skeleton div to the current element
element.style.position = 'relative';
element.appendChild(skeletonDiv);
// Get delay from the attribute
let delay = element.getAttribute('ms-code-skeleton');
// If attribute value is not a number, set default delay as 2000ms
if (isNaN(delay)) {
delay = 2000;
}
setTimeout(() => {
// Remove the skeleton loader div after delay
const skeletonDiv = element.querySelector('.skeleton-loader');
element.removeChild(skeletonDiv);
}, delay);
});
});
</script>
<style>
.skeleton-loader {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: inherit;
background: linear-gradient(to right, #222222 25%, #333333 50%, #222222 75%); /* Updated background colors */
background-size: 200% 100%;
z-index: 1;
animation: skeleton 1s infinite linear;
}
@keyframes skeleton {
0% { background-position: -100% 0; }
100% { background-position: 100% 0; }
}
[ms-code-skeleton] {
background-clip: padding-box;
}
</style>
Mode lumineux
A utiliser sur fond blanc
<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
skeletonElements.forEach(element => {
// Create a skeleton div
const skeletonDiv = document.createElement('div');
skeletonDiv.classList.add('skeleton-loader');
// Add the skeleton div to the current element
element.style.position = 'relative';
element.appendChild(skeletonDiv);
// Get delay from the attribute
let delay = element.getAttribute('ms-code-skeleton');
// If attribute value is not a number, set default delay as 2000ms
if (isNaN(delay)) {
delay = 2000;
}
setTimeout(() => {
// Remove the skeleton loader div after delay
const skeletonDiv = element.querySelector('.skeleton-loader');
element.removeChild(skeletonDiv);
}, delay);
});
});
</script>
<style>
.skeleton-loader {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: inherit; /* Inherit the border-radius of the parent element */
background: linear-gradient(to right, #f6f7f8 25%, #e0e0e0 50%, #f6f7f8 75%);
background-size: 200% 100%; /* Increase the size of the background image */
z-index: 1; /* Make sure the skeleton loader is on top of the content */
animation: skeleton 1s infinite linear;
}
@keyframes skeleton {
0% { background-position: -100% 0; }
100% { background-position: 100% 0; }
}
[ms-code-skeleton] {
background-clip: padding-box;
}
</style>
Mode sombre
A utiliser sur fond noir
<!-- 💙 MEMBERSCRIPT #23 v0.1 💙 SKELETON SCREENS/CONTENT LOADERS -->
<script>
window.addEventListener("DOMContentLoaded", (event) => {
const skeletonElements = document.querySelectorAll('[ms-code-skeleton]');
skeletonElements.forEach(element => {
// Create a skeleton div
const skeletonDiv = document.createElement('div');
skeletonDiv.classList.add('skeleton-loader');
// Add the skeleton div to the current element
element.style.position = 'relative';
element.appendChild(skeletonDiv);
// Get delay from the attribute
let delay = element.getAttribute('ms-code-skeleton');
// If attribute value is not a number, set default delay as 2000ms
if (isNaN(delay)) {
delay = 2000;
}
setTimeout(() => {
// Remove the skeleton loader div after delay
const skeletonDiv = element.querySelector('.skeleton-loader');
element.removeChild(skeletonDiv);
}, delay);
});
});
</script>
<style>
.skeleton-loader {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: inherit;
background: linear-gradient(to right, #222222 25%, #333333 50%, #222222 75%); /* Updated background colors */
background-size: 200% 100%;
z-index: 1;
animation: skeleton 1s infinite linear;
}
@keyframes skeleton {
0% { background-position: -100% 0; }
100% { background-position: 100% 0; }
}
[ms-code-skeleton] {
background-clip: padding-box;
}
</style>

#22 - Désactiver le bouton Soumettre jusqu'à ce que les champs requis soient remplis
Faites griser votre bouton d'envoi jusqu'à ce que toutes les valeurs requises soient renseignées.
<!-- 💙 MEMBERSCRIPT #22 v0.1 💙 DISABLE SUBMIT BUTTON UNTIL REQUIRED FIELDS ARE COMPLETE -->
<script>
window.onload = function() {
const forms = document.querySelectorAll('form[ms-code-submit-form]');
forms.forEach(form => {
const submitButton = form.querySelector('input[type="submit"]');
const requiredFields = form.querySelectorAll('input[required]');
form.addEventListener('input', function() {
const allFilled = Array.from(requiredFields).every(field => field.value.trim() !== '');
if (allFilled) {
submitButton.classList.add('submit-enabled');
} else {
submitButton.classList.remove('submit-enabled');
}
});
});
};
</script>
<!-- 💙 MEMBERSCRIPT #22 v0.1 💙 DISABLE SUBMIT BUTTON UNTIL REQUIRED FIELDS ARE COMPLETE -->
<script>
window.onload = function() {
const forms = document.querySelectorAll('form[ms-code-submit-form]');
forms.forEach(form => {
const submitButton = form.querySelector('input[type="submit"]');
const requiredFields = form.querySelectorAll('input[required]');
form.addEventListener('input', function() {
const allFilled = Array.from(requiredFields).every(field => field.value.trim() !== '');
if (allFilled) {
submitButton.classList.add('submit-enabled');
} else {
submitButton.classList.remove('submit-enabled');
}
});
});
};
</script>

#21 - Notifications personnalisées sur Toast
Affichez des boîtes à toasts personnalisées en cliquant sur un élément !
<!-- 💙 MEMBERSCRIPT #21 v0.1 💙 CUSTOM TOAST BOXES -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const toastTriggers = document.querySelectorAll("[ms-code-toast-trigger]");
toastTriggers.forEach(trigger => {
trigger.addEventListener("click", function() {
const triggerId = trigger.getAttribute("ms-code-toast-trigger");
const toastBox = document.querySelector(`[ms-code-toast-box="${triggerId}"]`);
if (toastBox) {
const fadeInDuration = 200;
const fadeOutDuration = 200;
const staticDuration = 2000;
const totalDuration = fadeInDuration + staticDuration + fadeOutDuration;
toastBox.style.opacity = "0";
toastBox.style.display = "block";
let currentTime = 0;
const fade = function() {
currentTime += 10;
const opacity = currentTime < fadeInDuration
? currentTime / fadeInDuration
: currentTime < fadeInDuration + staticDuration
? 1
: 1 - (currentTime - fadeInDuration - staticDuration) / fadeOutDuration;
toastBox.style.opacity = opacity;
if (currentTime < totalDuration) {
setTimeout(fade, 10);
} else {
toastBox.style.display = "none";
}
};
fade();
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #21 v0.1 💙 CUSTOM TOAST BOXES -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const toastTriggers = document.querySelectorAll("[ms-code-toast-trigger]");
toastTriggers.forEach(trigger => {
trigger.addEventListener("click", function() {
const triggerId = trigger.getAttribute("ms-code-toast-trigger");
const toastBox = document.querySelector(`[ms-code-toast-box="${triggerId}"]`);
if (toastBox) {
const fadeInDuration = 200;
const fadeOutDuration = 200;
const staticDuration = 2000;
const totalDuration = fadeInDuration + staticDuration + fadeOutDuration;
toastBox.style.opacity = "0";
toastBox.style.display = "block";
let currentTime = 0;
const fade = function() {
currentTime += 10;
const opacity = currentTime < fadeInDuration
? currentTime / fadeInDuration
: currentTime < fadeInDuration + staticDuration
? 1
: 1 - (currentTime - fadeInDuration - staticDuration) / fadeOutDuration;
toastBox.style.opacity = opacity;
if (currentTime < totalDuration) {
setTimeout(fade, 10);
} else {
toastBox.style.display = "none";
}
};
fade();
}
});
});
});
</script>

#19 - Ajouter l'URL d'un champ personnalisé à l'IFrame SRC
Créez une fonctionnalité d'intégration spécifique aux membres avec cette solution de champ personnalisé iframe !
<!-- 💙 MEMBERSCRIPT #19 v0.1 💙 ADD CUSTOM FIELD AS AN IFRAME SRC -->
<script>
document.addEventListener("DOMContentLoaded", function() {
// Parse member data from local storage
const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');
// Check if the user is logged in
if(memberData && memberData.id) {
// Get custom fields
const customFields = memberData.customFields;
// Select all elements with 'ms-code-field-link' attribute
const elements = document.querySelectorAll('[ms-code-field-link]');
// Iterate over all selected elements
elements.forEach(element => {
// Get custom field key from 'ms-code-field-link' attribute
const fieldKey = element.getAttribute('ms-code-field-link');
// If key exists in custom fields, set element src to the corresponding value
if(customFields.hasOwnProperty(fieldKey)) {
element.setAttribute('src', customFields[fieldKey]);
}
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #19 v0.1 💙 ADD CUSTOM FIELD AS AN IFRAME SRC -->
<script>
document.addEventListener("DOMContentLoaded", function() {
// Parse member data from local storage
const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');
// Check if the user is logged in
if(memberData && memberData.id) {
// Get custom fields
const customFields = memberData.customFields;
// Select all elements with 'ms-code-field-link' attribute
const elements = document.querySelectorAll('[ms-code-field-link]');
// Iterate over all selected elements
elements.forEach(element => {
// Get custom field key from 'ms-code-field-link' attribute
const fieldKey = element.getAttribute('ms-code-field-link');
// If key exists in custom fields, set element src to the corresponding value
if(customFields.hasOwnProperty(fieldKey)) {
element.setAttribute('src', customFields[fieldKey]);
}
});
}
});
</script>

#18 - Tronquer facilement le texte
Ajoutez un attribut et un simple script pour tronquer le texte de manière programmée !
<!-- 💙 MEMBERSCRIPT #18 v0.2 💙 - EASILY TRUNCATE TEXT -->
<script>
const elements = document.querySelectorAll('[ms-code-truncate]');
elements.forEach((element) => {
const charLimit = parseInt(element.getAttribute('ms-code-truncate'));
// Create a helper function that will recursively traverse the DOM tree
const traverseNodes = (node, count) => {
for (let child of node.childNodes) {
// If the node is a text node, truncate if necessary
if (child.nodeType === Node.TEXT_NODE) {
if (count + child.textContent.length > charLimit) {
child.textContent = child.textContent.slice(0, charLimit - count) + '...';
return count + child.textContent.length;
}
count += child.textContent.length;
}
// If the node is an element, recurse through its children
else if (child.nodeType === Node.ELEMENT_NODE) {
count = traverseNodes(child, count);
}
}
return count;
}
// Create a deep clone of the element to work on. This is so that we don't modify the original element
// until we have completely finished processing.
const clone = element.cloneNode(true);
// Traverse and truncate the cloned node
traverseNodes(clone, 0);
// Replace the original element with our modified clone
element.parentNode.replaceChild(clone, element);
});
</script>
<!-- 💙 MEMBERSCRIPT #18 v0.2 💙 - EASILY TRUNCATE TEXT -->
<script>
const elements = document.querySelectorAll('[ms-code-truncate]');
elements.forEach((element) => {
const charLimit = parseInt(element.getAttribute('ms-code-truncate'));
// Create a helper function that will recursively traverse the DOM tree
const traverseNodes = (node, count) => {
for (let child of node.childNodes) {
// If the node is a text node, truncate if necessary
if (child.nodeType === Node.TEXT_NODE) {
if (count + child.textContent.length > charLimit) {
child.textContent = child.textContent.slice(0, charLimit - count) + '...';
return count + child.textContent.length;
}
count += child.textContent.length;
}
// If the node is an element, recurse through its children
else if (child.nodeType === Node.ELEMENT_NODE) {
count = traverseNodes(child, count);
}
}
return count;
}
// Create a deep clone of the element to work on. This is so that we don't modify the original element
// until we have completely finished processing.
const clone = element.cloneNode(true);
// Traverse and truncate the cloned node
traverseNodes(clone, 0);
// Replace the original element with our modified clone
element.parentNode.replaceChild(clone, element);
});
</script>
Scripts des membres
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.

#158 - Emoji Feedback Widget for Memberstack
This widget lets your logged-in members quickly share how they feel using simple emoji buttons.
<!-- 💙 MEMBERSCRIPT #158 v1.0 💙 - EMOJI FEEDBACK WIDGET -->
<!--
Collect emoji-based feedback from logged-in members.
Saves submission state in localStorage and sends data to Make.com.
-->
<script>
(function() {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error('Memberstack DOM not found.');
return;
}
// Elements
const widget = document.querySelector('[data-ms-code="emoji-feedback-widget"]');
const closeBtn = widget.querySelector('[data-ms-code="emoji-feedback-close"]');
const buttons = widget.querySelectorAll('[data-ms-code="emoji-feedback-btn"]');
const thanks = widget.querySelector('[data-ms-code="emoji-feedback-thanks"]');
// Exit early if feedback is done or dismissed
if (
localStorage.getItem('emojiFeedbackDone') === 'true' ||
localStorage.getItem('emojiFeedbackClosed') === 'true'
) {
widget.style.display = 'none';
return;
}
// Handle close (×) click
closeBtn.addEventListener('click', e => {
e.preventDefault();
localStorage.setItem('emojiFeedbackClosed', 'true');
widget.style.display = 'none';
});
// Fetch member data
msDom.getCurrentMember()
.then(({ data: member }) => {
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const score = btn.getAttribute('data-value');
// Payload for Make.com
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || '',
email: member.auth.email || '',
pageUrl: window.location.href,
feedback: score,
timestamp: new Date().toISOString()
};
// Send feedback to Make
fetch('https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(res => {
if (!res.ok) throw new Error(res.statusText);
// Success state: hide emojis, show thank you
localStorage.setItem('emojiFeedbackDone', 'true');
widget.querySelector('[data-ms-code="emoji-feedback-buttons"]').style.display = 'none';
thanks.style.display = 'block';
})
.catch(err => {
console.error('Emoji feedback error:', err);
// Optionally add error handling UI here
});
});
});
})
.catch(err => console.error('Couldn’t get member:', err));
})();
</script>
<!-- 💙 MEMBERSCRIPT #158 v1.0 💙 - EMOJI FEEDBACK WIDGET -->
<!--
Collect emoji-based feedback from logged-in members.
Saves submission state in localStorage and sends data to Make.com.
-->
<script>
(function() {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error('Memberstack DOM not found.');
return;
}
// Elements
const widget = document.querySelector('[data-ms-code="emoji-feedback-widget"]');
const closeBtn = widget.querySelector('[data-ms-code="emoji-feedback-close"]');
const buttons = widget.querySelectorAll('[data-ms-code="emoji-feedback-btn"]');
const thanks = widget.querySelector('[data-ms-code="emoji-feedback-thanks"]');
// Exit early if feedback is done or dismissed
if (
localStorage.getItem('emojiFeedbackDone') === 'true' ||
localStorage.getItem('emojiFeedbackClosed') === 'true'
) {
widget.style.display = 'none';
return;
}
// Handle close (×) click
closeBtn.addEventListener('click', e => {
e.preventDefault();
localStorage.setItem('emojiFeedbackClosed', 'true');
widget.style.display = 'none';
});
// Fetch member data
msDom.getCurrentMember()
.then(({ data: member }) => {
buttons.forEach(btn => {
btn.addEventListener('click', () => {
const score = btn.getAttribute('data-value');
// Payload for Make.com
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || '',
email: member.auth.email || '',
pageUrl: window.location.href,
feedback: score,
timestamp: new Date().toISOString()
};
// Send feedback to Make
fetch('https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
})
.then(res => {
if (!res.ok) throw new Error(res.statusText);
// Success state: hide emojis, show thank you
localStorage.setItem('emojiFeedbackDone', 'true');
widget.querySelector('[data-ms-code="emoji-feedback-buttons"]').style.display = 'none';
thanks.style.display = 'block';
})
.catch(err => {
console.error('Emoji feedback error:', err);
// Optionally add error handling UI here
});
});
});
})
.catch(err => console.error('Couldn’t get member:', err));
})();
</script>

#157 - Range Slider Feedback Widget
A simple and friendly slider widget that lets logged-in members give quick feedback (0–10).
<!-- 💙 MEMBERSCRIPT #157 v1.0 💙 - RANGE SLIDER FEEDBACK WIDGET -->
<!--
A lightweight feedback widget that uses a range slider.
Prevents duplicate submissions with localStorage, and sends feedback to Make.com via webhook.
-->
<script>
Webflow.push(function() {
// Silently disable all form submissions
$('form').submit(function(e) {
e.preventDefault();
return false;
});
});
(function () {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error("Memberstack DOM not found. Did you include data-memberstack-app?");
return;
}
const widget = document.querySelector('[data-ms-code="feedback-widget"]');
const dialog = widget?.querySelector('[data-ms-code="feedback-dialog"]');
const toggle = widget?.querySelector('[data-ms-code="feedback-toggle"]');
const slider = widget?.querySelector('[data-ms-code="feedback-range"]');
const submit = widget?.querySelector('[data-ms-code="feedback-next"]');
const form = widget?.closest("form");
const done = localStorage.getItem("feedbackDone") === "true";
const closed = localStorage.getItem("feedbackClosed") === "true";
if (!widget || !dialog || !toggle || !slider || !submit || done || closed) {
if (widget) widget.style.display = "none";
return;
}
// Manual close button logic
toggle.addEventListener("click", (e) => {
e.preventDefault();
localStorage.setItem("feedbackClosed", "true");
widget.style.display = "none";
});
// Fetch logged-in member
msDom.getCurrentMember()
.then(({ data: member }) => {
slider.addEventListener("input", () => {
submit.disabled = false;
});
submit.addEventListener("click", () => {
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || "",
email: member.auth.email || "",
pageUrl: window.location.href,
feedback: slider.value,
timestamp: new Date().toISOString()
};
fetch("https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then((res) => {
if (!res.ok) throw new Error(res.statusText);
localStorage.setItem("feedbackDone", "true");
const msg = document.createElement("p");
msg.textContent = "Thanks for your feedback!";
msg.style.padding = "1em";
msg.style.textAlign = "center";
dialog.innerHTML = "";
dialog.appendChild(msg);
})
.catch((err) => {
console.error("Feedback error:", err);
dialog.insertAdjacentHTML(
"beforeend",
'<p style="color:red; text-align:center;">Oops! Could not send. Try again?</p>'
);
});
});
})
.catch((err) => console.error("Couldn’t get member:", err));
})();
</script>
<!-- 💙 MEMBERSCRIPT #157 v1.0 💙 - RANGE SLIDER FEEDBACK WIDGET -->
<!--
A lightweight feedback widget that uses a range slider.
Prevents duplicate submissions with localStorage, and sends feedback to Make.com via webhook.
-->
<script>
Webflow.push(function() {
// Silently disable all form submissions
$('form').submit(function(e) {
e.preventDefault();
return false;
});
});
(function () {
const msDom = window.$memberstackDom;
if (!msDom) {
console.error("Memberstack DOM not found. Did you include data-memberstack-app?");
return;
}
const widget = document.querySelector('[data-ms-code="feedback-widget"]');
const dialog = widget?.querySelector('[data-ms-code="feedback-dialog"]');
const toggle = widget?.querySelector('[data-ms-code="feedback-toggle"]');
const slider = widget?.querySelector('[data-ms-code="feedback-range"]');
const submit = widget?.querySelector('[data-ms-code="feedback-next"]');
const form = widget?.closest("form");
const done = localStorage.getItem("feedbackDone") === "true";
const closed = localStorage.getItem("feedbackClosed") === "true";
if (!widget || !dialog || !toggle || !slider || !submit || done || closed) {
if (widget) widget.style.display = "none";
return;
}
// Manual close button logic
toggle.addEventListener("click", (e) => {
e.preventDefault();
localStorage.setItem("feedbackClosed", "true");
widget.style.display = "none";
});
// Fetch logged-in member
msDom.getCurrentMember()
.then(({ data: member }) => {
slider.addEventListener("input", () => {
submit.disabled = false;
});
submit.addEventListener("click", () => {
const payload = {
memberId: member.id,
name: member.customFields["first-name"] || "",
email: member.auth.email || "",
pageUrl: window.location.href,
feedback: slider.value,
timestamp: new Date().toISOString()
};
fetch("https://hook.eu2.make.com/8wm1j323te1sybyweux6x33mh77vswvm", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
})
.then((res) => {
if (!res.ok) throw new Error(res.statusText);
localStorage.setItem("feedbackDone", "true");
const msg = document.createElement("p");
msg.textContent = "Thanks for your feedback!";
msg.style.padding = "1em";
msg.style.textAlign = "center";
dialog.innerHTML = "";
dialog.appendChild(msg);
})
.catch((err) => {
console.error("Feedback error:", err);
dialog.insertAdjacentHTML(
"beforeend",
'<p style="color:red; text-align:center;">Oops! Could not send. Try again?</p>'
);
});
});
})
.catch((err) => console.error("Couldn’t get member:", err));
})();
</script>

#156 - Encrypt Sensitive Data Before Sending to Memberstack
This script protects sensitive user data by encrypting it in the browser before it’s sent to Memberstack.
<!-- 💙 MEMBERSCRIPT #156 v1.0 💙 - ENCRYPT SENSITIVE DATA BEFORE SENDING TO MEMBERSTACK -->
<!--
This script encrypts input fields before they're submitted to Memberstack,
using AES-GCM with a passphrase-based modal.
-->
<script>
document.addEventListener('DOMContentLoaded', function () {
(function () {
const enc = new TextEncoder();
const dec = new TextDecoder();
// Show the passphrase modal
function showModal() {
return new Promise(resolve => {
const modal = document.querySelector('[data-ms-code="encrypt-modal"]');
if (!modal) return alert('Encryption modal missing from the page.');
const input = modal.querySelector('[data-ms-code="pass-input"]');
const remember = modal.querySelector('[data-ms-code="remember-pass"]');
const submit = modal.querySelector('[data-ms-code="submit-pass"]');
const closeButtons = modal.querySelectorAll(
'[data-ms-code="close-encrypt-modal"], [data-ms-code="close-encrypt-icon"]'
);
modal.style.display = 'flex';
input.value = '';
input.focus();
const cleanup = () => {
modal.style.display = 'none';
};
if (submit) {
submit.onclick = () => {
const pass = input.value;
const keep = remember.checked;
cleanup();
resolve({ pass, remember: keep });
};
}
closeButtons.forEach(btn => {
btn.onclick = () => {
cleanup();
resolve({ pass: null });
};
});
});
}
// Derive AES key using PBKDF2
async function deriveKey(pass, salt) {
const keyMaterial = await crypto.subtle.importKey(
'raw',
enc.encode(pass),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// Encrypt a string
async function encryptText(text, pass) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(pass, salt);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
enc.encode(text)
);
return [
btoa(String.fromCharCode(...salt)),
btoa(String.fromCharCode(...iv)),
btoa(String.fromCharCode(...new Uint8Array(encrypted)))
].join(':');
}
// Decrypt a string
async function decryptText(encrypted, pass) {
const [saltB64, ivB64, dataB64] = encrypted.split(':');
if (!saltB64 || !ivB64 || !dataB64) throw new Error('Invalid format');
const salt = Uint8Array.from(atob(saltB64), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
const data = Uint8Array.from(atob(dataB64), c => c.charCodeAt(0));
const key = await deriveKey(pass, salt);
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
return dec.decode(decrypted);
}
// Encrypt and submit form
document.querySelectorAll('[data-ms-code-encrypt]').forEach(btn => {
if (!btn.hasAttribute('data-ms-encrypt-attached')) {
btn.addEventListener('click', async e => {
e.preventDefault();
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
const fields = document.querySelectorAll('[data-ms-code-id]');
for (let field of fields) {
const value = field.value.trim();
if (!value) continue;
try {
const encrypted = await encryptText(value, passphrase);
field.value = encrypted;
} catch (err) {
console.error('Encryption error:', err);
alert('Encryption failed.');
return;
}
}
const form = btn.closest('form');
if (form) form.requestSubmit();
});
btn.setAttribute('data-ms-encrypt-attached', 'true');
}
});
// Add decrypt button logic
function attachDecryptButton() {
const decryptBtn = document.querySelector('[data-ms-code="decrypt-all"]');
if (!decryptBtn || decryptBtn.hasAttribute('data-ms-decrypt-attached')) return;
decryptBtn.addEventListener('click', async e => {
e.preventDefault();
const encryptedFields = document.querySelectorAll('[data-ms-code-id]');
if (encryptedFields.length === 0) return alert('No fields to decrypt.');
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
for (let field of encryptedFields) {
const encrypted = field.value.trim();
if (!encrypted) continue;
try {
const decrypted = await decryptText(encrypted, passphrase);
field.value = decrypted;
} catch (err) {
console.error('Decryption error:', err);
alert('One or more fields failed to decrypt.');
return;
}
}
});
decryptBtn.setAttribute('data-ms-decrypt-attached', 'true');
}
attachDecryptButton();
})();
});
</script>
<!-- 💙 MEMBERSCRIPT #156 v1.0 💙 - ENCRYPT SENSITIVE DATA BEFORE SENDING TO MEMBERSTACK -->
<!--
This script encrypts input fields before they're submitted to Memberstack,
using AES-GCM with a passphrase-based modal.
-->
<script>
document.addEventListener('DOMContentLoaded', function () {
(function () {
const enc = new TextEncoder();
const dec = new TextDecoder();
// Show the passphrase modal
function showModal() {
return new Promise(resolve => {
const modal = document.querySelector('[data-ms-code="encrypt-modal"]');
if (!modal) return alert('Encryption modal missing from the page.');
const input = modal.querySelector('[data-ms-code="pass-input"]');
const remember = modal.querySelector('[data-ms-code="remember-pass"]');
const submit = modal.querySelector('[data-ms-code="submit-pass"]');
const closeButtons = modal.querySelectorAll(
'[data-ms-code="close-encrypt-modal"], [data-ms-code="close-encrypt-icon"]'
);
modal.style.display = 'flex';
input.value = '';
input.focus();
const cleanup = () => {
modal.style.display = 'none';
};
if (submit) {
submit.onclick = () => {
const pass = input.value;
const keep = remember.checked;
cleanup();
resolve({ pass, remember: keep });
};
}
closeButtons.forEach(btn => {
btn.onclick = () => {
cleanup();
resolve({ pass: null });
};
});
});
}
// Derive AES key using PBKDF2
async function deriveKey(pass, salt) {
const keyMaterial = await crypto.subtle.importKey(
'raw',
enc.encode(pass),
{ name: 'PBKDF2' },
false,
['deriveKey']
);
return crypto.subtle.deriveKey(
{
name: 'PBKDF2',
salt: salt,
iterations: 100000,
hash: 'SHA-256'
},
keyMaterial,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
// Encrypt a string
async function encryptText(text, pass) {
const salt = crypto.getRandomValues(new Uint8Array(16));
const iv = crypto.getRandomValues(new Uint8Array(12));
const key = await deriveKey(pass, salt);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
enc.encode(text)
);
return [
btoa(String.fromCharCode(...salt)),
btoa(String.fromCharCode(...iv)),
btoa(String.fromCharCode(...new Uint8Array(encrypted)))
].join(':');
}
// Decrypt a string
async function decryptText(encrypted, pass) {
const [saltB64, ivB64, dataB64] = encrypted.split(':');
if (!saltB64 || !ivB64 || !dataB64) throw new Error('Invalid format');
const salt = Uint8Array.from(atob(saltB64), c => c.charCodeAt(0));
const iv = Uint8Array.from(atob(ivB64), c => c.charCodeAt(0));
const data = Uint8Array.from(atob(dataB64), c => c.charCodeAt(0));
const key = await deriveKey(pass, salt);
const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, data);
return dec.decode(decrypted);
}
// Encrypt and submit form
document.querySelectorAll('[data-ms-code-encrypt]').forEach(btn => {
if (!btn.hasAttribute('data-ms-encrypt-attached')) {
btn.addEventListener('click', async e => {
e.preventDefault();
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
const fields = document.querySelectorAll('[data-ms-code-id]');
for (let field of fields) {
const value = field.value.trim();
if (!value) continue;
try {
const encrypted = await encryptText(value, passphrase);
field.value = encrypted;
} catch (err) {
console.error('Encryption error:', err);
alert('Encryption failed.');
return;
}
}
const form = btn.closest('form');
if (form) form.requestSubmit();
});
btn.setAttribute('data-ms-encrypt-attached', 'true');
}
});
// Add decrypt button logic
function attachDecryptButton() {
const decryptBtn = document.querySelector('[data-ms-code="decrypt-all"]');
if (!decryptBtn || decryptBtn.hasAttribute('data-ms-decrypt-attached')) return;
decryptBtn.addEventListener('click', async e => {
e.preventDefault();
const encryptedFields = document.querySelectorAll('[data-ms-code-id]');
if (encryptedFields.length === 0) return alert('No fields to decrypt.');
let passphrase = sessionStorage.getItem('ms-encrypt-passphrase');
if (!passphrase) {
const { pass, remember } = await showModal();
if (!pass) return;
passphrase = pass;
if (remember) sessionStorage.setItem('ms-encrypt-passphrase', passphrase);
}
for (let field of encryptedFields) {
const encrypted = field.value.trim();
if (!encrypted) continue;
try {
const decrypted = await decryptText(encrypted, passphrase);
field.value = decrypted;
} catch (err) {
console.error('Decryption error:', err);
alert('One or more fields failed to decrypt.');
return;
}
}
});
decryptBtn.setAttribute('data-ms-decrypt-attached', 'true');
}
attachDecryptButton();
})();
});
</script>

#155 - Bulk Update Customer Subscriptions via Stripe & Make
Bulk update existing members to a new pricing plan with Stripe and Make

#154 - Two-Factor Authentication (2FA) for Memberstack Logins
Add an extra layer of security to your Memberstack logins by enabling Two-Factor Authentication (2FA).
<!--
MEMBERSCRIPT #154
---------------------------------
LOGIN PAGE SCRIPT
-->
<script>
(async function() {
const delay = ms => new Promise(r => setTimeout(r, ms));
async function routeLogin() {
try {
// Check if Memberstack is loaded
if (!window.$memberstackDom) {
console.log("Memberstack not loaded yet");
return;
}
// Get current member
const { data: member } = await window.$memberstackDom.getCurrentMember();
if (!member) return; // Exit if not logged in
// Get member JSON data
const jsonResponse = await window.$memberstackDom.getMemberJSON();
const memberData = jsonResponse.data || {};
// Check if 2FA is enabled
const needs2FA = memberData["2fa_enabled"] === true ||
jsonResponse["2fa_enabled"] === true;
// Check session storage for verification status
const verified = sessionStorage.getItem("2fa_verified") === "true";
console.log("2FA Status:", {
enabled: needs2FA,
verified: verified,
currentPath: window.location.pathname
});
// Handle 2FA redirect
if (needs2FA && !verified) {
if (!window.location.pathname.includes("/2fa-verify")) {
console.log("Redirecting to /2fa-verify");
window.location.href = "/2fa-verify";
}
return; // Stop further execution
}
// Handle success redirect
if (!window.location.pathname.includes("/success")) {
console.log("Redirecting to /success");
// Remove Memberstack's auto-redirect if login form exists
const loginForm = document.querySelector('[data-ms-form="login"]');
if (loginForm) {
loginForm.removeAttribute('data-ms-redirect');
}
window.location.href = "/success";
}
} catch (err) {
console.error("2FA routing error:", err);
}
}
// Wait for Memberstack to initialize
await delay(300);
routeLogin();
// Poll with cleanup
const pollInterval = setInterval(routeLogin, 500);
setTimeout(() => clearInterval(pollInterval), 10000);
})();
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SETTINGS PAGE SCRIPT
-->
<!-- Load otplib preset-browser -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const { data: member } = await ms.getCurrentMember();
if (!member) return;
const checkbox = document.querySelector('[data-ms-code="enable-2fa"]');
const qrContainer = document.querySelector('[data-ms-code="2fa-qr-container"]');
const qrImage = document.querySelector('[data-ms-code="2fa-qr-image"]');
// Hide QR container by default
qrContainer.style.display = "none";
// Load member JSON and ensure data object exists
const jsonObj = await ms.getMemberJSON(); // { data: ... } or { data: null }
if (!jsonObj.data) jsonObj.data = {};
const inner = jsonObj.data;
// Set checkbox initial state
const enabled = inner["2fa_enabled"] === true;
checkbox.checked = enabled;
checkbox.addEventListener("change", async (e) => {
const isChecked = e.target.checked;
// Reload member and JSON
const { data: member } = await ms.getCurrentMember();
const jsonObj2 = await ms.getMemberJSON();
if (!jsonObj2.data) jsonObj2.data = {};
const inner2 = jsonObj2.data;
if (isChecked) {
// Enable 2FA: generate secret and QR
const secret = window.otplib.authenticator.generateSecret();
const uri = window.otplib.authenticator.keyuri(member.email, "Memberscript #154", secret);
qrImage.src = "https://api.qrserver.com/v1/create-qr-code/?data=" + encodeURIComponent(uri);
qrContainer.style.display = "flex";
inner2["2fa_enabled"] = true;
inner2["2fa_secret"] = secret;
await ms.updateMember({
customFields: { "2fa-enabled": "true" }
});
} else {
// Disable 2FA: remove secret and hide QR
qrContainer.style.display = "none";
inner2["2fa_enabled"] = false;
delete inner2["2fa_secret"];
await ms.updateMember({
customFields: { "2fa-enabled": "false" }
});
}
// Persist only nested JSON
await ms.updateMemberJSON({ json: inner2 });
// ✅ Debugging: check result
const check = await ms.getMemberJSON();
console.log(check);
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SUCCESS / DASHBOARD PAGE SCRIPT
-->
<script>
window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
if (!member) return; // Not logged in, no redirect
window.$memberstackDom.getMemberJSON().then(json => {
const enabled = json["2fa_enabled"];
const verified = sessionStorage.getItem("2fa_verified") === "true";
if (enabled && !verified && window.location.pathname !== "/2fa-verify") {
window.location.href = "/2fa-verify";
}
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
TWO FACTOR VERIFICATION PAGE SCRIPT
-->
<!-- Include the Buffer polyfill -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<!-- Include the otplib library -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const memberstack = window.$memberstackDom;
const { data: member } = await memberstack.getCurrentMember();
if (!member) return;
const form = document.querySelector('[data-ms-form="2fa-verification"]');
if (!form) return;
const codeInput = form.querySelector('[data-ms-code="2fa-code"]');
const errorContainer = form.querySelector('[data-ms-error="2fa-code"]');
function showError(message) {
if (errorContainer) {
errorContainer.textContent = message;
errorContainer.style.display = 'block';
} else {
alert(message);
}
}
function clearError() {
if (errorContainer) {
errorContainer.textContent = '';
errorContainer.style.display = 'none';
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
e.stopImmediatePropagation(); // important to stop other listeners
clearError();
const code = codeInput.value.trim();
const { data: json } = await memberstack.getMemberJSON();
const secret = json?.["2fa_secret"];
if (!secret || !code) {
const msg = form.getAttribute('data-ms-error-msg-missing') || 'Please enter your 2FA code';
showError(msg);
return;
}
if (otplib.authenticator.check(code, secret)) {
sessionStorage.setItem('2fa_verified', 'true');
window.location.href = '/success';
} else {
const msg = form.getAttribute('data-ms-error-msg-invalid') || 'Oops, the 2FA code is incorrect. Try again.';
showError(msg);
}
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
LOGIN PAGE SCRIPT
-->
<script>
(async function() {
const delay = ms => new Promise(r => setTimeout(r, ms));
async function routeLogin() {
try {
// Check if Memberstack is loaded
if (!window.$memberstackDom) {
console.log("Memberstack not loaded yet");
return;
}
// Get current member
const { data: member } = await window.$memberstackDom.getCurrentMember();
if (!member) return; // Exit if not logged in
// Get member JSON data
const jsonResponse = await window.$memberstackDom.getMemberJSON();
const memberData = jsonResponse.data || {};
// Check if 2FA is enabled
const needs2FA = memberData["2fa_enabled"] === true ||
jsonResponse["2fa_enabled"] === true;
// Check session storage for verification status
const verified = sessionStorage.getItem("2fa_verified") === "true";
console.log("2FA Status:", {
enabled: needs2FA,
verified: verified,
currentPath: window.location.pathname
});
// Handle 2FA redirect
if (needs2FA && !verified) {
if (!window.location.pathname.includes("/2fa-verify")) {
console.log("Redirecting to /2fa-verify");
window.location.href = "/2fa-verify";
}
return; // Stop further execution
}
// Handle success redirect
if (!window.location.pathname.includes("/success")) {
console.log("Redirecting to /success");
// Remove Memberstack's auto-redirect if login form exists
const loginForm = document.querySelector('[data-ms-form="login"]');
if (loginForm) {
loginForm.removeAttribute('data-ms-redirect');
}
window.location.href = "/success";
}
} catch (err) {
console.error("2FA routing error:", err);
}
}
// Wait for Memberstack to initialize
await delay(300);
routeLogin();
// Poll with cleanup
const pollInterval = setInterval(routeLogin, 500);
setTimeout(() => clearInterval(pollInterval), 10000);
})();
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SETTINGS PAGE SCRIPT
-->
<!-- Load otplib preset-browser -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const { data: member } = await ms.getCurrentMember();
if (!member) return;
const checkbox = document.querySelector('[data-ms-code="enable-2fa"]');
const qrContainer = document.querySelector('[data-ms-code="2fa-qr-container"]');
const qrImage = document.querySelector('[data-ms-code="2fa-qr-image"]');
// Hide QR container by default
qrContainer.style.display = "none";
// Load member JSON and ensure data object exists
const jsonObj = await ms.getMemberJSON(); // { data: ... } or { data: null }
if (!jsonObj.data) jsonObj.data = {};
const inner = jsonObj.data;
// Set checkbox initial state
const enabled = inner["2fa_enabled"] === true;
checkbox.checked = enabled;
checkbox.addEventListener("change", async (e) => {
const isChecked = e.target.checked;
// Reload member and JSON
const { data: member } = await ms.getCurrentMember();
const jsonObj2 = await ms.getMemberJSON();
if (!jsonObj2.data) jsonObj2.data = {};
const inner2 = jsonObj2.data;
if (isChecked) {
// Enable 2FA: generate secret and QR
const secret = window.otplib.authenticator.generateSecret();
const uri = window.otplib.authenticator.keyuri(member.email, "Memberscript #154", secret);
qrImage.src = "https://api.qrserver.com/v1/create-qr-code/?data=" + encodeURIComponent(uri);
qrContainer.style.display = "flex";
inner2["2fa_enabled"] = true;
inner2["2fa_secret"] = secret;
await ms.updateMember({
customFields: { "2fa-enabled": "true" }
});
} else {
// Disable 2FA: remove secret and hide QR
qrContainer.style.display = "none";
inner2["2fa_enabled"] = false;
delete inner2["2fa_secret"];
await ms.updateMember({
customFields: { "2fa-enabled": "false" }
});
}
// Persist only nested JSON
await ms.updateMemberJSON({ json: inner2 });
// ✅ Debugging: check result
const check = await ms.getMemberJSON();
console.log(check);
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
SUCCESS / DASHBOARD PAGE SCRIPT
-->
<script>
window.$memberstackDom.getCurrentMember().then(({ data: member }) => {
if (!member) return; // Not logged in, no redirect
window.$memberstackDom.getMemberJSON().then(json => {
const enabled = json["2fa_enabled"];
const verified = sessionStorage.getItem("2fa_verified") === "true";
if (enabled && !verified && window.location.pathname !== "/2fa-verify") {
window.location.href = "/2fa-verify";
}
});
});
</script>
<!--
MEMBERSCRIPT #154
---------------------------------
TWO FACTOR VERIFICATION PAGE SCRIPT
-->
<!-- Include the Buffer polyfill -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/buffer.js"></script>
<!-- Include the otplib library -->
<script src="https://unpkg.com/@otplib/preset-browser@^12.0.0/index.js"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const memberstack = window.$memberstackDom;
const { data: member } = await memberstack.getCurrentMember();
if (!member) return;
const form = document.querySelector('[data-ms-form="2fa-verification"]');
if (!form) return;
const codeInput = form.querySelector('[data-ms-code="2fa-code"]');
const errorContainer = form.querySelector('[data-ms-error="2fa-code"]');
function showError(message) {
if (errorContainer) {
errorContainer.textContent = message;
errorContainer.style.display = 'block';
} else {
alert(message);
}
}
function clearError() {
if (errorContainer) {
errorContainer.textContent = '';
errorContainer.style.display = 'none';
}
}
form.addEventListener('submit', async (e) => {
e.preventDefault();
e.stopImmediatePropagation(); // important to stop other listeners
clearError();
const code = codeInput.value.trim();
const { data: json } = await memberstack.getMemberJSON();
const secret = json?.["2fa_secret"];
if (!secret || !code) {
const msg = form.getAttribute('data-ms-error-msg-missing') || 'Please enter your 2FA code';
showError(msg);
return;
}
if (otplib.authenticator.check(code, secret)) {
sessionStorage.setItem('2fa_verified', 'true');
window.location.href = '/success';
} else {
const msg = form.getAttribute('data-ms-error-msg-invalid') || 'Oops, the 2FA code is incorrect. Try again.';
showError(msg);
}
});
});
</script>

#153 - Instant Multilingual Site with Google Translate
Make your Webflow site multilingual in minutes with Google Translate and Memberstack
<!-- 💙 MEMBERSCRIPT #153 v1.0 💙 - FREE MULTILINGUAL SITE WITH GOOGLE TRANSLATE -->
<script>
// 1. Inject Google Translate script dynamically
const gtScript = document.createElement('script');
gtScript.src = "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
document.head.appendChild(gtScript);
// 2. Inject CSS to hide Google Translate UI
const style = document.createElement('style');
style.innerHTML = `
body { top: 0px !important; position: static !important; }
.goog-te-banner-frame, .skiptranslate,
#goog-gt-tt, .goog-te-balloon-frame,
.goog-text-highlight {
display: none !important;
background: none !important;
box-shadow: none !important;
}
`;
document.head.appendChild(style);
// 3. Google Translate init
window.googleTranslateElementInit = function () {
new google.translate.TranslateElement({
pageLanguage: 'en',
layout: google.translate.TranslateElement.FloatPosition.TOP_LEFT
}, 'google_translate_element');
};
// 4. Helper to get cookies
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [key, value] = cookie.trim().split('=');
if (key === name) return decodeURIComponent(value);
}
return null;
}
// 5. Language map
const languageMap = new Map([
["af","Afrikaans"], ["sq","Albanian"], ["ar","Arabic"], ["hy","Armenian"],
["az","Azerbaijani"], ["eu","Basque"], ["be","Belarusian"], ["bg","Bulgarian"],
["ca","Catalan"], ["zh-CN","ChineseSimplified"], ["zh-TW","ChineseTraditional"],
["hr","Croatian"], ["cs","Czech"], ["da","Danish"], ["nl","Dutch"], ["de","German"],
["en","English"], ["et","Estonian"], ["tl","Filipino"], ["fi","Finnish"],
["fr","French"], ["gl","Galician"], ["ka","Georgian"], ["el","Greek"],
["ht","Haitian"], ["iw","Hebrew"], ["hi","Hindi"], ["hu","Hungarian"],
["is","Icelandic"], ["id","Indonesian"], ["ga","Irish"], ["it","Italian"],
["ja","Japanese"], ["ko","Korean"], ["lv","Latvian"], ["lt","Lithuanian"],
["mk","Macedonian"], ["ms","Malay"], ["mt","Maltese"], ["no","Norwegian"],
["fa","Persian"], ["pl","Polish"], ["pt","Portuguese"], ["ro","Romanian"],
["ru","Russian"], ["sr","Serbian"], ["sk","Slovak"], ["sl","Slovenian"],
["es","Spanish"], ["sw","Swahili"], ["sv","Swedish"], ["th","Thai"],
["tr","Turkish"], ["uk","Ukrainian"], ["ur","Urdu"], ["vi","Vietnamese"],
["cy","Welsh"], ["yi","Yiddish"]
]);
// 6. Detect current language
let currentLang = getCookie("googtrans")?.split("/").pop() || "en";
// 7. Show language-specific content & set up language switch
document.addEventListener("DOMContentLoaded", function () {
const readableLang = languageMap.get(currentLang);
const langClass = `.languagespecific.${readableLang?.toLowerCase()}specific`;
const fallbackClass = `.languagespecific.englishspecific`;
if (document.querySelector(langClass)) {
document.querySelectorAll(langClass).forEach(el => el.style.display = 'block');
} else {
document.querySelectorAll(fallbackClass).forEach(el => el.style.display = 'block');
}
document.querySelectorAll('[data-ms-code-lang-select]').forEach(el => {
el.addEventListener('click', function (e) {
e.preventDefault();
const selectedLang = this.getAttribute('data-ms-code-lang');
if (selectedLang === 'en') {
document.cookie = "googtrans=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
document.cookie = "googtrans=;domain=.webflow.io;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
window.location.hash = "";
setTimeout(() => location.reload(true), 100);
} else {
const combo = document.querySelector('.goog-te-combo');
if (combo) combo.value = selectedLang;
window.location.hash = "#googtrans(en|" + selectedLang + ")";
location.reload();
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #153 v1.0 💙 - FREE MULTILINGUAL SITE WITH GOOGLE TRANSLATE -->
<script>
// 1. Inject Google Translate script dynamically
const gtScript = document.createElement('script');
gtScript.src = "//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit";
document.head.appendChild(gtScript);
// 2. Inject CSS to hide Google Translate UI
const style = document.createElement('style');
style.innerHTML = `
body { top: 0px !important; position: static !important; }
.goog-te-banner-frame, .skiptranslate,
#goog-gt-tt, .goog-te-balloon-frame,
.goog-text-highlight {
display: none !important;
background: none !important;
box-shadow: none !important;
}
`;
document.head.appendChild(style);
// 3. Google Translate init
window.googleTranslateElementInit = function () {
new google.translate.TranslateElement({
pageLanguage: 'en',
layout: google.translate.TranslateElement.FloatPosition.TOP_LEFT
}, 'google_translate_element');
};
// 4. Helper to get cookies
function getCookie(name) {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
const [key, value] = cookie.trim().split('=');
if (key === name) return decodeURIComponent(value);
}
return null;
}
// 5. Language map
const languageMap = new Map([
["af","Afrikaans"], ["sq","Albanian"], ["ar","Arabic"], ["hy","Armenian"],
["az","Azerbaijani"], ["eu","Basque"], ["be","Belarusian"], ["bg","Bulgarian"],
["ca","Catalan"], ["zh-CN","ChineseSimplified"], ["zh-TW","ChineseTraditional"],
["hr","Croatian"], ["cs","Czech"], ["da","Danish"], ["nl","Dutch"], ["de","German"],
["en","English"], ["et","Estonian"], ["tl","Filipino"], ["fi","Finnish"],
["fr","French"], ["gl","Galician"], ["ka","Georgian"], ["el","Greek"],
["ht","Haitian"], ["iw","Hebrew"], ["hi","Hindi"], ["hu","Hungarian"],
["is","Icelandic"], ["id","Indonesian"], ["ga","Irish"], ["it","Italian"],
["ja","Japanese"], ["ko","Korean"], ["lv","Latvian"], ["lt","Lithuanian"],
["mk","Macedonian"], ["ms","Malay"], ["mt","Maltese"], ["no","Norwegian"],
["fa","Persian"], ["pl","Polish"], ["pt","Portuguese"], ["ro","Romanian"],
["ru","Russian"], ["sr","Serbian"], ["sk","Slovak"], ["sl","Slovenian"],
["es","Spanish"], ["sw","Swahili"], ["sv","Swedish"], ["th","Thai"],
["tr","Turkish"], ["uk","Ukrainian"], ["ur","Urdu"], ["vi","Vietnamese"],
["cy","Welsh"], ["yi","Yiddish"]
]);
// 6. Detect current language
let currentLang = getCookie("googtrans")?.split("/").pop() || "en";
// 7. Show language-specific content & set up language switch
document.addEventListener("DOMContentLoaded", function () {
const readableLang = languageMap.get(currentLang);
const langClass = `.languagespecific.${readableLang?.toLowerCase()}specific`;
const fallbackClass = `.languagespecific.englishspecific`;
if (document.querySelector(langClass)) {
document.querySelectorAll(langClass).forEach(el => el.style.display = 'block');
} else {
document.querySelectorAll(fallbackClass).forEach(el => el.style.display = 'block');
}
document.querySelectorAll('[data-ms-code-lang-select]').forEach(el => {
el.addEventListener('click', function (e) {
e.preventDefault();
const selectedLang = this.getAttribute('data-ms-code-lang');
if (selectedLang === 'en') {
document.cookie = "googtrans=;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
document.cookie = "googtrans=;domain=.webflow.io;path=/;expires=Thu, 01 Jan 1970 00:00:01 GMT;";
window.location.hash = "";
setTimeout(() => location.reload(true), 100);
} else {
const combo = document.querySelector('.goog-te-combo');
if (combo) combo.value = selectedLang;
window.location.hash = "#googtrans(en|" + selectedLang + ")";
location.reload();
}
});
});
});
</script>

#152 - OTP Verification via WhatsApp in Webflow
Verify phone numbers via WhatsApp before allowing form submissions in Webflow.
<!-- 💙 MEMBERSCRIPT #152 v1.0 💙 - OTP VERIFICATION VIA WHATSAPP IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const phoneInput = document.querySelector('[data-ms-code="phone-number"]');
const form = phoneInput?.closest("form");
const submitBtn = form?.querySelector('[type="submit"]');
let isVerified = false;
let originalMsFormValue = "signup"; // update this if your form uses another Memberstack action
if (phoneInput && form && submitBtn && window.$WhatsAuthForm) {
// Temporarily disable Memberstack
form.removeAttribute("data-ms-form");
// Create error message
const errorMsg = document.createElement('div');
errorMsg.style.color = 'red';
errorMsg.style.marginTop = '8px';
errorMsg.style.display = 'none';
errorMsg.textContent = '⚠️ Please verify your phone number via WhatsApp.';
phoneInput.parentNode.appendChild(errorMsg);
// Initially disable submit
submitBtn.disabled = true;
submitBtn.style.opacity = 0.6;
submitBtn.style.cursor = 'not-allowed';
// Init WhatsAuth
window.$WhatsAuthForm.init({
inputSelector: '[data-ms-code="phone-number"]',
apiKey: "k07Zj8EwdAIzHzLcLPQh-5jCuREbSKXG", //REPLACE WITH YOUR API KEY
placeholder: phoneInput.getAttribute("data-ms-placeholder") || "",
primaryColor: phoneInput.getAttribute("data-ms-primary-color") || "",
secondaryColor: phoneInput.getAttribute("data-ms-secondary-color") || "",
btnText: phoneInput.getAttribute("data-ms-btn-text") || ""
});
// Watch for success class from WhatsAuth
const observer = new MutationObserver(() => {
if (phoneInput.classList.contains("whatsauth-success") && !isVerified) {
isVerified = true;
// Enable submit
submitBtn.disabled = false;
submitBtn.style.opacity = 1;
submitBtn.style.cursor = 'pointer';
// Hide error
errorMsg.style.display = 'none';
// Re-enable Memberstack
form.setAttribute("data-ms-form", originalMsFormValue);
}
});
observer.observe(phoneInput, { attributes: true, attributeFilter: ["class"] });
// Final form safeguard
form.addEventListener("submit", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
submitBtn.addEventListener("click", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #152 v1.0 💙 - OTP VERIFICATION VIA WHATSAPP IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const phoneInput = document.querySelector('[data-ms-code="phone-number"]');
const form = phoneInput?.closest("form");
const submitBtn = form?.querySelector('[type="submit"]');
let isVerified = false;
let originalMsFormValue = "signup"; // update this if your form uses another Memberstack action
if (phoneInput && form && submitBtn && window.$WhatsAuthForm) {
// Temporarily disable Memberstack
form.removeAttribute("data-ms-form");
// Create error message
const errorMsg = document.createElement('div');
errorMsg.style.color = 'red';
errorMsg.style.marginTop = '8px';
errorMsg.style.display = 'none';
errorMsg.textContent = '⚠️ Please verify your phone number via WhatsApp.';
phoneInput.parentNode.appendChild(errorMsg);
// Initially disable submit
submitBtn.disabled = true;
submitBtn.style.opacity = 0.6;
submitBtn.style.cursor = 'not-allowed';
// Init WhatsAuth
window.$WhatsAuthForm.init({
inputSelector: '[data-ms-code="phone-number"]',
apiKey: "k07Zj8EwdAIzHzLcLPQh-5jCuREbSKXG", //REPLACE WITH YOUR API KEY
placeholder: phoneInput.getAttribute("data-ms-placeholder") || "",
primaryColor: phoneInput.getAttribute("data-ms-primary-color") || "",
secondaryColor: phoneInput.getAttribute("data-ms-secondary-color") || "",
btnText: phoneInput.getAttribute("data-ms-btn-text") || ""
});
// Watch for success class from WhatsAuth
const observer = new MutationObserver(() => {
if (phoneInput.classList.contains("whatsauth-success") && !isVerified) {
isVerified = true;
// Enable submit
submitBtn.disabled = false;
submitBtn.style.opacity = 1;
submitBtn.style.cursor = 'pointer';
// Hide error
errorMsg.style.display = 'none';
// Re-enable Memberstack
form.setAttribute("data-ms-form", originalMsFormValue);
}
});
observer.observe(phoneInput, { attributes: true, attributeFilter: ["class"] });
// Final form safeguard
form.addEventListener("submit", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
submitBtn.addEventListener("click", function (e) {
if (!isVerified) {
e.preventDefault();
errorMsg.style.display = 'block';
}
});
}
});
</script>

#151 - Onboarding Tour For New Members
Launch a step-by-step product tour the first time a member logs in. Uses Memberstack’s JS API + Intro.js
<!-- 💙 MEMBERSCRIPT #151 v0.1 💙 - ONBOARDING TOUR FOR NEW MEMBERS -->
<script>
// 1. Wait for Memberstack v2 DOM
function ready(fn) {
if (window.$memberstackReady) return fn();
document.addEventListener("memberstack.ready", fn);
}
// 2. Collect all steps from the DOM
function collectSteps() {
// all elements with ms-code-step, in a NodeList
var els = document.querySelectorAll("[ms-code-step]");
// build an array of { order, element, intro }
var steps = Array.prototype.map.call(els, function(el) {
return {
order: parseInt(el.getAttribute("ms-code-step"), 10),
element: el,
intro: el.getAttribute("ms-code-intro") || ""
};
});
// sort by order ascending
return steps.sort(function(a, b) {
return a.order - b.order;
}).map(function(s) {
return { element: s.element, intro: s.intro };
});
}
// 3. Kick off the tour for first-time members
function launchTour(member) {
if (!member || !member.id) return; // only for logged-in
if (localStorage.getItem("ms-code-tour-shown")) return;
// Build steps dynamically
var options = {
steps: collectSteps(),
showProgress: true,
exitOnOverlayClick: false
};
introJs().setOptions(options).start();
localStorage.setItem("ms-code-tour-shown", "true");
}
// 4. Glue it together
ready(function() {
window.$memberstackDom
.getCurrentMember()
.then(function(res) {
launchTour(res.data);
})
.catch(function(err) {
console.error("MS-Code-Tour error:", err);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #151 v0.1 💙 - ONBOARDING TOUR FOR NEW MEMBERS -->
<script>
// 1. Wait for Memberstack v2 DOM
function ready(fn) {
if (window.$memberstackReady) return fn();
document.addEventListener("memberstack.ready", fn);
}
// 2. Collect all steps from the DOM
function collectSteps() {
// all elements with ms-code-step, in a NodeList
var els = document.querySelectorAll("[ms-code-step]");
// build an array of { order, element, intro }
var steps = Array.prototype.map.call(els, function(el) {
return {
order: parseInt(el.getAttribute("ms-code-step"), 10),
element: el,
intro: el.getAttribute("ms-code-intro") || ""
};
});
// sort by order ascending
return steps.sort(function(a, b) {
return a.order - b.order;
}).map(function(s) {
return { element: s.element, intro: s.intro };
});
}
// 3. Kick off the tour for first-time members
function launchTour(member) {
if (!member || !member.id) return; // only for logged-in
if (localStorage.getItem("ms-code-tour-shown")) return;
// Build steps dynamically
var options = {
steps: collectSteps(),
showProgress: true,
exitOnOverlayClick: false
};
introJs().setOptions(options).start();
localStorage.setItem("ms-code-tour-shown", "true");
}
// 4. Glue it together
ready(function() {
window.$memberstackDom
.getCurrentMember()
.then(function(res) {
launchTour(res.data);
})
.catch(function(err) {
console.error("MS-Code-Tour error:", err);
});
});
</script>

#150 - Save and Unsave Items to Your Collection (Pinterest-style)
A simple save/unsave system that lets members bookmark items into personal collections.
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 1 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const member = await ms.getCurrentMember();
const isLoggedIn = !!member;
let savedItems = {};
const fetchSavedItems = async () => {
try {
const { data } = await ms.getMemberJSON();
savedItems = data.savedItems || {};
} catch {
savedItems = {};
}
};
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Error saving items:", err);
}
};
const updateButtons = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'none' : 'inline-block';
});
document.querySelectorAll('[ms-code-unsave-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'inline-block' : 'none';
});
};
const onAddClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (!savedItems[category]) savedItems[category] = [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url });
updateButtons();
await persistSavedItems();
}
};
const onUnsaveClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
if (savedItems[category]) {
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
updateButtons();
await persistSavedItems();
}
};
const onDownloadClick = (e) => {
e.preventDefault();
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (url) {
const a = document.createElement('a');
a.href = url;
a.download = '';
document.body.appendChild(a);
a.click();
a.remove();
}
};
const attachListeners = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(b => b.addEventListener('click', onAddClick));
document.querySelectorAll('[ms-code-unsave-button]').forEach(b => b.addEventListener('click', onUnsaveClick));
document.querySelectorAll('[ms-code-download-button]').forEach(b => b.addEventListener('click', onDownloadClick));
};
await fetchSavedItems();
updateButtons();
attachListeners();
});
</script>
<!-- GENERATE PINTEREST GRID STYLE -->
<script>
$(document).ready(function () {
setTimeout(function() {
function resizeGridItem(item) {
grid = document.getElementsByClassName("grid")[0];
rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap'));
rowSpan = Math.ceil((item.querySelector('.content').getBoundingClientRect().height + rowGap) / (rowHeight + rowGap));
item.style.gridRowEnd = "span " + rowSpan;
}
function resizeAllGridItems() {
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
resizeGridItem(allItems[x]);
}
}
function resizeInstance(instance) {
item = instance.elements[0];
resizeGridItem(item);
}
window.onload = resizeAllGridItems();
window.addEventListener("resize", resizeAllGridItems);
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
imagesLoaded(allItems[x], resizeInstance);
}
setTimeout(function() { resizeInstance() }, 100);
}, 800);
})
</script>
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 2 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const wrapper = document.querySelector('[ms-code-collections-wrapper]');
const template = document.querySelector('[ms-code-folder-template]') || document.querySelector('[ms-code-folder]');
const emptyState = document.querySelector('[ms-code-empty]');
if (!wrapper || !template) return;
let member;
try {
member = await ms.getCurrentMember();
} catch {
wrapper.textContent = "Please log in to view your collections.";
return;
}
let savedItems = {};
try {
const { data } = await ms.getMemberJSON();
savedItems = data?.savedItems || {};
} catch {
wrapper.textContent = "Could not load your collections.";
return;
}
if (Object.keys(savedItems).length === 0) {
wrapper.innerHTML = '';
if (emptyState) emptyState.style.display = 'block';
return;
}
if (emptyState) emptyState.style.display = 'none';
wrapper.innerHTML = '';
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Failed to save", err);
}
};
const updateButtons = (modal, id, category) => {
const addBtn = modal.querySelector('[ms-code-add-button]');
const unsaveBtn = modal.querySelector('[ms-code-unsave-button]');
const exists = savedItems[category]?.some(item => item.id === id);
addBtn.style.display = exists ? 'none' : 'inline-block';
unsaveBtn.style.display = exists ? 'inline-block' : 'none';
};
Object.entries(savedItems).forEach(([category, items]) => {
const folderClone = template.cloneNode(true);
const titleEl = folderClone.querySelector('[ms-code-folder-title]');
if (titleEl) titleEl.textContent = `${category} (${items.length})`;
const imageContainer = folderClone.querySelector('[ms-code-folder-items]');
const imageTemplate = folderClone.querySelector('[ms-code-folder-image]');
if (imageTemplate) imageTemplate.style.display = 'none';
const modal = folderClone.querySelector('[ms-code-modal]');
const modalImg = folderClone.querySelector('[ms-code-modal-img]');
const modalClose = folderClone.querySelector('[ms-code-modal-close]');
const addButton = folderClone.querySelector('[ms-code-add-button]');
const unsaveButton = folderClone.querySelector('[ms-code-unsave-button]');
const downloadButton = folderClone.querySelector('[ms-code-download-button]');
const hiddenImage = folderClone.querySelector('[ms-code-image]');
items.forEach(item => {
const imgClone = imageTemplate.cloneNode(true);
imgClone.src = item.url;
imgClone.alt = category;
imgClone.style.display = 'block';
imgClone.style.objectFit = 'cover';
imgClone.style.width = '100%';
imgClone.style.height = 'auto';
imgClone.style.maxWidth = '100%';
imgClone.addEventListener('click', () => {
if (modal && modalImg) {
modalImg.src = item.url;
if (hiddenImage) hiddenImage.src = item.url;
const id = item.id;
addButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category] || [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url: item.url });
await persistSavedItems();
updateButtons(modal, id, category);
}
};
unsaveButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
await persistSavedItems();
modal.style.display = 'none';
location.reload();
};
downloadButton.onclick = (e) => {
e.preventDefault();
const a = document.createElement('a');
a.href = item.url;
a.download = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
updateButtons(modal, id, category);
}
modal.style.display = 'flex';
});
imageContainer.appendChild(imgClone);
});
if (modal && modalClose) {
modalClose.addEventListener('click', () => {
modal.style.display = 'none';
if (modalImg) modalImg.src = '';
});
}
wrapper.appendChild(folderClone);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 1 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const member = await ms.getCurrentMember();
const isLoggedIn = !!member;
let savedItems = {};
const fetchSavedItems = async () => {
try {
const { data } = await ms.getMemberJSON();
savedItems = data.savedItems || {};
} catch {
savedItems = {};
}
};
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Error saving items:", err);
}
};
const updateButtons = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'none' : 'inline-block';
});
document.querySelectorAll('[ms-code-unsave-button]').forEach(btn => {
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
const exists = savedItems[category]?.some(i => i.id === id);
btn.style.display = exists ? 'inline-block' : 'none';
});
};
const onAddClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const id = btn.getAttribute('ms-code-save');
const category = btn.getAttribute('ms-code-category');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (!savedItems[category]) savedItems[category] = [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url });
updateButtons();
await persistSavedItems();
}
};
const onUnsaveClick = async (e) => {
e.preventDefault();
if (!isLoggedIn) return;
const btn = e.currentTarget;
const id = btn.getAttribute('ms-code-unsave');
const category = btn.getAttribute('ms-code-category');
if (savedItems[category]) {
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
updateButtons();
await persistSavedItems();
}
};
const onDownloadClick = (e) => {
e.preventDefault();
const btn = e.currentTarget;
const container = btn.closest('[ms-code-save-item]');
const img = container?.querySelector('[ms-code-image]');
const url = img?.src;
if (url) {
const a = document.createElement('a');
a.href = url;
a.download = '';
document.body.appendChild(a);
a.click();
a.remove();
}
};
const attachListeners = () => {
document.querySelectorAll('[ms-code-add-button]').forEach(b => b.addEventListener('click', onAddClick));
document.querySelectorAll('[ms-code-unsave-button]').forEach(b => b.addEventListener('click', onUnsaveClick));
document.querySelectorAll('[ms-code-download-button]').forEach(b => b.addEventListener('click', onDownloadClick));
};
await fetchSavedItems();
updateButtons();
attachListeners();
});
</script>
<!-- GENERATE PINTEREST GRID STYLE -->
<script>
$(document).ready(function () {
setTimeout(function() {
function resizeGridItem(item) {
grid = document.getElementsByClassName("grid")[0];
rowHeight = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-auto-rows'));
rowGap = parseInt(window.getComputedStyle(grid).getPropertyValue('grid-row-gap'));
rowSpan = Math.ceil((item.querySelector('.content').getBoundingClientRect().height + rowGap) / (rowHeight + rowGap));
item.style.gridRowEnd = "span " + rowSpan;
}
function resizeAllGridItems() {
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
resizeGridItem(allItems[x]);
}
}
function resizeInstance(instance) {
item = instance.elements[0];
resizeGridItem(item);
}
window.onload = resizeAllGridItems();
window.addEventListener("resize", resizeAllGridItems);
allItems = document.getElementsByClassName("item");
for (x = 0; x < allItems.length; x++) {
imagesLoaded(allItems[x], resizeInstance);
}
setTimeout(function() { resizeInstance() }, 100);
}, 800);
})
</script>
<!-- 💙 MEMBERSCRIPT #150 v0.1 💙 - SAVE AND UNSAVE ITEMS TO YOUR COLLECTION PART 2 -->
<script>
document.addEventListener("DOMContentLoaded", async () => {
const ms = window.$memberstackDom;
const wrapper = document.querySelector('[ms-code-collections-wrapper]');
const template = document.querySelector('[ms-code-folder-template]') || document.querySelector('[ms-code-folder]');
const emptyState = document.querySelector('[ms-code-empty]');
if (!wrapper || !template) return;
let member;
try {
member = await ms.getCurrentMember();
} catch {
wrapper.textContent = "Please log in to view your collections.";
return;
}
let savedItems = {};
try {
const { data } = await ms.getMemberJSON();
savedItems = data?.savedItems || {};
} catch {
wrapper.textContent = "Could not load your collections.";
return;
}
if (Object.keys(savedItems).length === 0) {
wrapper.innerHTML = '';
if (emptyState) emptyState.style.display = 'block';
return;
}
if (emptyState) emptyState.style.display = 'none';
wrapper.innerHTML = '';
const persistSavedItems = async () => {
try {
await ms.updateMemberJSON({ json: { savedItems } });
} catch (err) {
console.error("Failed to save", err);
}
};
const updateButtons = (modal, id, category) => {
const addBtn = modal.querySelector('[ms-code-add-button]');
const unsaveBtn = modal.querySelector('[ms-code-unsave-button]');
const exists = savedItems[category]?.some(item => item.id === id);
addBtn.style.display = exists ? 'none' : 'inline-block';
unsaveBtn.style.display = exists ? 'inline-block' : 'none';
};
Object.entries(savedItems).forEach(([category, items]) => {
const folderClone = template.cloneNode(true);
const titleEl = folderClone.querySelector('[ms-code-folder-title]');
if (titleEl) titleEl.textContent = `${category} (${items.length})`;
const imageContainer = folderClone.querySelector('[ms-code-folder-items]');
const imageTemplate = folderClone.querySelector('[ms-code-folder-image]');
if (imageTemplate) imageTemplate.style.display = 'none';
const modal = folderClone.querySelector('[ms-code-modal]');
const modalImg = folderClone.querySelector('[ms-code-modal-img]');
const modalClose = folderClone.querySelector('[ms-code-modal-close]');
const addButton = folderClone.querySelector('[ms-code-add-button]');
const unsaveButton = folderClone.querySelector('[ms-code-unsave-button]');
const downloadButton = folderClone.querySelector('[ms-code-download-button]');
const hiddenImage = folderClone.querySelector('[ms-code-image]');
items.forEach(item => {
const imgClone = imageTemplate.cloneNode(true);
imgClone.src = item.url;
imgClone.alt = category;
imgClone.style.display = 'block';
imgClone.style.objectFit = 'cover';
imgClone.style.width = '100%';
imgClone.style.height = 'auto';
imgClone.style.maxWidth = '100%';
imgClone.addEventListener('click', () => {
if (modal && modalImg) {
modalImg.src = item.url;
if (hiddenImage) hiddenImage.src = item.url;
const id = item.id;
addButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category] || [];
if (!savedItems[category].some(i => i.id === id)) {
savedItems[category].push({ id, url: item.url });
await persistSavedItems();
updateButtons(modal, id, category);
}
};
unsaveButton.onclick = async (e) => {
e.preventDefault();
savedItems[category] = savedItems[category].filter(i => i.id !== id);
if (savedItems[category].length === 0) delete savedItems[category];
await persistSavedItems();
modal.style.display = 'none';
location.reload();
};
downloadButton.onclick = (e) => {
e.preventDefault();
const a = document.createElement('a');
a.href = item.url;
a.download = '';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
updateButtons(modal, id, category);
}
modal.style.display = 'flex';
});
imageContainer.appendChild(imgClone);
});
if (modal && modalClose) {
modalClose.addEventListener('click', () => {
modal.style.display = 'none';
if (modalImg) modalImg.src = '';
});
}
wrapper.appendChild(folderClone);
});
});
</script>

#149 - Favicon for Dark/Light Mode
Use this script to update your website's favicon based on the user's system color scheme preference.
<!-- 💙 MEMBERSCRIPT #149 v0.1 💙 - FAVICON FOR DARK/LIGHT MODE -->
<script>
// Helper: Retrieve or create a favicon element
function getFaviconElement() {
let favicon = document.querySelector('link[rel="icon"]') ||
document.querySelector('link[rel="shortcut icon"]');
if (!favicon) {
favicon = document.createElement('link');
favicon.rel = 'icon';
document.head.appendChild(favicon);
}
return favicon;
}
// Function to update the favicon based on dark mode
function updateFavicon(e) {
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
const favicon = getFaviconElement();
// Update these paths to your favicon assets in Webflow’s Asset Manager or a CDN
favicon.href = darkModeOn
? 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f85b2a9f281a373ca_Dark%20Mode%20Logo.png'
: 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f1c2fa3cebee1b150_Light%20Mode%20Logo.png';
}
// Initialize the favicon update on page load
updateFavicon();
// Listen for changes in the dark mode media query
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (typeof darkModeMediaQuery.addEventListener === 'function') {
darkModeMediaQuery.addEventListener('change', updateFavicon);
} else if (typeof darkModeMediaQuery.addListener === 'function') {
darkModeMediaQuery.addListener(updateFavicon);
}
</script>
<!-- 💙 MEMBERSCRIPT #149 v0.1 💙 - FAVICON FOR DARK/LIGHT MODE -->
<script>
// Helper: Retrieve or create a favicon element
function getFaviconElement() {
let favicon = document.querySelector('link[rel="icon"]') ||
document.querySelector('link[rel="shortcut icon"]');
if (!favicon) {
favicon = document.createElement('link');
favicon.rel = 'icon';
document.head.appendChild(favicon);
}
return favicon;
}
// Function to update the favicon based on dark mode
function updateFavicon(e) {
const darkModeOn = e ? e.matches : window.matchMedia('(prefers-color-scheme: dark)').matches;
const favicon = getFaviconElement();
// Update these paths to your favicon assets in Webflow’s Asset Manager or a CDN
favicon.href = darkModeOn
? 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f85b2a9f281a373ca_Dark%20Mode%20Logo.png'
: 'https://cdn.prod.website-files.com/67fcff014042c2f5945437c0/67fd000f1c2fa3cebee1b150_Light%20Mode%20Logo.png';
}
// Initialize the favicon update on page load
updateFavicon();
// Listen for changes in the dark mode media query
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (typeof darkModeMediaQuery.addEventListener === 'function') {
darkModeMediaQuery.addEventListener('change', updateFavicon);
} else if (typeof darkModeMediaQuery.addListener === 'function') {
darkModeMediaQuery.addListener(updateFavicon);
}
</script>

#148 - Disable Webflow Form Success Window
Use this script to override Webflow's default form submission behavior by hiding the success message.
<!-- 💙 MEMBERSCRIPT #148 v0.1 💙 - DISABLE WEBFLOW FORM SUCCESS WINDOW -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.querySelector('[ms-code-form="form"]');
const successEl = document.querySelector('[ms-code-success="true"]');
if (!form || !successEl) return;
const observer = new MutationObserver(() => {
const isVisible = window.getComputedStyle(successEl).display !== 'none';
if (isVisible) {
successEl.style.display = 'none';
form.style.display = 'block';
}
});
observer.observe(successEl, { attributes: true, attributeFilter: ['style'] });
// Cleanup observer when done (optional, for performance)
form.addEventListener('w-form-success', () => {
setTimeout(() => {
observer.disconnect();
}, 100);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #148 v0.1 💙 - DISABLE WEBFLOW FORM SUCCESS WINDOW -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const form = document.querySelector('[ms-code-form="form"]');
const successEl = document.querySelector('[ms-code-success="true"]');
if (!form || !successEl) return;
const observer = new MutationObserver(() => {
const isVisible = window.getComputedStyle(successEl).display !== 'none';
if (isVisible) {
successEl.style.display = 'none';
form.style.display = 'block';
}
});
observer.observe(successEl, { attributes: true, attributeFilter: ['style'] });
// Cleanup observer when done (optional, for performance)
form.addEventListener('w-form-success', () => {
setTimeout(() => {
observer.disconnect();
}, 100);
});
});
</script>

#147 - Age Verification Popup with Cookies in Webflow
Use this script to add an age verification popup to your Webflow site.
<!-- 💙 MEMBERSCRIPT #147 v0.1 💙 - AGE VERIFICATION POPUP WITH COOKIES -->
<script>
// Simple cookie helper functions
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; path=/";
}
function getCookie(name) {
const cname = name + "=";
const decodedCookie = decodeURIComponent(document.cookie);
const cookies = decodedCookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let c = cookies[i].trim();
if (c.indexOf(cname) === 0) {
return c.substring(cname.length, c.length);
}
}
return "";
}
document.addEventListener('DOMContentLoaded', function() {
// Select the age gate element via ms-code attribute
const ageGateEl = document.querySelector('[ms-code-agegate]');
// On page load: if the cookie exists, hide the age gate
if (getCookie('ms-code-ageVerified') === 'true') {
if (ageGateEl) ageGateEl.style.display = 'none';
}
// Listen for clicks on elements with ms-code-click
document.addEventListener('click', function(event) {
if (event.target.closest('[ms-code-click="confirmAge"]')) {
setCookie('ms-code-ageVerified', 'true', 30);
if (ageGateEl) ageGateEl.style.display = 'none';
} else if (event.target.closest('[ms-code-click="denyAge"]')) {
window.location.href = 'https://age-verification-popup-with-cookies.webflow.io/access-denied'; // Change URL as needed
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #147 v0.1 💙 - AGE VERIFICATION POPUP WITH COOKIES -->
<script>
// Simple cookie helper functions
function setCookie(name, value, days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; path=/";
}
function getCookie(name) {
const cname = name + "=";
const decodedCookie = decodeURIComponent(document.cookie);
const cookies = decodedCookie.split(';');
for (let i = 0; i < cookies.length; i++) {
let c = cookies[i].trim();
if (c.indexOf(cname) === 0) {
return c.substring(cname.length, c.length);
}
}
return "";
}
document.addEventListener('DOMContentLoaded', function() {
// Select the age gate element via ms-code attribute
const ageGateEl = document.querySelector('[ms-code-agegate]');
// On page load: if the cookie exists, hide the age gate
if (getCookie('ms-code-ageVerified') === 'true') {
if (ageGateEl) ageGateEl.style.display = 'none';
}
// Listen for clicks on elements with ms-code-click
document.addEventListener('click', function(event) {
if (event.target.closest('[ms-code-click="confirmAge"]')) {
setCookie('ms-code-ageVerified', 'true', 30);
if (ageGateEl) ageGateEl.style.display = 'none';
} else if (event.target.closest('[ms-code-click="denyAge"]')) {
window.location.href = 'https://age-verification-popup-with-cookies.webflow.io/access-denied'; // Change URL as needed
}
});
});
</script>

#146 - Stop Videos from Playing When A Modal Closes
Automatically stop video playback when closing modals in Webflow.
<!-- 💙 MEMBERSCRIPT #146 v0.1 💙 - STOP VIDEOS FROM PLAYING WHEN A MODAL CLOSES -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all modals on the page
var modals = document.querySelectorAll('[data-ms-modal="modal"]');
function initializeModal(modal) {
var iframe = modal.querySelector('iframe');
if (!iframe) return; // If no iframe found, do nothing
var originalSrc = iframe.dataset.src || iframe.src;
// Function to stop video playback
function stopVideo() {
iframe.src = "";
setTimeout(() => {
iframe.src = originalSrc;
}, 100);
}
// Attach event listeners to all close buttons inside the modal
var closeBtns = modal.querySelectorAll('[data-ms-modal="close"]');
closeBtns.forEach(function(closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.preventDefault();
stopVideo();
});
});
// Also close the modal when clicking outside the content
modal.addEventListener('click', function(e) {
if (e.target === modal) {
stopVideo();
}
});
}
// Initialize all modals
modals.forEach(initializeModal);
});
</script>
<!-- 💙 MEMBERSCRIPT #146 v0.1 💙 - STOP VIDEOS FROM PLAYING WHEN A MODAL CLOSES -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Select all modals on the page
var modals = document.querySelectorAll('[data-ms-modal="modal"]');
function initializeModal(modal) {
var iframe = modal.querySelector('iframe');
if (!iframe) return; // If no iframe found, do nothing
var originalSrc = iframe.dataset.src || iframe.src;
// Function to stop video playback
function stopVideo() {
iframe.src = "";
setTimeout(() => {
iframe.src = originalSrc;
}, 100);
}
// Attach event listeners to all close buttons inside the modal
var closeBtns = modal.querySelectorAll('[data-ms-modal="close"]');
closeBtns.forEach(function(closeBtn) {
closeBtn.addEventListener('click', function(e) {
e.preventDefault();
stopVideo();
});
});
// Also close the modal when clicking outside the content
modal.addEventListener('click', function(e) {
if (e.target === modal) {
stopVideo();
}
});
}
// Initialize all modals
modals.forEach(initializeModal);
});
</script>

#145 - Automatically Save & Prefill Forms
Automatically save and prefill forms in a browsers localStorage upon form submission.
<!-- 💙 MEMBERSCRIPT #145 v0.1 💙 - HOW TO PRE-FILL FORM INPUT FIELDS AT PAGE LOAD -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to store form data in localStorage
function storeFormData() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const value = field.value.trim();
if (value) {
localStorage.setItem(fieldId, value);
}
});
}
// Function to pre-fill form fields with stored data
function preFillForm() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const storedValue = localStorage.getItem(fieldId);
if (storedValue) {
field.value = storedValue;
}
});
}
// Handle form submission
const form = document.querySelector('#my-form, form[ms-code-form-id]');
if (form) {
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent default form submission
storeFormData();
// Refresh the page after storing data
setTimeout(function() {
location.reload();
}, 500); // Short delay to simulate form submission
});
}
// Pre-fill form fields when the page loads
preFillForm();
});
</script>
<!-- 💙 MEMBERSCRIPT #145 v0.1 💙 - HOW TO PRE-FILL FORM INPUT FIELDS AT PAGE LOAD -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Function to store form data in localStorage
function storeFormData() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const value = field.value.trim();
if (value) {
localStorage.setItem(fieldId, value);
}
});
}
// Function to pre-fill form fields with stored data
function preFillForm() {
const fields = document.querySelectorAll('input[ms-code-field-id], textarea[ms-code-field-id], select[ms-code-field-id]');
fields.forEach(function(field) {
const fieldId = field.getAttribute('ms-code-field-id');
const storedValue = localStorage.getItem(fieldId);
if (storedValue) {
field.value = storedValue;
}
});
}
// Handle form submission
const form = document.querySelector('#my-form, form[ms-code-form-id]');
if (form) {
form.addEventListener('submit', function(event) {
event.preventDefault(); // Prevent default form submission
storeFormData();
// Refresh the page after storing data
setTimeout(function() {
location.reload();
}, 500); // Short delay to simulate form submission
});
}
// Pre-fill form fields when the page loads
preFillForm();
});
</script>

#144 - Track Users Login History & Active Users
Automatically track a members login history, keep a login streak and total visits
<!-- 💙 MEMBERSCRIPT #144 v0.1 💙 - TRACK A USERS LOGIN HISTORY & ACTIVE USERS -->
<script>
(function(){
const memberstack = window.$memberstackDom;
// Helper: Execute callback when Memberstack is ready
function onMemberstackReady(cb) {
if (window.$memberstackReady) {
cb();
} else {
document.addEventListener("memberstack.ready", cb);
}
}
async function initTracking() {
// Check if a member is logged in (via localStorage)
const currentUser = JSON.parse(localStorage.getItem("_ms-mem") || "null");
if (!currentUser) {
console.warn("No logged-in member found. Tracking not applied.");
return;
}
// Retrieve member metadata
const memberJson = await memberstack.getMemberJSON();
let metadata = memberJson.data || {};
// Ensure userVisits exists as an array
metadata.userVisits = Array.isArray(metadata.userVisits) ? metadata.userVisits : [];
// Use ISO date (YYYY-MM-DD) to record one visit per day
const today = new Date().toISOString().split("T")[0];
if (!metadata.userVisits.includes(today)) {
metadata.userVisits.push(today);
}
// Helper: Compute consecutive login streak from userVisits
function computeStreak(visits) {
if (!visits.length) return 0;
// Ensure dates are unique and sorted ascending
const uniqueVisits = [...new Set(visits)].sort();
let streak = 1;
let currentDate = new Date(uniqueVisits[uniqueVisits.length - 1]);
for (let i = uniqueVisits.length - 2; i >= 0; i--) {
const prevDate = new Date(uniqueVisits[i]);
const diffDays = Math.floor((currentDate - prevDate) / (1000 * 60 * 60 * 24));
if (diffDays === 1) {
streak++;
currentDate = prevDate;
} else {
break;
}
}
return streak;
}
// Calculate the login streak and total visits
metadata.loginStreak = computeStreak(metadata.userVisits);
metadata.totalVisits = metadata.userVisits.length;
// Update Memberstack metadata
await memberstack.updateMemberJSON({ json: metadata });
console.log("User visits:", metadata.userVisits);
console.log("Login streak:", metadata.loginStreak);
console.log("Total visits:", metadata.totalVisits);
}
onMemberstackReady(initTracking);
})();
</script>
<!-- 💙 MEMBERSCRIPT #144 v0.1 💙 - TRACK A USERS LOGIN HISTORY & ACTIVE USERS -->
<script>
(function(){
const memberstack = window.$memberstackDom;
// Helper: Execute callback when Memberstack is ready
function onMemberstackReady(cb) {
if (window.$memberstackReady) {
cb();
} else {
document.addEventListener("memberstack.ready", cb);
}
}
async function initTracking() {
// Check if a member is logged in (via localStorage)
const currentUser = JSON.parse(localStorage.getItem("_ms-mem") || "null");
if (!currentUser) {
console.warn("No logged-in member found. Tracking not applied.");
return;
}
// Retrieve member metadata
const memberJson = await memberstack.getMemberJSON();
let metadata = memberJson.data || {};
// Ensure userVisits exists as an array
metadata.userVisits = Array.isArray(metadata.userVisits) ? metadata.userVisits : [];
// Use ISO date (YYYY-MM-DD) to record one visit per day
const today = new Date().toISOString().split("T")[0];
if (!metadata.userVisits.includes(today)) {
metadata.userVisits.push(today);
}
// Helper: Compute consecutive login streak from userVisits
function computeStreak(visits) {
if (!visits.length) return 0;
// Ensure dates are unique and sorted ascending
const uniqueVisits = [...new Set(visits)].sort();
let streak = 1;
let currentDate = new Date(uniqueVisits[uniqueVisits.length - 1]);
for (let i = uniqueVisits.length - 2; i >= 0; i--) {
const prevDate = new Date(uniqueVisits[i]);
const diffDays = Math.floor((currentDate - prevDate) / (1000 * 60 * 60 * 24));
if (diffDays === 1) {
streak++;
currentDate = prevDate;
} else {
break;
}
}
return streak;
}
// Calculate the login streak and total visits
metadata.loginStreak = computeStreak(metadata.userVisits);
metadata.totalVisits = metadata.userVisits.length;
// Update Memberstack metadata
await memberstack.updateMemberJSON({ json: metadata });
console.log("User visits:", metadata.userVisits);
console.log("Login streak:", metadata.loginStreak);
console.log("Total visits:", metadata.totalVisits);
}
onMemberstackReady(initTracking);
})();
</script>

#143 - Initial Based Profile Avatar
Generate a custom avatar with initials when a member has no profile picture.
<!-- 💙 MEMBERSCRIPT #143 v0.1 💙 - GENERATE INITIALS BASED AVATAR -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const checkMemberstack = setInterval(() => {
if (window.$memberstackDom) {
clearInterval(checkMemberstack);
window.$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) return console.log("No member data (logged out)");
const profileImage = document.querySelector('[data-ms-member="profile-image"]');
const avatarWrapper = document.querySelector('[data-ms-code="avatar"]');
const initialsDiv = avatarWrapper?.querySelector('.ms-avatar-initial');
if (data.profileImage) {
profileImage?.style.setProperty("display", "block");
avatarWrapper?.style.setProperty("display", "none");
} else {
profileImage?.style.setProperty("display", "none");
avatarWrapper?.style.setProperty("display", "flex");
// Get initials from available fields
const first = data.customFields["first-name"]?.trim().charAt(0).toUpperCase() || "";
const last = data.customFields["last-name"]?.trim().charAt(0).toUpperCase() || "";
let initials = first + last;
if (!initials) {
const fullName = data.customFields["name"]?.trim().split(" ") || [];
initials = fullName.length > 1
? (fullName[0].charAt(0) + fullName[1].charAt(0)).toUpperCase()
: fullName[0]?.charAt(0).toUpperCase() || "?";
}
if (initialsDiv) {
initialsDiv.textContent = initials;
} else {
avatarWrapper.innerHTML = `${initials}`;
}
}
}).catch(console.error);
}
}, 100);
});
</script>
<!-- 💙 MEMBERSCRIPT #143 v0.1 💙 - GENERATE INITIALS BASED AVATAR -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const checkMemberstack = setInterval(() => {
if (window.$memberstackDom) {
clearInterval(checkMemberstack);
window.$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) return console.log("No member data (logged out)");
const profileImage = document.querySelector('[data-ms-member="profile-image"]');
const avatarWrapper = document.querySelector('[data-ms-code="avatar"]');
const initialsDiv = avatarWrapper?.querySelector('.ms-avatar-initial');
if (data.profileImage) {
profileImage?.style.setProperty("display", "block");
avatarWrapper?.style.setProperty("display", "none");
} else {
profileImage?.style.setProperty("display", "none");
avatarWrapper?.style.setProperty("display", "flex");
// Get initials from available fields
const first = data.customFields["first-name"]?.trim().charAt(0).toUpperCase() || "";
const last = data.customFields["last-name"]?.trim().charAt(0).toUpperCase() || "";
let initials = first + last;
if (!initials) {
const fullName = data.customFields["name"]?.trim().split(" ") || [];
initials = fullName.length > 1
? (fullName[0].charAt(0) + fullName[1].charAt(0)).toUpperCase()
: fullName[0]?.charAt(0).toUpperCase() || "?";
}
if (initialsDiv) {
initialsDiv.textContent = initials;
} else {
avatarWrapper.innerHTML = `${initials}`;
}
}
}).catch(console.error);
}
}, 100);
});
</script>

#142 - Embed PDFs For Webflow
Easily embed a PDF on your Webflow site - for free, without any custom code.
<!-- 💙 MEMBERSCRIPT #142 v0.1 💙 - EMBED PDFS IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const pdfElements = document.querySelectorAll('[ms-code-pdf-src]');
pdfElements.forEach(function (element) {
const src = element.getAttribute('ms-code-pdf-src');
const height = element.getAttribute('ms-code-pdf-height') || '500px';
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.width = '100%';
iframe.style.height = height;
iframe.style.border = 'none';
// Set the iframe to block to remove any inline element gaps
iframe.style.display = 'block';
iframe.setAttribute('scrolling', 'auto');
element.innerHTML = '';
element.appendChild(iframe);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #142 v0.1 💙 - EMBED PDFS IN WEBFLOW -->
<script>
document.addEventListener("DOMContentLoaded", function () {
const pdfElements = document.querySelectorAll('[ms-code-pdf-src]');
pdfElements.forEach(function (element) {
const src = element.getAttribute('ms-code-pdf-src');
const height = element.getAttribute('ms-code-pdf-height') || '500px';
const iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.width = '100%';
iframe.style.height = height;
iframe.style.border = 'none';
// Set the iframe to block to remove any inline element gaps
iframe.style.display = 'block';
iframe.setAttribute('scrolling', 'auto');
element.innerHTML = '';
element.appendChild(iframe);
});
});
</script>

#141 - Démarrer l'intégration de YouTube à un moment précis
Activez les liens partageables et lancez la lecture des vidéos à une heure donnée.
<!-- 💙 MEMBERSCRIPT #141 v0.1 💙 - START YOUTUBE VIDEO AT SPECIFIC TIME -->
<script>
(function() {
// Function to get URL parameters
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
// Function to update YouTube embed src within Embedly iframe
function updateYouTubeEmbed(embedly_iframe, startTime) {
var embedly_src = embedly_iframe.src;
var youtube_src_match = embedly_src.match(/src=([^&]+)/);
if (youtube_src_match) {
var youtube_src = decodeURIComponent(youtube_src_match[1]);
var new_youtube_src = youtube_src.replace(/(\?|&)start=\d+/, '');
new_youtube_src += (new_youtube_src.includes('?') ? '&' : '?') + 'start=' + startTime;
var new_embedly_src = embedly_src.replace(/src=([^&]+)/, 'src=' + encodeURIComponent(new_youtube_src));
embedly_iframe.src = new_embedly_src;
}
}
// Get all elements with ms-code-yt-start attribute
var elements = document.querySelectorAll('[ms-code-yt-start]');
elements.forEach(function(element) {
var paramName = element.getAttribute('ms-code-yt-start');
var startTime = getUrlParameter(paramName);
var defaultStartTime = element.getAttribute('ms-code-yt-start-default');
// If no URL parameter, use the default start time (if specified)
if (!startTime && defaultStartTime) {
startTime = defaultStartTime;
}
// If we have a start time (either from URL or default), update the embed
if (startTime) {
var iframe = element.querySelector('iframe.embedly-embed');
if (iframe) {
updateYouTubeEmbed(iframe, startTime);
}
}
});
})();
</script>
<!-- 💙 MEMBERSCRIPT #141 v0.1 💙 - START YOUTUBE VIDEO AT SPECIFIC TIME -->
<script>
(function() {
// Function to get URL parameters
function getUrlParameter(name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^]*)');
var results = regex.exec(location.search);
return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
}
// Function to update YouTube embed src within Embedly iframe
function updateYouTubeEmbed(embedly_iframe, startTime) {
var embedly_src = embedly_iframe.src;
var youtube_src_match = embedly_src.match(/src=([^&]+)/);
if (youtube_src_match) {
var youtube_src = decodeURIComponent(youtube_src_match[1]);
var new_youtube_src = youtube_src.replace(/(\?|&)start=\d+/, '');
new_youtube_src += (new_youtube_src.includes('?') ? '&' : '?') + 'start=' + startTime;
var new_embedly_src = embedly_src.replace(/src=([^&]+)/, 'src=' + encodeURIComponent(new_youtube_src));
embedly_iframe.src = new_embedly_src;
}
}
// Get all elements with ms-code-yt-start attribute
var elements = document.querySelectorAll('[ms-code-yt-start]');
elements.forEach(function(element) {
var paramName = element.getAttribute('ms-code-yt-start');
var startTime = getUrlParameter(paramName);
var defaultStartTime = element.getAttribute('ms-code-yt-start-default');
// If no URL parameter, use the default start time (if specified)
if (!startTime && defaultStartTime) {
startTime = defaultStartTime;
}
// If we have a start time (either from URL or default), update the embed
if (startTime) {
var iframe = element.querySelector('iframe.embedly-embed');
if (iframe) {
updateYouTubeEmbed(iframe, startTime);
}
}
});
})();
</script>

#140 - Confirmer la concordance des entrées
Vérifier une entrée avant d'autoriser la soumission - idéal pour éviter les informations erronées !
<!-- 💙 MEMBERSCRIPT #140 v0.1 💙 - CONFIRM MATCHING INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
const inputPairs = form.querySelectorAll('[ms-code-conf-input]');
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
if (!submitButton) {
console.error('Submit button not found in the form');
return;
}
function validateForm() {
let fieldsMatch = true;
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
const errorElement = form.querySelector(`[ms-code-conf-error="${confType}"]`);
if (confirmInput && errorElement) {
if (input.value && confirmInput.value) {
if (input.value !== confirmInput.value) {
errorElement.style.removeProperty('display');
fieldsMatch = false;
} else {
errorElement.style.display = 'none';
}
} else {
errorElement.style.display = 'none';
}
}
});
if (fieldsMatch) {
submitButton.style.removeProperty('pointer-events');
submitButton.disabled = false;
} else {
submitButton.style.pointerEvents = 'none';
submitButton.disabled = true;
}
}
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
if (confirmInput) {
input.addEventListener('input', validateForm);
confirmInput.addEventListener('input', validateForm);
}
});
// Initial validation
validateForm();
// Extra precaution: prevent form submission if fields don't match
form.addEventListener('submit', function(event) {
if (submitButton.disabled) {
event.preventDefault();
console.log('Form submission blocked: Fields do not match');
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #140 v0.1 💙 - CONFIRM MATCHING INPUTS -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const forms = document.querySelectorAll('form');
forms.forEach(form => {
const inputPairs = form.querySelectorAll('[ms-code-conf-input]');
const submitButton = form.querySelector('input[type="submit"], button[type="submit"]');
if (!submitButton) {
console.error('Submit button not found in the form');
return;
}
function validateForm() {
let fieldsMatch = true;
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
const errorElement = form.querySelector(`[ms-code-conf-error="${confType}"]`);
if (confirmInput && errorElement) {
if (input.value && confirmInput.value) {
if (input.value !== confirmInput.value) {
errorElement.style.removeProperty('display');
fieldsMatch = false;
} else {
errorElement.style.display = 'none';
}
} else {
errorElement.style.display = 'none';
}
}
});
if (fieldsMatch) {
submitButton.style.removeProperty('pointer-events');
submitButton.disabled = false;
} else {
submitButton.style.pointerEvents = 'none';
submitButton.disabled = true;
}
}
inputPairs.forEach(input => {
const confType = input.getAttribute('ms-code-conf-input');
const confirmInput = form.querySelector(`[ms-code-conf="${confType}"]`);
if (confirmInput) {
input.addEventListener('input', validateForm);
confirmInput.addEventListener('input', validateForm);
}
});
// Initial validation
validateForm();
// Extra precaution: prevent form submission if fields don't match
form.addEventListener('submit', function(event) {
if (submitButton.disabled) {
event.preventDefault();
console.log('Form submission blocked: Fields do not match');
}
});
});
});
</script>

#139 - Réinitialiser le formulaire après l'avoir soumis
Créer un bouton dans l'état de réussite du formulaire qui permet de le soumettre à nouveau.
<!-- 💙 MEMBERSCRIPT #139 v0.1 💙 - RESET FORM BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all "Add another" buttons
const resetButtons = document.querySelectorAll('[ms-code-reset-form]');
// Add click event listener to each button
resetButtons.forEach(function(resetButton) {
resetButton.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default link behavior
// Find the closest form and success message elements
const formWrapper = this.closest('.w-form');
const form = formWrapper.querySelector('form');
const successMessage = formWrapper.querySelector('.w-form-done');
// Reset the form
form.reset();
// Hide the success message
successMessage.style.display = 'none';
// Show the form
form.style.display = 'block';
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #139 v0.1 💙 - RESET FORM BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all "Add another" buttons
const resetButtons = document.querySelectorAll('[ms-code-reset-form]');
// Add click event listener to each button
resetButtons.forEach(function(resetButton) {
resetButton.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default link behavior
// Find the closest form and success message elements
const formWrapper = this.closest('.w-form');
const form = formWrapper.querySelector('form');
const successMessage = formWrapper.querySelector('.w-form-done');
// Reset the form
form.reset();
// Hide the success message
successMessage.style.display = 'none';
// Show the form
form.style.display = 'block';
});
});
});
</script>
Besoin d'aide avec MemberScripts ? Rejoignez notre communauté Slack de plus de 5 500 membres ! 🙌
Les MemberScripts sont une ressource communautaire de Memberstack - si vous avez besoin d'aide pour les faire fonctionner avec votre projet, rejoignez le Slack de Memberstack 2.0 et demandez de l'aide !
Rejoignez notre SlackDécouvrez les entreprises qui ont réussi avec Memberstack
Ne vous contentez pas de nous croire sur parole, consultez les entreprises de toutes tailles qui font confiance à Memberstack pour leur authentification et leurs paiements.
Commencez à construire vos rêves
Memberstack est 100% gratuit jusqu'à ce que vous soyez prêt à vous lancer - alors, qu'attendez-vous ? Créez votre première application et commencez à construire dès aujourd'hui.