`;
}
function cardMarkup(post) {
const title = textOnly(post?.title?.rendered || "Published blog article");
const link = safeURL(post?.link);
const image = postImage(post);
const category = topicTerm(post);
const categoryName = textOnly(category.name || "Blog article");
const date = displayDate(post);
const location = locationMeta(post);
const summary = excerpt(post) || "Practical information and clear next steps for your doctor search.";
return `
${image.url ? `
` : placeholderMarkup()}
${escapeHTML(categoryName)}
${escapeHTML(summary)}
${date ? `${escapeHTML(date)}` : ""}
${readingTime(post)} min read
${location.cities.map(name => `City: ${escapeHTML(name)}`).join("")}
${location.provinces.map(name => `Province: ${escapeHTML(name)}`).join("")}
Read More →
`;
}
function updateStatus(count) {
if (!elements.status) return;
const active = [state.topic, state.province, state.city, state.search].filter(Boolean);
elements.status.textContent = `${count} published blog article${count === 1 ? "" : "s"}${active.length ? " matched your filters" : " available"}.`;
}
function updateActiveTopic() {
$$("[data-topic]").forEach(link => {
const active = (link.dataset.topic || "") === state.topic;
link.classList.toggle("is-active", active);
if (active) link.setAttribute("aria-current", "page");
else link.removeAttribute("aria-current");
});
}
function render() {
if (!state.loaded) return;
const posts = filteredPosts();
updateStatus(posts.length);
updateActiveTopic();
if (!posts.length) {
elements.featured.innerHTML = `
No published blog article matches those filters. Try another topic, province, city or search term.
`;
elements.articles.innerHTML = `
Clear one or more filters to see additional blog articles.
`;
elements.loadWrap.hidden = true;
return;
}
elements.featured.innerHTML = featuredMarkup(posts[0]);
const remaining = posts.slice(1);
const visible = remaining.slice(0, state.visible);
elements.articles.innerHTML = visible.length
? visible.map(cardMarkup).join("")
: `
More published blog articles will appear here automatically.
`;
elements.loadWrap.hidden = remaining.length <= state.visible;
}
function syncURL() {
if (!window.history?.replaceState) return;
const url = new URL(window.location.href);
const values = {
topic: state.topic,
province: state.province,
city: state.city,
q: state.search,
sort: state.sort === "newest" ? "" : state.sort
};
Object.entries(values).forEach(([key, value]) => {
if (value) url.searchParams.set(key, value);
else url.searchParams.delete(key);
});
url.hash = "fadc-blog-results";
window.history.replaceState({}, "", url);
}
function applyState({ scroll = false } = {}) {
state.visible = 9;
render();
syncURL();
if (scroll) {
$(".fadc-content")?.scrollIntoView({ behavior: "smooth", block: "start" });
}
}
function categoryLink(definition) {
const category = matchingCategory(definition);
return category ? safeURL(category.link, `/category/${definition.slug}/`) : `/category/${definition.slug}/`;
}
function categoryCount(definition) {
const category = matchingCategory(definition);
return category ? Number(category.count || 0) : 0;
}
function taxonomyTerm(definition, taxonomy) {
const map = taxonomy === "post_city" ? state.cityBySlug : state.provinceBySlug;
const accepted = [definition.slug, ...(definition.aliases || [])];
for (const slug of accepted) {
if (map.has(slug)) return map.get(slug);
}
return null;
}
function taxonomyLink(definition, taxonomy) {
const term = taxonomyTerm(definition, taxonomy);
const path = taxonomy === "post_city" ? "city" : taxonomy;
return term ? safeURL(term.link, `/${path}/${definition.slug}/`) : `/${path}/${definition.slug}/`;
}
function taxonomyCount(definition, taxonomy) {
const term = taxonomyTerm(definition, taxonomy);
return term ? Number(term.count || 0) : 0;
}
function updateArchiveLinks() {
if (elements.topicLinks) {
elements.topicLinks.innerHTML = topicDefinitions
.filter(definition => categoryCount(definition) > 0)
.map(definition => `
${escapeHTML(definition.label)}${categoryCount(definition)}
`).join("");
}
if (elements.cityLinks) {
elements.cityLinks.innerHTML = [...cityDefinitions]
.filter(definition => taxonomyCount(definition, "post_city") > 0)
.sort((left, right) => left.label.localeCompare(right.label))
.map(definition => `
${escapeHTML(definition.label)}${taxonomyCount(definition, "post_city")}
`).join("");
}
}
function refreshCityOptions({ province = elements.province.value, preserve = true } = {}) {
const current = preserve ? elements.city.value : "";
const allowedCities = cityDefinitions
.filter(definition => taxonomyCount(definition, "post_city") > 0)
.filter(definition => !province || definition.province === province)
.sort((left, right) => left.label.localeCompare(right.label));
if (province) {
const provinceName = definitionFor(provinceDefinitions, province)?.label || "selected province";
elements.city.innerHTML = [
`
`,
...allowedCities.map(definition => `
`)
].join("");
} else {
const groups = provinceDefinitions.map(provinceDefinition => {
const cities = cityDefinitions
.filter(definition => taxonomyCount(definition, "post_city") > 0)
.filter(definition => definition.province === provinceDefinition.slug)
.sort((left, right) => left.label.localeCompare(right.label));
if (!cities.length) return "";
return `
`;
}).join("");
elements.city.innerHTML = `
${groups}`;
}
elements.city.value = allowedCities.some(definition => definition.slug === current) ? current : "";
return elements.city.value;
}
function restoreFromURL() {
const params = new URLSearchParams(window.location.search);
state.topic = params.get("topic") || "";
state.province = params.get("province") || "";
state.city = params.get("city") || "";
state.search = params.get("q") || "";
state.sort = ["newest", "oldest", "title"].includes(params.get("sort")) ? params.get("sort") : "newest";
if (!topicDefinitions.some(item => item.slug === state.topic)) state.topic = "";
if (!provinceDefinitions.some(item => item.slug === state.province)) state.province = "";
if (!cityDefinitions.some(item => item.slug === state.city)) state.city = "";
const restoredCity = definitionFor(cityDefinitions, state.city);
if (restoredCity && state.province && restoredCity.province !== state.province) state.city = "";
elements.searchInput.value = state.search;
elements.province.value = state.province;
refreshCityOptions({ province: state.province, preserve: false });
elements.city.value = state.city;
elements.sort.value = state.sort;
}
elements.searchForm?.addEventListener("submit", event => {
if (!state.loaded) return;
event.preventDefault();
state.search = elements.searchInput.value.trim();
applyState({ scroll: true });
});
elements.topics?.addEventListener("click", event => {
const link = event.target.closest("[data-topic]");
if (!link || !state.loaded) return;
event.preventDefault();
state.topic = link.dataset.topic || "";
applyState({ scroll: true });
});
elements.filterForm?.addEventListener("submit", event => {
if (!state.loaded) return;
event.preventDefault();
state.province = elements.province.value;
state.city = elements.city.value;
state.sort = elements.sort.value;
applyState({ scroll: true });
});
elements.province?.addEventListener("change", () => {
refreshCityOptions({ province: elements.province.value });
});
elements.sort?.addEventListener("change", () => {
if (!state.loaded) return;
state.sort = elements.sort.value;
applyState();
});
elements.clear?.addEventListener("click", () => {
state.topic = "";
state.province = "";
state.city = "";
state.search = "";
state.sort = "newest";
elements.searchInput.value = "";
elements.province.value = "";
refreshCityOptions({ province: "", preserve: false });
elements.sort.value = "newest";
applyState({ scroll: true });
});
elements.loadMore?.addEventListener("click", () => {
state.visible += 9;
render();
});
restoreFromURL();
async function fetchCollection(endpoint, maximumPages = 5) {
const requestPage = async page => {
const separator = endpoint.includes("?") ? "&" : "?";
const response = await fetch(`${endpoint}${separator}page=${page}`, {
credentials: "same-origin",
headers: { Accept: "application/json" }
});
if (!response.ok) throw new Error(`REST feed returned ${response.status}`);
return { response, items: await response.json() };
};
const first = await requestPage(1);
const totalPages = Math.min(
Math.max(Number(first.response.headers.get("X-WP-TotalPages")) || 1, 1),
maximumPages
);
if (totalPages === 1) return Array.isArray(first.items) ? first.items : [];
const remaining = await Promise.all(
Array.from({ length: totalPages - 1 }, (_, index) => requestPage(index + 2))
);
return [first, ...remaining].flatMap(result => Array.isArray(result.items) ? result.items : []);
}
const siteOrigin = window.location.protocol === "file:" ? "https://findadoctorcanada.ca" : window.location.origin;
const postsURL = `${siteOrigin}/wp-json/wp/v2/posts?per_page=100&_embed=1&status=publish`;
function refreshVisibleTaxonomyControls() {
const visibleTopics = new Set(
topicDefinitions
.filter(definition => categoryCount(definition) > 0)
.map(definition => definition.slug)
);
elements.topics?.querySelectorAll("[data-topic]").forEach(link => {
const slug = link.dataset.topic || "";
link.hidden = Boolean(slug) && !visibleTopics.has(slug);
});
const currentProvince = state.province;
const availableProvinces = provinceDefinitions
.filter(definition => taxonomyCount(definition, "province") > 0);
elements.province.innerHTML = [
'
',
...availableProvinces.map(definition =>
`
`
)
].join("");
if (availableProvinces.some(definition => definition.slug === currentProvince)) {
elements.province.value = currentProvince;
} else {
state.province = "";
elements.province.value = "";
}
}
function collectEmbeddedTerms(posts, taxonomy) {
const terms = new Map();
posts.forEach(post => {
const seen = new Set();
postTerms(post, taxonomy).forEach(term => {
if (!term?.slug || seen.has(term.slug)) return;
seen.add(term.slug);
const current = terms.get(term.slug) || { ...term, count: 0 };
current.count += 1;
terms.set(term.slug, current);
});
});
return [...terms.values()];
}
fetchCollection(postsURL)
.then(posts => {
state.posts = Array.isArray(posts) ? posts : [];
state.categories = collectEmbeddedTerms(state.posts, "category");
state.cities = collectEmbeddedTerms(state.posts, "post_city");
state.provinces = collectEmbeddedTerms(state.posts, "province");
state.categoryById = new Map(state.categories.map(category => [Number(category.id), category]));
state.categoryBySlug = new Map(state.categories.map(category => [String(category.slug), category]));
state.cityBySlug = new Map(state.cities.map(term => [String(term.slug), term]));
state.provinceBySlug = new Map(state.provinces.map(term => [String(term.slug), term]));
refreshVisibleTaxonomyControls();
state.loaded = true;
refreshCityOptions({ province: state.province, preserve: true });
if (state.city) elements.city.value = state.city;
updateArchiveLinks();
render();
})
.catch(error => {
console.warn("FADC Blog could not load the WordPress post feed.", error);
if (elements.status) {
elements.status.textContent = "Live post filtering is temporarily unavailable. The normal article and archive links remain available.";
}
});
})();