v0.1

Data TablesWebflow CMS
#219 - Like / Unlike Buttons For CMS Items
Toggle likes on Webflow CMS items and show a live like count.
Build a searchable member directory and profile pages powered by Memberstack Data Tables.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #220 v0.1 💙 MEMBER DIRECTORY WITH SEARCH & PROFILE DETAIL -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
const CONFIG = {
pageSize: 100,
searchDebounce: 200
};
const memberstack = window.$memberstackDom;
if (!memberstack) {
console.warn("Memberscript # number220: Memberstack not found");
return;
}
function show(el) { if (el) el.style.display = ""; }
function hide(el) { if (el) el.style.display = "none"; }
async function fetchAllRecords(table) {
const all = [];
let skip = 0;
let page;
do {
const result = await memberstack.queryDataRecords({
table: table,
query: { take: CONFIG.pageSize, skip: skip }
});
page = result.data?.records || result.data || [];
all.push.apply(all, page);
skip += page.length;
} while (page.length === CONFIG.pageSize);
return all;
}
function bindFields(parent, data) {
const fields = parent.querySelectorAll("[data-ms-field]");
fields.forEach(function(el) {
const name = el.getAttribute("data-ms-field");
const type = el.getAttribute("ms-field") || "text";
const value = data[name] || "";
// Hide the element entirely when the field has no value
if (!value) {
el.style.display = "none";
return;
}
if (type === "date") {
const d = new Date(value);
if (!isNaN(d)) {
var months = ["January","February","March","April","May","June",
"July","August","September","October","November","December"];
el.textContent = "Joined " + months[d.getMonth()] + " " + d.getFullYear();
} else {
el.textContent = value;
}
} else if (type === "array") {
const items = value.split(",").map(function(s) { return s.trim(); }).filter(Boolean);
const tpl = el.querySelector('[data-ms-code="array-template"]');
el.innerHTML = "";
items.forEach(function(item) {
if (tpl) {
const clone = tpl.cloneNode(true);
clone.removeAttribute("data-ms-code");
clone.textContent = item;
el.appendChild(clone);
} else {
var span = document.createElement("span");
span.textContent = item;
el.appendChild(span);
}
});
} else if (type === "image") {
el.src = value;
el.alt = data.name || "";
} else if (type === "html") {
el.innerHTML = value;
} else {
el.textContent = value;
}
});
}
// ─── DIRECTORY funcPAGE(List + Search) ───
const listContainer = document.querySelector('[data-ms-code="list-container"]');
const template = listContainer ? listContainer.querySelector('[data-ms-code="list-template"]') : null;
if (listContainer && template) {
const tableName = listContainer.getAttribute("ms-code-table") || "members";
const detailPage = listContainer.getAttribute("ms-code-detail-page") || "/member-profile";
const idParam = listContainer.getAttribute("ms-code-id-param") || "id";
const sortAttr = listContainer.getAttribute("ms-code-sort");
const loadingEl = listContainer.querySelector('[data-ms-code="list-loading"]');
const emptyEl = listContainer.querySelector('[data-ms-code="list-empty"]');
const errorEl = listContainer.querySelector('[data-ms-code="list-error"]');
const searchInput = document.querySelector("[data-member-search]");
const templateClone = template.cloneNode(true);
templateClone.removeAttribute("data-ms-code");
// Remove template + any static placeholder items inside the container
const placeholders = listContainer.querySelectorAll("a. propmember-list_item, .member-list_item");
placeholders.forEach(function(el) { el.remove(); });
hide(emptyEl);
hide(errorEl);
show(loadingEl);
const renderedItems = [];
try {
const records = await fetchAllRecords(tableName);
// Client-side sort keywordif ms-code-sort is set(e.g. "name:asc")
if (sortAttr) {
const parts = sortAttr.split(":");
const sortField = parts[0];
const sortDir = parts[1] || "asc";
records.sort(function(a, b) {
const valA = (a.data?.[sortField] || "").toString().toLowerCase();
const valB = (b.data?.[sortField] || "").toString().toLowerCase();
if (valA < valB) return sortDir === "asc" ? -1 : 1;
if (valA > valB) return sortDir === "asc" ? 1 : -1;
return 0;
});
}
hide(loadingEl);
if (records.length === 0) {
show(emptyEl);
} else {
records.forEach(function(record) {
const item = templateClone.cloneNode(true);
const data = record.data || {};
if (record.createdAt) data.createdAt = record.createdAt;
item.href = detailPage + "?" + idParam + "=" + record.id;
bindFields(item, data);
// Cache searchable text keywordfor client-side filtering(no extra API calls)
item._searchText = (
(data.name || "") + " " + (data.email || "")
).toLowerCase();
listContainer.appendChild(item);
renderedItems.push(item);
});
}
// Client-side search — single load, filter locally
if (searchInput) {
let searchTimeout;
searchInput.addEventListener("input", function(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(function() {
const query = e.target.value.toLowerCase().trim();
let visibleCount = 0;
renderedItems.forEach(function(item) {
if (!query || item._searchText.includes(query)) {
item.style.display = "";
visibleCount++;
} else {
item.style.display = "none";
}
});
if (visibleCount === 0) {
show(emptyEl);
} else {
hide(emptyEl);
}
}, CONFIG.searchDebounce);
});
}
} catch (err) {
console.error("Memberscript # number220: Failed to load directory", err);
hide(loadingEl);
show(errorEl);
}
}
// ─── PROFILE DETAIL PAGE ───
const detailContainer = document.querySelector('[data-ms-code="detail-container"]');
if (detailContainer) {
const detailTable = detailContainer.getAttribute("ms-code-table") || "members";
const detailParam = detailContainer.getAttribute("ms-code-id-param") || "id";
const detailLoading = detailContainer.querySelector('[data-ms-code="detail-loading"]');
const detailError = detailContainer.querySelector('[data-ms-code="detail-error"]');
const detailContent = detailContainer.querySelector('[data-ms-code="detail-content"]');
hide(detailContent);
hide(detailError);
show(detailLoading);
const params = new URLSearchParams(window.location.search);
const recordId = params.get(detailParam);
if (!recordId) {
hide(detailLoading);
show(detailError);
return;
}
try {
const result = await memberstack.getDataRecord({
table: detailTable,
recordId: recordId
});
const record = result?.data;
const data = record?.data || record || {};
if (record?.createdAt) data.createdAt = record.createdAt;
if (!data || Object.keys(data).length === 0) {
hide(detailLoading);
show(detailError);
return;
}
hide(detailLoading);
show(detailContent);
bindFields(detailContent, data);
} catch (err) {
console.error("Memberscript # number220: Failed to load profile", err);
hide(detailLoading);
show(detailError);
}
}
});
</script>Download the second Make Blueprint here: https://drive.google.com/file/d/1Swlm0GFpuqM4hFKMSNVCQzV_Ty545sL_/view?usp=drive_link
Import this into Make.com to get started
More scripts in Data Tables