#232 - Waitlist With Display Position

Beta waitlist with live queue position and total. Members join and see exactly where they stand in the queue.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

304 lines
Paste this into Webflow
<!-- 💙 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>

Script Info

Versionv0.1
PublishedMay 25, 2026
Last UpdatedMay 25, 2026

Need Help?

Join our Slack community for support, questions, and script requests.

Join Slack Community
Back to All Scripts

Related Scripts

More scripts in Forms