PageSourceSearch

https://www.cbe.ab.ca/scripts/school-info.js

js cbe.ab.ca collected 2026-09-24 08:59:17 UTC 9,852 bytes, 284 lines download raw bytes

1// Used on Home, Contact, About Us
2const customProgramOrder = [
3	"Regular",
4	"Chinese (Mandarin) Bilingual",
5	"German Bilingual",
6	"Spanish Bilingual",
7	"Early French Immersion",
8	"Late French Immersion",
9	"Arts-Centred",
10	"Science",
11	"All Girls",
12	"Indigenous Focus",
13	"Montessori",
14	"Traditional Learning Centre (TLC)",
15	"Advanced Placement (AP)",
16	"International Baccalaureate (IB)",
17	"International Baccalaureate (French Immersion)",
18	"International Baccalaureate (Career)",
19	"Alternative High School",
20	"Home Education",
21	"Online Learning",
22	"Outreach",
23	"Pregnant & Parenting Teens"
24];
25
26const alernativePrograms = [
27	'Spanish Bilingual', 'All Boys', 'Alternative High School', 'Traditional Learning Centre (TLC)',
28	'Early French Immersion', 'Late French Immersion', 'German Bilingual', 'Montessori', 'Online Learning',
29	'Chinese (Mandarin) Bilingual', 'Outreach', 'International Baccalaureate (IB)',
30	'Home Education', 'Science', 'International Baccalaureate (Career)',
31	'Pregnant & Parenting Teens', 'Indigenous Focus', 'Arts-Centred',
32	'All Girls', 'International Baccalaureate (French Immersion)'
33]
34
35/*
36 * Program visibility
37 *
38 * The program fragment's `hidden` field controls where a program appears:
39 *   "hidden-everywhere"       → not listed anywhere, including the program
40 *                               listings on Home / Contact / About Us
41 *   "hidden-in-search-only"   → only hidden from search/filter drop-downs
42 *                               (Find a School, School Directory); still listed
43 *                               here
44 *   "no" / "" / anything else → shown
45 */
46const PROGRAM_HIDDEN_EVERYWHERE = 'hidden-everywhere';
47
48function isProgramHiddenEverywhere(hidden) {
49	return String(hidden == null ? '' : hidden).trim().toLowerCase() === PROGRAM_HIDDEN_EVERYWHERE;
50}
51
52function mergeGradeRanges(ranges) {
53	// Sort by start grade as number
54	ranges.sort((a, b) => Number(a.StartGrade) - Number(b.StartGrade));
55
56	const merged = [];
57	for (const range of ranges) {
58		const start = Number(range.StartGrade);
59		const end = Number(range.EndGrade);
60
61		if (!merged.length) {
62			merged.push({ start, end });
63		} else {
64			const last = merged[merged.length - 1];
65			if (last.end + 1 === start) {
66				last.end = end;
67			} else {
68				merged.push({ start, end });
69			}
70		}
71	}
72
73	return merged.map(r => `${formatGrade(String(r.start))} - ${formatGrade(String(r.end))}`);
74}
75
76// strip the "fragment://" prefix off a fragment reference to get the bare uuid
77function stripFragment(ref) {
78	return typeof ref === 'string' ? ref.replace('fragment://', '') : '';
79}
80
81// All data now comes from window.schoolProfileData (fragment-based).
82(window.schoolProfileData?.ready || Promise.resolve()).then(() => {
83	const schools = window.schoolProfileData?.schools || [];
84	const wards = window.schoolProfileData?.wards || [];
85	const areas = window.schoolProfileData?.areas || [];
86	const programs = window.schoolProfileData?.programs || [];
87
88	// fragment lookup maps, keyed by bare uuid
89	const wardMap = new Map(wards.map(w => [w.id, w]));
90	const areaMap = new Map(areas.map(a => [a.id, a]));
91	const programMap = new Map(programs.map(p => [p.id, p]));
92
93	const currentSchoolProfile =
94		schools.find(x => padSchoolCode(x?.profile?.schoolcode) === schoolCode) ||
95		schools.find(x => x?.profile?.schoolname === schoolName) ||
96		schools.find(x => x?.profile?.schooldisplayname === schoolName);
97
98	const profile = currentSchoolProfile?.profile || {};
99
100	// resolve ward + area fragments
101	const matchedWard = wardMap.get(stripFragment(profile.wardFragment)) || null;
102	const matchedArea = areaMap.get(stripFragment(profile.areaFragment)) || null;
103
104	const trustee = matchedWard?.data?.trustees?.[0]?.trustee || '';
105	// area fragment: directors live at data.eduDirectors[].name
106	const eduDirectors = (matchedArea?.data?.eduDirectors || [])
107		.map(d => d?.name)
108		.filter(Boolean);
109
110	// ward/area names sourced from the resolved fragments (not profile.wardname/areaname)
111	const wardName = matchedWard?.data?.trustees?.[0]?.wardname || matchedWard?.title || '';
112	const areaName = matchedArea?.data?.area?.areaname || matchedArea?.title || '';
113
114	// assistant principals (top-level assistants[] on the school fragment)
115	const currentAssistants = (currentSchoolProfile?.assistants || [])
116		.map(a => a?.name)
117		.filter(Boolean);
118
119	// enrollment rows { grades, number }, sorted ascending by grade
120	const enrollmentRows = (Array.isArray(currentSchoolProfile?.enrollment) ? currentSchoolProfile.enrollment : [])
121		.slice()
122		.sort((a, b) => Number(a?.grades) - Number(b?.grades));
123
124	// resolve each program fragment to its name + grade range
125	// Programs marked "hidden-everywhere" are dropped here, so they appear in
126	// neither the program listings nor the Regular/Alternative section checks.
127	const resolvedPrograms = (currentSchoolProfile?.programs || [])
128		.map(pg => {
129			const prog = programMap.get(stripFragment(pg.programFragment));
130			return {
131				// program name = data.programs.name (matches customProgramOrder); title is fallback
132				name: prog?.data?.programs?.name || prog?.title || '',
133				hidden: prog?.data?.programs?.hidden ?? pg.hidden ?? '',
134				lowGrade: pg.programLowGrade ?? profile.lowgrade,
135				highGrade: pg.programHighGrade ?? profile.highgrade
136			};
137		})
138		.filter(p => p.name && !isProgramHiddenEverywhere(p.hidden));
139
140	//console.log({ currentSchoolProfile, matchedWard, matchedArea, resolvedPrograms })
141
142
143	$(document).ready(function () {
144		// principal
145		const principal = profile.principal?.trim();
146		if (principal) {
147			$('.js-principal').html(`
148			<div class="leadership-label">Principal</div>
149			<div class="leadership-name">${principal}</div>
150		`)
151			$('.contact-principal').html(`
152			<p class="team-label">Principal</p>
153			<p class="team-name">${principal}</p>
154		`)
155		} else {
156			$('.js-principal').remove()
157			$('.contact-principal').remove()
158		}
159
160		// assistant principal
161		if (currentAssistants.length > 0) {
162			// home page
163			currentAssistants.forEach(x => {
164				const $leadershipItem = $('<div>').addClass('leadership-item')
165				$leadershipItem.html(`
166				<div class="leadership-label">Assistant Principal</div>
167				<div class="leadership-name">${x}</div>
168			`)
169				$('.js-leadership').append($leadershipItem)
170			})
171
172			currentAssistants.length > 2 ? $('.js-leadership').addClass('two-by-two') : $('.js-leadership').removeClass('two-by-two')
173
174			// contact page
175			currentAssistants.forEach(x => {
176				const $leadershipContactItem = $('<div>');
177				$leadershipContactItem.html(`
178				<p class="team-label">Assistant Principal</p>
179				<p class="team-name">${x}</p>
180			`)
181
182				$('.js-contact-leadership').append($leadershipContactItem)
183			})
184		}
185
186		// education director
187		eduDirectors.forEach(name => {
188			$('.js-edudirector').append($('<div>').text(name))
189		})
190
191		// trustee
192		if (trustee) {
193			$('.js-trustee').append($('<div>').text(trustee))
194		}
195
196		// ward (contact page)
197		wardName ? $('.js-ward').text(wardName.replace('Ward ', '')) : '';
198
199		// area
200		areaName ? $('.js-area').text(areaName.replace('Area ', '')) : '';
201
202		// school map image — prefer profile.schoolmap.src, otherwise fall back to the school-code image 
203		if (currentSchoolProfile) {
204			if (profile.schoolmap?.src) {
205				$('.js-school-map').attr('src', profile.schoolmap.src).attr('alt', profile.schoolmap.alt || '');
206			} else {
207				setImageWithPriority('.js-school-map', `/images/${padSchoolCode(profile.schoolcode)}-map`);
208			}
209		}
210
211		// grades and enrollment
212		let enrollment = 0;
213		enrollmentRows.forEach(x => {
214			const grade = formatGrade(String(x?.grades));
215			const count = Number(x?.number) || 0;
216			enrollment += count;
217
218			const $gradeEnrollment = $("<div>").addClass("grade-enrollment-item").html(`
219			<span>Grade ${grade}: </span>${x?.number}
220		`)
221
222			$('.js-grade-enrolment').append($gradeEnrollment)
223		})
224
225		if (profile.lowgrade && profile.highgrade) {
226			$('.js-grades').text(`${formatGrade(profile.lowgrade)} - ${profile.highgrade}`)
227		}
228
229		$('.js-enrollment').text(enrollment)
230
231		if (!enrollmentRows.length) {
232			$(".grade-enrollment, .about-school-info.enrolment").remove()
233		}
234
235		// Programs
236		if (resolvedPrograms.length > 0) {
237			let html = '';
238			let programNames = [];
239
240			// group grade ranges by program name
241			const grouped = resolvedPrograms.reduce((acc, item) => {
242				const key = item.name;
243				if (!acc[key]) acc[key] = [];
244				acc[key].push({ StartGrade: item.lowGrade, EndGrade: item.highGrade });
245				return acc;
246			}, {});
247
248			let formattedPrograms = Object.entries(grouped).map(([programName, ranges]) => {
249				const mergedRanges = mergeGradeRanges(ranges);
250				return {
251					programName,
252					output: `${programName} (${mergedRanges.join(', ')})`
253				};
254			});
255
256			formattedPrograms.sort((a, b) => {
257				const rawA = customProgramOrder.indexOf(a.programName);
258				const rawB = customProgramOrder.indexOf(b.programName);
259				const indexA = rawA === -1 ? Infinity : rawA;
260				const indexB = rawB === -1 ? Infinity : rawB;
261				return indexA - indexB;
262			});
263
264			html = formattedPrograms.map(x => `<div class="program-item">${x?.output}</div>`).join(' ')
265
266			// render programs
267			$('.js-programs, .js-home-programs').html(html)
268
269			resolvedPrograms.forEach(x => { programNames.push(x.name) })
270
271			// render regular program section on how to register page if regular program exists
272			programNames.includes('Regular') ? $('.regular-program.program-wrapper').show() : $('.regular-program.program-wrapper').hide();
273
274			// render alternative program section on how to register page if one of the alternative programs exists
275			const alernativeProgramExists = alernativePrograms.some(value => programNames.includes(value));
276			alernativeProgramExists ? $('.alternative-program.program-wrapper').show() : $('.alternative-program.program-wrapper').hide()
277
278		} else {
279			$('.js-programs, .js-home-programs').remove();
280		}
281
282	})
283
284});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.