v0.1

Forms
#231 - Clean Checkbox Form Data in Webflow
Clean up checkbox form data — only send checked items as grouped labels, no true/false.
Beta waitlist with live queue position and total. Members join and see exactly where they stand in the queue.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #232 v0.3 💙 WAITLIST + QUEUE POSITION(DATA TABLES) -->
<style>
[data-ms-code="waitlist"] [data-ms-code="loading"],
[data-ms-code="waitlist"] [data-ms-code="joined"],
[data-ms-code="waitlist"] [data-ms-code="error"],
[data-ms-code="waitlist"] [data-ms-code="not-joined"],
[data-ms-code="waitlist"] [data-ms-code="logged-out"] {
display: none;
}
[data-ms-code="waitlist"] [data-ms-code-state="content"] {
display: flex !important;
}
[data-ms-code="waitlist"] [data-ms-code="joined"] [data-ms-code="queue-error"] {
display: none;
}
[data-ms-code="waitlist"] [data-ms-code="joined"][data-ms-queue-error=" keywordtrue"] [data-ms-code="queue-error"] {
display: flex !important;
}
</style>
<script>
document.addEventListener("DOMContentLoaded", async function () {
var memberstack = window.$memberstackDom;
var root = document.querySelector('[data-ms-code="waitlist"]');
if (!root) {
console.warn("Memberscript # number232: Add data-ms-code=\"waitlist\" to your section wrapper.");
return;
}
function cfg(name, fallback) {
return root.hasAttribute(name) ? root.getAttribute(name) : fallback;
}
var TABLE = cfg("ms-code-table", "waitlist");
var OWNER = cfg("ms-code-owner-field", "member");
var JOINED_AT = cfg("ms-code-date-field", "joined_at");
var PAGE_SIZE = parseInt(cfg("ms-code-max-records", " number100"), 10) || 100;
if (PAGE_SIZE > 100) PAGE_SIZE = 100;
var DEBUG = cfg("ms-code-debug", " keywordfalse") === " keywordtrue";
var panels = {
loading: root.querySelectorAll('[data-ms-code="loading"]'),
joined: root.querySelectorAll('[data-ms-code="joined"]'),
error: root.querySelectorAll('[data-ms-code="error"]'),
notJoined: root.querySelectorAll('[data-ms-code="not-joined"]'),
loggedOut: root.querySelectorAll('[data-ms-code="logged-out"]')
};
var positionEls = root.querySelectorAll('[data-ms-field="position"]');
var totalEls = root.querySelectorAll('[data-ms-field="total"]');
var emailEls = root.querySelectorAll('[data-ms-field="email"]');
var joinBtns = root.querySelectorAll('[data-ms-action="submit"]');
var EMAIL_COL = "email";
if (emailEls[0]) {
var ek = emailEls[0].getAttribute("data-ms-field");
if (ek) EMAIL_COL = ek;
}
function show(which) {
Object.keys(panels).forEach(function (key) {
panels[key].forEach(function (el) {
if (key === which) el.setAttribute("data-ms-code-state", "content");
else el.removeAttribute("data-ms-code-state");
});
});
panels.joined.forEach(function (el) {
el.removeAttribute("data-ms-queue-error");
});
}
function memberEmail(member) {
if (!member) return "";
return (
(member.auth && member.auth.email) ||
member.email ||
(member.customFields && (member.customFields.email || member.customFields["email"])) ||
""
);
}
function recData(record) {
return (record && record.data) || record || {};
}
function recField(record, field) {
var d = recData(record);
return d[field];
}
function ownerId(record) {
var v = recField(record, OWNER);
if (v && typeof v === "object" && v.id) return String(v.id);
if (v != null) return String(v);
return "";
}
function isMine(record, memberId) {
return ownerId(record) === String(memberId);
}
function joinedTime(record) {
var v = recField(record, JOINED_AT);
if (!v) return 0;
var t = typeof v === "number" ? v : Date.parse(String(v));
return isFinite(t) ? t : 0;
}
function setText(els, text) {
els.forEach(function (el) {
el.textContent = text;
});
}
function parsePage(result) {
var rows = (result && result.data && result.data.records) || (result && result.data) || [];
return Array.isArray(rows) ? rows : [];
}
async function loadAllRows() {
var all = [];
var skip = 0;
var page;
do {
var res = await memberstack.queryDataRecords({
table: TABLE,
query: { take: PAGE_SIZE, skip: skip }
});
page = parsePage(res);
all = all.concat(page);
skip += page.length;
} while (page.length === PAGE_SIZE);
return all;
}
function statsFor(rows, memberId) {
var sorted = rows.slice().sort(function (a, b) {
var d = joinedTime(a) - joinedTime(b);
return d !== 0 ? d : String(a.id || "").localeCompare(String(b.id || ""));
});
var position = null;
for (var i = 0; i < sorted.length; i++) {
if (isMine(sorted[i], memberId)) {
position = i + 1;
break;
}
}
return { position: position, total: sorted.length };
}
function findMine(rows, memberId) {
for (var i = 0; i < rows.length; i++) {
if (isMine(rows[i], memberId)) return rows[i];
}
return null;
}
function stampRecord(record, memberId, email) {
var out = record ? Object.assign({}, record) : { id: "pending" };
out.data = Object.assign({}, recData(record));
out.data[OWNER] = out.data[OWNER] || memberId;
out.data[JOINED_AT] = out.data[JOINED_AT] || new Date().toISOString();
if (email) out.data[EMAIL_COL] = email;
return out;
}
async function renderStats(memberId, member, extraRow) {
var rows;
try {
rows = await loadAllRows();
} catch (err) {
if (DEBUG) console.warn("Memberscript # number232: query failed", err);
rows = extraRow ? [extraRow] : [];
panels.joined.forEach(function (el) {
el.setAttribute("data-ms-queue-error", " keywordtrue");
});
}
if (extraRow && !findMine(rows, memberId)) {
rows.push(extraRow);
}
var stats = statsFor(rows, memberId);
setText(positionEls, stats.position != null ? String(stats.position) : "—");
setText(totalEls, stats.total != null ? String(stats.total) : "—");
var mine = findMine(rows, memberId) || extraRow;
var em = (mine && recField(mine, EMAIL_COL)) || memberEmail(member);
if (em) setText(emailEls, String(em));
if (DEBUG) {
console.log("Memberscript # number232", {
rows: rows.length,
position: stats.position,
total: stats.total,
positionEls: positionEls.length,
totalEls: totalEls.length,
emailCol: EMAIL_COL
});
}
}
async function joinWaitlist(memberId, member) {
var email = memberEmail(member);
var data = {};
data[OWNER] = memberId;
data[JOINED_AT] = new Date().toISOString();
if (email) data[EMAIL_COL] = email;
if (DEBUG) console.log("Memberscript # number232: creating row", data);
try {
var res = await memberstack.createDataRecord({ table: TABLE, data: data });
return stampRecord((res && res.data) || null, memberId, email);
} catch (e1) {
try {
var fb = {};
fb[OWNER] = { id: memberId };
fb[JOINED_AT] = data[JOINED_AT];
if (email) fb[EMAIL_COL] = email;
var res2 = await memberstack.createDataRecord({ table: TABLE, data: fb });
return stampRecord((res2 && res2.data) || null, memberId, email);
} catch (e2) {
if (DEBUG) console.warn("Memberscript # number232: create failed", e2);
return null;
}
}
}
show("loading");
setText(positionEls, "—");
setText(totalEls, "—");
if (!memberstack || !memberstack.getCurrentMember || !memberstack.queryDataRecords || !memberstack.createDataRecord) {
show("error");
return;
}
var member;
try {
var mr = await memberstack.getCurrentMember();
member = (mr && mr.data) || mr;
} catch (e) {
member = null;
}
if (!member || !member.id) {
show("loggedOut");
joinBtns.forEach(function (b) {
b.disabled = true;
});
return;
}
var memberId = member.id;
var onList = false;
var myRow = null;
try {
myRow = findMine(await loadAllRows(), memberId);
onList = !!myRow;
} catch (e) {
if (DEBUG) console.warn("Memberscript # number232: initial load failed", e);
show("error");
return;
}
joinBtns.forEach(function (btn) {
btn.addEventListener("click", async function (ev) {
if (ev.preventDefault) ev.preventDefault();
if (onList) return;
show("loading");
var row = await joinWaitlist(memberId, member);
if (!row) {
try {
myRow = findMine(await loadAllRows(), memberId);
row = myRow;
} catch (e) {
row = null;
}
}
if (!row) {
show("error");
return;
}
onList = true;
myRow = row;
await renderStats(memberId, member, row);
show("joined");
});
});
if (onList) {
await renderStats(memberId, member, myRow);
show("joined");
} else {
var previewEmail = memberEmail(member);
if (previewEmail) setText(emailEls, String(previewEmail));
show("notJoined");
}
});
</script>More scripts in Forms