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.

#17 - Création d'un lien à partir d'un champ personnalisé
Utilisez des champs personnalisés pour remplir les cibles de liens avec un seul attribut !
<!-- 💙 MEMBERSCRIPT #17 v0.3 💙 ADD CUSTOM FIELD AS A LINK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');
if (!memberData?.id) return;
document.querySelectorAll('[ms-code-field-link]').forEach(element => {
const fieldKey = element.getAttribute('ms-code-field-link');
const fieldValue = memberData.customFields?.[fieldKey]?.trim();
if (!fieldValue) {
element.style.display = 'none';
return;
}
try {
// Add protocol if missing and validate URL
const url = !/^https?:\/\//i.test(fieldValue) ? 'https://' + fieldValue : fieldValue;
new URL(url); // Will throw if invalid URL
element.href = url;
element.rel = 'noopener noreferrer';
element.target = '_blank';
} catch {
element.style.display = 'none';
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #17 v0.3 💙 ADD CUSTOM FIELD AS A LINK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberData = JSON.parse(localStorage.getItem('_ms-mem') || '{}');
if (!memberData?.id) return;
document.querySelectorAll('[ms-code-field-link]').forEach(element => {
const fieldKey = element.getAttribute('ms-code-field-link');
const fieldValue = memberData.customFields?.[fieldKey]?.trim();
if (!fieldValue) {
element.style.display = 'none';
return;
}
try {
// Add protocol if missing and validate URL
const url = !/^https?:\/\//i.test(fieldValue) ? 'https://' + fieldValue : fieldValue;
new URL(url); // Will throw if invalid URL
element.href = url;
element.rel = 'noopener noreferrer';
element.target = '_blank';
} catch {
element.style.display = 'none';
}
});
});
</script>

#16 - Fin de la session après X minutes d'inactivité
Ce script redirige les utilisateurs inactifs vers une page "Session expirée" après une certaine période d'inactivité. Un compte à rebours est affiché sur la page et se réinitialise en fonction de l'activité de la page.
Add this code before the closing </body> tag any page which needs a countdown timer.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
let logoutTimer;
let countdownInterval;
let initialTime;
function startLogoutTimer() {
// Get the logout time from the HTML element
const timeElement = document.querySelector('[ms-code-logout-timer]');
const timeParts = timeElement.textContent.split(':');
const minutes = parseInt(timeParts[0], 10);
const seconds = parseInt(timeParts[1], 10);
const LOGOUT_TIME = (minutes * 60 + seconds) * 1000; // Convert to milliseconds
// Store the initial time value
if (!initialTime) {
initialTime = LOGOUT_TIME;
}
// Clear the previous timer, if any
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
let startTime = Date.now();
// Start a new timer to redirect the user after the specified time
logoutTimer = setTimeout(() => {
window.location.href = "/expired";
startLogoutTimer(); // Start the logout timer again
}, LOGOUT_TIME);
// Start a countdown timer
countdownInterval = setInterval(() => {
let elapsed = Date.now() - startTime;
let remaining = LOGOUT_TIME - elapsed;
updateCountdownDisplay(remaining);
}, 1000); // update every second
}
function updateCountdownDisplay(remainingTimeInMs) {
// convert ms to MM:SS format
let minutes = Math.floor(remainingTimeInMs / 1000 / 60);
let seconds = Math.floor((remainingTimeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = `${minutes}:${seconds}`;
}
// Call this function whenever the user interacts with the page
function resetLogoutTimer() {
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = formatTime(initialTime); // Reset to the initial time
startLogoutTimer();
}
function formatTime(timeInMs) {
let minutes = Math.floor(timeInMs / 1000 / 60);
let seconds = Math.floor((timeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return `${minutes}:${seconds}`;
}
// Call this function when the user logs in
function cancelLogoutTimer() {
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
}
// Start the timer when the page loads
startLogoutTimer();
// Add event listeners to reset timer on any page activity
document.addEventListener('mousemove', resetLogoutTimer);
document.addEventListener('keypress', resetLogoutTimer);
document.addEventListener('touchstart', resetLogoutTimer);
</script>
Add this code to your /expired page before the closing </body> tag.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
window.addEventListener('load', () => {
window.$memberstackDom.getCurrentMember().then(async ({ data: member }) => {
if (member) {
try {
await window.$memberstackDom.logout();
console.log('Logged out successfully');
setTimeout(() => {
location.reload();
}, 1000); // Refresh the page 1 second after logout
} catch (error) {
console.error(error);
}
}
});
});
</script>
Add this code before the closing </body> tag any page which needs a countdown timer.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
let logoutTimer;
let countdownInterval;
let initialTime;
function startLogoutTimer() {
// Get the logout time from the HTML element
const timeElement = document.querySelector('[ms-code-logout-timer]');
const timeParts = timeElement.textContent.split(':');
const minutes = parseInt(timeParts[0], 10);
const seconds = parseInt(timeParts[1], 10);
const LOGOUT_TIME = (minutes * 60 + seconds) * 1000; // Convert to milliseconds
// Store the initial time value
if (!initialTime) {
initialTime = LOGOUT_TIME;
}
// Clear the previous timer, if any
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
let startTime = Date.now();
// Start a new timer to redirect the user after the specified time
logoutTimer = setTimeout(() => {
window.location.href = "/expired";
startLogoutTimer(); // Start the logout timer again
}, LOGOUT_TIME);
// Start a countdown timer
countdownInterval = setInterval(() => {
let elapsed = Date.now() - startTime;
let remaining = LOGOUT_TIME - elapsed;
updateCountdownDisplay(remaining);
}, 1000); // update every second
}
function updateCountdownDisplay(remainingTimeInMs) {
// convert ms to MM:SS format
let minutes = Math.floor(remainingTimeInMs / 1000 / 60);
let seconds = Math.floor((remainingTimeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = `${minutes}:${seconds}`;
}
// Call this function whenever the user interacts with the page
function resetLogoutTimer() {
const timeElement = document.querySelector('[ms-code-logout-timer]');
timeElement.textContent = formatTime(initialTime); // Reset to the initial time
startLogoutTimer();
}
function formatTime(timeInMs) {
let minutes = Math.floor(timeInMs / 1000 / 60);
let seconds = Math.floor((timeInMs / 1000) % 60);
minutes = minutes < 10 ? "0" + minutes : minutes;
seconds = seconds < 10 ? "0" + seconds : seconds;
return `${minutes}:${seconds}`;
}
// Call this function when the user logs in
function cancelLogoutTimer() {
clearTimeout(logoutTimer);
clearInterval(countdownInterval);
}
// Start the timer when the page loads
startLogoutTimer();
// Add event listeners to reset timer on any page activity
document.addEventListener('mousemove', resetLogoutTimer);
document.addEventListener('keypress', resetLogoutTimer);
document.addEventListener('touchstart', resetLogoutTimer);
</script>
Add this code to your /expired page before the closing </body> tag.
<!-- 💙 MEMBERSCRIPT #16 v0.2 💙 LOGOUT AFTER X MINUTES OF INACTIVITY -->
<script>
window.addEventListener('load', () => {
window.$memberstackDom.getCurrentMember().then(async ({ data: member }) => {
if (member) {
try {
await window.$memberstackDom.logout();
console.log('Logged out successfully');
setTimeout(() => {
location.reload();
}, 1000); // Refresh the page 1 second after logout
} catch (error) {
console.error(error);
}
}
});
});
</script>

#15 - Rafraîchir la page après une durée définie au clic
Actualiser la page au bout d'un certain temps lorsqu'un élément est cliqué.
<!-- 💙 MEMBERSCRIPT #15 v0.1 💙 TRIGGER REFRESH ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("click", function(event) {
const clickedElement = event.target.closest('[ms-code-refresh]');
if (clickedElement) {
const refreshDelay = parseInt(clickedElement.getAttribute('ms-code-refresh'));
if (!isNaN(refreshDelay)) {
setTimeout(function() {
location.reload();
}, refreshDelay);
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #15 v0.1 💙 TRIGGER REFRESH ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
document.addEventListener("click", function(event) {
const clickedElement = event.target.closest('[ms-code-refresh]');
if (clickedElement) {
const refreshDelay = parseInt(clickedElement.getAttribute('ms-code-refresh'));
if (!isNaN(refreshDelay)) {
setTimeout(function() {
location.reload();
}, refreshDelay);
}
}
});
});
</script>

#14 - Créer un état de chargement au clic
Simuler un état de chargement personnalisé lorsqu'un élément est cliqué.
<!-- 💙 MEMBERSCRIPT #14 v0.1 💙 CREATE LOADING STATE ON CLICK -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous" ></script>
<script>
$(document).ready(function() {
// Bind click event to elements with ms-code-loading-trigger attribute
$(document).on('click', '[ms-code-loading-trigger]', function() {
// Get the value of ms-code-loading-trigger attribute
var triggerValue = $(this).attr("ms-code-loading-trigger");
// Find the matching loading element
var loadingElement = $("[ms-code-loading-element='" + triggerValue + "']");
// Find the matching subject element
var subjectElement = $("[ms-code-loading-subject='" + triggerValue + "']");
// Show the loading element (with flex display), append it to the subject element
loadingElement.css('display', 'flex').appendTo(subjectElement);
// After 5 seconds (5000 milliseconds), hide the loading element
setTimeout(function() {
loadingElement.hide();
}, 5000);
});
});
</script>
<!-- 💙 MEMBERSCRIPT #14 v0.1 💙 CREATE LOADING STATE ON CLICK -->
<script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous" ></script>
<script>
$(document).ready(function() {
// Bind click event to elements with ms-code-loading-trigger attribute
$(document).on('click', '[ms-code-loading-trigger]', function() {
// Get the value of ms-code-loading-trigger attribute
var triggerValue = $(this).attr("ms-code-loading-trigger");
// Find the matching loading element
var loadingElement = $("[ms-code-loading-element='" + triggerValue + "']");
// Find the matching subject element
var subjectElement = $("[ms-code-loading-subject='" + triggerValue + "']");
// Show the loading element (with flex display), append it to the subject element
loadingElement.css('display', 'flex').appendTo(subjectElement);
// After 5 seconds (5000 milliseconds), hide the loading element
setTimeout(function() {
loadingElement.hide();
}, 5000);
});
});
</script>

#13 - Filtrer les groupes d'éléments JSON
Affichez des listes filtrées à vos membres sur la base d'une propriété JSON !
<!-- 💙 MEMBERSCRIPT #13 v0.2 💙 FILTER ITEM GROUPS FROM JSON BASED ON ATTRIBUTE VALUE -->
<script>
(function() {
const memberstack = window.$memberstackDom;
let member;
const fetchMemberJSON = async function() {
member = await memberstack.getMemberJSON();
return member;
}
const filterItems = async function(printList) {
const filterAttr = printList.getAttribute('ms-code-list-filter');
if (!filterAttr) return;
const filters = filterAttr.split(',').map(filter => filter.trim());
const jsonGroup = printList.getAttribute('ms-code-print-list');
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
const itemContainer = document.createElement('div');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
const itemTemplate = placeholder.outerHTML;
items.forEach(item => {
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
let skipItem = false;
filters.forEach(filter => {
const exclude = filter.startsWith('!');
const filterKey = exclude ? filter.substring(1) : filter;
if ((exclude && item.hasOwnProperty(filterKey)) || (!exclude && !item.hasOwnProperty(filterKey))) {
skipItem = true;
}
});
if (skipItem) return; // Skip this item
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
printList.innerHTML = itemContainer.innerHTML;
};
// Fetch member JSON
let intervalId = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalId);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Fetch member JSON on each click
let intervalIdClick = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalIdClick);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
});
});
})();
</script>
<!-- 💙 MEMBERSCRIPT #13 v0.2 💙 FILTER ITEM GROUPS FROM JSON BASED ON ATTRIBUTE VALUE -->
<script>
(function() {
const memberstack = window.$memberstackDom;
let member;
const fetchMemberJSON = async function() {
member = await memberstack.getMemberJSON();
return member;
}
const filterItems = async function(printList) {
const filterAttr = printList.getAttribute('ms-code-list-filter');
if (!filterAttr) return;
const filters = filterAttr.split(',').map(filter => filter.trim());
const jsonGroup = printList.getAttribute('ms-code-print-list');
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
const itemContainer = document.createElement('div');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
const itemTemplate = placeholder.outerHTML;
items.forEach(item => {
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
let skipItem = false;
filters.forEach(filter => {
const exclude = filter.startsWith('!');
const filterKey = exclude ? filter.substring(1) : filter;
if ((exclude && item.hasOwnProperty(filterKey)) || (!exclude && !item.hasOwnProperty(filterKey))) {
skipItem = true;
}
});
if (skipItem) return; // Skip this item
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
printList.innerHTML = itemContainer.innerHTML;
};
// Fetch member JSON
let intervalId = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalId);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Fetch member JSON on each click
let intervalIdClick = setInterval(async () => {
member = await fetchMemberJSON();
if (member && member.data) {
clearInterval(intervalIdClick);
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(filterItems);
}
}, 500);
});
});
})();
</script>

#12 - Ajouter des éléments à des groupes JSON sur simple clic
Ajoutez un élément/une propriété aux éléments JSON précédemment créés en un seul clic !
<!-- 💙 MEMBERSCRIPT #12 v0.1 💙 ADD ITEM TO JSON GROUP ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Add click event listener to the document
document.addEventListener("click", async function(event) {
const target = event.target;
// Check if the clicked element has ms-code-update-json-item attribute
const updateJsonItem = target.closest('[ms-code-update-json-item]');
if (updateJsonItem) {
const key = updateJsonItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');
const jsonGroup = updateJsonItem.closest('[ms-code-print-list]').getAttribute('ms-code-print-list');
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
if (member.data && member.data[jsonGroup] && member.data[jsonGroup][key]) {
// Get the value to be added to the item in the member JSON
const updateValue = updateJsonItem.getAttribute('ms-code-update-json-item');
// Update the member JSON with the new value
member.data[jsonGroup][key][updateValue] = true;
// Update the member JSON using the Memberstack SDK
await memberstack.updateMemberJSON({
json: member.data
});
// Optional: Display a success message or perform any other desired action
console.log('Member JSON updated successfully');
} else {
console.error(`Could not find item with key: ${key}`);
}
}
// Check if the clicked element has ms-code-update attribute
const updateButton = target.closest('[ms-code-update="json"]');
if (updateButton) {
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
// Optional: Display a success message or perform any other desired action
console.log('Member JSON saved to local storage');
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #12 v0.1 💙 ADD ITEM TO JSON GROUP ON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Add click event listener to the document
document.addEventListener("click", async function(event) {
const target = event.target;
// Check if the clicked element has ms-code-update-json-item attribute
const updateJsonItem = target.closest('[ms-code-update-json-item]');
if (updateJsonItem) {
const key = updateJsonItem.closest('[ms-code-item-key]').getAttribute('ms-code-item-key');
const jsonGroup = updateJsonItem.closest('[ms-code-print-list]').getAttribute('ms-code-print-list');
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
if (member.data && member.data[jsonGroup] && member.data[jsonGroup][key]) {
// Get the value to be added to the item in the member JSON
const updateValue = updateJsonItem.getAttribute('ms-code-update-json-item');
// Update the member JSON with the new value
member.data[jsonGroup][key][updateValue] = true;
// Update the member JSON using the Memberstack SDK
await memberstack.updateMemberJSON({
json: member.data
});
// Optional: Display a success message or perform any other desired action
console.log('Member JSON updated successfully');
} else {
console.error(`Could not find item with key: ${key}`);
}
}
// Check if the clicked element has ms-code-update attribute
const updateButton = target.closest('[ms-code-update="json"]');
if (updateButton) {
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
// Optional: Display a success message or perform any other desired action
console.log('Member JSON saved to local storage');
}
});
});
</script>

#11 - Ouvrir automatiquement la fenêtre modale de connexion
Montrer à tous les visiteurs déconnectés une fenêtre modale de connexion immédiatement après la visite de la page. Les Memberstack personnalisés et par défaut fonctionnent.
<!-- 💙 MEMBERSCRIPT #11 v0.1 💙 SHOW LOGIN MODAL IF MEMBER IS NOT LOGGED IN -->
<!-- KEEP THIS FOR DEFAULT MEMBERSTACK MODAL -->
<script>
function handleRedirect(redirect) {
if (redirect && (window.location.pathname !== redirect)) return window.location.href = redirect;
window.location.reload()
}
$memberstackDom.getCurrentMember().then(async ({
data
}) => {
if (!data) {
const {
data
} = await $memberstackDom.openModal("login");
handleRedirect(data.redirect)
}
})
</script>
<!-- KEEP THIS FOR YOUR OWN CUSTOM MODAL -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) {
const loginModal = document.querySelector('[ms-code-modal="login"]');
if (loginModal) {
loginModal.style.display = "flex";
}
}
});
</script>
<!-- 💙 MEMBERSCRIPT #11 v0.1 💙 SHOW LOGIN MODAL IF MEMBER IS NOT LOGGED IN -->
<!-- KEEP THIS FOR DEFAULT MEMBERSTACK MODAL -->
<script>
function handleRedirect(redirect) {
if (redirect && (window.location.pathname !== redirect)) return window.location.href = redirect;
window.location.reload()
}
$memberstackDom.getCurrentMember().then(async ({
data
}) => {
if (!data) {
const {
data
} = await $memberstackDom.openModal("login");
handleRedirect(data.redirect)
}
})
</script>
<!-- KEEP THIS FOR YOUR OWN CUSTOM MODAL -->
<script>
$memberstackDom.getCurrentMember().then(({ data }) => {
if (!data) {
const loginModal = document.querySelector('[ms-code-modal="login"]');
if (loginModal) {
loginModal.style.display = "flex";
}
}
});
</script>

#10 - Afficher/supprimer un élément en fonction d'un champ personnalisé
Vérifier si un membre a rempli un champ personnalisé. Si c'est le cas, l'élément cible est affiché.
<!-- 💙 MEMBERSCRIPT #10 v0.2 💙 HIDE ELEMENTS IF CUSTOM FIELD IS BLANK -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get the `_ms-mem` object from the local storage
const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
// Get all the elements that have the `ms-code-customfield` attribute
const elements = document.querySelectorAll('[ms-code-customfield]');
// Iterate over each element
elements.forEach(element => {
// Get the value of the `ms-code-customfield` attribute
const customField = element.getAttribute('ms-code-customfield');
// If customField starts with '!', we invert the logic
if (customField.startsWith('!')) {
const actualCustomField = customField.slice(1); // remove the '!' from the start
// If the custom field is empty, remove the element from the DOM
if (msMem.customFields && msMem.customFields[actualCustomField]) {
element.parentNode.removeChild(element);
}
} else {
// Check if the user has the corresponding custom field in Memberstack
if (!msMem.customFields || !msMem.customFields[customField]) {
// If the custom field is empty, remove the element from the DOM
element.parentNode.removeChild(element);
}
}
});
});
</script>
<!-- 💙 MEMBERSCRIPT #10 v0.2 💙 HIDE ELEMENTS IF CUSTOM FIELD IS BLANK -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Get the `_ms-mem` object from the local storage
const msMem = JSON.parse(localStorage.getItem('_ms-mem'));
// Get all the elements that have the `ms-code-customfield` attribute
const elements = document.querySelectorAll('[ms-code-customfield]');
// Iterate over each element
elements.forEach(element => {
// Get the value of the `ms-code-customfield` attribute
const customField = element.getAttribute('ms-code-customfield');
// If customField starts with '!', we invert the logic
if (customField.startsWith('!')) {
const actualCustomField = customField.slice(1); // remove the '!' from the start
// If the custom field is empty, remove the element from the DOM
if (msMem.customFields && msMem.customFields[actualCustomField]) {
element.parentNode.removeChild(element);
}
} else {
// Check if the user has the corresponding custom field in Memberstack
if (!msMem.customFields || !msMem.customFields[customField]) {
// If the custom field is empty, remove the element from the DOM
element.parentNode.removeChild(element);
}
}
});
});
</script>

#9 - Compte à rebours réel, basé sur l'utilisateur
Définir dynamiquement un compte à rebours par utilisateur et masquer les éléments lorsque le temps est écoulé.
<!-- 💙 MEMBERSCRIPT #9 v0.1 💙 COUNTDOWN BY USER -->
<script>
// Step 1: Get the current date and time
const currentDate = new Date();
// Step 2: Calculate the new date and time based on the attribute values
const addTime = (date, unit, value) => {
const newDate = new Date(date);
switch (unit) {
case 'hour':
newDate.setHours(newDate.getHours() + value);
break;
case 'minute':
newDate.setMinutes(newDate.getMinutes() + value);
break;
case 'second':
newDate.setSeconds(newDate.getSeconds() + value);
break;
case 'millisecond':
newDate.setMilliseconds(newDate.getMilliseconds() + value);
break;
}
return newDate;
};
// Retrieve attribute values and calculate the new date and time
const hourAttr = document.querySelector('[ms-code-time-hour]');
const minuteAttr = document.querySelector('[ms-code-time-minute]');
const secondAttr = document.querySelector('[ms-code-time-second]');
const millisecondAttr = document.querySelector('[ms-code-time-millisecond]');
const countdownDateTime = localStorage.getItem('countdownDateTime');
let newDate;
if (countdownDateTime) {
newDate = new Date(countdownDateTime);
} else {
newDate = currentDate;
if (hourAttr.hasAttribute('ms-code-time-hour')) {
const hours = parseInt(hourAttr.getAttribute('ms-code-time-hour'));
newDate = addTime(newDate, 'hour', isNaN(hours) ? 0 : hours);
}
if (minuteAttr.hasAttribute('ms-code-time-minute')) {
const minutes = parseInt(minuteAttr.getAttribute('ms-code-time-minute'));
newDate = addTime(newDate, 'minute', isNaN(minutes) ? 0 : minutes);
}
if (secondAttr.hasAttribute('ms-code-time-second')) {
const seconds = parseInt(secondAttr.getAttribute('ms-code-time-second'));
newDate = addTime(newDate, 'second', isNaN(seconds) ? 0 : seconds);
}
if (millisecondAttr.hasAttribute('ms-code-time-millisecond')) {
const milliseconds = parseInt(millisecondAttr.getAttribute('ms-code-time-millisecond'));
newDate = addTime(newDate, 'millisecond', isNaN(milliseconds) ? 0 : milliseconds);
}
localStorage.setItem('countdownDateTime', newDate);
}
// Step 4: Update the text of the elements to show the continuous countdown
const countdownElements = [hourAttr, minuteAttr, secondAttr, millisecondAttr];
const updateCountdown = () => {
const currentTime = new Date();
const timeDifference = newDate - currentTime;
if (timeDifference > 0) {
const timeParts = [
Math.floor(timeDifference / (1000 * 60 * 60)) % 24, // Hours
Math.floor(timeDifference / (1000 * 60)) % 60, // Minutes
Math.floor(timeDifference / 1000) % 60, // Seconds
Math.floor(timeDifference) % 1000, // Milliseconds
];
// Update the text of the elements with the countdown values
countdownElements.forEach((element, index) => {
const timeValue = timeParts[index];
if (index === 3) {
element.innerText = timeValue.toString().padStart(2, '0').slice(0, 2); // Display only two digits for milliseconds
} else {
element.innerText = timeValue < 10 ? `0${timeValue}` : timeValue;
}
});
// Update the countdown every 10 milliseconds
setTimeout(updateCountdown, 10);
} else {
// Countdown has reached zero or has passed
countdownElements.forEach((element) => {
element.innerText = '00';
});
// Remove elements with ms-code-countdown="hide-on-end" attribute
const hideOnEndElements = document.querySelectorAll('[ms-code-countdown="hide-on-end"]');
hideOnEndElements.forEach((element) => {
element.remove();
});
// Optionally, you can perform additional actions or display a message when the countdown ends
}
};
// Start the countdown
updateCountdown();
</script>
<!-- 💙 MEMBERSCRIPT #9 v0.1 💙 COUNTDOWN BY USER -->
<script>
// Step 1: Get the current date and time
const currentDate = new Date();
// Step 2: Calculate the new date and time based on the attribute values
const addTime = (date, unit, value) => {
const newDate = new Date(date);
switch (unit) {
case 'hour':
newDate.setHours(newDate.getHours() + value);
break;
case 'minute':
newDate.setMinutes(newDate.getMinutes() + value);
break;
case 'second':
newDate.setSeconds(newDate.getSeconds() + value);
break;
case 'millisecond':
newDate.setMilliseconds(newDate.getMilliseconds() + value);
break;
}
return newDate;
};
// Retrieve attribute values and calculate the new date and time
const hourAttr = document.querySelector('[ms-code-time-hour]');
const minuteAttr = document.querySelector('[ms-code-time-minute]');
const secondAttr = document.querySelector('[ms-code-time-second]');
const millisecondAttr = document.querySelector('[ms-code-time-millisecond]');
const countdownDateTime = localStorage.getItem('countdownDateTime');
let newDate;
if (countdownDateTime) {
newDate = new Date(countdownDateTime);
} else {
newDate = currentDate;
if (hourAttr.hasAttribute('ms-code-time-hour')) {
const hours = parseInt(hourAttr.getAttribute('ms-code-time-hour'));
newDate = addTime(newDate, 'hour', isNaN(hours) ? 0 : hours);
}
if (minuteAttr.hasAttribute('ms-code-time-minute')) {
const minutes = parseInt(minuteAttr.getAttribute('ms-code-time-minute'));
newDate = addTime(newDate, 'minute', isNaN(minutes) ? 0 : minutes);
}
if (secondAttr.hasAttribute('ms-code-time-second')) {
const seconds = parseInt(secondAttr.getAttribute('ms-code-time-second'));
newDate = addTime(newDate, 'second', isNaN(seconds) ? 0 : seconds);
}
if (millisecondAttr.hasAttribute('ms-code-time-millisecond')) {
const milliseconds = parseInt(millisecondAttr.getAttribute('ms-code-time-millisecond'));
newDate = addTime(newDate, 'millisecond', isNaN(milliseconds) ? 0 : milliseconds);
}
localStorage.setItem('countdownDateTime', newDate);
}
// Step 4: Update the text of the elements to show the continuous countdown
const countdownElements = [hourAttr, minuteAttr, secondAttr, millisecondAttr];
const updateCountdown = () => {
const currentTime = new Date();
const timeDifference = newDate - currentTime;
if (timeDifference > 0) {
const timeParts = [
Math.floor(timeDifference / (1000 * 60 * 60)) % 24, // Hours
Math.floor(timeDifference / (1000 * 60)) % 60, // Minutes
Math.floor(timeDifference / 1000) % 60, // Seconds
Math.floor(timeDifference) % 1000, // Milliseconds
];
// Update the text of the elements with the countdown values
countdownElements.forEach((element, index) => {
const timeValue = timeParts[index];
if (index === 3) {
element.innerText = timeValue.toString().padStart(2, '0').slice(0, 2); // Display only two digits for milliseconds
} else {
element.innerText = timeValue < 10 ? `0${timeValue}` : timeValue;
}
});
// Update the countdown every 10 milliseconds
setTimeout(updateCountdown, 10);
} else {
// Countdown has reached zero or has passed
countdownElements.forEach((element) => {
element.innerText = '00';
});
// Remove elements with ms-code-countdown="hide-on-end" attribute
const hideOnEndElements = document.querySelectorAll('[ms-code-countdown="hide-on-end"]');
hideOnEndElements.forEach((element) => {
element.remove();
});
// Optionally, you can perform additional actions or display a message when the countdown ends
}
};
// Start the countdown
updateCountdown();
</script>

#8 - Copier dans le presse-papiers
Permettre aux visiteurs de copier des éléments tels que des codes de réduction dans leur presse-papiers en un seul clic.
<!-- 💙 MEMBERSCRIPT #8 v0.1 💙 SIMPLE COPY ELEMENT TO CLIPBOARD -->
<script>
// Step 1: Click the element with the attribute ms-code-copy="trigger"
const triggerElement = document.querySelector('[ms-code-copy="trigger"]');
triggerElement.addEventListener('click', () => {
// Step 2: Copy text from the element with the attribute ms-code-copy="subject" to the clipboard
const subjectElement = document.querySelector('[ms-code-copy="subject"]');
const subjectText = subjectElement.innerText;
// Create a temporary textarea element to facilitate the copying process
const tempTextArea = document.createElement('textarea');
tempTextArea.value = subjectText;
document.body.appendChild(tempTextArea);
// Select the text within the textarea and copy it to the clipboard
tempTextArea.select();
document.execCommand('copy');
// Remove the temporary textarea
document.body.removeChild(tempTextArea);
});
</script>
<!-- 💙 MEMBERSCRIPT #8 v0.1 💙 SIMPLE COPY ELEMENT TO CLIPBOARD -->
<script>
// Step 1: Click the element with the attribute ms-code-copy="trigger"
const triggerElement = document.querySelector('[ms-code-copy="trigger"]');
triggerElement.addEventListener('click', () => {
// Step 2: Copy text from the element with the attribute ms-code-copy="subject" to the clipboard
const subjectElement = document.querySelector('[ms-code-copy="subject"]');
const subjectText = subjectElement.innerText;
// Create a temporary textarea element to facilitate the copying process
const tempTextArea = document.createElement('textarea');
tempTextArea.value = subjectText;
document.body.appendChild(tempTextArea);
// Select the text within the textarea and copy it to the clipboard
tempTextArea.select();
document.execCommand('copy');
// Remove the temporary textarea
document.body.removeChild(tempTextArea);
});
</script>

#7 - Éléments de chargement à retardement
Retarder l'apparition des éléments pendant une durée déterminée afin de donner à la page le temps de se mettre à jour avec les données correctes concernant les membres.
<!-- 💙 MEMBERSCRIPT #7 v0.1 💙 DELAY LOADING ELEMENTS -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const elementsToDelay = document.querySelectorAll('[ms-code-delay]');
elementsToDelay.forEach(element => {
element.style.visibility = "hidden"; // Hide the element initially
});
setTimeout(function() {
elementsToDelay.forEach(element => {
element.style.visibility = "visible"; // Make the element visible after the delay
element.style.opacity = "0"; // Set the initial opacity to 0
element.style.animation = "fadeIn 0.5s"; // Apply the fadeIn animation
element.addEventListener("animationend", function() {
element.style.opacity = "1"; // Set opacity to 1 at the end of the animation
});
});
}, 1000);
});
</script>
<style>
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>
<!-- 💙 MEMBERSCRIPT #7 v0.1 💙 DELAY LOADING ELEMENTS -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const elementsToDelay = document.querySelectorAll('[ms-code-delay]');
elementsToDelay.forEach(element => {
element.style.visibility = "hidden"; // Hide the element initially
});
setTimeout(function() {
elementsToDelay.forEach(element => {
element.style.visibility = "visible"; // Make the element visible after the delay
element.style.opacity = "0"; // Set the initial opacity to 0
element.style.animation = "fadeIn 0.5s"; // Apply the fadeIn animation
element.addEventListener("animationend", function() {
element.style.opacity = "1"; // Set opacity to 1 at the end of the animation
});
});
}, 1000);
});
</script>
<style>
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
</style>

#6 - Créer des groupes d'éléments à partir du JSON des membres
Afficher des groupes JSON aux membres connectés en utilisant un élément placeholder construit dans le Webflow.
<!-- 💙 MEMBERSCRIPT #6 v0.1 💙 CREATE ITEM GROUPS FROM JSON -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Function to display nested/sub items
const displayNestedItems = async function(printList) {
const jsonGroup = printList.getAttribute('ms-code-print-list');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
if (!placeholder) return;
const itemTemplate = placeholder.outerHTML;
const itemContainer = document.createElement('div'); // Create a new container for the items
const member = await memberstack.getMemberJSON();
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
if (items.length === 0) return; // If no items, exit the function
items.forEach(item => {
if (Object.keys(item).length === 0) return;
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
// Add item key attribute
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
// Replace the existing list with the new items
printList.innerHTML = itemContainer.innerHTML;
};
// Call displayNestedItems function on initial page load for each instance
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(printList => {
displayNestedItems(printList);
});
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
printLists.forEach(printList => {
displayNestedItems(printList);
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #6 v0.1 💙 CREATE ITEM GROUPS FROM JSON -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Function to display nested/sub items
const displayNestedItems = async function(printList) {
const jsonGroup = printList.getAttribute('ms-code-print-list');
const placeholder = printList.querySelector(`[ms-code-print-item="${jsonGroup}"]`);
if (!placeholder) return;
const itemTemplate = placeholder.outerHTML;
const itemContainer = document.createElement('div'); // Create a new container for the items
const member = await memberstack.getMemberJSON();
const items = member.data && member.data[jsonGroup] ? Object.values(member.data[jsonGroup]) : [];
if (items.length === 0) return; // If no items, exit the function
items.forEach(item => {
if (Object.keys(item).length === 0) return;
const newItem = document.createElement('div');
newItem.innerHTML = itemTemplate;
const itemElements = newItem.querySelectorAll('[ms-code-item-text]');
itemElements.forEach(itemElement => {
const jsonKey = itemElement.getAttribute('ms-code-item-text');
const value = item && item[jsonKey] ? item[jsonKey] : '';
itemElement.textContent = value;
});
// Add item key attribute
const itemKey = Object.keys(member.data[jsonGroup]).find(k => member.data[jsonGroup][k] === item);
newItem.firstChild.setAttribute('ms-code-item-key', itemKey);
itemContainer.appendChild(newItem.firstChild);
});
// Replace the existing list with the new items
printList.innerHTML = itemContainer.innerHTML;
};
// Call displayNestedItems function on initial page load for each instance
const printLists = document.querySelectorAll('[ms-code-print-list]');
printLists.forEach(printList => {
displayNestedItems(printList);
});
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
printLists.forEach(printList => {
displayNestedItems(printList);
});
});
});
});
</script>

#5 - Remplir du texte à partir d'un simple élément JSON
Utilisez Member JSON pour mettre à jour le texte de n'importe quel élément de votre page.
<!-- 💙 MEMBERSCRIPT #5 v0.1 💙 FILL TEXT BASED ON SIMPLE ITEM IN JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Function to fill text elements with attribute ms-code-fill-text
const fillTextElements = async function() {
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Fill text elements with attribute ms-code-fill-text
const textElements = document.querySelectorAll('[ms-code-fill-text]');
textElements.forEach(element => {
const jsonKey = element.getAttribute('ms-code-fill-text');
const value = member.data && member.data[jsonKey] ? member.data[jsonKey] : '';
element.textContent = value;
});
};
// Function to handle script #4 event
const handleScript4Event = async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
await fillTextElements();
};
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", handleScript4Event);
});
// Call fillTextElements function on initial page load
fillTextElements();
});
</script>
<!-- 💙 MEMBERSCRIPT #5 v0.1 💙 FILL TEXT BASED ON SIMPLE ITEM IN JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const memberstack = window.$memberstackDom;
// Function to fill text elements with attribute ms-code-fill-text
const fillTextElements = async function() {
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Fill text elements with attribute ms-code-fill-text
const textElements = document.querySelectorAll('[ms-code-fill-text]');
textElements.forEach(element => {
const jsonKey = element.getAttribute('ms-code-fill-text');
const value = member.data && member.data[jsonKey] ? member.data[jsonKey] : '';
element.textContent = value;
});
};
// Function to handle script #4 event
const handleScript4Event = async function() {
// Add a delay of 500ms
await new Promise(resolve => setTimeout(resolve, 500));
await fillTextElements();
};
// Add click event listener to elements with ms-code-update="json"
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", handleScript4Event);
});
// Call fillTextElements function on initial page load
fillTextElements();
});
</script>

#4 - Mettre à jour le JSON des membres dans le stockage local au moment du clic
Ajoutez cet attribut à tout bouton/élément qui doit déclencher une mise à jour JSON après un court délai.
<!-- 💙 MEMBERSCRIPT #4 v0.1 💙 UPDATE MEMBER JSON IN LOCAL STORAGE ON BUTTON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
const memberstack = window.$memberstackDom;
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #4 v0.1 💙 UPDATE MEMBER JSON IN LOCAL STORAGE ON BUTTON CLICK -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const updateButtons = document.querySelectorAll('[ms-code-update="json"]');
updateButtons.forEach(button => {
button.addEventListener("click", async function() {
const memberstack = window.$memberstackDom;
// Add a delay
await new Promise(resolve => setTimeout(resolve, 500));
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
});
});
</script>

#3 - Enregistrer le JSON des membres dans le stockage local lors du chargement de la page
Créer un objet localstorage contenant le JSON du membre connecté au chargement de la page
<!-- 💙 MEMBERSCRIPT #3 v0.1 💙 SAVE JSON TO LOCALSTORAGE ON PAGE LOAD -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
</script>
<!-- 💙 MEMBERSCRIPT #3 v0.1 💙 SAVE JSON TO LOCALSTORAGE ON PAGE LOAD -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const memberstack = window.$memberstackDom;
// Retrieve the current member JSON data
const member = await memberstack.getMemberJSON();
// Save the member JSON as a local storage item
localStorage.setItem("memberJSON", JSON.stringify(member));
});
</script>

#2 - Ajouter des groupes d'éléments au JSON des membres
Permet aux membres d'ajouter des éléments/groupes imbriqués à leur JSON de membre.
<!-- 💙 MEMBERSCRIPT #2 v0.1 💙 ADD ITEM GROUPS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const msCodeForm2 = document.querySelector('[ms-code="form2"]');
const jsonList = msCodeForm2.getAttribute("ms-code-json-list");
msCodeForm2.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
// Check if the friends group already exists
if (!member.data[jsonList]) {
// Create a new friends group object
member.data[jsonList] = {};
}
// Retrieve the input values for creating the subgroup
const inputs = msCodeForm2.querySelectorAll('[ms-code-json-name]');
const subgroup = {};
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
subgroup[jsonName] = newItem;
});
// Generate a unique key for the new subgroup prefixed with the main group name
const timestamp = Date.now();
const subgroupKey = `${jsonList}-${timestamp}`;
// Create the new subgroup within the friends group
member.data[jsonList][subgroupKey] = subgroup;
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Log a message to the console
console.log(`New subgroup with key ${subgroupKey} has been created within ${jsonList} group.`);
// Reset the input values
inputs.forEach(input => {
input.value = "";
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #2 v0.1 💙 ADD ITEM GROUPS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const msCodeForm2 = document.querySelector('[ms-code="form2"]');
const jsonList = msCodeForm2.getAttribute("ms-code-json-list");
msCodeForm2.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
// Check if the friends group already exists
if (!member.data[jsonList]) {
// Create a new friends group object
member.data[jsonList] = {};
}
// Retrieve the input values for creating the subgroup
const inputs = msCodeForm2.querySelectorAll('[ms-code-json-name]');
const subgroup = {};
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
subgroup[jsonName] = newItem;
});
// Generate a unique key for the new subgroup prefixed with the main group name
const timestamp = Date.now();
const subgroupKey = `${jsonList}-${timestamp}`;
// Create the new subgroup within the friends group
member.data[jsonList][subgroupKey] = subgroup;
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Log a message to the console
console.log(`New subgroup with key ${subgroupKey} has been created within ${jsonList} group.`);
// Reset the input values
inputs.forEach(input => {
input.value = "";
});
});
});
</script>

#1 - Ajouter des éléments au JSON des membres
Permettre aux membres d'enregistrer des éléments simples dans leur JSON sans écrire de code.
<!-- 💙 MEMBERSCRIPT #1 v0.1 💙 ADD INDIVIDUAL ITEMS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const forms = document.querySelectorAll('[ms-code="form1"]');
forms.forEach(form => {
const jsonType = form.getAttribute("ms-code-json-type");
const jsonList = form.getAttribute("ms-code-json-list");
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
if (jsonType === "group") {
// Check if the group already exists
if (!member.data[jsonList]) {
// Create a new group object
member.data[jsonList] = {};
}
// Iterate over the inputs with ms-code-json-name attribute
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList][jsonName] = newItem;
});
// Log a message to the console for group type
console.log(`Item(s) have been added to member JSON with group key ${jsonList}.`);
} else if (jsonType === "array") {
// Check if the array already exists
if (!member.data[jsonList]) {
// Create a new array
member.data[jsonList] = [];
}
// Retrieve the input values for the array type
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList].push(newItem);
});
// Log a message to the console for array type
console.log(`Item(s) have been added to member JSON with array key ${jsonList}.`);
} else {
// Retrieve the input value and key for the basic item type
const input = form.querySelector('[ms-code-json-name]');
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonName] = newItem;
// Log a message to the console for basic item type
console.log(`Item ${newItem} has been added to member JSON with key ${jsonName}.`);
}
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Reset the input values
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
input.value = "";
});
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #1 v0.1 💙 ADD INDIVIDUAL ITEMS TO MEMBER JSON -->
<script>
document.addEventListener("DOMContentLoaded", function() {
const forms = document.querySelectorAll('[ms-code="form1"]');
forms.forEach(form => {
const jsonType = form.getAttribute("ms-code-json-type");
const jsonList = form.getAttribute("ms-code-json-list");
form.addEventListener('submit', async function(event) {
event.preventDefault(); // Prevent the default form submission
// Retrieve the current member JSON data
const memberstack = window.$memberstackDom;
const member = await memberstack.getMemberJSON();
// Create a new member.data object if it doesn't already exist
if (!member.data) {
member.data = {};
}
if (jsonType === "group") {
// Check if the group already exists
if (!member.data[jsonList]) {
// Create a new group object
member.data[jsonList] = {};
}
// Iterate over the inputs with ms-code-json-name attribute
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList][jsonName] = newItem;
});
// Log a message to the console for group type
console.log(`Item(s) have been added to member JSON with group key ${jsonList}.`);
} else if (jsonType === "array") {
// Check if the array already exists
if (!member.data[jsonList]) {
// Create a new array
member.data[jsonList] = [];
}
// Retrieve the input values for the array type
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonList].push(newItem);
});
// Log a message to the console for array type
console.log(`Item(s) have been added to member JSON with array key ${jsonList}.`);
} else {
// Retrieve the input value and key for the basic item type
const input = form.querySelector('[ms-code-json-name]');
const jsonName = input.getAttribute('ms-code-json-name');
const newItem = input.value;
member.data[jsonName] = newItem;
// Log a message to the console for basic item type
console.log(`Item ${newItem} has been added to member JSON with key ${jsonName}.`);
}
// Update the member JSON with the new data
await memberstack.updateMemberJSON({
json: member.data
});
// Reset the input values
const inputs = form.querySelectorAll('[ms-code-json-name]');
inputs.forEach(input => {
input.value = "";
});
});
});
});
</script>
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.

#118 - Enregistrer la dernière adresse IP du membre
Mettez à jour un champ personnalisé avec l'adresse IP la plus récente à partir de laquelle vos membres se sont connectés.
<!-- 💙 MEMBERSCRIPT #118 v0.1 💙 - SAVE LAST IP ADDRESS -->
<script>
const memberstack = window.$memberstackDom;
memberstack.getCurrentMember().then(async (response) => {
if (response && response.data) {
const member = response.data;
try {
// Fetch the current IP address from the ipify API
const ipResponse = await fetch('https://api.ipify.org?format=json');
const ipData = await ipResponse.json();
const currentIpAddress = ipData.ip;
// Retrieve the stored IP address from Memberstack's custom fields
const storedIpAddress = member.customFields["last-ip"];
// Check if the IP address has changed and update if necessary
if (currentIpAddress !== storedIpAddress) {
await memberstack.updateMember({
customFields: {
"last-ip": currentIpAddress
}
});
// Optional: Uncomment the line below to log a message when the IP is updated
// console.log('IP address updated');
} else {
// Optional: Uncomment the line below to log when the IP remains unchanged
// console.log('IP address unchanged, no update needed');
}
} catch (error) {
// Log any errors encountered during the fetch or update process
console.error('Error checking or updating member IP:', error);
}
} else {
// Optional: Uncomment the line below to log when no member is logged in
// console.log('No member is currently logged in');
}
});
</script>
<!-- 💙 MEMBERSCRIPT #118 v0.1 💙 - SAVE LAST IP ADDRESS -->
<script>
const memberstack = window.$memberstackDom;
memberstack.getCurrentMember().then(async (response) => {
if (response && response.data) {
const member = response.data;
try {
// Fetch the current IP address from the ipify API
const ipResponse = await fetch('https://api.ipify.org?format=json');
const ipData = await ipResponse.json();
const currentIpAddress = ipData.ip;
// Retrieve the stored IP address from Memberstack's custom fields
const storedIpAddress = member.customFields["last-ip"];
// Check if the IP address has changed and update if necessary
if (currentIpAddress !== storedIpAddress) {
await memberstack.updateMember({
customFields: {
"last-ip": currentIpAddress
}
});
// Optional: Uncomment the line below to log a message when the IP is updated
// console.log('IP address updated');
} else {
// Optional: Uncomment the line below to log when the IP remains unchanged
// console.log('IP address unchanged, no update needed');
}
} catch (error) {
// Log any errors encountered during the fetch or update process
console.error('Error checking or updating member IP:', error);
}
} else {
// Optional: Uncomment the line below to log when no member is logged in
// console.log('No member is currently logged in');
}
});
</script>

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

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

#115 - Générer un mot de passe aléatoire
Inscription sans friction. Exiger ou permettre aux membres de définir un mot de passe à l'avenir.
<!-- 💙 MEMBERSCRIPT #115 v0.1 💙 - GENERATE PASSWORD-->
<script>
document.addEventListener('DOMContentLoaded', function() {
var passwordInput = document.querySelector('[data-ms-member="password"]');
if (passwordInput) {
// Function to generate random password
function generatePassword() {
var timestamp = Date.now().toString(36);
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+{}[]|:;<>,.?/~';
var randomChars = '';
for (var i = 0; i < 16; i++) {
randomChars += characters.charAt(Math.floor(Math.random() * characters.length));
}
return (timestamp + randomChars).slice(0, 32);
}
// Generate and set password
passwordInput.value = generatePassword();
// Block password managers and prevent editing
passwordInput.setAttribute('autocomplete', 'off');
passwordInput.setAttribute('readonly', 'readonly');
// Prevent copy and paste
passwordInput.addEventListener('copy', function(e) {
e.preventDefault();
});
passwordInput.addEventListener('paste', function(e) {
e.preventDefault();
});
// Prevent dragging
passwordInput.addEventListener('dragstart', function(e) {
e.preventDefault();
});
// Prevent context menu
passwordInput.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #115 v0.1 💙 - GENERATE PASSWORD-->
<script>
document.addEventListener('DOMContentLoaded', function() {
var passwordInput = document.querySelector('[data-ms-member="password"]');
if (passwordInput) {
// Function to generate random password
function generatePassword() {
var timestamp = Date.now().toString(36);
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+{}[]|:;<>,.?/~';
var randomChars = '';
for (var i = 0; i < 16; i++) {
randomChars += characters.charAt(Math.floor(Math.random() * characters.length));
}
return (timestamp + randomChars).slice(0, 32);
}
// Generate and set password
passwordInput.value = generatePassword();
// Block password managers and prevent editing
passwordInput.setAttribute('autocomplete', 'off');
passwordInput.setAttribute('readonly', 'readonly');
// Prevent copy and paste
passwordInput.addEventListener('copy', function(e) {
e.preventDefault();
});
passwordInput.addEventListener('paste', function(e) {
e.preventDefault();
});
// Prevent dragging
passwordInput.addEventListener('dragstart', function(e) {
e.preventDefault();
});
// Prevent context menu
passwordInput.addEventListener('contextmenu', function(e) {
e.preventDefault();
});
}
});
</script>

#114 - Bouton de défilement vers le haut
Ajoutez un bouton qui fera défiler la page vers le haut lorsqu'il sera cliqué,
<!-- 💙 MEMBERSCRIPT #114 v0.1 💙 - SCROLL TO TOP BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var scrollTopButton = document.querySelector('[ms-code-scroll-top="button"]');
if (scrollTopButton) {
// Set initial styles
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
scrollTopButton.style.transition = 'opacity 0.3s, visibility 0.3s';
// Function to check scroll position and toggle button visibility
function toggleButtonVisibility() {
if (window.pageYOffset > 300) {
scrollTopButton.style.opacity = '1';
scrollTopButton.style.visibility = 'visible';
} else {
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
}
}
// Initial check on page load
toggleButtonVisibility();
// Check on scroll
window.addEventListener('scroll', toggleButtonVisibility);
// Scroll to top when button is clicked
scrollTopButton.addEventListener('click', function() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
});
</script>
<!-- 💙 MEMBERSCRIPT #114 v0.1 💙 - SCROLL TO TOP BUTTON -->
<script>
document.addEventListener('DOMContentLoaded', function() {
var scrollTopButton = document.querySelector('[ms-code-scroll-top="button"]');
if (scrollTopButton) {
// Set initial styles
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
scrollTopButton.style.transition = 'opacity 0.3s, visibility 0.3s';
// Function to check scroll position and toggle button visibility
function toggleButtonVisibility() {
if (window.pageYOffset > 300) {
scrollTopButton.style.opacity = '1';
scrollTopButton.style.visibility = 'visible';
} else {
scrollTopButton.style.opacity = '0';
scrollTopButton.style.visibility = 'hidden';
}
}
// Initial check on page load
toggleButtonVisibility();
// Check on scroll
window.addEventListener('scroll', toggleButtonVisibility);
// Scroll to top when button is clicked
scrollTopButton.addEventListener('click', function() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
});
</script>

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

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

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

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

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

#108 - Boutons de soumission de formulaire personnalisés
Créez n'importe quel élément dans Webflow et utilisez-le pour soumettre n'importe quel type de formulaire.
<!-- 💙 MEMBERSCRIPT #108 v0.1 💙 CUSTOM FORM SUBMIT BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-submit-new attribute
const newSubmitButtons = document.querySelectorAll('[ms-code-submit-new]');
// Add click event listeners to each new submit button
newSubmitButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default action if it's a link
// Get the value of the ms-code-submit-new attribute
const submitId = this.getAttribute('ms-code-submit-new');
// Find the corresponding old submit button
const oldSubmitButton = document.querySelector(`[ms-code-submit-old="${submitId}"]`);
// If found, trigger a click on the old submit button
if (oldSubmitButton) {
oldSubmitButton.click();
} else {
console.error(`No matching old submit button found for ID: ${submitId}`);
}
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #108 v0.1 💙 CUSTOM FORM SUBMIT BUTTON -->
<script>
// Wait for the DOM to be fully loaded
document.addEventListener('DOMContentLoaded', function() {
// Find all elements with the ms-code-submit-new attribute
const newSubmitButtons = document.querySelectorAll('[ms-code-submit-new]');
// Add click event listeners to each new submit button
newSubmitButtons.forEach(button => {
button.addEventListener('click', function(e) {
e.preventDefault(); // Prevent default action if it's a link
// Get the value of the ms-code-submit-new attribute
const submitId = this.getAttribute('ms-code-submit-new');
// Find the corresponding old submit button
const oldSubmitButton = document.querySelector(`[ms-code-submit-old="${submitId}"]`);
// If found, trigger a click on the old submit button
if (oldSubmitButton) {
oldSubmitButton.click();
} else {
console.error(`No matching old submit button found for ID: ${submitId}`);
}
});
});
});
</script>

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

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

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

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

#103 - Formulaire de réservation personnalisé
Ajoutez un formulaire de réservation personnalisé à votre site web qui crée un événement dans le calendrier Google.
<!-- 💙 MEMBERSCRIPT #103 v0.1 💙 CUSTOM BOOKING FORM -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.33/moment-timezone-with-data.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
function getNextBusinessDays() {
let businessDays = [];
let currentDate = moment();
currentDate.add(1, 'days');
while (businessDays.length < 14) {
if (currentDate.day() !== 0 && currentDate.day() !== 6) {
let formattedDay = currentDate.format('dddd, MMMM D');
let rawDay = currentDate.format('YYYY-MM-DD');
businessDays.push({formattedDay, rawDay});
}
currentDate.add(1, 'days');
}
return businessDays;
}
function generateTimeSlots() {
let slots = [];
let startHour = 9;
let endHour = 16.5;
let currentTime = moment().startOf('day').add(startHour, 'hours');
while (currentTime.hour() + (currentTime.minute() / 60) <= endHour) {
let formattedTime = currentTime.format('h:mm A');
let timeValue = currentTime.format('HH:mm');
slots.push({formattedTime, timeValue});
currentTime.add(30, 'minutes');
}
return slots;
}
function updateTimestamp(day, time, timezone) {
let timestampInput = document.getElementById('timestamp');
if (!timestampInput) {
timestampInput = document.createElement('input');
timestampInput.type = 'hidden';
timestampInput.id = 'timestamp';
timestampInput.name = 'timestamp';
document.querySelector('form').appendChild(timestampInput);
}
let datetime = moment.tz(`${day} ${time}`, "YYYY-MM-DD HH:mm", timezone);
timestampInput.value = datetime.valueOf();
}
function populateFields() {
const days = getNextBusinessDays();
const times = generateTimeSlots();
const daySelect = document.querySelector('[ms-code-booking="day"]');
const timeSelect = document.querySelector('[ms-code-booking="time"]');
const form = daySelect.closest('form');
const timezone = form.getAttribute('ms-code-booking-timezone') || moment.tz.guess();
days.forEach(({formattedDay, rawDay}) => {
let option = new Option(formattedDay, rawDay);
daySelect.appendChild(option);
});
times.forEach(({formattedTime, timeValue}) => {
let option = new Option(formattedTime, timeValue);
timeSelect.appendChild(option);
});
function handleSelectChange() {
if (daySelect.value && timeSelect.value) {
updateTimestamp(daySelect.value, timeSelect.value, timezone);
}
}
daySelect.addEventListener('change', handleSelectChange);
timeSelect.addEventListener('change', handleSelectChange);
}
populateFields();
});
</script>
<!-- 💙 MEMBERSCRIPT #103 v0.1 💙 CUSTOM BOOKING FORM -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.33/moment-timezone-with-data.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
function getNextBusinessDays() {
let businessDays = [];
let currentDate = moment();
currentDate.add(1, 'days');
while (businessDays.length < 14) {
if (currentDate.day() !== 0 && currentDate.day() !== 6) {
let formattedDay = currentDate.format('dddd, MMMM D');
let rawDay = currentDate.format('YYYY-MM-DD');
businessDays.push({formattedDay, rawDay});
}
currentDate.add(1, 'days');
}
return businessDays;
}
function generateTimeSlots() {
let slots = [];
let startHour = 9;
let endHour = 16.5;
let currentTime = moment().startOf('day').add(startHour, 'hours');
while (currentTime.hour() + (currentTime.minute() / 60) <= endHour) {
let formattedTime = currentTime.format('h:mm A');
let timeValue = currentTime.format('HH:mm');
slots.push({formattedTime, timeValue});
currentTime.add(30, 'minutes');
}
return slots;
}
function updateTimestamp(day, time, timezone) {
let timestampInput = document.getElementById('timestamp');
if (!timestampInput) {
timestampInput = document.createElement('input');
timestampInput.type = 'hidden';
timestampInput.id = 'timestamp';
timestampInput.name = 'timestamp';
document.querySelector('form').appendChild(timestampInput);
}
let datetime = moment.tz(`${day} ${time}`, "YYYY-MM-DD HH:mm", timezone);
timestampInput.value = datetime.valueOf();
}
function populateFields() {
const days = getNextBusinessDays();
const times = generateTimeSlots();
const daySelect = document.querySelector('[ms-code-booking="day"]');
const timeSelect = document.querySelector('[ms-code-booking="time"]');
const form = daySelect.closest('form');
const timezone = form.getAttribute('ms-code-booking-timezone') || moment.tz.guess();
days.forEach(({formattedDay, rawDay}) => {
let option = new Option(formattedDay, rawDay);
daySelect.appendChild(option);
});
times.forEach(({formattedTime, timeValue}) => {
let option = new Option(formattedTime, timeValue);
timeSelect.appendChild(option);
});
function handleSelectChange() {
if (daySelect.value && timeSelect.value) {
updateTimestamp(daySelect.value, timeSelect.value, timezone);
}
}
daySelect.addEventListener('change', handleSelectChange);
timeSelect.addEventListener('change', handleSelectChange);
}
populateFields();
});
</script>

#102 - Redimensionner automatiquement la hauteur du Textarea
Augmenter ou diminuer la hauteur d'un textarea en fonction de son contenu.
<!-- 💙 MEMBERSCRIPT #102 v0.1 💙 RESIZE TEXTAREA VERTICALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[data-ms-post="content"], [ms-code-resize-input="height"]');
elements.forEach(element => {
if (element.tagName.toLowerCase() === 'textarea') {
element.addEventListener('input', function() {
autoResize(this);
}, false);
}
});
function autoResize(element) {
const maxHeight = parseInt(getComputedStyle(element).maxHeight, 10);
element.style.height = 'auto';
element.style.overflow = 'hidden'; // Prevents scrollbar appearance during height adjustment
if (element.scrollHeight > maxHeight) {
element.style.height = `${maxHeight}px`;
element.style.overflow = 'auto'; // Adds scrollbar when content exceeds max height
} else {
element.style.height = `${element.scrollHeight}px`;
}
}
});
</script>
<!-- 💙 MEMBERSCRIPT #102 v0.1 💙 RESIZE TEXTAREA VERTICALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[data-ms-post="content"], [ms-code-resize-input="height"]');
elements.forEach(element => {
if (element.tagName.toLowerCase() === 'textarea') {
element.addEventListener('input', function() {
autoResize(this);
}, false);
}
});
function autoResize(element) {
const maxHeight = parseInt(getComputedStyle(element).maxHeight, 10);
element.style.height = 'auto';
element.style.overflow = 'hidden'; // Prevents scrollbar appearance during height adjustment
if (element.scrollHeight > maxHeight) {
element.style.height = `${maxHeight}px`;
element.style.overflow = 'auto'; // Adds scrollbar when content exceeds max height
} else {
element.style.height = `${element.scrollHeight}px`;
}
}
});
</script>

#101 - Redimensionner automatiquement la largeur d'entrée
Augmenter ou réduire la largeur d'une entrée en fonction de son contenu.
<!-- 💙 MEMBERSCRIPT #101 v0.1 💙 RESIZE INPUT HORIZONTALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[ms-code-resize-input="width"]');
// Store the initial widths
const initialWidths = new Map();
elements.forEach(element => {
initialWidths.set(element, element.offsetWidth);
});
elements.forEach(element => {
element.addEventListener('input', function() {
autoResizeWidth(this);
});
});
function autoResizeWidth(element) {
// Find the nearest hidden measure element
const measurer = element.nextElementSibling.getAttribute('ms-code-resize-input') === 'hidden-measure'
? element.nextElementSibling
: null;
if (!measurer) return; // Exit if no measurer is found
measurer.textContent = element.value;
const maxWidth = parseInt(getComputedStyle(element).maxWidth, 10);
const minWidth = initialWidths.get(element);
const contentWidth = measurer.offsetWidth;
if (contentWidth > minWidth && contentWidth < maxWidth) {
element.style.width = `${contentWidth}px`;
} else if (contentWidth >= maxWidth) {
element.style.width = `${maxWidth}px`;
} else {
element.style.width = `${minWidth}px`;
}
}
});
</script>
<!-- 💙 MEMBERSCRIPT #101 v0.1 💙 RESIZE INPUT HORIZONTALLY -->
<script>
document.addEventListener('DOMContentLoaded', function() {
const elements = document.querySelectorAll('[ms-code-resize-input="width"]');
// Store the initial widths
const initialWidths = new Map();
elements.forEach(element => {
initialWidths.set(element, element.offsetWidth);
});
elements.forEach(element => {
element.addEventListener('input', function() {
autoResizeWidth(this);
});
});
function autoResizeWidth(element) {
// Find the nearest hidden measure element
const measurer = element.nextElementSibling.getAttribute('ms-code-resize-input') === 'hidden-measure'
? element.nextElementSibling
: null;
if (!measurer) return; // Exit if no measurer is found
measurer.textContent = element.value;
const maxWidth = parseInt(getComputedStyle(element).maxWidth, 10);
const minWidth = initialWidths.get(element);
const contentWidth = measurer.offsetWidth;
if (contentWidth > minWidth && contentWidth < maxWidth) {
element.style.width = `${contentWidth}px`;
} else if (contentWidth >= maxWidth) {
element.style.width = `${maxWidth}px`;
} else {
element.style.width = `${minWidth}px`;
}
}
});
</script>

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

#99 - Fichiers d'entrée personnalisés
Transformez n'importe quoi en fichier d'entrée !
<!-- 💙 MEMBERSCRIPT #99 v0.1 💙 CUSTOM FILE UPLOAD INPUT -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const uploadButtons = document.querySelectorAll('[ms-code-file_uploader]');
uploadButtons.forEach(button => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.name = button.getAttribute('ms-code-file_uploader');
fileInput.accept = button.getAttribute('ms-code-file_types');
document.body.appendChild(fileInput);
button.addEventListener('click', function (e) {
e.preventDefault();
fileInput.click();
});
fileInput.addEventListener('change', function () {
const fileName = fileInput.files[0].name;
button.querySelector('div').textContent = fileName;
});
});
});
</script>
<!-- 💙 MEMBERSCRIPT #99 v0.1 💙 CUSTOM FILE UPLOAD INPUT -->
<script>
document.addEventListener('DOMContentLoaded', function () {
const uploadButtons = document.querySelectorAll('[ms-code-file_uploader]');
uploadButtons.forEach(button => {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.style.display = 'none';
fileInput.name = button.getAttribute('ms-code-file_uploader');
fileInput.accept = button.getAttribute('ms-code-file_types');
document.body.appendChild(fileInput);
button.addEventListener('click', function (e) {
e.preventDefault();
fileInput.click();
});
fileInput.addEventListener('change', function () {
const fileName = fileInput.files[0].name;
button.querySelector('div').textContent = fileName;
});
});
});
</script>
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.