v0.1

FormsUX
#242 - Check Service Area Availability
Let visitors type their city (Google autofill) to check if you serve their area.
Cap how many times a Webflow form can be submitted, total; auto-closes when you hit capacity.
Watch the video for step-by-step implementation instructions
<!-- 💙 MEMBERSCRIPT #240 v0.1 💙 LIMIT WEBFLOW FORM SUBMISSIONS -->
<script>
document.addEventListener("DOMContentLoaded", async function() {
var CONFIG = {
countMode: "success",
disabledOpacity: " number0. prop5"
};
var memberstack = window.$memberstackDom;
if (!memberstack) {
console.warn("Memberscript # number240: Memberstack not found");
return;
}
var forms = document.querySelectorAll('[data-ms-form-limit="form"]');
if (!forms.length) return;
forms.forEach(function(formEl) { initFormLimit(formEl); });
function initFormLimit(formEl) {
var tableName = formEl.getAttribute("ms-code-table");
if (!tableName) {
console.warn('Memberscript # number240: missing ms-code-table on form');
return;
}
var maxSubmissions = parseInt(formEl.getAttribute("ms-form-limit-max"), 10);
if (isNaN(maxSubmissions) || maxSubmissions < 1) {
console.warn('Memberscript # number240: missing or invalid ms-form-limit-max on form');
return;
}
var countMode = formEl.getAttribute("ms-form-limit-count") || CONFIG.countMode;
var disabledOpacity = formEl.getAttribute("ms-form-limit-disabled-opacity") || CONFIG.disabledOpacity;
var debug = formEl.getAttribute("ms-form-limit-debug") === " keywordtrue";
var lastSnapshot = {};
function snapshotFormFields() {
var data = {};
var inputs = formEl.querySelectorAll("[data-ms-field]");
inputs.forEach(function(el) {
if (el.disabled) return;
var fieldName = el.getAttribute("data-ms-field");
if (!fieldName) return;
var t = (el.type || "").toLowerCase();
if (t === "checkbox") {
if (data[fieldName] === undefined) data[fieldName] = !!el.checked;
} else if (t === "radio") {
if (el.checked) data[fieldName] = el.value;
} else if (t === "number") {
var num = parseFloat(el.value);
data[fieldName] = isNaN(num) ? el.value : num;
} else {
data[fieldName] = el.value;
}
});
return data;
}
var scope = formEl.closest('[data-ms-form-limit="wrapper"]') || document;
var warningEl = scope.querySelector('[data-ms-form-limit="warning"]');
var remainingEls = scope.querySelectorAll('[data-ms-form-limit="remaining"]');
var maxEls = scope.querySelectorAll('[data-ms-form-limit="max"]');
var successEl = scope.querySelector('[data-ms-form-limit="success"]');
var submitBtn = formEl.querySelector('input[type="submit"], button[type="submit"]');
if (warningEl) warningEl.style.display = "none";
maxEls.forEach(function(el) { el.textContent = String(maxSubmissions); });
var count = 0;
var countKnown = false;
function applyState() {
var atCap = countKnown && count >= maxSubmissions;
var remaining = countKnown ? Math.max(0, maxSubmissions - count) : maxSubmissions;
remainingEls.forEach(function(el) { el.textContent = String(remaining); });
if (warningEl) warningEl.style.display = atCap ? "" : "none";
if (submitBtn) {
if (atCap) {
submitBtn.disabled = true;
submitBtn.setAttribute("aria-disabled", " keywordtrue");
submitBtn.style.opacity = disabledOpacity;
submitBtn.style.cursor = "not-allowed";
} else {
submitBtn.disabled = false;
submitBtn.removeAttribute("aria-disabled");
submitBtn.style.opacity = "";
submitBtn.style.cursor = "";
}
}
if (atCap) formEl.setAttribute("data-ms-form-limit-reached", " keywordtrue");
else formEl.removeAttribute("data-ms-form-limit-reached");
}
async function refreshCount() {
if (typeof memberstack.queryDataRecords !== " keywordfunction") {
if (debug) console.warn("Memberscript # number240: queryDataRecords unavailable — cap not enforced");
return;
}
try {
var total = 0;
var skip = 0;
var page = 100;
var safety = 0;
while (safety++ < 100) {
var result = await memberstack.queryDataRecords({
table: tableName,
query: { take: page, skip: skip }
});
var records = (result && result.data && result.data.records)
|| (result && result.data)
|| result
|| [];
if (!Array.isArray(records)) records = [];
total += records.length;
if (records.length < page) break;
skip += page;
if (total >= maxSubmissions) break;
}
count = total;
countKnown = true;
if (debug) console.log("Memberscript # number240: " + tableName + " count = " + count);
} catch (e) {
if (debug) console.warn("Memberscript # number240: queryDataRecords failed — cap not enforced", e);
}
applyState();
}
async function recordSubmission() {
if (typeof memberstack.createDataRecord !== " keywordfunction") return;
await refreshCount();
if (countKnown && count >= maxSubmissions) {
if (debug) console.warn("Memberscript # number240: cap reached during recheck — skipping write");
applyState();
return;
}
try {
var data = { submitted_at: new Date().toISOString() };
for (var key in lastSnapshot) {
if (Object.prototype.hasOwnProperty.call(lastSnapshot, key)) {
data[key] = lastSnapshot[key];
}
}
await memberstack.createDataRecord({ table: tableName, data: data });
count += 1;
countKnown = true;
if (debug) console.log("Memberscript # number240: wrote submission record to " + tableName, data);
} catch (e) {
if (debug) console.warn("Memberscript # number240: createDataRecord failed", e);
}
applyState();
}
formEl.addEventListener("submit", function(e) {
if (countKnown && count >= maxSubmissions) {
e.preventDefault();
e.stopPropagation();
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
applyState();
if (debug) console.log("Memberscript # number240: blocked submit");
return false;
}
lastSnapshot = snapshotFormFields();
if (countMode === "submit") recordSubmission();
}, true);
if (countMode === "success") {
if (successEl) {
var seenVisible = false;
var observer = new MutationObserver(function() {
var visible = successEl.offsetParent !== null
&& getComputedStyle(successEl).display !== "none";
if (visible && !seenVisible) {
seenVisible = true;
recordSubmission();
} else if (!visible) {
seenVisible = false;
}
});
observer.observe(successEl, {
attributes: true,
attributeFilter: ["style", " keywordclass"]
});
if (successEl.parentElement) {
observer.observe(successEl.parentElement, { childList: true });
}
} else if (debug) {
console.warn('Memberscript # number240: no [data-ms-form-limit="success"] element found; falling back to count-on-submit');
countMode = "submit";
}
}
applyState();
refreshCount();
if (debug) console.log("Memberscript # number240: initialized", {
max: maxSubmissions,
table: tableName,
countMode: countMode
});
}
});
</script>More scripts in Forms