PageSourceSearch

https://www.kitepharma.com/Areas/Kite/Content/mapbox/assets/mapbox/js/locator.js

js kitepharma.com collected 2026-10-02 06:21:54 UTC 21,973 bytes, 621 lines download raw bytes

1/**
2 * @file Initializes the map and all the corresponding components
3 */
4
5import { initializeMap, handleClustering, getSlug } from './map.js';
6import { initializeMiniMap } from './minimap.js';
7
8import { addGeocoder } from './geocoder.js';
9import { sortCenters } from './centers.js';
10
11import { updateCenters } from './centers.js';
12import { updateCentersList } from './list.js';
13import { updateDetails, openAllPanels } from './details.js';
14import { updateIsochrone, updateExtendedIsochrone } from './isochrone.js';
15
16import { createElement } from './utilities.js';
17import { print } from './print.js';
18
19import { statesByPostalCode } from './list.js';
20
21// Have to be Included in the view
22// import { theme } from '../data/theme.js';
23
24window.mapboxgl.accessToken = theme.map.api;
25
26//To control styles from application end
27document.body.classList.add('mapbox-locator');
28
29setupTheme(theme);
30
31let selectedPlace = null;
32let isochrone_mode = 'time';
33let currentLocationMarker = null;
34
35const geographicCenter = [-98.5, 37.5];
36
37let map;
38
39// Callback when point selected
40const selectCenterCallback = (center, userFlow) => {
41	// Add center id to params
42	const url = new URL(window.location.href);
43	url.searchParams.set('center', center.id);
44	url.searchParams.delete('search');
45	window.history.pushState(null, '', url.toString());
46
47	const slug = getSlug(center.name);
48
49	// Remove active class from markers
50	document.querySelectorAll('.locator__marker--active').forEach((marker) => {
51		marker.classList.remove('locator__marker--active');
52	});
53
54	// Add active class to marker
55	document.querySelectorAll(`[data-center='${slug}']`).forEach((marker) => {
56		marker.classList.add('locator__marker--active');
57	});
58
59	updateDetails(map, theme, center, selectedPlace, userFlow);
60
61	map.flyTo({ center: center.location, zoom: 8.5 });
62};
63
64// Load data
65//configure the ftech centger API URL from view
66const centers = await fetch(theme.centersApi).then((r) => r.json());
67
68// Initialize map
69map = await initializeMap('locator-map', theme, geographicCenter, centers, selectCenterCallback);
70
71initializeMiniMap(map, centers);
72
73// Add centers to sidebar
74await updateCenters('locator-centers', centers, selectedPlace, selectCenterCallback);
75
76// Add centers to list view
77await updateCentersList('locator-list', centers, selectedPlace, selectCenterCallback);
78
79// Add geocoder
80const geocoder = await addGeocoder(map, 'locator-search', centers, async (place, placeName) => {
81	selectedPlace = place;
82
83	const url = new URL(window.location.href);
84	url.searchParams.set('search', placeName);
85	window.history.pushState(null, '', url.toString());
86
87	updateWithLocation(place, placeName);
88});
89
90// If search parameter is present, search for it
91const urlParams = new URLSearchParams(window.location.search);
92const search = urlParams.get('search');
93const center = urlParams.get('center');
94
95if (center) {
96	const selectedCenter = centers.find((c) => center === c.id);
97
98	if (selectedCenter) {
99		selectCenterCallback(selectedCenter);
100	}
101} else if (search) {
102	geocoder.query(search);
103
104	const input = document.querySelector('#locator-search input');
105
106	// Blur input after 500ms
107	setTimeout(() => {
108		if (input) input.blur();
109	}, 1000);
110}
111
112initializeClose();
113initializeListSearch();
114initializeLocate();
115initializeModeButtons();
116initializeResetLocation();
117
118// Add copy
119
120const copyButton = document.querySelector('#centers-copy-link');
121
122copyButton.addEventListener('click', () => {
123	const currentURL = window.location.href;
124	copyButton.focus();
125	navigator.clipboard.writeText(currentURL);
126
127	copyButton.innerHTML = copyButton.innerHTML.replace('Copy link', 'Copied!');
128
129	setTimeout(() => {
130		copyButton.innerHTML = copyButton.innerHTML.replace('Copied!', 'Copy link');
131	}, 3000);
132});
133
134// Add print events
135document.querySelector('#centers-print').addEventListener('click', () => {
136	document.getElementById('centers-print').focus();
137	print(map);
138});
139
140/* window.addEventListener('beforeprint', (event) => {
141	if (document.querySelector('.locator__map')) {
142		const container = document.querySelector('.locator__map');
143		container.style.width = '640px';
144		container.style.height = '360px';
145		map.resize();
146	}
147});
148
149window.addEventListener('afterprint', (event) => {
150	if (document.querySelector('.locator__map')) {
151		const container = document.querySelector('.locator__map');
152		container.style.width = '100%';
153		container.style.height = '100%';
154		map.resize();
155	}
156}); */
157
158function setupTheme(theme) {
159	// Add CSS variables to locator element
160	const locator = document.getElementById('locator');
161	locator.style.setProperty('--font', theme.font.family);
162	locator.style.setProperty('--primaryColor', theme.colors.primary);
163	locator.style.setProperty('--secondaryColor', theme.colors.secondary);
164	locator.style.setProperty('--secondaryLight', theme.colors.secondaryLight);
165	locator.style.setProperty('--secondaryLighter', theme.colors.secondaryLighter);
166	locator.style.setProperty('--secondaryLightest', theme.colors.secondaryLightest);
167
168	// Add logo to logo element
169	const logo = document.getElementById('locator-logo');
170	logo.src = theme.logo.url;
171}
172
173/**
174 * Adds event listeners to the locate button and updates location when pressed
175 */
176function initializeLocate() {
177	const findButton = document.getElementById('locator-find');
178
179	findButton.addEventListener('click', () => {
180		if (window.navigator.geolocation) {
181			// Geolocation available
182
183			window.navigator.geolocation.getCurrentPosition((location) => {
184				selectedPlace = [location.coords.longitude, location.coords.latitude];
185
186				updateWithLocation(selectedPlace);
187			});
188		}
189	});
190}
191
192function initializeModeButtons() {
193	const listModeButton = document.getElementById('locator-list-mode');
194	const mapModeButton = document.getElementById('locator-map-mode');
195
196	const locatorContainer = document.getElementById('locator');
197
198	listModeButton.addEventListener('click', () => {
199		locatorContainer.classList.add('locator--mode-list');
200
201		listModeButton.classList.add('active');
202		mapModeButton.classList.remove('active');
203
204		openAllPanels();
205	});
206
207	mapModeButton.addEventListener('click', () => {
208		locatorContainer.classList.remove('locator--mode-list');
209
210		listModeButton.classList.remove('active');
211		mapModeButton.classList.add('active');
212	});
213}
214
215/**
216 * Adds event listener to the reset button and resets the map app (reset
216s selected place, remove map marker, isochrones, route and clears geolocator)
217 */
218function initializeResetLocation() {
219	const resetButton = document.getElementById('locator-reset');
220
221	resetButton.addEventListener('click', () => {
222		selectedPlace = null;
223
224		// Remove has-location class
225		const locator = document.getElementById('locator');
226		locator.classList.remove('locator--has-location');
227
228		// Remove search center id from params
229		const url = new URL(window.location.href);
230		url.searchParams.delete('center');
231		url.searchParams.delete('search');
232		window.history.pushState(null, '', url.toString());
233
234		// Reset geocoder
235		geocoder.clear();
236
237		// Clear map layers
238		[
239			'route',
240			'time_isochrone',
241			'extended_time_isochrone',
242			'distance_isochrone',
243			'extended_distance_isochrone'
244		].forEach((source) => {
245			map.getSource(source).setData({ type: 'FeatureCollection', features: [] });
246		});
247
248		// Remove marker
249		if (currentLocationMarker) {
250			currentLocationMarker.remove();
251			currentLocationMarker = null;
252		}
253
254		// Zoom out
255		map.flyTo({ center: geographicCenter, zoom: 3.25 });
256	});
257}
258
259/**
260 * Adds event listener to the close button and closes the details panel and removes active class from markers and removes route
261 */
262function initializeClose() {
263	const closeButton = document.getElementById('locator-close');
264
265	closeButton.addEventListener('click', () => {
266		// Remove search center id from params
267		const url = new URL(window.location.href);
268		url.searchParams.delete('center');
269		url.searchParams.delete('search');
270		window.history.pushState(null, '', url.toString());
271
272		// Closing details panel
273		const locator = document.getElementById('locator');
274		locator.classList.remove('opened');
275
276		// Remove active class from markers
277		document.querySelectorAll('.locator__marker--active').forEach((marker) => {
278			marker.classList.remove('locator__marker--active');
279		});
280
281		// Remove route
282		['route'].forEach((source) => {
283			map.getSource(source).setData({ type: 'FeatureCollection', features: [] });
284		});
285	});
286}
287
288function initializeListSearch() {
289	const searchInput = document.querySelector('#locator-list-search');
290	const searchReset = document.querySelector('#locator-list-reset');
291
292	function search(searchText) {
293		let filteredCenters = centers;
294
295		if (searchText.length >= 2) {
296			filteredCenters = centers.filter((center) => {
297				const centerState = statesByPostalCode[center.state];
298
299				return (
300					center.name.toLowerCase().includes(searchText) ||
301					centerState.toLowerCase().includes(searchText)
302				);
303			});
304		}
305
306		updateCentersList('locator-list', filteredCenters, selectedPlace, selectCenterCallback);
307	}
308
309	searchInput.addEventListener('input', (e) => {
310		console.log('search', e.target.value);
311
312		const searchText = e.target.value.toLowerCase();
313
314		search(searchText);
315		if (e.target.value.length > 0) {
316			searchReset.style.display = "block";
317		} else {
318			searchReset.style.display = "none";
319		}
320	});
321
322	searchReset.addEventListener('click', () => {
323		searchInput.value = '';
324
325		search('');
326		searchReset.style.display = "none";
327	});
328}
329
330/**
331 * Updates the map with the selected place, adds centers, adds locator pin, centers map, adds isochrones
332 * @param {Array} selectedPlace - [longitude, latitude]
333 * @param {string} selectedPlaceName - Name of the selected place
334 * @returns
335 */
336function updateWithLocation(selectedPlace, selectedPlaceName) {
337	const locator = document.getElementById('locator');
338	locator.classList.add('locator--has-location');
339
340	// Update centers after selecting instantly without selectedPlace
341	updateCenters('locator-centers', centers, null, selectCenterCallback);
342
343	const closest = sortCenters(centers, selectedPlace);
344	const within25 = closest.filter((center) => center.distance < 25);
345
346	// Get times to centers
347	if (selectedPlace) {
348		// Update with selected place which will get travel times
349		updateCenters('locator-centers', centers, selectedPlace, selectCenterCallback);
350	}
351
352	let closestCenters = closest.slice(0, 3).map((center) => center.location);
353
354	const points = [selectedPlace, ...closest.slice(0, 3).map((center) => center.location)].reduce(
355		(a, c) => {
356			a[0].push(c[0]);
357			a[1].push(c[1]);
358
359			return a;
360		},
361		[[], []]
362	);
363
364	const extent = [
365		[Math.min(...points[0]), Math.min(...points[1])],
366		[Math.max(...points[0]), Math.max(...points[1])]
367	];
368
369	if (within25.length >= 3) {
370		map.flyTo({ center: selectedPlace, zoom: 9 });
371	} else if (window.innerWidth > 960) {
372		/*
373		map.fitBounds(extent, {
374			padding: { top: 180, bottom: 60, left: 420 + 12, right: 60 }
375		});
376		*/
377
378		const maxX = Math.max(
379			...closestCenters.map((center) => {
380				return Math.abs(selectedPlace[0] - center[0]);
381			})
382		);
383
384		const maxY = Math.max(
385			...closestCenters.map((center) => {
386				return Math.abs(selectedPlace[1] - center[1]);
387			})
388		);
389
390		const newExtent = [
391			[selectedPlace[0] - maxX, selectedPlace[1] - maxY],
392			[selectedPlace[0] + maxX, selectedPlace[1] + maxY]
393		];
394
395		map.fitBounds(newExtent, {
396			padding: { top: 180, bottom: 60, left: 420 + 12 + 60, right: 60 }
397		});
398	} else {
399		map.flyTo({ center: selectedPlace, zoom: 5 });
400	}
401
402	// Add location marker
403	if (!currentLocationMarker) {
404		const element = createElement(
405			`<div class='locator__marker'>
406				<div class='locator__marker__icon marker__icon--secondary'>
407					<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg">
408						<path d="M40.6897 17.8448C40.6897 23.8343 35.8343 28.6897 29.8448 28.6897C23.8554 28.6897 19 23.8343 19 17.8448C19 11.8554 23.8554 7 29.8448 7C35.8343 7 40.6897 11.8554 40.6897 17.8448Z" fill="var(--primaryColor)"/>
409						<path fill-rule="evenodd" clip-rule="evenodd" d="M29.2069 52.931L29.2069 28.6897H30.4828L30.4828 52.931L29.2069 52.931Z" fill="var(--primaryColor)"/>
410					</svg>
411				</div>
412				<div class='locator__map__popup locator__map__popup--your-location'>
413					<div class='locator__map__popup__contents'>
414						<span class='locator__map__popup--your-location'>${dictionary.yourLocation}</span>
415						<span class='locator__map__popup--your-location-details'></span>
416					</div>
417				</div>
418			</div>`
419		);
420
421		currentLocationMarker = new mapboxgl.Marker(element).setLngLat(selectedPlace).addTo(map);
422	} else {
423		currentLocationMarker.setLngLat(selectedPlace);
424	}
425
426	// Update text in pin
427	const popup = document.querySelector('.locator__map__popup--your-location-details');
428	popup.innerHTML = selectedPlaceName || '';
429
430	// If no close centers, don't add isochrone
431	if (closest[0].distance > 100) return;
432
433	// Add isochrone to map after selecting location
434	updateIsochrone(map, selectedPlace, isochrone_mode);
435	updateExtendedIsochrone(map, selectedPlace, isochrone_mode);
436}
437
438// GTM custom events tracking
439// Locator search box click
440document.querySelector(".mapboxgl-ctrl-geocoder--input").addEventListener('click', () => {
441	//Push search box GTM when focused - Event level tracking
442	if (dictionary && dictionary.gtmSearchBox) {
443		var gtmSearchBox = Object.assign({}, dictionary.gtmSearchBox);
444		if (dictionary.fnGTMPushData) {
445			dictionary.fnGTMPushData(gtmSearchBox);
446		}
447	}
448});
449// global events from ATC center details
450document.body.addEventListener("click", function (e) {
451	// Bio link
452    if (e.target && e.target.classList.contains("bio-link") && dictionary && dictionary.gtmBioLink) {
453		let yourLocation = "",
454			gtmBioLink = Object.assign({}, dictionary.gtmBioLink),
455			bioDetails = e.target.closest(".locator__detail__bio__details"),
456			bioName = bioDetails.querySelector(".locator__detail__bio__name").textContent.trim(),
457			cDetails = JSON.parse(document.querySelector("#locator-selected-details").getAttribute("data-cdetails"));
458
459		if (document.querySelector(".mapboxgl-ctrl-geocoder--input").value != "") {
460			yourLocation = " | " + document.querySelector(".mapboxgl-ctrl-geocoder--input").value;
461		}
462
463		if (gtmBioLink.eventLabel.indexOf("{BIO LINK CENTER DETAILS}") >= 0) {
464			let eventLabel = 
465				cDetails.id + " | "
466				+ cDetails.name + " | "
467				+ cDetails.primaryAddress_State + " | "
468				+ cDetails.primaryAddress_City + " | "
469				+ cDetails.primaryAddress_Zip
470				+ yourLocation;
471			// [ID] | [Practice Name] | [Practice State abbreviation] | [Practice City] | [Practice Zip] | [Value available in search bar filed] | Care team Member {CARE TEAM MEMBER NAME}
472			gtmBioLink.eventLabel = (gtmBioLink.eventLabel.replace("{BIO LINK CENTER DETAILS}", eventLabel));
473			gtmBioLink.eventLabel = (gtmBioLink.eventLabel.replace("{CARE TEAM MEMBER NAME}", bioName));
474		} else if (gtmBioLink.eventLabel.indexOf("{BIO name}") >= 0) {
475			gtmBioLink.eventLabel = (gtmBioLink.eventLabel.replace("{BIO name}", bioName));
476		} else {
477			gtmBioLink.eventLabel = bioName;
478		}
479		
480		if (dictionary.fnGTMPushData) {
481			dictionary.fnGTMPushData(gtmBioLink);
482		}
483	}
484
485	// Directions link
486    if (e.target && e.target.classList.contains("directions-link") && dictionary && dictionary.gtmDirectionLink) {
487		let yourLocation = "",
488			gtmDirectionLink = Object.assign({}, dictionary.gtmDirectionLink),
489			cDetails = JSON.parse(document.querySelector("#locator-selected-details").getAttribute("data-cdetails"));
490		
491		if (document.querySelector(".mapboxgl-ctrl-geocoder--input").value != "") {
492			yourLocation = " | " + document.querySelector(".mapboxgl-ctrl-geocoder--input").value;
493		}
494
495		if (gtmDirectionLink.eventLabel.indexOf("{GET DIRECTIONS LINK CENTER DETAILS}") >= 0) {
496			let eventLabel = 
497				cDetails.id + " | "
498				+ cDetails.name + " | "
499				+ cDetails.primaryAddress_State + " | "
500				+ cDetails.primaryAddress_City + " | "
501				+ cDetails.primaryAddress_Zip
502				+ yourLocation;
503			// [ID] | [Practice Name] | [Practice State abbreviation] | [Practice City] | [Practice Zip] | [Value available in search bar filed] | Get Directions via Google Maps
504			gtmDirectionLink.eventLabel = (gtmDirectionLink.eventLabel.replace("{GET DIRECTIONS LINK CENTER DETAILS}", eventLabel));
505		} else if (gtmDirectionLink.eventLabel.indexOf("{Practice Name}") >= 0) {
506			gtmDirectionLink.eventLabel = (gtmDirectionLink.eventLabel.replace("{Practice Name}", cDetails.name));
507		} else {
508			gtmDirectionLink.eventLabel = cDetails.name;
509		}
510		
511		if (dictionary.fnGTMPushData) {
512			dictionary.fnGTMPushData(gtmDirectionLink);
513		}
514	}
515
516	// Center link
517    if (e.target && e.target.classList.contains("center-link") && dictionary && dictionary.gtmCenterLink) {
518		let yourLocation = "",
519			gtmCenterLink = Object.assign({}, dictionary.gtmCenterLink),
520			cDetails = JSON.parse(document.querySelector("#locator-selected-details").getAttribute("data-cdetails"));
521
522		if (document.querySelector(".mapboxgl-ctrl-geocoder--input").value != "") {
523			yourLocation = " | " + document.querySelector(".mapboxgl-ctrl-geocoder--input").value;
524		}
525
526		if (gtmCenterLink.eventLabel.indexOf("{CENTER WEBSITE LINK CENTER DETAILS}") >= 0) {
527			let eventLabel = 
528				cDetails.id + " | "
529				+ cDetails.name + " | "
530				+ cDetails.primaryAddress_State + " | "
531				+ cDetails.primaryAddress_City + " | "
532				+ cDetails.primaryAddress_Zip
533				+ yourLocation;
534			// [ID] | [Practice Name] | [Practice State abbreviation] | [Practice City] | [Practice Zip] | [Value available in search bar filed] | Click Website Link
535			gtmCenterLink.eventLabel = (gtmCenterLink.eventLabel.replace("{CENTER WEBSITE LINK CENTER DETAILS}", eventLabel));
536		} else if (gtmCenterLink.eventLabel.indexOf("{Center Link}") >= 0) {
537			gtmCenterLink.eventLabel = (gtmCenterLink.eventLabel.replace("{Center Link}", e.target.getAttribute("href")));
538		}
539		
540		if (dictionary.fnGTMPushData) {
541			dictionary.fnGTMPushData(gtmCenterLink);
542		}
543	}
544
545	// Center email
546    if (e.target && e.target.classList.contains("center-email") && dictionary && dictionary.gtmCenterEmail) {
547		let yourLocation = "",
548			gtmCenterEmail = Object.assign({}, dictionary.gtmCenterEmail),
549			cDetails = JSON.parse(document.querySelector("#locator-selected-details").getAttribute("data-cdetails"));
550		
551		if (document.querySelector(".mapboxgl-ctrl-geocoder--input").value != "") {
552			yourLocation = " | " + document.querySelector(".mapboxgl-ctrl-geocoder--input").value;
553		}
554
555		if (gtmCenterEmail.eventLabel.indexOf("{CENTER EMAIL LINK CENTER DETAILS}") >= 0) {
556			let eventLabel = 
557				cDetails.id + " | "
558				+ cDetails.name + " | "
559				+ cDetails.primaryAddress_State + " | "
560				+ cDetails.primaryAddress_City + " | "
561				+ cDetails.primaryAddress_Zip
562				+ yourLocation;
563			// [ID] | [Practice Name] | [Practice State abbreviation] | [Practice City] | [Practice Zip] | [Value available in search bar filed] | ATC Email
564			gtmCenterEmail.eventLabel = (gtmCenterEmail.eventLabel.replace("{CENTER EMAIL LINK CENTER DETAILS}", eventLabel));
565		} else if (gtmCenterEmail.eventLabel.indexOf("{Center Email}") >= 0) {
566			gtmCenterEmail.eventLabel = (gtmCenterEmail.eventLabel.replace("{Center Email}", cDetails.email || ""));
567		} else {
568			gtmCenterEmail.eventLabel = cDetails.email || "";
569		}
570
571		if (dictionary.fnGTMPushData) {
572			dictionary.fnGTMPushData(gtmCenterEmail);
573		}
574	}
575
576	// Locator Print > Center Details
577    if (e.target && e.target.classList.contains("locator__print-center-details") && dictionary && dictionary.gtmPrintCenterDetails) {
578		let yourLocation = "",
579			gtmPrintCenterDetails = Object.assign({}, dictionary.gtmPrintCenterDetails),
580			cDetails = JSON.parse(document.querySelector("#locator-selected-details").getAttribute("data-cdetails"));
581		
582		if (document.querySelector(".mapboxgl-ctrl-geocoder--input").value != "") {
583			yourLocation = " | " + document.querySelector(".mapboxgl-ctrl-geocoder--input").value;
584		}
585
586		if (gtmPrintCenterDetails.eventLabel.indexOf("{PRINT ATC VIEW CENTER DETAILS}") >= 0) {
587			let eventLabel = 
588				cDetails.id + " | "
589				+ cDetails.name + " | "
590				+ cDetails.primaryAddress_State + " | "
591				+ cDetails.primaryAddress_City + " | "
592				+ cDetails.primaryAddress_Zip
593				+ yourLocation;
594			// [ID] | [Practice Name] | [Practice State abbreviation] | [Practice City] | [Practice Zip] | [Value available in search bar filed] | Print Information for ATC View
595			gtmPrintCenterDetails.eventLabel = (gtmPrintCenterDetails.eventLabel.replace("{PRINT ATC VIEW CENTER DETAILS}", eventLabel));
596		}
597		
598		if (dictionary.fnGTMPushData) {
599			dictionary.fnGTMPushData(gtmPrintCenterDetails);
600		}
601	}
602
603	// Locator Print > Centers
604    if (e.target && e.target.classList.contains("locator__print-centers") && dictionary && dictionary.gtmPrintCenters) {
605		let yourLocation = "",
606			gtmPrintCenters = Object.assign({}, dictionary.gtmPrintCenters);
607		
608		if (document.querySelector(".mapboxgl-ctrl-geocoder--input").value != "") {
609			yourLocation = document.querySelector(".mapboxgl-ctrl-geocoder--input").value;
610		}
611
612		if (gtmPrintCenters.eventLabel.indexOf("{PRINT LIST VIEW ATC CENTERS}") >= 0) {
613			// [Value available in search bar filed] | Print Information for List View
614			gtmPrintCenters.eventLabel = (gtmPrintCenters.eventLabel.replace("{PRINT LIST VIEW ATC CENTERS}", yourLocation));
615		}
616		
617		if (dictionary.fnGTMPushData) {
618			dictionary.fnGTMPushData(gtmPrintCenters);
619		}
620	}
621});

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.