#220 - Searchable Member Directory and Profile Pages

Build a searchable member directory and profile pages powered by Memberstack Data Tables.

Video Tutorial

tutorial.mov

Watch the video for step-by-step implementation instructions

The Code

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

Tutorial

Download the second Make Blueprint here: https://drive.google.com/file/d/1Swlm0GFpuqM4hFKMSNVCQzV_Ty545sL_/view?usp=drive_link

Make.com Blueprint

Download Blueprint

Import this into Make.com to get started

Datei herunterladen
How to use:
  1. Download the JSON blueprint above
  2. Navigate to Make.com and create a new scenario
  3. Click the 3-dot menu and select Import Blueprint
  4. Upload your file and connect your accounts

Script Info

Versionv0.1
PublishedMar 24, 2026
Last UpdatedMar 24, 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 Data Tables