How to create user likes/bookmarks with no external tools. Just Webflow and Memberstack.
Hi All
I wanted to create some documentation to explain a process I've recently worked on, since I didn't find a 'how-to' for this using Memberstack 2.0. This guide might be useful for those who, like me, aren't coding experts.
Here's how my 'like' button system operates, using my classes and attribute names:
Firstly, you need to create the CMS collection list from which you'd like users to be able to 'like' items. Every CMS item should be equipped with a 'like' button.
Assign the buttons a class 'like-button'. This button needs to have a custom attribute (data-cms-item="Unique CMS Item ID"
). This "Unique CMS Item ID" must be a unique identifier for each item and should be linked to a Webflow CMS attribute. This can be done by clicking on the little purple button.
When the 'like' button is pressed, the combo class 'is-liked' is appended to the button via the custom code (provided below). Prior to publishing the site, pre-style this class and remove it from the button.
The following JavaScript code reads the user's JSON file stored in Memberstack (assuming the user is logged in). In the JSON file, it either creates or adds to the array 'likes', using the Unique CMS Item ID value. Furthermore, the code checks each 'like' button and will append the class 'is-liked' if it's present in the user's 'likes' array.
Here's the code:
<script>
document.addEventListener('DOMContentLoaded', (event) => {
const memberstack = window.$memberstackDom;
// Get all the like buttons
let likeButtons = document.querySelectorAll('.like-button');
// Add an event listener to each like button
likeButtons.forEach(likeButton => {
let likeButtonLastClickedAt = 0;
likeButton.addEventListener('click', async function () {
// If the last click was less than 500ms ago, ignore this one
const now = Date.now();
if (now - likeButtonLastClickedAt < 500) {
return;
}
likeButtonLastClickedAt = now;
// Optimistically update the UI
const cmsData = this.getAttribute('data-cms-item');
if (this.classList.contains('is-liked')) {
this.classList.remove('is-liked');
} else {
this.classList.add('is-liked');
}
// Get the current member
let member = await memberstack.getCurrentMember();
// If the member is not logged in, redirect to /signup
if (!member || !member.data) {
window.location.href = '/signup';
return;
}
// Get current member's JSON
let memberJson = await memberstack.getMemberJSON();
// Unwrap unnecessary "data" objects
while (memberJson.data) {
memberJson = memberJson.data;
}
// Create the likes array if it doesn't exist
if (!memberJson.likes) {
memberJson.likes = [];
}
// Check if cmsData is already in the array, if not, add it
if (!memberJson.likes.includes(cmsData)) {
memberJson.likes.push(cmsData);
} else {
// If the item is already liked, remove it from the array
memberJson.likes = memberJson.likes.filter(item => item !== cmsData);
}
// Update member's JSON asynchronously
memberstack.updateMemberJSON({ json: memberJson }).catch((error) => {
console.error("Failed to update member JSON: ", error);
// If there was an error updating the JSON, revert the button style
if (this.classList.contains('is-liked')) {
this.classList.remove('is-liked');
} else {
this.classList.add('is-liked');
}
});
});
// Initialise the button state based on the member's likes
(async function initButtonState() {
// Get the current member
let member = await memberstack.getCurrentMember();
// If the member is not logged in, do nothing
if (!member || !member.data) {
return;
}
let memberJson = await memberstack.getMemberJSON();
// Unwrap unnecessary "data" objects
while (memberJson.data) {
memberJson = memberJson.data;
}
let cmsData = likeButton.getAttribute('data-cms-item');
// If the member has already liked the item, style the button as 'is-liked'
if (memberJson.likes && memberJson.likes.includes(cmsData)) {
likeButton.classList.add('is-liked');
}
})();
});
});
</script>
Now that we have stored the user's 'likes', we can display these 'likes' on a separate page.
Using JavaScript, we can use conditional visibility on the Webflow CMS list to hide items if they don't match a value from the user's 'likes' array in JSON. To set this up, I duplicated the initial CMS list onto a new page, e.g., "Your Likes". Each item therefore already had the custom attribute (data-cms-item="Unique CMS Item ID"
). It's important to note that all CMS items will load on the "Your Likes" page, however, we're merely hiding ones that don't match the value in 'likes'. For the following code to work in its current state, the individual CMS item class should be called 'featured-frequencies-card'. (the item we are hiding)
Here is the code:
<script>
document.addEventListener('DOMContentLoaded', async () => {
const memberstack = window.$memberstackDom;
let memberJson = await memberstack.getMemberJSON();
// Unwrap unnecessary "data" objects
while (memberJson.data) {
memberJson = memberJson.data;
}
// Assuming each CMS item has a data attribute 'data-cms-item'
// which matches the names stored in the user's 'likes' array
let cmsItems = document.querySelectorAll('.featured-frequencies-card');
cmsItems.forEach(item => {
let itemName = item.getAttribute('data-cms-item');
// If the user has not liked this item, hide it
if (!memberJson.likes.includes(itemName)) {
item.style.display = 'none';
}
});
});
</script>
Also, on the "Your Likes" page, you can append the 'is-liked' class to the buttons. This will cause them to automatically appear as 'liked'
Note: If a user is not logged in and they press the like button, they are redirected to /signup
Notes: Just updated the first code to include some error handling, and to apply the "is-liked" class optimistically, before the computation is done.
Hope this helped!
as I said I'm not a coder.
I'm just a guy with GPT 4 and some time.
Comments
5 comments
Fantastic!! You just saved me (and others) a ton of time. Thanks so much for documenting and sharing this solution 🙏
This has been added to the help center! Thank you again Daniel Thomas. 🙏
https://docs.memberstack.com/hc/en-us/articles/22268128435739
Hi Josh, just to let you know, the code Daniel sent in this thread works. But the code you added to the help center doesn't. Just thought I'd let you know!
Hi Josh, just to let you know, the code Daniel sent in this thread works. But the code you added to the help center doesn't. Just thought I'd let you know!
Thank you Josh Dean. It looks like the editor stripped the arrow functions somehow. I just updated the doc.
Please sign in to leave a comment.