Skip to main content

Rejected Whatcom Staff Page Sort by Team Sort Alphabetical Feature

Staff directory with photos and contact info for three team members: Adam Brayton, Alexi Guddal, Alexis Bryson.

For demonstration purposes since it is going completely away, there is this video.

Add to html block on the page 

<div class="hidden-team-links" style="display:none;">
  <a href="https://whatcomcd.specialdistrict.org/all-staff-teams?mode=team&team=Administration%20%26%20Finance">
    Administration &amp; Finance
  </a><br>

  <a href="https://whatcomcd.specialdistrict.org/all-staff-teams?mode=team&team=Climate%20Resilience%20and%20Preparedness">
    Climate Resilience and Preparedness
  </a><br>

  <a href="https://whatcomcd.specialdistrict.org/all-staff-teams?mode=team&team=Conservation%20Planning">
    Conservation Planning
  </a><br>

  <a href="https://whatcomcd.specialdistrict.org/all-staff-teams?mode=team&team=Engineering">
    Engineering
  </a><br>

  <a href="https://whatcomcd.specialdistrict.org/all-staff-teams?mode=team&team=Habitat%20Restoration">
    Habitat Restoration
  </a><br>

  <a href="https://whatcomcd.specialdistrict.org/all-staff-teams?mode=team&team=Outreach%20Education">
    Outreach Education
  </a>
</div>
 

<!-- Staff Directory: Expand All | By Team | A–Z -->
<style>
  /* ---------- Controls bar (mode buttons) ---------- */
  .items .staff-controls {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    padding: 0 1rem;
    margin: 1rem 0 .25rem;
  }
  .items .staff-controls button {
    background: #f5f5f5;
    border: 1px solid #ddd;
    border-radius: 6px;
    padding: .5rem .9rem;
    cursor: pointer;
  }
  .items .staff-controls button[aria-pressed="true"] {
    background: #fff;
    border-color: #bbb;
    font-weight: 600;
  }

  /* ---------- Tab bar ---------- */
  .items .tablist {
    display: flex;
    flex-wrap: wrap;
    gap: .5rem;
    margin: .5rem 0 0;
    padding: 0 1rem;
  }
  .items .tablist [role="tab"] {
    background: #f5f5f5;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 4px 4px 0 0;
    padding: .45rem .8rem;
    cursor: pointer;
    white-space: nowrap;
  }
  .items .tablist [role="tab"]:hover { background: #e9e9e9; }
  .items .tablist [role="tab"][aria-selected="true"] {
    background: #fff;
    border-color: #ccc;
    font-weight: 600;
  }

  /* ---------- Panels + cards layout ---------- */
  .items .tabpanels { width: 100%; }
  .items .tabpanels [role="tabpanel"] {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    padding: 1rem;
    width: 100%;
    box-sizing: border-box;
  }
  .items .tabpanels [role="tabpanel"][hidden] { display: none !important; }

  .items .tabpanels .poc-instance {
    flex: 0 0 calc((100% - 2rem)/3);
    max-width: calc((100% - 2rem)/3);
    box-sizing: border-box;
  }
  @media (max-width: 800px) {
    .items .tabpanels .poc-instance {
      flex: 0 0 calc((100% - 1rem)/2);
      max-width: calc((100% - 1rem)/2);
    }
  }
  @media (max-width: 480px) {
    .items .tabpanels .poc-instance {
      flex: 0 0 100%;
      max-width: 100%;
    }
  }

  /* ---------- Team headers for Expand All ---------- */
  .items .staff-heading {
    display: block;
    width: 100%;
    margin: 32px 0 12px 6px;
    font-size: 28px;
    line-height: 1.35;
    font-weight: 700;
    clear: both;
  }
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
  const container = document.querySelector('.items');
  if (!container) return;

  // ---------- Helpers ----------
  const slugify = s => (s || '')
    .toString()
    .normalize('NFKD').replace(/[\u0300-\u036f]/g, '') // strip accents
    .toLowerCase()
    .replace(/&/g, ' and ')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');

  const getParams = () => {
    const search = new URLSearchParams(location.search);
    // Accept hash as either "#team=Foo", "#mode=alpha&letter=A", or "#foo-bar" (team slug)
    const rawHash = (location.hash || '').replace(/^#/, '');
    const hashAsParams = new URLSearchParams(
      rawHash.includes('=') ? rawHash : ''
    );
    const hashAsTeamSlug = rawHash && !rawHash.includes('=') ? rawHash : '';

    // Priority: query params > hash params > bare hash team slug
    return {
      mode: search.get('mode') || hashAsParams.get('mode') || null,
      team: search.get('team') || hashAsParams.get('team') || (hashAsTeamSlug || null),
      letter: search.get('letter') || hashAsParams.get('letter') || null
    };
  };

  const setURL = ({ mode, team, letter }, replace = true) => {
    const url = new URL(location.href);
    url.searchParams.delete('mode');
    url.searchParams.delete('team');
    url.searchParams.delete('letter');
    if (mode)   url.searchParams.set('mode', mode);
    if (team)   url.searchParams.set('team', team);
    if (letter) url.searchParams.set('letter', letter);
    // For expand+team jump, keep a clean hash to the team header
    if (mode === 'expand' && team) {
      url.hash = slugify(team);
    } else {
      url.hash = '';
    }
    (replace ? history.replaceState : history.pushState).call(history, null, '', url.toString());
  };

  // ---------- Grab & parse cards ----------
  const originals = Array.from(container.querySelectorAll('article.poc-instance'));
  if (!originals.length) return;

  function parseTitle(card) {
    const titleEl = card.querySelector('h3 span, h3');
    const raw = titleEl ? titleEl.textContent.trim() : '';
    const m = raw.match(/^(.*?)\s*\(([^)]+)\)\s*$/);
    const name = m ? m[1].trim() : raw;
    const team = m ? m[2].trim() : 'Other';
    return { name, team, raw, titleElSelector: titleEl ? (titleEl.matches('h3 span') ? 'h3 span' : 'h3') : 'h3' };
  }

  const data = originals.map((node, i) => {
    const parsed = parseTitle(node);
    return { i, node, ...parsed, teamSlug: slugify(parsed.team), firstLetter: (parsed.name.charAt(0) || '#').toUpperCase() };
  });

  const teams = Array.from(new Set(data.map(d => d.team))).sort((a,b)=>a.localeCompare(b));
  const letters = Array.from(new Set(data.map(d => d.firstLetter))).sort((a,b)=>a.localeCompare(b));

  // ---------- Build UI skeleton ----------
  container.innerHTML = '';

  const controls = document.createElement('div');
  controls.className = 'staff-controls';
  container.appendChild(controls);

  const modes = [
    { key: 'expand', label: 'Expand all' },
    { key: 'team',   label: 'By team' },
    { key: 'alpha',  label: 'A–Z' }
  ];
  let activeMode = 'expand';

  const tablist = document.createElement('div');
  tablist.className = 'tablist';
  tablist.setAttribute('role', 'tablist');
  container.appendChild(tablist);

  const panels = document.createElement('div');
  panels.className = 'tabpanels';
  container.appendChild(panels);

  function cloneCard(d, { showTeamInTitle }) {
    const clone = d.node.cloneNode(true);
    const t = clone.querySelector(d.titleElSelector) || clone.querySelector('h3');
    if (t) t.textContent = showTeamInTitle ? d.raw : d.name;
    return clone;
  }

  function resetTabs() {
    tablist.innerHTML = '';
    panels.innerHTML = '';
  }

  function wireKeyboard(keys) {
    keys.forEach((k, idx) => {
      const tab = document.getElementById(`tab-${k}`);
      tab.addEventListener('keydown', e => {
        let next;
        if (e.key === 'ArrowRight') next = (idx + 1) % keys.length;
        if (e.key === 'ArrowLeft')  next = (idx - 1 + keys.length) % keys.length;
        if (e.key === 'Home')       next = 0;
        if (e.key === 'End')        next = keys.length - 1;
        if (next !== undefined) {
          e.preventDefault();
          document.getElementById(`tab-${keys[next]}`).focus();
          activate(keys[next], keys);
        }
      });
    });
  }

  function activate(activeKey, keys) {
    keys.forEach(k => {
      const tab = document.getElementById(`tab-${k}`);
      const panel = document.getElementById(`panel-${k}`);
      const isActive = k === activeKey;
      tab?.setAttribute('aria-selected', isActive);
      if (isActive) tab?.removeAttribute('tabindex'); else tab?.setAttribute('tabindex', '-1');
      if (panel) panel.hidden = !isActive;
    });
  }

  // ---------- Renderers ----------
  function renderExpandAll(scrollToTeamSlug = null) {
    resetTabs();
    const key = 'All';
    const tab = document.createElement('button');
    tab.setAttribute('role', 'tab');
    tab.id = `tab-${key}`;
    tab.textContent = 'All';
    tab.setAttribute('aria-selected', 'true');
    tablist.appendChild(tab);

    const panel = document.createElement('div');
    panel.setAttribute('role', 'tabpanel');
    panel.id = `panel-${key}`;
    panels.appendChild(panel);

    const grouped = [...data].sort((a,b) => {
      const t = a.team.localeCompare(b.team);
      return t !== 0 ? t : a.name.localeCompare(b.name);
    });

    let lastTeam = null;
    let jumpTarget = null;
    grouped.forEach(d => {
      if (d.team !== lastTeam) {
        const h2 = document.createElement('h2');
        h2.className = 'staff-heading';
        h2.textContent = d.team;
        h2.id = d.teamSlug;              // <— anchor target for Expand All
        panel.appendChild(h2);
        lastTeam = d.team;
        if (scrollToTeamSlug && d.teamSlug === scrollToTeamSlug) {
          jumpTarget = h2;
        }
      }
      panel.appendChild(cloneCard(d, { showTeamInTitle: false }));
    });

    if (jumpTarget) {
      // Small delay to ensure layout is ready
      requestAnimationFrame(() => jumpTarget.scrollIntoView({ behavior: 'smooth', block: 'start' }));
    }
  }

  function renderByTeam(activeTeamKey = 'All Teams') {
    resetTabs();

    const keys = ['All Teams', ...teams.map(t => `team-${slugify(t)}`)];
    const labelFromKey = k => k === 'All Teams'
      ? 'All Teams'
      : teams.find(t => `team-${slugify(t)}` === k);

    // Build tabs/panels
    keys.forEach((k, idx) => {
      const human = labelFromKey(k);
      const tab = document.createElement('button');
      tab.setAttribute('role', 'tab');
      tab.id = `tab-${k}`;
      const count = k === 'All Teams'
        ? data.length
        : data.filter(d => `team-${d.teamSlug}` === k).length;
      tab.textContent = `${human} (${count})`;
      tab.setAttribute('aria-selected', idx === 0 ? 'true' : 'false');
      if (idx !== 0) tab.setAttribute('tabindex', '-1');
      tab.addEventListener('click', () => {
        activate(k, keys);
        // reflect in URL for shareable link
        const teamName = k === 'All Teams' ? null : human;
        setURL({ mode: 'team', team: teamName }, false);
      });
      tablist.appendChild(tab);

      const panel = document.createElement('div');
      panel.setAttribute('role', 'tabpanel');
      panel.id = `panel-${k}`;
      if (idx !== 0) panel.hidden = true;
      panels.appendChild(panel);

      const subset = k === 'All Teams'
        ? data
        : data.filter(d => `team-${d.teamSlug}` === k);

      subset
        .slice()
        .sort((a,b)=>a.name.localeCompare(b.name))
        .forEach(d => panel.appendChild(cloneCard(d, { showTeamInTitle: false })));
    });

    wireKeyboard(keys);

    // Activate requested team
    if (keys.includes(activeTeamKey)) {
      activate(activeTeamKey, keys);
      // Scroll the active panel into view
 const panelEl = document.getElementById(`panel-${activeTeamKey}`);
if (panelEl) {
  const headerOffset = 120; // adjust to match your sticky header height
  const elementPosition = panelEl.getBoundingClientRect().top + window.scrollY;
  const offsetPosition = elementPosition - headerOffset;

  window.scrollTo({
    top: offsetPosition,
    behavior: 'smooth'
  });
}

    } else {
      activate('All Teams', keys);
    }
  }

  function renderAlpha(activeLetter = 'All') {
    resetTabs();

    const keys = ['All', ...letters];
    keys.forEach((letter, idx) => {
      const tab = document.createElement('button');
      tab.setAttribute('role', 'tab');
      tab.id = `tab-${letter}`;
      const count = letter === 'All'
        ? data.length
        : data.filter(d => d.firstLetter === letter).length;
      tab.textContent = `${letter} (${count})`;
      tab.setAttribute('aria-selected', idx === 0 ? 'true' : 'false');
      if (idx !== 0) tab.setAttribute('tabindex', '-1');
      tab.addEventListener('click', () => {
        activate(letter, keys);
        setURL({ mode: 'alpha', letter }, false);
      });
      tablist.appendChild(tab);

      const panel = document.createElement('div');
      panel.setAttribute('role', 'tabpanel');
      panel.id = `panel-${letter}`;
      if (idx !== 0) panel.hidden = true;
      panels.appendChild(panel);

      const subset = letter === 'All'
        ? data
        : data.filter(d => d.firstLetter === letter);

      subset
        .slice()
        .sort((a,b)=>a.name.localeCompare(b.name))
        .forEach(d => panel.appendChild(cloneCard(d, { showTeamInTitle: true })));
    });

    wireKeyboard(keys);
    if (keys.includes(activeLetter)) activate(activeLetter, keys); else activate('All', keys);
  }

  // ---------- Controls ----------
  let activeModeBtn = null;
  modes.forEach(m => {
    const btn = document.createElement('button');
    btn.type = 'button';
    btn.textContent = m.label;
    btn.setAttribute('aria-pressed', m.key === 'expand' ? 'true' : 'false');
    btn.addEventListener('click', () => {
      activeModeBtn?.setAttribute('aria-pressed', 'false');
      btn.setAttribute('aria-pressed', 'true');
      activeModeBtn = btn;
      if (m.key === 'expand') { renderExpandAll(); setURL({ mode: 'expand' }, false); }
      if (m.key === 'team')   { renderByTeam('All Teams'); setURL({ mode: 'team' }, false); }
      if (m.key === 'alpha')  { renderAlpha('All'); setURL({ mode: 'alpha' }, false); }
    });
    controls.appendChild(btn);
    if (m.key === 'expand') activeModeBtn = btn; // default pressed
  });

  // ---------- Initialization with deep links ----------
  function bootFromURL() {
    const { mode, team, letter } = getParams();

    // Default view
    if (!mode) {
      renderExpandAll();
      setURL({ mode: 'expand' }, true);
      return;
    }

    if (mode === 'expand') {
      renderExpandAll(team ? slugify(team) : null);
    } else if (mode === 'team') {
      const targetKey = team ? `team-${slugify(team)}` : 'All Teams';
      renderByTeam(targetKey);
    } else if (mode === 'alpha') {
      const L = (letter || 'All').toUpperCase();
      renderAlpha(L);
    } else {
      renderExpandAll();
    }

    // Reflect mode button state
    controls.querySelectorAll('button').forEach(b => b.setAttribute('aria-pressed', 'false'));
    const btn = Array.from(controls.querySelectorAll('button')).find(b => b.textContent.startsWith(
      mode === 'expand' ? 'Expand' : mode === 'team' ? 'By team' : 'A–Z'
    ));
    if (btn) { btn.setAttribute('aria-pressed', 'true'); activeModeBtn = btn; }

    // Normalize URL (encode exact params + hash)
    setURL({ mode, team, letter }, true);
  }

  // Rehydrate on back/forward and hash changes
  window.addEventListener('popstate', bootFromURL);
  window.addEventListener('hashchange', bootFromURL);

  bootFromURL();
});
</script>

We could have had it all. - Adele