@@ -209,7 +209,7 @@ class="name-autocomplete vf-form__input"
209209 </div>
210210
211211 <div class="filter-group">
212- <h2 class="vf-form__legend">EMBL History </h2>
212+ <h2 class="vf-form__legend">EMBL history </h2>
213213 <div class="group-filter-grid">
214214 <?php $ render_search_filter ("groupName " , "EMBL group " , "All EMBL groups " ); ?>
215215 <?php $ render_search_filter ("unitName " , "EMBL unit " , "All EMBL units " ); ?>
@@ -241,7 +241,12 @@ class="name-autocomplete vf-form__input"
241241
242242 <div>
243243 <h2 id="alumni-results-heading" class="visually-hidden">Alumni search results</h2>
244- <div id="alumni-results-container" class="vf-u-margin__top--600" role="region" aria-labelledby="alumni-results-heading" aria-busy="true"></div>
244+ <div id="alumni-results-container" class="vf-u-margin__top--600" role="region" aria-labelledby="alumni-results-heading" aria-busy="true">
245+ <div class="alumni-loading" role="status" aria-live="polite" aria-label="Loading alumni records">
246+ <span class="alumni-loading-spinner" aria-hidden="true"></span>
247+ <span class="alumni-loading-text">Loading alumni records...</span>
248+ </div>
249+ </div>
245250 <nav class="vf-pagination" aria-label="Results pages">
246251 <ul class="vf-pagination__list paginationListOnDemand"></ul>
247252 </nav>
@@ -367,6 +372,54 @@ function cleanString(v) {
367372 return (v || "").toString().trim();
368373 }
369374
375+ /* Normalizes API field names so variants like camelCase and lowercase can be matched. */
376+ function normalizeApiFieldName(v) {
377+ return cleanString(v).toLowerCase().replace(/[^a-z0-9]/g, "");
378+ }
379+
380+ /* Reads the first matching field value from an alumni record. */
381+ function getAlumniField(record, fieldNames, fallback = "") {
382+ if (!record || typeof record !== "object") return fallback;
383+
384+ const candidates = Array.isArray(fieldNames) ? fieldNames : [fieldNames];
385+
386+ for (const fieldName of candidates) {
387+ if (Object.prototype.hasOwnProperty.call(record, fieldName) && record[fieldName] != null) {
388+ return record[fieldName];
389+ }
390+ }
391+
392+ const normalizedFields = Object.keys(record).reduce((acc, key) => {
393+ acc[normalizeApiFieldName(key)] = record[key];
394+ return acc;
395+ }, {});
396+
397+ for (const fieldName of candidates) {
398+ const matchedValue = normalizedFields[normalizeApiFieldName(fieldName)];
399+ if (matchedValue != null) {
400+ return matchedValue;
401+ }
402+ }
403+
404+ return fallback;
405+ }
406+
407+ /* Maps incoming alumni records to the field names this template expects. */
408+ function normalizeAlumniRecord(record) {
409+ const researchFocus = getAlumniField(record, ["researchfocus", "research_focus", "researchFocus", "researchinterests"], "");
410+
411+ return {
412+ ...record,
413+ groupName: getAlumniField(record, ["groupName", "groupname"], ""),
414+ unitName: getAlumniField(record, ["unitName", "unitname"], ""),
415+ current_career_level: getAlumniField(record, ["current_career_level", "currentcareerlevel", "currentCareerLevel"], ""),
416+ staff_category: getAlumniField(record, ["staff_category", "staffcategory", "staffCategory"], ""),
417+ researchfocus: researchFocus,
418+ research_focus: researchFocus,
419+ biography: getAlumniField(record, ["biography"], "")
420+ };
421+ }
422+
370423 /* Returns a normalized sort key for name-based ordering. */
371424 function getSortValue(person, sortBy) {
372425 if (sortBy === "first_name") {
@@ -683,9 +736,9 @@ function renderResults() {
683736 ].filter(isValid).map(escapeHtml).join(", ");
684737
685738 const hasBiography = isValid(p.biography);
686- const hasResearchFocus = isValid(p.research_focus );
739+ const hasResearchFocus = isValid(p.researchfocus );
687740 const biographyHtml = hasBiography ? formatOptionalText(p.biography) : "";
688- const researchFocusHtml = hasResearchFocus ? formatOptionalText(p.research_focus ) : "";
741+ const researchFocusHtml = hasResearchFocus ? formatOptionalText(p.researchfocus ) : "";
689742 const profileTabLinks = [
690743 hasBiography ? `
691744 <button
@@ -726,13 +779,13 @@ class="vf-link profile-tab-link"
726779
727780 ${currentLevelAndSector ? `
728781 <p class="vf-summary__meta vf-u-margin__bottom--100">
729- <span>Current Level and Sector :</span> <span class="vf-u-text-color--grey">${currentLevelAndSector}</span>
782+ <span>Current level and sector :</span> <span class="vf-u-text-color--grey">${currentLevelAndSector}</span>
730783 </p>
731784 ` : ""}
732785
733786 ${emblHistory ? `
734787 <p class="vf-summary__meta vf-u-margin__bottom--100">
735- <span>EMBL History -</span> <span class="vf-u-text-color--grey">${emblHistory}</span>
788+ <span>EMBL history -</span> <span class="vf-u-text-color--grey">${emblHistory}</span>
736789 </p>
737790 ` : ""}
738791
@@ -1463,7 +1516,7 @@ function clearAllFilters(e) {
14631516 try {
14641517 dom.resultsContainer.setAttribute("aria-busy", "true");
14651518 const res = await fetch(DATA_URL);
1466- state.allData = await res.json();
1519+ state.allData = ( await res.json()).map(normalizeAlumniRecord );
14671520 sortAlumniData(state.allData);
14681521 state.filtered = [...state.allData];
14691522
@@ -1639,6 +1692,34 @@ function clearAllFilters(e) {
16391692 font-weight: 600;
16401693}
16411694
1695+ .alumni-loading {
1696+ display: flex;
1697+ align-items: center;
1698+ justify-content: center;
1699+ gap: 0.75rem;
1700+ min-height: 8rem;
1701+ color: #54585a;
1702+ }
1703+
1704+ .alumni-loading-spinner {
1705+ width: 1.5rem;
1706+ height: 1.5rem;
1707+ border: 3px solid #d9d9d9;
1708+ border-top-color: #2f6fb7;
1709+ border-radius: 50%;
1710+ animation: alumni-spinner-rotate 0.8s linear infinite;
1711+ }
1712+
1713+ .alumni-loading-text {
1714+ font-size: 0.95rem;
1715+ }
1716+
1717+ @keyframes alumni-spinner-rotate {
1718+ to {
1719+ transform: rotate(360deg);
1720+ }
1721+ }
1722+
16421723.filters-layout {
16431724 display: flex;
16441725 flex-direction: column;
0 commit comments