`, '') : ``; /* Get the tracking pixels wrapper element */ const wrapper = document.querySelector('#pixels-wrapper'); /* If the wrapper element already exists, append to it. If not, create it with the pixels inside */ if (wrapper) { wrapper.insertAdjacentHTML('beforeend', pixels); } else { document.body.insertAdjacentHTML('beforeend', `
`); } }, /* Tracks events emitted from the timeslots-widget web component */ trackFromEmitted: async (type, event) => { const { reportToWheelhouse, trackWompEvent } = this.helpers; const { scrollLeft, scrollTop } = document.body; let pixel; let provider; let index; let hasTimeslots = 0; const providerCardEl = event.target.closest('.card.provider'); try { if (providerCardEl) { index = Number(providerCardEl.dataset.position); provider = this.state.results.providers[index - 1]; hasTimeslots = providerCardEl.querySelector('timeslots-widget, directory-timeslots') ? 1 : 0; } } catch (err) { console.error('fromEmitted error:\n', err); } switch (type) { case 'phone': if (typeof event.detail === 'string') { await reportToWheelhouse([ { key: "tealium_event", value: "amp_event" }, { key: "event_category", value: "Contact" }, { key: "event_action", value: "Phone" }, { key: "event_label", value: event.detail }, { key: "scroll_x", value: scrollLeft }, { key: "scroll_y", value: scrollTop } ]); /* Internal tracking */ await trackWompEvent({ ec: "Direct Contact", ea: "Phone Click-to-Call", el: event.detail }); if (provider) { await trackWompEvent({ ec: "Provider Click-To-Call", ea: `${provider.Name}, ${index}, timeslots: ${hasTimeslots}`, el: JSON.stringify(provider['@search.features']) }); } /* Telium pixel */ pixel = siteSearch.helpers.createPixel({ event_category: "Direct Contact", event_action: "Phone Click-to-Call", event_label: event.detail, tealium_event: 'womp_directory_event', tealium_event_type: 'event' }); } else { console.error('event.detail value must be a string representing the phone number clicked'); return; } break; case 'timeslot': reportToWheelhouse([ { key: "tealium_event", value: "amp_event" }, { key: "event_category", value: "Schedule Appointment" }, { key: "event_action", value: "Pick a Time" }, { key: "scroll_x", value: scrollLeft }, { key: "scroll_y", value: scrollTop } ]); const action = event.detail?.action; trackWompEvent({ ec: "Directory Journeys", ea: action && action.length ? action : "Provider Directory", el: 'Timeslot Clicks' }); /* Internal tracking */ if (provider) { trackWompEvent({ ec: "Provider Timeslot Click", ea: `${provider.Name}, ${index}, timeslots: ${hasTimeslots}`, el: JSON.stringify(provider['@search.features']) }); } pixel = siteSearch.helpers.createPixel({ event_category: "Directory Journeys", event_action: action && action.length ? action : "Provider Directory", event_label: "Timeslot Clicks", tealium_event: 'womp_directory_event', tealium_event_type: 'event', }); break; default: console.error('Please provide a valid event type [phone, timeslot]'); return; } siteSearch.helpers.trackIt(pixel); }, getGoogleLocation: (elId = "custom-location") => { const value = document.querySelector("#" + elId).value; if (value !== this.state.filters.location) { this.state.updatedLocation = true; } let coords; if (value?.length > 1) { fetch( `https://maps.googleapis.com/maps/api/geocode/json?address=${ value }&key=AIzaSyC9aTi6UWN30wj-e4uJHJ5j0y6szBTQ4BY` ) .then((res) => res.json()) .then((res) => { if (res.results[0]) { AMP.setState({locationState: {validZip: 1}}); const geo = res.results[0].geometry.location; coords = { latitude: geo.lat, longitude: geo.lng, }; console.debug('state:', this.helpers.deepCopy(this.state)); this.state.filters = { ...this.state.filters, coordinates: coords, userProvidedLocation: true, }; document.querySelector("#switch").checked = false; this.handleSubmit(); this.state.filters.location = value; this.state.filters.locationType = "user"; } else { console.error('invalid zip:', validZip); AMP.setState({locationState: {validZip: 0}}); return; } }); } }, /* Scrolls to a specific anchor on the page with a specified offset to account for the fixed nav */ scrollToTarget: (el, headerOffset = 45) => { let offsetPosition = el.getBoundingClientRect().top - headerOffset; window.scrollBy({ top: offsetPosition, behavior: "smooth" }); }, pxInViewport: el => { const bounding = el.getBoundingClientRect(); const offset = bounding.height + bounding.top; return offset < 0 ? 0 : offset; }, waitForGlobal: (key, callback) => { if (window[key]) { callback(); } else { setTimeout(() => { this.helpers.waitForGlobal(key, callback); }, 100); } }, /** * Returns a Promise that resolves once * the required property is available on the object. * Rejects if maxWait occurs. * * @param {String} propName - property name * @param {String} scope - object to test for property * @param {Number} maxWait - ms to wait before rejecting * @param {Number} checkInterval - ms to wait between checks */ waitForProp( propName, scope = window, maxWait = 20000, checkInterval = 150, ) { return new Promise((resolve, reject) => { /* check now, maybe we do not need to wait */ if ( (scope == window && window[propName]) || (scope != window && scope && scope[propName]) ) { resolve(scope[propName]); } const timeout = setTimeout(() => { clearInterval(interval); reject(`could not find ${scope.toString()}['${propName}']`); }, maxWait); const interval = setInterval(() => { if ( (scope == window && window[propName]) || (scope != window && scope && scope[propName]) ) { clearInterval(interval); clearTimeout(timeout); resolve(scope[propName]); } else { return; } }, checkInterval); }); }, showPosition: async (position) => { const { latitude, longitude } = position.coords; await fetch( `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=AIzaSyC9aTi6UWN30wj-e4uJHJ5j0y6szBTQ4BY` ) .then((res) => res.json()) .then((res) => { const parts = res.results[0].address_components.filter((x) => x.types .join('') .match( /locality|administrative_area_level_1|administrative_area_level_3/gi ) ); const newLoc = `${parts[0].long_name}, ${parts[1].short_name}`; /* LocationLabel.innerHTML = `Location: ${newLoc} ▾`; */ this.state.filters.location = newLoc; /** **/ /** **/ }); this.state.filters = { ...this.state.filters, coordinates: { latitude, longitude, }, locationType: "user", }; this.handleSubmit(); }, handleGeoLocationError: (err) => { console.error('geo:\n', err.message); this.helpers.getGoogleLocation(); }, formatAddress: (type, data) => { if (!data) return ''; let formattedAddress = ''; if (type == 'infoWindow') { const { City, Region, PostalCode, Address } = data; const regex = new RegExp(`${City}, |${Region},|, ${PostalCode}`, 'gi'); formattedAddress = Address.replace(regex, '').trim(); formattedAddress = `${formattedAddress}No providers found as this location
' : ''}${this.state.results.error.message}
`; return; } /* Update state now that an initial search has been completed */ this.state.filters.init = false; /* Grabs the total number of results */ const count = this.state.filters.LocationsOnly ? this.state.results.locations.length : this.state.results.providersCount; /* Calculate how many pages we need for the number of results returned. */ this.state.filters.pages = this.state.filters.LocationsOnly ? Math.ceil(count / post.top) : Math.ceil(this.state.results["@odata.count"] / post.top); /* Add badges for the applied filters */ const filtersElem = document.getElementById('filterButtons'); filtersElem ? filtersElem.innerHTML = '' : ''; const filterWrapperDiv = document.getElementById('filterWrapper'); const filtersSorted = this.state.results.info.filters.sort((a,b) => a.text ? -1 : 1); const filtersBadged = []; try { if (filtersSorted.length === 0 || (filtersSorted.length === 1 && filtersSorted[0].facets[0].name === 'IsClinic')) { filterWrapperDiv?.classList.remove('has-filter'); } } catch (err) { console.error('couldn’t remove filter:\n', err); } try { for (let filter of siteSearch.state.results.info.filters) { try { if (filter.facets[0].name === 'IsClinic') { continue; } } catch {} let filterButton = document.createElement('span'); filterButton.classList.add('filter-button'); filterButton.setAttribute('tabindex', 0); let filterText = filter.value; /* LocationId becomes LocationName */ if ( filter.facets.length && 'name' in filter.facets[0] && filter.facets[0].name === 'LocationId' && 'info' in this.state.results && 'locations' in this.state.results.info && this.state.results.info.locations?.length ) { const locIndex = this.state.results.info.locations?.findIndex( (loc) => loc.id == filterText ); if (locIndex >= 0) { filterText = this.state.results.info.locations[locIndex].name; } else { this.helpers.removeParamValue('locationid', filterText); const locIdFiltStr = this.state.filters.LocationId; const locIdArr = locIdFiltStr.split(','); const locIdIndex = locIdArr.findIndex((id) => id == filterText); locIdArr.splice(locIdIndex, 1); this.state.filters.LocationId = locIdArr.join(','); continue; } } filterButton.innerHTML = `✖ ${filterText}`; const { facets } = filter; /* if the text for a search is returned... */ if (filter.text) { filterButton.setAttribute('data-text', filter.text); for (let facet of facets) { try { const facetFilter = this.state.filters[facet.name]; if (facetFilter) { facetFilter[facet.value] = {...facetFilter[facet.value], text: filter.text, auto: filter.auto == true}; filtersBadged.push(`${facet.name}${facet.value}`); } } catch (err) { throw(err); } } /* otherwise... */ } else { if (filter.value == 1) { continue; } /* everything else should have a length of 1 but let's check to be safe. If the value has already had a badge created, break out of this current loop iteration. */ if (facets.length === 1 && filtersBadged.includes(`${facets[0].name}${facets[0].value}`)) { continue; /* If a badge doesn't already exist, we'll add it to the badged array and keep on in this interation */ } else { filtersBadged.push(`${facets[0].name}${facets[0].value}`); } } /* Create the data attribute that allows us to remove the filter on click */ let dataFacets = filter.facets; if (!(dataFacets && dataFacets.length)) { const { facet, value } = filter; /* If we have facet and value strings, each with a length, create the fallback data */ if (facet?.length && value?.length) { dataFacets = [{ name: facet, value: value }]; } } /* Set the data-facets attribute */ filterButton.setAttribute('data-facets', JSON.stringify(dataFacets)); /* Create an event listener to handle clicks, removing filters */ filterButton.addEventListener('click', (event) => { const { target } = event; const { dataset } = target; this.clearSlugIfOnlyDistanceRemains(target); let theRegEx; if (dataset.text) { theRegEx = new RegExp(dataset.text, 'gi'); } if (theRegEx) { window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, ' ').replaceAll(theRegEx, '').replaceAll(/ +/gi, ' ').trim(); if (this.state.query) { this.inputSearchEl.value = this.state.query; document.getElementById('searchBox').classList.add('is-populated'); } else { this.inputSearchEl.value = ''; document.getElementById('searchBox').classList.remove('is-populated'); } } let clearedInsurance = false; let clearInsurancePromise; const facets = JSON.parse(dataset.facets); for (let facet of facets) { const { name: facetName, value } = facet; const state = this.state.filters[facetName]; if (state) { if (typeof state === 'string') { const newState = state.replace(value, '').replace(/^,/,'').replace(',,',','); this.state.filters[facetName] = newState || false; if (!newState) { switch (facetName) { case 'acceptingNewPatients': case 'location': case 'offersVideoVisits': this.state.filters[facetName] = false; break; case 'locationId': this.state.filters.LocationId = false; break; default: break; } } } else if (typeof state === 'object') { state[value].active = false; /* Catch weird values that have ',' in them */ try { let csv = value.split(','); for (let val of csv) { state[val].active = false; } } catch (err) { console.error('failure removing cvs filter:\n', err); } } } if (facetName == 'InsuranceAccepted') { localStorage.removeItem('omniSearchInsurance'); clearedInsurance = true; clearInsurancePromise = fetch('https://www.providence.org/integrationapi/clearinsuranceselection'); } } try { let removeParam = JSON.parse(target.getAttribute('data-facets'))[0].name; let url = new URL(window.location); let searchParams = new URLSearchParams(url.search); searchParams.delete(removeParam); searchParams.delete(removeParam.toLowerCase()); url.search = searchParams.toString(); window.history.replaceState({}, 'Find a Doctor', url); } catch (err) { console.error('failure updating url:\n', err); } if (document.querySelectorAll('.filter-button')?.length === 1) { filterWrapperDiv?.classList.remove('has-filter'); if (!!this.slugObjDiv && this.state.query === '') { this.clearSlug(); } } target.remove(); if (window.innerWidth >= siteSearch.mobileViewBreakpoint) { if (clearedInsurance) { Promise.allSettled([clearInsurancePromise]) .then(() => this.handleSubmit()); } else { this.handleSubmit(); } } }); /* Add the filter button to the DOM */ filtersElem?.prepend(filterButton); filterWrapperDiv?.classList.add('has-filter'); } /* Add badges for Virtual and New Patient visit type selections */ if (this.state.filters.visitType) { const { clinicVisits, videoVisits } = this.state.filters.visitType; const hasClinicVisits = clinicVisits && clinicVisits?.active; const hasVideoVisits = videoVisits && videoVisits?.active; const handleVisitFiltersClick = (event) => { this.clearSlugIfOnlyDistanceRemains(event.target); const { value } = event.target.dataset; const { filters } = this.state; const state = filters.visitType; if (state) { state[value].active = false; } try { if (document.querySelectorAll('.filter-button').length === 1) { filterWrapperDiv?.classList.remove('has-filter'); if (!!this.slugObjDiv && this.state.query === '') { this.clearSlug(); } } } catch (err) { console.error('failure removing has-filter:\n', err); } event.target.remove(); this.filtersUpdated = true; if (window.innerWidth >= siteSearch.mobileViewBreakpoint) { this.handleSubmit(); } }; handleVisitFiltersClick.bind(this); if (hasClinicVisits) { let filterButton = document.createElement('span'); filterButton.classList.add('filter-button'); filterButton.innerHTML = `✖ Accepting New Patients`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', 'clinicVisits'); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem?.prepend(filterButton); filterWrapperDiv?.classList.add('has-filter'); } if (hasVideoVisits) { let filterButton = document.createElement('span'); filterButton.classList.add('filter-button'); filterButton.innerHTML = `✖ Video Visits`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', 'videoVisits'); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem?.prepend(filterButton); filterWrapperDiv?.classList.add('has-filter'); } /* Create an event listener to handle clicks, removing filters */ document.querySelectorAll('.visit-filter').forEach(handleVisitFiltersClick); } /* Add badges for special search keywords */ if (specialKeywordsArr.length > 0) { const handleSpecialFiltersClick = (event) => { const specialSearch = event.target.innerText.replace('✖ ', '').trim(); let fromSession = sessionStorage.getItem('specialSearch'); /* it should already exist, but to be safe... */ fromSession !== null ? fromSession = JSON.parse(fromSession) : fromSession = []; const specialIndex = fromSession.findIndex((item) => { return item.toLowerCase() === specialSearch.toLowerCase(); }); fromSession.splice(specialIndex, 1); if (fromSession.length > 0) { sessionStorage.setItem('specialSearch', JSON.stringify(fromSession)); } else { sessionStorage.removeItem('specialSearch'); } window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, ' ').replaceAll(specialSearch, '').replaceAll(/ +/gi, ' ').trim(); if (this.state.query) { this.inputSearchEl.value = this.state.query; } else { this.inputSearchEl.value = ''; document.getElementById('searchBox').classList.remove('is-populated'); } if (fromSession.length < 1) { this.state.specialSearch = false; try { if (document.querySelectorAll('.filter-button').length === 1) { filterWrapperDiv?.classList.remove('has-filter'); if (!!this.slugObjDiv && this.state.query === '') { this.clearSlug(); } } } catch (err) { console.error('failure removing has-filter:\n', err); } } this.clearSlugIfOnlyDistanceRemains(event.target); if (window.innerWidth >= siteSearch.mobileViewBreakpoint) { this.handleSubmit(); } }; handleSpecialFiltersClick.bind(this); /* "specialists only" */ let soIndex = specialKeywordsArr.findIndex((item) => item.toLowerCase() === 'specialists only'); if (soIndex !== -1) { let filterButton = document.createElement('span'); filterButton.classList.add('filter-button'); filterButton.innerHTML = `✖ ${specialKeywordsArr[soIndex]}`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', 'specialSearch'); filterButton.addEventListener('click', handleSpecialFiltersClick); filtersElem?.prepend(filterButton); filterWrapperDiv?.classList.add('has-filter'); } } } catch (err) { console.error('facets failed to load:\n', err); } if (this.state.filters.page > 0) { let baseUrl = 'doctors.pacificmedicalcenters.org'; if(window.location.origin.toLowerCase().includes('swedish')) { baseUrl = 'schedule.swedish.org'; } else if(window.location.origin.toLowerCase().includes('providence')) { baseUrl = 'www.providence.org'; } const canon = document.querySelector('link[rel="canonical"]'); const newCanon = document.createElement('span'); newCanon.innerHTML = ``; canon.parentNode.replaceChild(newCanon, canon); } /* If we're sending a page that is greater than results paginated, reset to page one and redo the search. */ if (results.providersCount > 0 && results.results.length === 0) { this.state.filters.page = 1; return this.handleSubmit(); } else if (this.state.filters.pages === 0) { this.state.filters.page = 1; } /* Determine if we have results or not and display appropriate text. */ let resultsHtml = ''; if (this.state.description) { resultsHtml += `${this.state.description}
`; this.state.description = false; } /** * If we have the testing parameter added for internal testing and the type object is present on the API * response, then we're going to render that content above the search results. */ if (isTest && results.info?.type?.length) { resultsHtml += `${results.info.type.join(', ')}
${count.toLocaleString()} results
`; } /* We'll use this to detmine if we show the map or not */ let withLocations = 0; /* If LocationsOnly is set, render the locations for the current page */ if (LocationsOnly){ resultsHtml += this.renderLocations(results.locations); } else { this.state.slotsType = this.helpers.getSlotsType(); /* For each results on the current page, create a card and append it to our results container */ results.results.forEach((result, i) => { const { Addresses, AcceptingNewPatients, AllowsDirectSchedule, AllowsOpenSchedule, AppointmentRequestUrl, DaysUntilFollowUpAppt, DaysUntilNextAppt, Degrees, GeocodedCoordinate, ImageUrl, ImageHeight, ImageSourceUrl, ImageWidth, LocationId, LocationIds, LocationName, LocationNames, LocationsIsClinic, Name, NewClinicVisit, NewVideoVisit, Npi, Phones, PracticeGroup, PrimarySpecialties, ProfileUrl, ProviderOrganization, ProviderTitle, Rating, RatingCount, ReviewCount, SjhScheduleProviderNum, SubSpecialties, Tier, VirtualCare, acceptingNewPatients, clinicVisits, distance, id, virtual } = result; const searchFeatures = result['@search.features']; const width = (150 / ImageHeight) * ImageWidth; let badges = result.badges ? Object.values(result.badges) : []; /* Combine subspecialties into one badge */ if (badges.length) { let SubSpecialtiesNameArr = []; badges.forEach((badge, index) => { if (badge.includes('Subspecialty')) { let noLabel = badge.replace('Subspecialty: ', ''); SubSpecialtiesNameArr.push(noLabel); } }); badges = badges.filter((badge) => !badge.includes('Subspecialty')); if (SubSpecialtiesNameArr.length) { badges.push(`Specialt${SubSpecialtiesNameArr.length > 1 ? 'ies' : 'y'}: ${SubSpecialtiesNameArr.join(', ')}`); } } /* sort badges */ badges = [ ...badges.filter(x => /^Primary Specialty:/i.test(x)), ...badges.filter(x => /^(Specialty:|Specialties:)/i.test(x)), ...badges.filter(x => /^Keywords:/i.test(x)), ...badges.filter(x => !/^(Primary Specialty:|Specialty:|Specialties:|Keywords:)/i.test(x)) ]; const supportsOdhp = (acceptingNewPatients == 1 && AllowsOpenSchedule == 1) || AllowsDirectSchedule == 1; const onlineBooking = supportsOdhp || SjhScheduleProviderNum ? true : false; let AcceptingClinicVisits = AcceptingNewPatients == 1 ? `${descr}
${rating} ${offersVirtual} ${AcceptingClinicVisits} ${isTest && Tier ? `${curr}
`, '') + '${key}: ${searchFeatures[key]?.similarityScore.toFixed(3)}
`, '') + '