HTML Report for https://www.providence.org/doctors
This tool is an ongoing experiment in better HTML checking, and its behavior remains subject to changeShowing results for https://www.providence.org/doctors
You can view the notes...
view-source:https://www.providence.org/doctors
| # | results |
|---|---|
| 1 |
Error
On line 1, column 79419 <!doctype html><html amp><head><style amp-custom="">body { overflow-x: hidden; position: relative; font-family: sans-serif; } a img { border: none; } #ampcontainer { max-height: 100vh; max-width: 1200px; margin: auto; } h1 { margin: 0 0; color: #222; } /*minor css reset*/ html, body, iframe,fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; } body, div, form, input { box-sizing: border-box; } #geoLocatorInput { font-size: 16px; } #geoLocatorSuggestions input[type="submit"] { color: #000; } #wm_contentarea { /* max-width: 700px; */ margin: 0 auto; } body { padding: 0; margin: 0; } header { z-index: 2; } header, main { position: relative; } #map { height: 100vh; position: fixed; right: 0; top: 0; z-index: 1; } #wm_map { width: 100%; height: 100%; } .filter-button:hover { cursor: pointer; } html { scroll-behavior: smooth; } body { font-family: Helvetica,Arial,sans-serif; min-height: 100vh; width: 100vw; /** childReplacement generalVar **/ --color-primary: #00338e; --color-secondary: #258199; --color-blue-1: #004b8e; --color-blue-2: #0c1153; --color-gray: #ececec; --color-gray-2: #e4e4e4; --color-gray-3: #5b5b5b; /** end childReplacement generalVar **/ display: flex; flex-direction: column; background: var(--color-gray); } .color-headings { color: #0537A4; } input:focus, textarea:focus, select:focus { outline: none; } main { min-height: calc(100vh - 300px); } .hidden { display: none; } .mt-1 { margin-top: 1rem; } .loader, .loader:before, .loader:after { background: var(--color-primary); -webkit-animation: load1 1s infinite ease-in-out; animation: load1 1s infinite ease-in-out; width: 1em; height: 4em; } .loader { color: var(--color-primary); text-indent: -9999em; margin: 88px auto; position: relative; font-size: 11px; -webkit-transform: translateZ(0); -ms-transform: translateZ(0); transform: translateZ(0); -webkit-animation-delay: -0.16s; animation-delay: -0.16s; } .loader:before, .loader:after { position: absolute; top: 0; content: ""; } .loader:before { left: -1.5em; -webkit-animation-delay: -0.32s; animation-delay: -0.32s; } .loader:after { left: 1.5em; } @-webkit-keyframes load1 { 0%, 80%, 100% { box-shadow: 0 0; height: 4em; } 40% { box-shadow: 0 -2em; height: 5em; } } @keyframes load1 { 0%, 80%, 100% { box-shadow: 0 0; height: 4em; } 40% { box-shadow: 0 -2em; height: 5em; } } .bg-white { background-color: #fff; } #visitTypeFilters { display: flex; flex-wrap: wrap; } #visitTypeButtons { margin-right: 4px; margin-top: 0; } #Availability { margin-top: 0; } #searchBox>div:not(:first-child) { border-left: 1px solid var(--color-gray-2); } #searchBox button, .btn-primary { background: var(--color-primary); border: none; border-radius: 4px; color: white; padding: 5px; font-family: 'Barlow'; } #searchBox button { padding: 10px 15px; margin: 5px 0 5px 10px; } #searchBox .input-group { width: 100%; } #advancedFilters { display: flex; flex-wrap: wrap; } #visitTypeFilters { display: flex; flex-wrap: wrap; } #switch, #switchDistanceSort { height: 0; width: 0; visibility: hidden; display: none; } label[for="switch"], label[for="switchDistanceSort"] { display: flex; align-items: center; width: 100%; flex-wrap: wrap; } #switchLabel, #switchDistanceSortLabel { cursor: pointer; text-indent: -9999px; width: 44px; height: 24px; background: #EEEEEE; display: block; border-radius: 100px; position: relative; } #switchLabel:after, #switchDistanceSortLabel:after { content: ""; position: absolute; top: 2px; left: 3px; width: 20px; height: 20px; background: #fff; border-radius: 90px; transition: 0.3s; } input:checked+label>#switchLabel, input:checked+label>#switchDistanceSortLabel { background: var(--color-primary); } input:checked+label>#switchLabel:after, input:checked+label>#switchDistanceSortLabel:after { left: calc(100% - 2px); transform: translateX(-100%); } #switchLabel:active::after, #switchDistanceSortLabel:active::after { width: 10px; } .input-group label, .input-group input { display: block; } .input-group label { font-size: 12px; font-weight: 600; padding: 17px 0 0 5px; } #qClear { display: none; } .is-dirty #qClear { display: inline-block; } #query::placeholder { color: #9A9A9A; } .input-group input { padding: 5px; border: none; width: 100%; background: none; color: #515151; font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 24px; letter-spacing: 0px; text-align: left; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { -webkit-appearance: none; } input[type="number"] { -moz-appearance: textfield; } button { cursor: pointer; } .btn-group { display: flex; margin-top: 1rem; } #pagination .btn-group { margin-bottom: 48px; margin-top: 0; } .btn { background: none; border: 1px solid #DDDDDD; padding: 10px 40px 10px 16px; border-radius: 8px; font-family: 'Barlow'; font-size: 16px; color: #515151; } .btn-link { border: none; } .btn:hover, .btn.active { background: #edf2f7; } .btn-group .btn { border-radius: 0; border-left: none; } .btn-group .btn:first-child { border-left: 1px solid var(--color-gray-2); border-radius: 4px 0 0 4px; } .btn-group .btn:last-child { border-radius: 0 4px 4px 0; } .dropdown.active .dropdown-menu { display: block; } .filtered-list { max-height: 130px; overflow-y: scroll; scrollbar-color: #515151; } .filtered-list::-webkit-scrollbar { background-color: transparent; width: 5px; } .filtered-list::-webkit-scrollbar-thumb { background: #515151; width: 5px; border-radius: 4px; background-clip: padding-box; } .dropdown-menu { position: absolute; z-index: 1000; display: none; min-width: 10px; font-size: 1rem; color: #212529; text-align: left; background-color: #fff; background-clip: padding-box; box-shadow: 0px 2px 16px 0px #00000005; box-shadow: 0px 3px 11px 0px #0000000A; box-shadow: 0px 2px 8px 0px #00000014; border: none; border-radius: 4px; max-height: 260px; overflow-y: auto; overflow-x: hidden; padding: 8px 0; margin-top: 8px; scrollbar-color: #515151; } .dropdown-menu ul { list-style: none; padding: 0.5rem 0; margin: 0; } .search-wrap, .dropdown-menu select { height: 30px; width: 100%; width: -webkit-fill-available; padding: 5px; margin: 0 8px; border: 1px solid #DDDDDD; border-radius: 5px; outline: none; color: #000; font-family: 'Barlow'; } .search-wrap { line-height: 0; } .search-wrap input { width: calc(100% - 35px); border: none; font-family: Barlow; font-size: 12px; font-weight: 400; line-height: 12px; letter-spacing: 0.10499999672174454px; color: #515151; } .dropdown { margin-right: 24px; } .dropdown:last-of-type { margin-right: 0; } .dropdown-item { display: block; padding: 0.25rem 1rem; clear: both; font-weight: 400; color: #212529; text-align: inherit; text-decoration: none; background: transparent; border: none; font-size: 14px; line-height: 20px; cursor: pointer; font-family: 'Barlow'; } #insuranceList .facet-value.active, .facet-value:hover, .time-value:hover { background: #EEEEEE; } .dropdown.facet .dropdown-toggle:hover, #LocationLabel:hover, .input-group.mb-3.input-hover:hover, .input-hover:hover { outline: 4px solid #E9F1FF; border: 1px solid #00338E; background: initial; } .active .input-hover.input-group, .input-hover.active, .input-group.mb-3.input-hover:focus, .input-hover:focus { border: 1px solid #00338E; } .dropdown-toggle.filtered { border-color: var(--color-primary); } .card { background-color: #fff; border-radius: 10px; padding: 1rem; box-shadow: 0px 0px 5px rgb(0 0 0 / 10%); margin: 1rem auto; transition: height 200ms ease-out; border: 2px solid #fff; grid-template-areas: 'image info' 'image locations' 'image data'; column-gap: 10px; display: grid; grid-template-columns: 120px calc(100% - 130px); } .card.selected { border: 2px solid var(--color-primary); } timeslots-widget { width: 100%; } .provider-image { width: 120px; height: 100%; min-height: 150px; position: relative; grid-area: image; } .provider-image img { object-fit: cover; object-position: top; } .card.provider-image .virtualVisits { position: absolute; } .provider-image a { width: 100%; object-fit: contain; display: block; top: 0; min-height: 150px; position: relative; } .provider-info { grid-area: info; } .locations-and-timeslots { grid-area: locations; /* width: calc(100% - 60px); */ } .remaining-data { grid-area: data; } .location-n { color: #222222; margin: 0px; font-family: Barlow; font-size: 18px; font-weight: 500; line-height: 27px; letter-spacing: 0px; text-align: left; max-width: calc(100% - 32px); } .hov-ul:hover { text-decoration: underline; } small.practice-group { margin-top: 5px; display: block; font-style: italic; color: #545454; } #pagination .btn-group .btn:not(.active) { background: #fff; } #pagination .btn { text-decoration: none; } .py-1 { padding-top: 0.5rem; padding-bottom: 0.5rem; } .py-3 { padding-top: 1.5rem; padding-bottom: 16px; } .flex { display: flex; } .flex-columns { flex-direction: column; } .flex-wrap { flex-wrap: wrap; } /** childReplacement containerMaxWidth **/ .container { max-width: 1285px; margin: 0 auto; } /** end childReplacement containerMaxWidth **/ .row { box-sizing: border-box; display: -ms-flexbox; display: -webkit-box; display: flex; -ms-flex: 0 1 auto; -webkit-box-flex: 0; flex: 0 1 auto; -ms-flex-direction: row; -webkit-box-orient: horizontal; -webkit-box-direction: normal; flex-direction: row; -ms-flex-wrap: wrap; flex-wrap: wrap; margin-right: -1rem; margin-left: -1rem; } .row.reverse { -ms-flex-direction: row-reverse; -webkit-box-orient: horizontal; -webkit-box-direction: reverse; flex-direction: row-reverse; } .col.reverse { -ms-flex-direction: column-reverse; -webkit-box-orient: vertical; -webkit-box-direction: reverse; flex-direction: column-reverse; } .col-xs, .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { box-sizing: border-box; -ms-flex: 0 0 auto; -webkit-box-flex: 0; flex: 0 0 auto; padding-right: 1rem; padding-left: 1rem; } .col-xs { -webkit-flex-grow: 1; -ms-flex-positive: 1; -webkit-box-flex: 1; flex-grow: 1; -ms-flex-preferred-size: 0; flex-basis: 0; max-width: 100%; } .col-xs-1 { -ms-flex-preferred-size: 8.333%; flex-basis: 8.333%; max-width: 8.333%; } .col-xs-2 { -ms-flex-preferred-size: 16.667%; flex-basis: 16.667%; max-width: 16.667%; } .col-xs-3 { -ms-flex-preferred-size: 25%; flex-basis: 25%; max-width: 25%; } .col-xs-4 { -ms-flex-preferred-size: 33.333%; flex-basis: 33.333%; max-width: 33.333%; } .col-xs-5 { -ms-flex-preferred-size: 41.667%; flex-basis: 41.667%; max-width: 41.667%; } .col-xs-6 { -ms-flex-preferred-size: 50%; flex-basis: 50%; max-width: 50%; } .col-xs-7 { -ms-flex-preferred-size: 58.333%; flex-basis: 58.333%; max-width: 58.333%; } .col-xs-8 { -ms-flex-preferred-size: 66.667%; flex-basis: 66.667%; max-width: 66.667%; } .col-xs-9 { -ms-flex-preferred-size: 75%; flex-basis: 75%; max-width: 75%; } .col-xs-10 { -ms-flex-preferred-size: 83.333%; flex-basis: 83.333%; max-width: 83.333%; } .col-xs-11 { -ms-flex-preferred-size: 91.667%; flex-basis: 91.667%; max-width: 91.667%; } .col-xs-12 { -ms-flex-preferred-size: 100%; flex-basis: 100%; max-width: 100%; } .col-xs-offset-1 { margin-left: 8.333%; } .col-xs-offset-2 { margin-left: 16.667%; } .col-xs-offset-3 { margin-left: 25%; } .col-xs-offset-4 { margin-left: 33.333%; } .col-xs-offset-5 { margin-left: 41.667%; } .col-xs-offset-6 { margin-left: 50%; } .col-xs-offset-7 { margin-left: 58.333%; } .col-xs-offset-8 { margin-left: 66.667%; } .col-xs-offset-9 { margin-left: 75%; } .col-xs-offset-10 { margin-left: 83.333%; } .col-xs-offset-11 { margin-left: 91.667%; } .start-xs { -ms-flex-pack: start; -webkit-box-pack: start; justify-content: flex-start; text-align: start; } .center-xs { -ms-flex-pack: center; -webkit-box-pack: center; justify-content: center; text-align: center; } .end-xs { -ms-flex-pack: end; -webkit-box-pack: end; justify-content: flex-end; text-align: end; } .top-xs { -ms-flex-align: start; -webkit-box-align: start; align-items: flex-start; } .middle-xs { -ms-flex-align: center; -webkit-box-align: center; align-items: center; } .bottom-xs { -ms-flex-align: end; -webkit-box-align: end; align-items: flex-end; } .around-xs { -ms-flex-pack: distribute; justify-content: space-around; } .between-xs { -ms-flex-pack: justify; -webkit-box-pack: justify; justify-content: space-between; } .first-xs { -ms-flex-order: -1; -webkit-box-ordinal-group: 0; order: -1; } .last-xs { -ms-flex-order: 1; -webkit-box-ordinal-group: 2; order: 1; } @media only screen and (min-width: 768px) { .container { width: 736px; } .col-sm, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { box-sizing: border-box; -ms-flex: 0 0 auto; -webkit-box-flex: 0; flex: 0 0 auto; padding-right: 1rem; padding-left: 1rem; } .col-sm { -webkit-flex-grow: 1; -ms-flex-positive: 1; -webkit-box-flex: 1; flex-grow: 1; -ms-flex-preferred-size: 0; flex-basis: 0; max-width: 100%; } .col-sm-1 { -ms-flex-preferred-size: 8.333%; flex-basis: 8.333%; max-width: 8.333%; } .col-sm-2 { -ms-flex-preferred-size: 16.667%; flex-basis: 16.667%; max-width: 16.667%; } .col-sm-3 { -ms-flex-preferred-size: 25%; flex-basis: 25%; max-width: 25%; } .col-sm-4 { -ms-flex-preferred-size: 33.333%; flex-basis: 33.333%; max-width: 33.333%; } .col-sm-5 { -ms-flex-preferred-size: 41.667%; flex-basis: 41.667%; max-width: 41.667%; } .col-sm-6 { -ms-flex-preferred-size: 50%; flex-basis: 50%; max-width: 50%; } .col-sm-7 { -ms-flex-preferred-size: 58.333%; flex-basis: 58.333%; max-width: 58.333%; } .col-sm-8 { -ms-flex-preferred-size: 66.667%; flex-basis: 66.667%; max-width: 66.667%; } .col-sm-9 { -ms-flex-preferred-size: 75%; flex-basis: 75%; max-width: 75%; } .col-sm-10 { -ms-flex-preferred-size: 83.333%; flex-basis: 83.333%; max-width: 83.333%; } .col-sm-11 { -ms-flex-preferred-size: 91.667%; flex-basis: 91.667%; max-width: 91.667%; } .col-sm-12 { -ms-flex-preferred-size: 100%; flex-basis: 100%; max-width: 100%; } .col-sm-offset-1 { margin-left: 8.333%; } .col-sm-offset-2 { margin-left: 16.667%; } .col-sm-offset-3 { margin-left: 25%; } .col-sm-offset-4 { margin-left: 33.333%; } .col-sm-offset-5 { margin-left: 41.667%; } .col-sm-offset-6 { margin-left: 50%; } .col-sm-offset-7 { margin-left: 58.333%; } .col-sm-offset-8 { margin-left: 66.667%; } .col-sm-offset-9 { margin-left: 75%; } .col-sm-offset-10 { margin-left: 83.333%; } .col-sm-offset-11 { margin-left: 91.667%; } .start-sm { -ms-flex-pack: start; -webkit-box-pack: start; justify-content: flex-start; text-align: start; } .center-sm { -ms-flex-pack: center; -webkit-box-pack: center; justify-content: center; text-align: center; } .end-sm { -ms-flex-pack: end; -webkit-box-pack: end; justify-content: flex-end; text-align: end; } .top-sm { -ms-flex-align: start; -webkit-box-align: start; align-items: flex-start; } .middle-sm { -ms-flex-align: center; -webkit-box-align: center; align-items: center; } .bottom-sm { -ms-flex-align: end; -webkit-box-align: end; align-items: flex-end; } .around-sm { -ms-flex-pack: distribute; justify-content: space-around; } .between-sm { -ms-flex-pack: justify; -webkit-box-pack: justify; justify-content: space-between; } .first-sm { -ms-flex-order: -1; -webkit-box-ordinal-group: 0; order: -1; } .last-sm { -ms-flex-order: 1; -webkit-box-ordinal-group: 2; order: 1; } } @media only screen and (min-width: 1000px) { #map { width: 48vw; } header { padding-top: 1.5rem; padding-bottom: 1.5rem; } div.topbtns.flex { display: none; } .container { width: calc(100vw - 160px); max-width: 1272px; } .col-md, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { box-sizing: border-box; -ms-flex: 0 0 auto; -webkit-box-flex: 0; flex: 0 0 auto; padding-right: 1rem; padding-left: 1rem; } .col-md { -webkit-flex-grow: 1; -ms-flex-positive: 1; -webkit-box-flex: 1; flex-grow: 1; -ms-flex-preferred-size: 0; flex-basis: 0; max-width: 100%; } .col-md-1 { -ms-flex-preferred-size: 8.333%; flex-basis: 8.333%; max-width: 8.333%; } .col-md-2 { -ms-flex-preferred-size: 16.667%; flex-basis: 16.667%; max-width: 16.667%; } .col-md-3 { -ms-flex-preferred-size: 25%; flex-basis: 25%; max-width: 25%; } .col-md-4 { -ms-flex-preferred-size: 33.333%; flex-basis: 33.333%; max-width: 33.333%; } .col-md-5 { -ms-flex-preferred-size: 41.667%; flex-basis: 41.667%; max-width: 41.667%; } .col-md-6 { -ms-flex-preferred-size: 50%; flex-basis: 50%; max-width: 50%; } .col-md-7 { -ms-flex-preferred-size: 58.333%; flex-basis: 58.333%; max-width: 58.333%; } .col-md-8 { -ms-flex-preferred-size: 66.667%; flex-basis: 66.667%; max-width: 66.667%; } .col-md-9 { -ms-flex-preferred-size: 75%; flex-basis: 75%; max-width: 75%; } .col-md-10 { -ms-flex-preferred-size: 83.333%; flex-basis: 83.333%; max-width: 83.333%; } .col-md-11 { -ms-flex-preferred-size: 91.667%; flex-basis: 91.667%; max-width: 91.667%; } .col-md-12 { -ms-flex-preferred-size: 100%; flex-basis: 100%; max-width: 100%; } .col-md-offset-1 { margin-left: 8.333%; } .col-md-offset-2 { margin-left: 16.667%; } .col-md-offset-3 { margin-left: 25%; } .col-md-offset-4 { margin-left: 33.333%; } .col-md-offset-5 { margin-left: 41.667%; } .col-md-offset-6 { margin-left: 50%; } .col-md-offset-7 { margin-left: 58.333%; } .col-md-offset-8 { margin-left: 66.667%; } .col-md-offset-9 { margin-left: 75%; } .col-md-offset-10 { margin-left: 83.333%; } .col-md-offset-11 { margin-left: 91.667%; } .start-md { -ms-flex-pack: start; -webkit-box-pack: start; justify-content: flex-start; text-align: start; } .center-md { -ms-flex-pack: center; -webkit-box-pack: center; justify-content: center; text-align: center; } .end-md { -ms-flex-pack: end; -webkit-box-pack: end; justify-content: flex-end; text-align: end; } .top-md { -ms-flex-align: start; -webkit-box-align: start; align-items: flex-start; } .middle-md { -ms-flex-align: center; -webkit-box-align: center; align-items: center; } .bottom-md { -ms-flex-align: end; -webkit-box-align: end; align-items: flex-end; } .around-md { -ms-flex-pack: distribute; justify-content: space-around; } .between-md { -ms-flex-pack: justify; -webkit-box-pack: justify; justify-content: space-between; } .first-md { -ms-flex-order: -1; -webkit-box-ordinal-group: 0; order: -1; } .last-md { -ms-flex-order: 1; -webkit-box-ordinal-group: 2; order: 1; } .card .provider-image .virtualVisits { display: none; } } @media only screen and (min-width: 1200px) { .col-lg, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { box-sizing: border-box; -ms-flex: 0 0 auto; -webkit-box-flex: 0; flex: 0 0 auto; padding-right: 1rem; padding-left: 1rem; } .col-lg { -webkit-flex-grow: 1; -ms-flex-positive: 1; -webkit-box-flex: 1; flex-grow: 1; -ms-flex-preferred-size: 0; flex-basis: 0; max-width: 100%; } .col-lg-1 { -ms-flex-preferred-size: 8.333%; flex-basis: 8.333%; max-width: 8.333%; } .col-lg-2 { -ms-flex-preferred-size: 16.667%; flex-basis: 16.667%; max-width: 16.667%; } .col-lg-3 { -ms-flex-preferred-size: 25%; flex-basis: 25%; max-width: 25%; } .col-lg-4 { -ms-flex-preferred-size: 33.333%; flex-basis: 33.333%; max-width: 33.333%; } .col-lg-5 { -ms-flex-preferred-size: 41.667%; flex-basis: 41.667%; max-width: 41.667%; } .col-lg-6 { -ms-flex-preferred-size: 50%; flex-basis: 50%; max-width: 50%; } .col-lg-7 { -ms-flex-preferred-size: 58.333%; flex-basis: 58.333%; max-width: 58.333%; } .col-lg-8 { -ms-flex-preferred-size: 66.667%; flex-basis: 66.667%; max-width: 66.667%; } .col-lg-9 { -ms-flex-preferred-size: 75%; flex-basis: 75%; max-width: 75%; } .col-lg-10 { -ms-flex-preferred-size: 83.333%; flex-basis: 83.333%; max-width: 83.333%; } .col-lg-11 { -ms-flex-preferred-size: 91.667%; flex-basis: 91.667%; max-width: 91.667%; } .col-lg-12 { -ms-flex-preferred-size: 100%; flex-basis: 100%; max-width: 100%; } .col-lg-offset-1 { margin-left: 8.333%; } .col-lg-offset-2 { margin-left: 16.667%; } .col-lg-offset-3 { margin-left: 25%; } .col-lg-offset-4 { margin-left: 33.333%; } .col-lg-offset-5 { margin-left: 41.667%; } .col-lg-offset-6 { margin-left: 50%; } .col-lg-offset-7 { margin-left: 58.333%; } .col-lg-offset-8 { margin-left: 66.667%; } .col-lg-offset-9 { margin-left: 75%; } .col-lg-offset-10 { margin-left: 83.333%; } .col-lg-offset-11 { margin-left: 91.667%; } .start-lg { -ms-flex-pack: start; -webkit-box-pack: start; justify-content: flex-start; text-align: start; } .center-lg { -ms-flex-pack: center; -webkit-box-pack: center; justify-content: center; text-align: center; } .end-lg { -ms-flex-pack: end; -webkit-box-pack: end; justify-content: flex-end; text-align: end; } .top-lg { -ms-flex-align: start; -webkit-box-align: start; align-items: flex-start; } .middle-lg { -ms-flex-align: center; -webkit-box-align: center; align-items: center; } .bottom-lg { -ms-flex-align: end; -webkit-box-align: end; align-items: flex-end; } .around-lg { -ms-flex-pack: distribute; justify-content: space-around; } .between-lg { -ms-flex-pack: justify; -webkit-box-pack: justify; justify-content: space-between; } .first-lg { -ms-flex-order: -1; -webkit-box-ordinal-group: 0; order: -1; } .last-lg { -ms-flex-order: 1; -webkit-box-ordinal-group: 2; order: 1; } } @media only screen and (max-width: 999.98px) { header { position: fixed; width: 100vw; padding: 0.75rem 0; } header > .container { padding: 0 24px; } #query { margin-bottom: 0; text-overflow: ellipsis; } #searchBox .input-group label { padding: 18px 0 0 0; } div#autocomplete > h5 { font-family: Barlow; font-size: 14px; font-weight: 500; line-height: 20px; letter-spacing: 0em; text-align: left; margin: 8px 16px; color: #515151; } #autocomplete br { display: none; } #autocomplete h5 ~ h5 { border-top: 1px solid #DDDDDD; padding-top: 16px; } div[data-autocomplete] { font-family: Barlow; font-size: 14px; font-weight: 400; line-height: 20px; letter-spacing: 0em; color: #515151; } #visitTypeFilters button.btn.visitType { padding: 24px 0 0 0; } #visitTypeFilters button.btn.visitType.videoVisits { padding: 24px 0 24px 0; } /** childReplacement resultsMarginWithFullHeader **/ #results-wrapper { margin-top: 148px; } /** end childReplacement resultsMarginWithFullHeader **/ .card { margin: 1rem; } .gm-style-iw-a { display: none; } #fullsearch:checked ~ header .container.search-filters { top: 75px; } #fullsearch:checked ~ main { z-index: -1; } #showmap:checked ~ main { height: calc(100vh - 100px); margin-top: 100px; } #showmap:checked ~ main .py-3 { padding-top: 1rem; padding-bottom: 0; } #showmap:checked ~ main .card { margin: 0.5rem; scroll-snap-align: start; scroll-margin-left: 16px; flex-wrap: nowrap; grid-template-areas: 'image info info' 'locations locations locations' 'data data data'; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: auto 1fr; } #showmap:checked ~ main .card .rating_comment_count { font-size: 12px; } #showmap:checked ~ main #map { width: 100vw; z-index: 1; } #showmap:checked ~ main timeslots-widget { display: none; } #showmap:checked ~ main #results-wrapper { height: auto; position: fixed; bottom: 0; overflow: hidden; z-index: 1; width: 100vw; } #showmap:checked ~ main #results-wrapper > .row { margin: 0; } #showmap:checked ~ main #results-wrapper > .row #pagination{ margin-top: 16px; } #showmap:checked ~ main #results { display: flex; overflow-x: auto; padding-bottom: 0; scroll-snap-type: x mandatory; } #showmap:checked ~ main #results > p { display: none; } #showmap:checked ~ main .provider-info { width: auto; } #showmap:checked ~ main .provider-info .virtualVisits { display: none; } #showmap:not(:checked) ~ main .topbtns.flex { margin: 0 24px; } h5.results-text { margin: 0 24px 16px; } #results > p { margin: 0 24px; } .search-filters { position: absolute; z-index: 100000; height: calc(100vh - 40px); background: #fff; top: -100vh; transition: all 300ms ease; overflow-y: auto; width: 100%; } .topbtns.flex label { border-radius: 25px; line-height: 40px; padding: 0 10px; } .topbtns.flex label:first-of-type { background: #fff; flex-grow: 1; margin-right: 10px; border: 2px solid var(--color-blue-1); color: var(--color-blue-1); text-align: center; font-weight: bold; } label.btn.togglebtn { flex-grow: 0; border: 2px solid rgb(170,170,170); background: rgb(170, 170, 170); color: #fff; } .togglebtn span { display: inline-block; width: 40px; background: #f8f8f8; height: 20px; vertical-align: middle; margin-right: 5px; border-radius: 10px; position: relative; transition: all .2s linear; } .togglebtn span::before { content: ""; display: block; background: #aaa; height: 14px; width: 14px; position: absolute; left: 3px; top: 3px; border-radius: 50%; transition: all .2s linear; } #showmap:checked ~ main .togglebtn { background: var(--color-blue-1); border-color: var(--color-blue-1); } #showmap:checked ~ main .togglebtn span::before { background: var(--color-blue-1); left: 22px; } #showmap:checked ~ main .card.card-body { overflow-y: scroll; max-height: 250px; width: 291px; min-width: min(291px, calc(100vw - 64px)); margin: 0 5px; } #showmap:checked ~ main .card.card-body:first-of-type { margin-left: 0; } #showmap:checked ~ main .card.card-body:last-of-type { margin-right: 0; } #showmap:checked ~ main .card.card-body .reviews>.stats { margin-left: 0; } #showmap:checked ~ main .card.card-body directory-timeslots::part(time) { margin: 0; padding: 0; } #showmap:checked ~ main .card.card-body directory-timeslots::part(day) { min-width: 80px; } #showmap:checked ~ main .card .provider-image { width: 70px; height: 70px; min-height: 50px; overflow-y: clip; } #showmap:checked ~ main .card .new-patients { margin-top: 5px; } #showmap:checked ~ main .card .provider-image amp-img { border-radius: 50px; max-width: 65px; max-height: 65px; } #showmap:checked ~ main .card .provider-image img { object-fit: cover; } #showmap:checked ~ main .star-ratings-css { display: none; } .card .provider-image .virtualVisits.infoWindow { display: none; } #showmap:checked ~ main .card .provider-image .virtualVisits.infoWindow { display: block; } .dropdown { width: 100%; margin-left: 0; margin-right: 0; } .dropdown-toggle { width: 100%; min-height: 56px; } .dropdown-menu { position: relative; max-width: calc(100vw - 2.25rem); width: 100%; } .dropdown-item { white-space: revert; padding: .5rem 1rem; } .facet .facet-value::before { float: left; margin-top: -2px; } /* #pagination .btn-group .btn { padding: 10px clamp(10px, 4vw, 20px); } */ } @media only screen and (max-width: 449.98px) { .card { grid-template-areas: 'image info' 'locations locations' 'data data'; } } .omni-checkbox.active { font-weight: 600; } .facet-value.active .filter-checked, .time-value.active .filter-checked { display: inline-block; } .facet-value.active .filter-unchecked, .time-value.active .filter-unchecked, .filter-checked { display: none; } .filter-unchecked, .filter-checked { margin-right: 8px; } .omni-checkbox::before { content: '\2610'; font-size: 16px; margin-right: 2px; } .omni-checkbox.active::before { content: '\2611'; } .facet .facet-value::before { content: '\2610'; margin-right: 2px; } .facet .facet-value.active::before { content: '\2611'; } .omni-checkbox::before { content: '\2610'; font-size: 16px; margin-right: 2px; } .omni-checkbox.active::before { content: '\2611'; } #autocomplete { background: #fff; border: 1px solid var(--color-gray-2); border-top: none; border-radius: 0 0 5px 5px; position: absolute; top: 60px; left: -1px; width: 100%; padding-bottom: 5px; z-index: 1; } div[data-autocomplete] { display: block; padding: 5px 1rem; cursor: pointer; font-size: smaller; font-family: 'Barlow'; } div[data-autocomplete].selected, div[data-autocomplete]:hover { background: #4f90cd29; } #autocomplete > h5 { margin: 10px 1rem 0.5rem; font-family: 'Barlow'; } #autocomplete > small { padding: 5px 1rem 0.5rem; display: block; color: gray; } .poi-info-window div, .poi-info-window a { font-family: 'Barlow'; color: #222222; font-size: 14px; } .poi-info-window .provider { display: flex; align-items: center; cursor: pointer; } .poi-info-window .provider:not(:last-child) { margin-bottom: 5px; } .poi-info-window .provider .provider-thumbnail { width: 45px; height: 45px; border-radius: 50px; background-position: center; background-size: cover; z-index: -1; } .poi-info-window .provider-info { flex-grow: 1; font-family: 'Lora'; } .poi-info-window .provider-info h4 { font-size: 16px; color: #0537A4; } .poi-info-window .provider-info h4:hover { text-decoration: underline; } .poi-info-window .provider-info .provider-specialty{ color: #424242; line-height: 19.6px; } .poi-info-window .virtualVisits.infoWindow { width: 18px; height: 18px; } .poi-info-window .virtualVisits.infoWindow svg { width: 10px; padding-left: 4px; } .poi-info-window .provider-info h4:nth-child(2) { color: var(--color-blue-2); font-weight: normal; } .provider-thumbnail-wrapper { position: relative; margin: 7px 5px 3px 11px; } .provider-thumbnail-wrapper.hasVirtual { margin: 7px 5px 0 11px; } .virtualVisits.infoWindow { position: absolute; background: white; border-radius: 60px; width: 20px; height: 20px; box-shadow: 0 0px 5px rgb(0 0 0 / 30%); top: -3px; left: -8px; } .virtualVisits.infoWindow span { font-size: 16px; vertical-align: middle; line-height: 19px; padding-left: 2px; } svg { fill: currentColor; vertical-align: middle; max-width: 100%; max-height: 100%; pointer-events: none; } /* Hide X from search input */ #search-input::-webkit-search-cancel-button { display: none; } #wm_footer { position: relative; z-index: 1; background-color: var(--color-primary); } /** childReplacement footerCSS **/ #wm_footer.heritage ~ #footNote, #footNote.heritage { display: none; } #wm_footer.heritage ~ #footNote.heritage { display: block; } #footNote.heritage a { text-decoration: none; border-left: 1px solid #fff; padding-left: 1em; margin-left: 5px; } .quick-links { column-count: 2; } #wm_footer.heritage a { text-decoration: none; } #wm_footer.heritage .logo img { max-width: 250px; max-height: 50px; object-fit: contain; } #wm_footer { font-family: 'Barlow', sans-serif; display: -ms-flexbox; display: flex; flex-direction: column; background: #0c2e6e; line-height: 1.6; } .wm_footer { font-size: 0.875rem; clear: both; color: #fff; z-index: 10; } #wm_footer .onesite, #wm_footer .heritage { display:none; } #wm_footer::before { background: #0c2e6e; } #footNote::before { background: ##primary-theme-color-alt##; } .wm_footer h3 { margin: 0 0 16px; text-transform: uppercase; font-size: 0.875rem; } .wm_footer ul { list-style: none; margin: 0; padding: 0; } .wm_footer a, #footNote a { color: inherit; display: inline-block; text-decoration: none; } .wm_footer a.underline, #footNote a.underline { text-decoration: underline; } .wm_footer h4~a { margin-bottom: 1em; display: block; } .wm_footer a.btn{ text-decoration: none; display: block; } .wm_footer ul li a { padding: 8px 0; } .wm_footer ul li:last-of-type { margin: 0; } #footNote { font-size: 0.875rem; font-family: 'Barlow',sans-serif; background: #041e50; width: 100%; } #footNote.wm_footer { flex-direction: column; } .screenreader { position: absolute; height: 1px; width: 1px; overflow: hidden; clip: rect(1px,1px,1px,1px); } #footNote .social a { display: inline-block; margin-right: 1em; } #footNote .social amp-img[src*="twitter"] { margin-top: 1px; } #footNote .social { margin-bottom: 2rem; } /* Should not be necessary... but being safe */ [id^="foot-note-"], [id^="wm_footer-"] { display: none; } #wm_footer .contact-btn { padding: 10px; height: initial; border-radius: 35px; width: fit-content; line-height: 24px; padding: 14px 48px; margin-top: 2em; border-width: 1px; font-size: 1rem; font-weight: bold; } #wm_footer a.contact-btn:hover { text-decoration: none; } .footer-info { display: -ms-flexbox; display: flex; margin: 1em auto; line-height: 1.5; flex-wrap: wrap; } #footNote .footer-info a { margin-right: 20px; margin-bottom: 1em; } #footNote .footer-info a+span { margin-right: 20px; } #wm_footer a:hover, #footNote a:hover { text-decoration: underline; } .mission-and-values p { margin: auto; padding: 0 0 8px; } .mission-and-values a { padding: 16px 0 0; } @media screen and (max-width: 999px) { #wm_footer .contact-btn { width: 100%; } .wm_footer ul { column-gap: 10px; } #footNote .legal { line-height: 2.5; } #wm_footer.heritage .logo { margin-bottom: 20px; display: block; } #wm_footer.heritage .logo img { max-width: 280px; margin: auto; } #wm_footer.heritage .quick-links, #wm_footer.heritage ul {column-count: 1;} #wm_footer.heritage ~ div#footNote.heritage { display: none; } .mission, .values { width: 50%; } .mission-and-values { display: flex; margin-top: 2em; gap: 1em; } .more-links { display: flex; gap: 1em; } .more-links .d-col { width: 50%; padding-right: 1em; } } @media screen and (max-width: 1400px) { #wm_footer, #footNote { margin: 0 auto; } } @media screen and (min-width: 1264px) { .footer-section-wrapper { max-width: 1264px; } } @media screen and (min-width: 1264px) { .footer-section-wrapper { max-width: 1264px; } } @media screen and (min-width: 1000px) { #wm_footer { flex-direction: column; } #fnWrap { display: block; } .footer-bs-wrap { max-width: calc(100vw - 6rem); margin: auto; } .footer-section-wrapper { flex-direction: row; display: flex; padding: 64px 0; max-width: 1264px; margin: auto; } .wm_footer .btn { margin: 0 50px 0 0; } #wm_footer .d-col { width: 450px; padding: 0 10px; margin-right: auto; } #wm_footer #footNote .d-col { width: 100%; padding: 0; } #wm_footer .more-links { display: flex; width: 50%; } #wm_footer .d-col.large-col { width: 500px; margin-right: auto; } #footNote .legal { -ms-flex-positive: 1; flex-grow: 1; } } @media screen and (min-width: 1000px) and (max-width: 1200px) { .mission, .values { max-width: 50%; padding: 0 15px; } } @media screen and (max-width: 1200px) { .mission-and-values { display: flex; } .mission-and-values > div { display: flex; flex-direction: column; width: 100%; } } @media screen and (min-width: 1200px) { .mission, .values { padding: 0 15px; } } @media screen and (max-width: 768px) { .footer-section-wrapper { padding: 48px 0; display: flex; flex-direction: column; margin: 0 24px; } #fnWrap { flex-direction: column; } } @media screen and (max-width: 575px) { .more-links, .mission-and-values { flex-direction: column; } #wm_footer .more-links .d-col, #wm_footer .mission, #wm_footer .values { width: 100%; } #wm_footer, #footNote { max-width: none; } #wm_footer .contact-btn { width: 100%; margin: 2em auto; text-align: center; box-sizing: border-box; } .footer-info { flex-direction: column; } .footer-info a+span { display: none; } } /* Request a Consultation style override hotfix */ .apptRequest a.all.btn.fill { background: #3572A9; border-radius: 0; } /** end childReplacement footerCSS **/ main.no-locations #maptoggle { display: none; } main { min-height: calc(100vh - 100px); } /** childReplacement logoSizeCSS **/ header amp-img { width: 200px; height: 38px; } /** end childReplacement logoSizeCSS **/ /* Search + filter styles */ #custom-location, #location-selection { margin-bottom: 10px; margin-top: 4px; padding-left: 16px; } #availability-dropdown-menu { max-height: fit-content; padding: 0; min-width: 180px; } #availability-dropdown-menu .location-label { padding: 0 16px; font-weight: 500; color: #515151; } #timesList, #daysList { max-height: fit-content; overflow: auto; } .availability-divider { padding-top: 7.5px; border-top: 1px solid #DDDDDD; margin: 0 15px; } #autocomplete .availability-divider { padding: 0; margin-top: 10px; margin-bottom: 5px; } .provider-info h3, .provider-info h4 { margin: 0; font-weight: 600; } .provider-info a, .remaining-data a { text-decoration: none; display: inline-block; } .read-more-state { display: none; } .read-more-target { opacity: 0; max-height: 0; font-size: 0; transition: .25s ease; overflow: hidden; } .read-more-state:checked + .read-more-target { opacity: 1; font-size: inherit; max-height: 999em; margin: 1rem 0 0; } .read-more-state~div .read-more-trigger .compress, .read-more-state~div .read-more-trigger .less-location, .read-more-state:checked~div .read-more-trigger .expand, .read-more-state:checked~div .read-more-trigger .more-location { display: none; } .read-more-state:checked~div .read-more-trigger .compress, .read-more-state~div .read-more-trigger .expand, .read-more-state:checked~div .read-more-trigger .less-location { display: inline; } .read-more-trigger { font-family: Barlow; font-size: 16px; font-weight: 500; line-height: 24px; letter-spacing: 0px; text-align: left; cursor: pointer; display: block; margin-top: 8px; color: #0537A4; display: inline-block; } .loc-wrap { display: inline-block; width: 24px; height: 24px; text-align: center; position: relative; } .loc-wrap .expand { position: absolute; right: 8px; bottom: -1px; } .loc-wrap .compress { position: absolute; right: 8px; bottom: 4px; } .read-more-target .phone { margin-bottom: 0.75rem; } .virtualVisits svg { color: #446aac; vertical-align: baseline; } .virtualVisits, .new-patients { font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 24px; letter-spacing: 0px; text-align: left; color: #515151; } .new-patients svg { color: rgb(26 217 77); height: 16px; width: 20px; } .virtualVisits + .new-patients ~ .read-more-wrap { margin-top: 10px; } .read-more-wrap { margin-top: 16px; } .distance { display: block; font-family: Barlow; font-size: 16px; font-weight: 500; line-height: 24px; letter-spacing: 0px; text-align: left; color: #515151; } .badges { display: flex; flex-wrap: wrap; margin-top: 0.5rem; } .badges>span { padding: 4px 8px 4px 16px; margin-right: 16px; margin-top: 5px; border-radius: 20px; background-color: #F5F5F5; font-family: Barlow; font-size: 14px; font-weight: 400; line-height: 20px; letter-spacing: 0em; text-align: left; color: #515151; } .badges>span strong { font-weight: 500; } .star-ratings-css { unicode-bidi: bidi-override; color: #c5c5c5; font-size: 16px; /* margin-top: 5px; margin-bottom: 0.5rem; */ position: relative; padding: 0; display: flex; } .rating-info { width: 16px; height: 16px; } .star-ratings-css > div { width: calc(16px * 5); position: relative; min-width: 100px; } .star-ratings-css > span { font-size: 12px; line-height: 22px; /* margin-left: 5px; */ } /* Hide review section for providers with an average review of 0 */ .reviews.hide-zero-average_rating0 { display: none; } .reviews { color: #777; margin: 0; } .reviews>span { line-height: 16px; height: 16px; display: inline-block; vertical-align: middle; } .stars { color: #e9ab00; height: 16px; width: 120px; position: relative; } .stars .full, .stars .empty { position: absolute; display: block; width: 100%; height: 100%; } .stars .full .full-s, .stars .empty .empty-s { display: inline-block; width: 20%; max-width: 20px; height: 100%; background: no-repeat center/ 16px 16px url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 16 14"><path fill="rgb(221, 221, 221)" d="M1.46875 8.40625C0.53125 7.53125 0 6.28125 0 4.96875V4.8125C0 2.625 1.5625 0.75 3.71875 0.40625C5.15625 0.15625 6.59375 0.625 7.625 1.625L8 2L8.375 1.625C9.375 0.625 10.8438 0.15625 12.25 0.40625C14.4062 0.75 16 2.625 16 4.8125V4.96875C16 6.28125 15.4375 7.53125 14.5 8.40625L8.84375 13.6875C8.625 13.9062 8.3125 14 8 14C7.65625 14 7.34375 13.9062 7.125 13.6875L1.46875 8.40625Z"></path></svg>'); } .stars .full .full-s { background: no-repeat center/ 16px 16px url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 16 14"><path fill="rgb(232, 177, 9)" d="M1.46875 8.40625C0.53125 7.53125 0 6.28125 0 4.96875V4.8125C0 2.625 1.5625 0.75 3.71875 0.40625C5.15625 0.15625 6.59375 0.625 7.625 1.625L8 2L8.375 1.625C9.375 0.625 10.8438 0.15625 12.25 0.40625C14.4062 0.75 16 2.625 16 4.8125V4.96875C16 6.28125 15.4375 7.53125 14.5 8.40625L8.84375 13.6875C8.625 13.9062 8.3125 14 8 14C7.65625 14 7.34375 13.9062 7.125 13.6875L1.46875 8.40625Z"></path></svg>'); } .stars .full .full-s:last-of-type { background: no-repeat left/ 16px 16px url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 16 14"><path fill="rgb(232, 177, 9)" d="M1.46875 8.40625C0.53125 7.53125 0 6.28125 0 4.96875V4.8125C0 2.625 1.5625 0.75 3.71875 0.40625C5.15625 0.15625 6.59375 0.625 7.625 1.625L8 2L8.375 1.625C9.375 0.625 10.8438 0.15625 12.25 0.40625C14.4062 0.75 16 2.625 16 4.8125V4.96875C16 6.28125 15.4375 7.53125 14.5 8.40625L8.84375 13.6875C8.625 13.9062 8.3125 14 8 14C7.65625 14 7.34375 13.9062 7.125 13.6875L1.46875 8.40625Z"></path></svg>'); } .stars.s0 .full { width: 0%; } .stars.s1 .full { width: 20%; } .stars.s2 .full { width: 40%; } .stars.s3 .full { width: 60%; } .stars.s4 .full { width: 80%; } .reviews>.stats { margin-left: 0.5em; } #dist, .reviewer { color: #666; } .star-ratings-css-bottom { padding: 0; display: block; z-index: 0; } #map #virtual-only { display: none; } #map #wm_map { display: none; } #map.show-map #virtual-only { display: none; } #map.show-map #wm_map { display: block; } .dropdown.active button { border: 1px solid #00338E; } #InsuranceAccepted .dropdown-menu { width: 400px; } #insuranceList svg { display: none; } #PrimarySpecialties .dropdown-menu { width: 400px; } /** childReplacement noApptAvailCSS **/ .no-appt-avail h4 { font-weight: 500; line-height: 25px; color: #222222; text-align: left; font-size: 18px; font-family: 'Lora'; margin: 8px 0 16px; } .no-appt-avail a { color: #00338e; text-decoration: underline; } /** end childReplacement noApptAvailCSS **/ .fp-return-patients { text-align: center; border-radius: 5px; margin: 0 2px; padding: 15px 5px; line-height: 1; display: block; } .fp-return-patients a { font-weight: 600; color: #3572A9; text-decoration: underline; } @media only screen and (min-width: 1000px) { #map #virtual-only { display: block; } .new-patients.mobile-only { text-indent: -24px; padding-left: 28px; } } @media only screen and (max-width: 999.98px) { #InsuranceAccepted .dropdown-menu { width: 100%; } #PrimarySpecialties .dropdown-menu { width: 100%; } div#searchBox .hover-placeholder { font-size: 12px; top: 5px; color: #9A9A9A; } } /* Internal test styles */ .tier > span { background: green; color: white; padding: 5px 10px; border-radius: 50px; display: inline-block; margin: 10px 0 5px; font-size: 12px; } div#test__organizations { background: #ececec; padding: 10px; border-radius: 5px; margin: 10px 0 10px; } div#test__organizations h4 { margin: 0 0 5px; text-transform: uppercase; font-size: 13px; } div#test__organizations p { margin: 0; font-size: 14px; } /* Error Message Styling */ .location .dc.textInput.invalid { border: 2px solid #cc0033; } .tool_tip { /* font */ font-family: Helvetica; font-size: 13px; font-weight: bold; color: #cc0033; line-height: 20px; /* background */ background-color: #fce4e4; border: 1px solid #fcc2c3; border-radius: 7px; /* positioning */ float: left; position: absolute; padding: 7px 11px 4px; margin-top: 15px; top: 104px; left: 1px; } /* Left Arrow Styles */ .tool_tip:after, .tool_tip:before { content: ''; border: 7px solid transparent; position: absolute; top: 10px; } .tool_tip:after { border-bottom: 7px solid #fce4e4; top: -14px; left: 60px; } .tool_tip:before { border-bottom: 7px solid #fcc2c3; top: -15px; left: 60px; } /* Brand Update Styles */ .provider-image amp-img { border-radius: 5px; } .provider-info h3 { font-family: Lora; font-size: 20px; font-weight: 600; line-height: 28px; letter-spacing: 0px; text-align: left; padding-left: 3px; } .resultDescription { font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 24px; letter-spacing: 0px; text-align: left; color: #515151; margin: 0; padding-left: 3px; } .reviews .stats { font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 16px; letter-spacing: 0px; text-align: left; color: #515151; } .rating_comment_count { font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 24px; letter-spacing: 0px; text-align: left; color: #0537A4; } @media only screen and (min-width: 1000px) { .vv-and-patients { display: flex; line-height: 24px; height: 24px; } .vv-and-patients.desktop-only .virtualVisits { margin-right: 24px; } .mobile-only { display: none; } .col-xs-12.col-md-6.py-3 { padding-top: 0; } .search-filters .dropdown-toggle { min-width: 180px; height: 56px; position: relative; } div.dropdown-menu { min-width: 180px; } a.survey { padding: 0 8px; } .dd-svg { width: 24px; height: 24px; text-align: center; position: absolute; right: 8px; } #pagination { max-width: 50%; padding: 0; } .ellipsis-prev + .page-link, .ellipsis-prev + .page-link + .page-link + .active + .page-link + .page-link{ display: none; } } /* Specific rule to fix vv and np section for small screens */ @media screen and (max-width: 1199.98px) { .vv-and-patients.desktop-only { display: none; } .virtualVisits.mobile-only, .new-patients.mobile-only { display: block; } #pagination .btn-group .btn { min-height: auto; } #pagination .ellipsis + .page-link { margin-right: 0; } } @media only screen and (max-width: 999.98px) { .desktop-only { display: none; } #showmap:checked .mobile-map-hide, #showmap:checked ~ main .mobile-map-hide { display: none!important; } #showmap:checked ~ main #pagination .btn-group { margin-bottom: 16px; } div.provider-info{ display: block; } .new-patients.mobile-only { text-indent: -28px; padding-left: 32px; } input#fullsearch:checked ~ header .container.search-filters { top: 40px; } #visitTypeButtons { flex-direction: column; margin: 0; } button.btn.visitType { text-align: left; } .dropdown-toggle svg{ position: absolute; right: 16px; top: 28px; } .dropdown { position: relative; } #searchBox #search-submit { display: none; } #search-submit2 { width: 100%; background: #9A9A9A; border: none; border-radius: 80px; font-family: Barlow; font-size: 16px; font-weight: 500; line-height: 24px; color: #515151; padding: 14px 48px 14px 48px; margin-top: 8px; } .is-dirty #search-submit2 { background: #00338E; color: #fff; } .mobile-only.close-menu { color: #0537A4; border: none; margin: 16px auto 0; padding: 14px 48px 14px 48px; display: block; text-align: center; } .badges>span { width: fit-content; } .badges { display: flex; flex-wrap: wrap; flex-direction: column; } button#clearFilters { position: initial; } #pagination .btn.page-link { border-radius: 40px; line-height: 48px; width: 48px; height: 48px; box-shadow: -1px 1px 2px #000000; } a.next-page, a.previous-page { line-height: 48px; } .mobile-hide, .mobile-hide + .page-link, .ellipsis-prev + a, .ellipsis-prev + a + a, .ellipsis-prev ~ .active + a, .ellipsis-prev ~ .active + a + a { display: none; } #Location div.dropdown-menu { max-width: 100vw; } div#mobile-location-selection { display: none; } #locationSelectionInput:checked ~ #mobile-location-selection { display: block; } #Location div.dropdown-menu { max-width: 100vw; } #locationSelectionLabel { border: 1px solid #DDDDDD; border-radius: 8px; display: block; padding: 8px 40px 8px 16px; position: relative; } #locationSelectionText { font-family: Barlow; font-size: 14px; font-weight: 400; line-height: 14px; letter-spacing: 0.10499999672174454px; color: #515151; text-overflow: ellipsis; max-width: 200px; display: block; overflow: hidden; white-space: nowrap; } #locationSelectionLabel .dd-svg svg { position: absolute; right: 16px; top: 12px; } .loc-prov-svg-selected, #locationSelectionInput:checked + label .loc-prov-svg { display: none; } #locationSelectionInput:checked + label .loc-prov-svg-selected { display: inline-block; } #Location #mobile-location-selection { max-height: 200px; } input#custom-location { padding: 0 16px 0 16px; } .poi-info-window .provider-info h4 { font-size: 14px; } } .search-filters .dropdown-toggle { text-align: left; } .prov-svg-selected, .active .prov-svg, #availability-days-checkboxes { display: none; } .active .prov-svg-selected { display: inline-block; } .address { font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 24px; letter-spacing: 0px; text-align: left; color: #0537A4; margin-left: 31px; } a.phoneLink { color: #515151; font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 24px; letter-spacing: 0px; text-align: left; text-decoration: none; margin-left: 31px; } .med-group-affiliations { font-family: Barlow; font-size: 16px; font-weight: 400; line-height: 24px; letter-spacing: 0px; text-align: left; color: #515151; margin-left: 32px; } .provider-link { height: fit-content; } .provider-info { gap: 8px; display: grid; } .provider-info .provider-link:hover { text-decoration: underline; text-decoration-color:#0537A4; } .provider-link:active { color: #0537A4; } .location-name .medgroups { width: initial; height: initial; text-align: left; padding: 0; } h4.location-name { font-family: Barlow; font-size: 16px; font-weight: 500; line-height: 24px; letter-spacing: 0px; text-align: left; margin: 8px 0 0 0; } .remaining-data .distance { margin-left: 27px; } .location-name span { width: 24px; height: 24px; text-align: center; display: inline-block; padding-right: 8px; } .location-name { display: -webkit-box; } .vv-svg { text-align: center; width: 24px; height: 24px; display: inline-block; position: relative; } .vv-svg svg{ position: absolute; top: 6px; left: 2px; } .ovv { padding-left: 8px; } .anp { margin-left: 5px; } .mobile-only .vv-svg svg { left: 2px; } .new-patients svg { top: 9px; height: 19px; } #advancedFilters .facet .facet-value::before, #Availability .facet-value::before{ content: ''; margin: 0; } #clearFilters { font-family: Barlow; font-size: 16px; font-weight: 500; line-height: 20px; letter-spacing: 0px; text-align: left; position: absolute; top: -8px; right: -127px; color: #0537A4; display: none; background: none; } .hasFilter #clearFilters { display: block; } .pos-rel { position: relative; } .input-group.mb-3 { border: 1px solid #DDDDDD; border-radius: 8px; margin-top: 1rem; position: relative; display: flex; padding: 3px 10px; justify-content: space-between; height: 56px; } #searchBox { display: flex; margin-top: 48px; } #searchBox button { padding: 10px 48px; margin: 1rem 0 0 24px; font-family: Barlow; font-size: 16px; font-weight: 500; line-height: 24px; letter-spacing: 0px; text-align: center; background: #D2D2D2; color: #515151; border-radius: 80px; } .is-dirty #search-submit { background: #00338E; color: #fff; } #filterBy { padding-left: 1rem; font-family: Barlow; font-size: 16px; font-weight: 500; line-height: 27px; letter-spacing: 0px; text-align: left; color: #515151; margin-top: 1rem; } .toggle::before { content: ""; display: block; background: #FFFFFF; height: 20px; width: 20px; position: absolute; left: 1px; top: 1px; border-radius: 50%; transition: all .2s linear; } button.btn.visitType { position: relative; border: none; color: #515151; font-size: 16px; padding: 10px 12px 10px 0; } button.btn.visitType.videoVisits { padding: 10px 24px 10px 0; } .active .toggle::before { left: 22px; background: #FFFFFF; } .active .toggle { background: #00338E; } .toggle:focus { border-color: #C8DAF1; background: #EEEEEE; } .btn.visitType:hover, .visitType.btn.active { background: none; } .toggle { pointer-events: none; width: 43px; min-width: 40px; background: #EEEEEE; height: 22px; vertical-align: middle; margin-right: 8px; border-radius: 12px; position: relative; border: 1px solid #e6e6e6; cursor: pointer; display: inline-block; font-family: Barlow; font-size: 16px; } .pill-svg { text-align: center; width: 24px; height: 24px; display: inline-block; pointer-events: none; } #results-num { color: #515151; font-family: 'Barlow'; font-size: 16px; margin-bottom: 24px; } .c-b { color: #0537A4; } .single-page .previous-page, .first-page .previous-page, .last-page .next-page { pointer-events: none; } #pagination { display: flex; justify-content: center; } #pagination.no-pages { display: none; } #pagination .active.page-link { background: #00338E; color: #FFFFFF; } #pagination .btn { background: #FFFFFF; border-radius: 40px; padding: 0; width: 64px; border: none; min-height: 64px; cursor: pointer; text-align: center; line-height: 64px; font-size: 18px; margin-right: 8px; } #pagination .btn.no-m { margin-right: 0px; } .previous-page { border-radius: 40px; padding: 0; width: 40px; border: none; border-radius: 20px; font-size: 10px; min-height: 40px; cursor: pointer; text-align: center; line-height: 64px; } .next-page { padding: 0; width: 40px; border: none; border-radius: 20px; font-size: 10px; min-height: 40px; cursor: pointer; text-align: center; line-height: 64px; } .first-page .previous-page .arrow-left-active,.arrow-left-inactive, .arrow-right-inactive, .single-page .page-svg.arrow-left-active, .last-page .arrow-right-active { display: none; } .single-page .page-svg.arrow-left-inactive, .first-page .arrow-left-inactive, .last-page .arrow-right-inactive, .last-page .arrow-left-active, .active.mobile-hide { display: inline-block; } .ellipsis { pointer-events: none; } .results-text { font-family: "Barlow"; font-size: 14px; color: #222222; margin: 0; } .results-text + h5 { margin: 0; } #Location .dropdown-menu { max-height: initial; max-width: 250px; } #location-selection { font-size: 14px; margin: 0; padding: 0 30px 0 16px; -webkit-padding-end: 30px; white-space: nowrap; text-overflow: ellipsis; border-radius: 8px; border: 1px solid #DDDDDD; -webkit-appearance: none; background: url(data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2212%22%20height%3D%227%22%20viewBox%3D%220%200%2012%207%22%3E%3Cpath%20d%3D%22M5.28125%205.71875L1.28125%201.71875C1%201.4375%200.90625%201%201.0625%200.625C1.21875%200.25%201.59375%200%202%200H10C10.4062%200%2010.75%200.25%2010.9062%200.625C11.0625%201%2010.9688%201.4375%2010.6875%201.71875L6.6875%205.71875C6.3125%206.125%205.65625%206.125%205.28125%205.71875Z%22%3E%3C%2Fpath%3E%3C%2Fsvg%3E) no-repeat calc(100% - 8px) 50%; } .location-label { font-family: Barlow; font-size: 14px; line-height: 20px; color: #515151; } .area-label { margin-top: 8px; } #custom-location, #searchRadius { font-family: Barlow; font-size: 14px; font-weight: 400; line-height: 30px; letter-spacing: 0.10499999672174454px; text-align: left; border-radius: 8px; border: 1px solid #DDDDDD; } #searchRadius { width: 45px; margin: 0px 4px 4px 4px; text-align: center; height: 30px; } #location-selection::-webkit-scrollbar-thumb { background: #515151; width: 5px; border-radius: 5px; } #location-selection::-webkit-scrollbar { background-color: transparent; width: 5px; } #updateLocation { margin: 24px 0; border-radius: 80px; height: 36px; } .r-consult { border-radius: 80px; } .r-consult:hover { background-color: #0C2E6E; } .r-consult:focus { outline: 5px solid #C8DAF1; } label.distanceSort.dc { margin-top: 16px; } /* Finish Brand Update Styles */ /** childReplacement fullHeader **/ @media screen and (max-width: 1000px) { label.services-input-label, label.get-care-input-label, label.billing-support-input-label { width: 100%; } } header { padding-top: 0; padding-bottom: 48px; } .wmHeader a { text-decoration: none; } .wmHeader svg { width: 20px; height: 20px; fill: currentColor; vertical-align: middle; max-width: 100%; max-height: 100%; pointer-events: none; } /* Added hidden text that should be only available to screenreaders */ .screenreader { position: absolute; /* Outside the DOM flow */ height: 1px; width: 1px; /* Nearly collapsed */ overflow: hidden; clip: rect(1px 1px 1px 1px); /* IE 7+ only support clip without commas */ clip: rect(1px, 1px, 1px, 1px); /* All other browsers */ } .pointer-events-none { pointer-events: none; } #pwa-header-filler { display: none; } /*CSS that 'freezes' the page when menus are open. works with elements added ing after render JS */ #ampcontainer { max-height: 100vh; max-width: 1200px; margin: auto; } input#clear:checked + #ampcontainer { max-height: none; } #browserNotice { position: relative; padding: 10px; text-align: center; z-index: 10; display: none; } .wmHeader { position: fixed; left: 0; width: 100%; z-index: 100; top: 0; font-family: "Barlow", sans-serif; } .wmHeader::before, #topheader::before, #browserNotice::before { content: ""; background: #fff; position: absolute; top: 0; left: 50%; bottom: 0; width: 100vw; -ms-transform: translateX(-50%); transform: translateX(-50%); z-index: -1; box-shadow: 0 1px 3px 0 #cecece; } #browserNotice::before { background: #fce068; } #topheader::before { background: #0c2e6e; box-shadow: none; } #header { overflow: visible; /*z-index: 1;*/ width: 100%; text-align: left; height: 5.5em; position: relative; display: -ms-flexbox; display: flex; -ms-flex-align: center; align-items: center; -ms-flex-pack: justify; justify-content: space-between; } #header label.close-menu { display: none; } #header label, #header a { min-width: 40px; text-align: right; position: relative; color: ##link-color##; } .wmHeader .logo { text-align: left; width: 172px; height: 60px; padding: 10px 0; overflow: hidden; } /*custom sizes for 3-logo set*/ .wmHeader.providence.legacy #header { -ms-flex-positive: 1; flex-grow: 1; } .wmHeader.providence.legacy nav { -ms-flex-positive: 0; flex-grow: 0; } #header .logo.os { min-width: 40%; } /*end custom sizes for 3-logo set*/ .wmHeader .logo > amp-img, .wmHeader .logo > img { max-width: 100%; max-height: 100%; min-height: 100%; } .wmHeader .logo img { object-fit: contain; } .wmHeader input[type="radio"], .wmHeader input[type="checkbox"], input#clear, .amp-only + .geo-locator { display: none; } #topheader { display: flex; flex-direction: row; justify-content: flex-end; position: absolute; top: -1px; left: 0; width: 100%; text-align: right; color: #fff; height: 0; font-size: 0.9rem; line-height: 40px; font-weight: 500; } .nav-wrapper a.navlink { color: #fff; position: relative; } #onesite-submenu a:hover, .nav-wrapper a.navlink:hover, .account-wrapper .my-account:hover, .geo-locator:hover { color: #00338e; background-color: #fff; } /* Sidebar CSS */ .wmHeader nav, .wmHeader label.clear { background: #fff; } .wmHeader li { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; -ms-flex-pack: justify; justify-content: space-between; position: relative; } .wmHeader li > a:last-child:first-child { width: 100%; } .wmHeader nav a, .wmHeader nav label { display: block; padding: 20px 5px; color: inherit; font-size: 16px; -ms-flex-wrap: nowrap; flex-wrap: nowrap; } .wmHeader label { cursor: pointer; } .wmHeader nav ul { list-style: none; margin: 0; padding: 0; } .wmHeader .submenu { display: none; color: black; background: rgb(255, 255, 255); } .wmHeader .submenu .first { margin-top: 16px; } .wmHeader .submenu.last { right: 0; } .wmHeader .submenu .last { margin-bottom: 16px; } .submenu-title { line-height: 1.7rem; font-family: Lora; padding: 0.5rem 3rem 0.5rem 1.5rem; font-size: 1.13rem; font-weight: 500; margin-top: 1rem; } .wmHeader .active-parent { position: absolute; top: 0; left: 0; right: 0; bottom: 0; display: none; } #top-nav .submenu a { padding: 0.5rem 3rem 0.5rem 1.5rem; color: #222; white-space: nowrap; } #top-nav .submenu a[title="View all"] { color: #00338e; } #top-nav .submenu a:hover { background: #335ca5; color: #fff; } .wmHeader .submenu li { text-align: left; display: block; overflow: hidden; } .wmHeader nav .pre a { font-weight: normal; padding: 5px; } .wmHeader .pre h4 { margin: 10px 0; } .wmHeader .pre h4 a { font-weight: bold; } .wmHeader label.clear { position: absolute; left: 5px; top: 10px; z-index: 3; width: 40px; padding: 10px; } .wmHeader nav .pre h3 { position: relative; border-bottom: 1px solid; margin: 5px 10px 5px 0; } .site-search { height: 0px; width: 0; z-index: 9; overflow: hidden; text-align: left; color: ##primary-theme-color##; } .site-search h4 { margin: 0; } input:checked + label + .site-search { height: auto; overflow: visible; width: 100%; padding: 0 0 0 1rem; background: #fff; } input:checked + label + .site-search + #site-search-id { display: none; } .site-search .search-form { overflow: hidden; } .search-form .typeahead { display: flex; align-items: center; background: #f5f5f5; border-radius: 40px; } .search-form .typeahead a { padding: 0; } .search-form .typeahead button { position: relative; cursor: pointer; color: #222; width: 1.25rem; padding: 0; margin: 5px 16px; background: none; border: none; } .site-search label.clear { top: -60px; right: 0; left: auto; height: 60px; line-height: 39px; width: 50px; text-align: center; color: #00386a; background: #fff; } #search-input { text-overflow: ellipsis; } .mobile-menu-list { display: flex; flex-wrap: nowrap; } .mobile-menu-list svg { width: 1.5rem; height: 1.5rem; } #header .mobile-menu-list label { color: #222; } #wmH#wmHader.providence #header label, #wmH#wmHader.legacy li.onesite { display: none; } /*regional mychart links*/ .reg, #wmH#wmHader.legacy .logo.onesite, #wmH#wmHader.providence .logo.onesite, #wmH#wmHader:not(.providence) .logo.providence { display: none; } #topheader li.reg { background: ##accent-color2-alt##; color: ##link-color##; } .ak .reg.ak, .mt .reg.mt, .or .reg.or, .wa .reg.wa, .ca .reg.ca, .kadlec .reg.kadlec, .heritage .reg.heritage { display: block; } /* https://app.asana.com/0/1140716869201116/1200044015012966 */ #onesite-submenu { display: flex; } #onesite-submenu a, .heritage-submenu a { padding: 0 13px; font-size: 16px; } /*.gold-text { color: #ffc16e; }*/ #topheader li.reg.wa.mt.or.ca.ak { background: #00338e; } .account-btn { border: none; color: inherit; font-family: "Barlow", sans-serif; font-size: 0.9rem; height: 40px; width: fit-content; display: block; } .account-btn:hover { cursor: pointer; } #account-menu { position: absolute; top: 100%; margin-top: 0; right: 0; padding: 0px 15px; border-radius: 0 0 7px 7px; box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.25); /*overflow: hidden;*/ width: 325px; z-index: 10; max-width: 100vw; } #topheader .my-account ul li a { font-size: 15px; padding: 5px; display: block; } #topheader .my-account ul li { margin: 15px 0; line-height: 1.5em; } #topheader .my-account a { padding: 0 13px; white-space: nowrap; } #topheader .my-account label { padding: 0 13px; display: block; } #topheader .my-account .submenu::before { height: 1px; top: 0px; } #topheader .my-account .submenu { border-radius: 0 0 7px 7px; } #topheader .my-account { position: relative; white-space: nowrap; width: fit-content; margin-left: auto; } #topheader .my-account.verified .chevron-down-icon { width: 9px; height: 9px; margin-left: 8px; } #topheader .my-account.verified input:checked + label .chevron-down-icon { transform: rotate(180deg); } #site-search-id { padding: 0; } #site-search-id svg { width: 1rem; height: 1rem; color: ##primary-theme-color##; } .site-search label.clear { top: -40px; right: 0; height: 40px; line-height: 21px; width: 88px; white-space: nowrap; } label.bgoverlay { position: fixed; top: 0; left: 0; bottom: 0; right: 0; opacity: 0.5; display: none; cursor: auto; } input:not(:checked) + label.bgoverlay { display: block; } .search-container { display: flex; align-items: center; padding-left: 24px; } .search-container label { display: block; margin: 5px 16px; padding: 0; } .search-container .overlay { position: absolute; } .search-container #site-search + label { display: none; } .search-container #site-search:checked + label { display: block; } .search-container #search-input { border: 0; font-size: 1em; padding: 0; min-height: 40px; background: transparent; width: 100%; -webkit-appearance: none; } svg.search-icon { width: 1rem; height: 1rem; } .search-clear-label svg { width: 20px; height: 20px; fill: currentColor; vertical-align: middle; max-width: 100%; max-height: 100%; pointer-events: none; } .wmHeader svg.input-icon { width: 14px; height: 14px; vertical-align: baseline; } .wmHeader nav .services-input-label a { display: initial; padding: 0; font-size: initial; } /*mobile only */ @media screen and (max-width: 999px) { div#topheader { justify-content: space-between; } .wmHeader #topheader nav { height: 40px; background: #0c2e6e; left: 0; text-align: left; overflow: initial; position: relative; width: 100%; } .wmHeader li.geo-locator { display: inline-block; } label#geoLocatorLabel { padding: 0 24px; } div#geoLocatorMenu { width: 100vw; left: 0; } #header { padding: 0 24px; background: #fff; } header nav.main-nav label.services-input-label a { padding: 0; } .wmHeader .desktoponly, .pre .spacer, ul.module-alphabet-list { display: none; } #ampcontainer { padding-top: calc(5.5em + 40px); } .PWAMP #ampcontainer { padding-top: 2rem; } .search-container .search { display: none; } #site-search-id svg { width: 1.25rem; height: 1.25rem; } svg.search-icon, svg.search-clear-label { width: 1.25rem; height: 1.25rem; } .site-search { position: absolute; top: calc((40px + 5.5rem) / 2); right: 0; } .search-container #search-input { font-size: 1.5em; } .search-form .typeahead svg { width: 1.7em; height: 1.7em; } .wmHeader nav { right: -100vw; height: 100vh; overflow: auto; position: absolute; top: 0; z-index: 100000; transition: right 0.3s ease-in-out; } .wmHeader nav.main-nav { top: 128px; } .wmHeader nav .account-wrapper input:checked + label + .submenu { display: flex; flex-direction: column; } .main-nav { top: calc(5.5em + 40px); width: 60vw; } .account-wrapper { flex-grow: 1; } #accountnav { border-top: 2px solid #eee; padding-top: 10px; flex-direction: column; } #topheader .my-account input:checked + label + label { background-color: white; color: #00338e; } #topheader .my-account label, #topheader .my-account a { padding: 0 24px; } #top-nav .toggle-close, #top-nav .toggle-open { stroke: #222; stroke-width: 1px; } #top-nav input:checked + label + .submenu, #top-nav input:checked + label .toggle-close { display: inline-block; } #top-nav .toggle-close, #top-nav input:checked + label .toggle-open { display: none; } #top-nav { margin-top: 1rem; margin-bottom: calc(5.5rem + 40px); } #topheader nav > ul { display: flex; justify-content: space-between; } .wmHeader nav .logo { margin-right: calc(50vw - 50px); -ms-transform: translateX(50%); transform: translateX(50%); } .wmHeader li .btn { margin: 0 20px 10px; } .wmHeader #menu:checked ~ label ~ nav { right: 0; } .wmHeader #menu:checked ~ label + #header label.menu { display: none; } .wmHeader #menu:checked ~ label + #header label.close-menu { display: block; } label.overlay { position: absolute; top: 128px; left: 0; right: 0; z-index: 1; background: rgba(0, 0, 0, 0.25); opacity: 1; transition: opacity 0.3s ease-in-out; } .search-container input:checked + label.overlay { height: 110vh; opacity: 0; } input:checked + .menu-overlay { height: 100vh; top: calc(40px+5.5em); } .wmHeader nav.main-nav a, .wmHeader nav.main-nav label { padding: 1rem 3rem; color: #222; font-size: 1.1em; font-weight: 500; line-height: 1.19rem; } .wmHeader nav label { display: flex; justify-content: space-between; } .wmHeader #top-nav .submenu { padding: 0 0 0.75rem 0; } .wmHeader #top-nav .submenu a { padding: 0.63rem 3rem 0.63rem 4rem; font-size: 0.875rem; font-weight: 400; line-height: 1.19rem; } #top-nav .main-nav .submenu a { padding-left: 4rem; padding-right: 4rem; } .wmHeader.onesite #accountnav { display: none; } .wmHeader.onesite #accountnav#onesite-submenu { display: flex; } .wmHeader.onesite #accountnav#onesite-submenu a { padding: 10px; font-size: 15px; } #geoLocatorAmpMobile { text-align: left; color: #fff; } input:checked + label + .site-search { width: calc(100% - 250px); padding-right: 24px; } .wmHeader .submenu-title { display: none; } div#topheader { height: auto; min-height: 40px; position: relative; flex-wrap: wrap; } .nav-wrapper-mobile { display: flex; justify-content: center; align-items: center; } .nav-wrapper-mobile a { color: #fff; font-size: 15px; line-height: 40px; text-decoration: underline; padding-left: 5px; white-space: nowrap; } } /*desktop only*/ @media screen and (min-width: 1000px) { #topheader .mobileonly, #top-nav .mobileonly, .wm_footer a.btn.mobileonly { display: none; } .wmHeader { position: relative; display: -ms-flexbox; display: flex; max-width: calc(1280px - 0.5rem); margin: auto; -ms-flex-pack: justify; justify-content: space-between; padding: 40px 0 0; -ms-flex-align: center; align-items: center; } .wmHeader.providence { padding: 40px 0 0; -ms-flex-pack: center; justify-content: center; } #onesite-submenu a:after, .nav-wrapper a.navlink:after, .account-wrapper .my-account:after, .geo-locator:after { content: ""; position: absolute; border-bottom: 2px solid #0c2e6e; width: 32px; height: 2px; left: -webkit-calc(50% - 16px); left: calc(50% - 16px); bottom: 0; pointer-events: none; } .wmHeader nav label:hover ~ .submenu, header nav label:focus ~ .submenu, .wmHeader nav li:focus-within .submenu, header nav label ~ .submenu:hover { display: flex; flex-direction: column; } #onesite-submenu a.mychart::after { content: none; } #wmH#wmHader nav { max-height: 5.5rem; } #wmH#wmHader nav label:hover ~ .submenu, #wmH#wmHader nav label:focus ~ .submenu, #wmH#wmHader nav li:focus-within .submenu, #wmH#wmHader nav label ~ .submenu:hover { display: flex; flex-direction: column; } #top-nav > li:hover > label, #top-nav > li:focus-within > label, #top-nav > li:hover > a, #top-nav > li:focus-within > a { color: #00338e; } #top-nav > li:focus-within > label::after, #top-nav > li:hover > label::after, #top-nav > li:focus-within > a::after, #top-nav > li:hover > a::after { content: ""; position: absolute; border-bottom: 2px solid #00338e; width: 32px; height: 2px; left: -webkit-calc(50% - 16px); left: calc(50% - 16px); bottom: 0; z-index: 1; } #topheader { height: 2.5rem; z-index: 1; display: flex; flex-direction: row; padding: 0; } #header { width: auto; position: static; } #header .search { display: none; } #header label, #header a { color: #fff; } #header .menu, .wmHeader nav .logo, .wmHeader.legacy nav .reg.logo, .wmHeader nav label.clear { display: none; } .wmHeader nav { display: flex; margin-left: auto; } .wmHeader nav > ul { display: -ms-flexbox; display: flex; -ms-flex-pack: end; justify-content: flex-end; } .wmHeader nav ul li { text-align: center; -ms-flex-align: center; align-items: center; } .wmHeader nav > ul > li > a, .wmHeader nav > ul > li > label { padding: 2.13em 1.1em; white-space: nowrap; color: rgb(34, 34, 34); font-size: 1em; font-weight: 500; line-height: 1.19rem; } .wmHeader nav .btn { line-height: 40px; height: 40px; font-size: 90%; text-transform: none; padding: 0 20px; margin-left: 20px; } .wmHeader .submenu { position: absolute; top: 100%; box-shadow: 0 40px 80px rgba(0, 0, 0, 0.12); } .wmHeader nav .submenu.pre { width: 600px; } .wmHeader nav .submenu.pre .drop { display: -ms-flexbox; display: flex; -ms-flex-wrap: wrap; flex-wrap: wrap; } .wmHeader nav .submenu.pre .drop > * { width: 50%; text-align: left; } .wmHeader nav .submenu.pre .drop > .grid-full { width: 100%; } ul.module-alphabet-list { display: -ms-flexbox; display: flex; } .wmHeader .submenu::before { content: ""; position: absolute; background: inherit; display: block; top: -8px; width: 100%; height: 10px; left: 0; } .wmHeader nav .submenu.cols { column-count: 3; width: 550px; } .wmHeader .active-parent { top: -10px; bottom: -10px; } .navbar-pipe { margin: 0 12px; border-right: 1px solid hsla(0,0%,100%,.5); height: 12px; } #topheader nav { background: none; color: inherit; padding: 0; -ms-flex-positive: 0; flex-grow: 0; } #topheader nav label, #topheader nav > ul > li > a { padding: 0 11px; font-size: 16px; } #topheader .my-account.verified .chevron-down-icon { display: none; } } @media screen and (max-width: 768px) { .main-nav { width: 100vw; } } @media screen and (max-width: 575px) { #mychart-user span { display: none; } input:checked + label + .site-search { width: 100%; } .wmHeader input:checked + label + nav { width: 100vw; } } @media screen and (max-width: 350px) { div#topheader { flex-direction: column; height: auto; } } @media screen and (max-width: 1400px) and (min-width: 1000px) { body .wmHeader { max-width: calc(100vw - 160px); } #topheader { padding: 0 0.3em; } } /* Fontello, used for region=heritage */ @font-face { font-family: "fontello"; src: url("https://amp-providers.providence.org/7772/fontello.eot#iefix") format("embedded-opentype"), url("https://amp-providers.providence.org/7772/fontello.woff") format("woff"), url("https://amp-providers.providence.org/7772/fontello.ttf") format("truetype"); font-weight: normal; font-style: normal; } icon[class^="icon-"]:before, icon[class*=" icon-"]:before { font-family: "fontello"; text-align: center; display: block; } .icon-loc:before { content: "\e803"; } .icon-map:before { content: "\e804"; } .icon-patient:before { content: "\e805"; } .icon-stetho:before { content: "\e809"; } .icon-cross:before { content: "\e813"; } .icon-cash:before { content: "\e810"; -webkit-transform: scale(0.7); -ms-transform: scale(0.7); transform: scale(0.7); } /* End Fontello */ /* Geo Locator */ #geoLocatorMenu { display: none; position: absolute; box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); background: rgb(255, 255, 255); padding: 25px; border-radius: 0 0 7px 7px; width: 400px; top: 40px; right: -7px; z-index: 9; text-align: left; color: #222; } input:checked + label + #geoLocatorMenu, input:checked + label + #geoLocatorMenu + .bgoverlay { display: block; } #geoLocatorMenu h3 { font-family: Lora, serif; font-size: 1.5em; margin: 0; } #geoLocatorMenu p { padding-bottom: 12px; line-height: 26px; font-size: 14px; font-weight: 100; } #geoLocatorLabel { white-space: nowrap; line-height: 40px; color: inherit; pointer-events: all; display: block; } #geoLocatorLabel > span { font-size: 16px; } #topheader #geo-locator-ck:checked + label { color: #0c2e6e; background: #fff; } #geoLocatorClear { position: absolute; top: 25px; right: 14px; width: 40px; z-index: 10; padding: 0; } #geoLocatorSuggestions input { -webkit-appearance: none; background: none; border-radius: 2px; min-height: 40px; padding: 5px 10px; width: 100%; font-size: 16px; border: 2px solid #007a87; } .typeahead__query { position: relative; margin: auto; } #geoLocatorInput { text-align: left; display: inline-block; width: 100%; text-overflow: ellipsis; word-wrap: break-word; line-height: 24px; min-height: calc(22px + 1.25rem); padding: 10px; background: #fff; border: 2px solid #222; border-radius: 3px; } .typeahead__query * { box-sizing: border-box; outline: 0; } #topheader svg#icon-cross { width: 16px; height: 16px; } #topheader svg.account { vertical-align: text-bottom; margin-right: 5px; width: 15px; height: 15px; display: none; } .wmHeader nav .navlink { padding: 0 0 0 8px; line-height: 40px; height: 40px; width: auto; } .nav-wrapper { display: flex; align-items: center; justify-content: flex-end; } .wmHeader .nav-wrapper a { font-size: 1em; } #geoLocatorAmp { width: 185px; white-space: nowrap; } .amp-only #geoLocatorAmp a.navlink { padding: 0 13px; } #geoLocatorSuggestions { position: absolute; background: #fff; border-top-left-radius: 0; border-top-right-radius: 0; z-index: 9; overflow-y: scroll; scrollbar-color: #515151; max-height: 260px; width: 100%; border-bottom-left-radius: 7px; border-bottom-right-radius: 7px; border: 1px solid #ccc; display: none; } #geoLocatorSuggestions::-webkit-scrollbar { background-color: transparent; width: 5px; } #geoLocatorSuggestions::-webkit-scrollbar-thumb { background: #515151; width: 5px; border-radius: 4px; background-clip: padding-box; } #topheader #geoUseCurrentLocation { color: #00338e; font-size: 1em; text-align: left; padding: 10px; line-height: 20px; } #geoLocatorSuggestions input[type="submit"] { padding: 10px; border-radius: 0; } #geoLocatorSuggestions label:hover, #geoLocatorSuggestions input:hover { background-color: #f5f5f5; cursor: pointer; } @media only screen and (min-width: 969px) and (max-width: 999px) { header .search-filters { margin-top: 128px; } } /** end childReplacement fullHeader **/ .hide-content svg, .location-name.Pacific.Medical.Centers.Swedish .swed-svg, .location-name.Pacific.Medical.Centers.Providence .prov-svg, .location-name.Providence.Swedish .swed-svg { display: none; } .location-name.Providence .prov-svg, .location-name.Jude.Medical.Center .prov-svg, .location-name.Mission.Heritage .prov-svg, .location-name.Jude.Heritage .prov-svg, .location-name.Joseph.Heritage .prov-svg, .location-name.Mary.High.Desert .prov-svg, .location-name.Facey .prov-svg, .location-name.Joseph.Hospital .prov-svg, .location-name.Santa.Rosa.Memorial .prov-svg, .location-name.Petaluma.Valley.Hospital .prov-svg, .location-name.Sacred.Heart.Medical .prov-svg, .location-name.Saint.Johns.Health.Cancer .prov-svg, .location-name.Doctors.of.Saint .prov-svg, .location-name.Pavilion.for.Women.and.Children .prov-svg, .location-name.Swedish .swed-svg, .location-name.Pacific.Medical.Centers .pacmed-svg, .location-name.Covenant .cov-svg, .location-name.Grace.Clinics .grace-svg, .location-name.Grace.Clinic .grace-svg, .location-name.Kadlec .kadlec-svg { display: inline; width: 24px; } .dpo-Providence .prov-svg, .dpo-JudeMedicalCenter .prov-svg, .dpo-MissionHeritage .prov-svg, .dpo-JudeHeritage .prov-svg, .dpo-JosephHeritage .prov-svg, .dpo-MaryHighDesert .prov-svg, .dpo-Facey .prov-svg, .dpo-JosephHospital .prov-svg, .dpo-Swedish .swed-svg, .dpo-PacificMedicalCenters .pacmed-svg, .dpo-Covenant .cov-svg, .dpo-CovenantMedicalGroup .cov-svg, .dpo-GraceClinics .grace-svg, .dpo-GraceClinic .grace-svg, .dpo-Kadlec .kadlec-svg{ display: inline; width: 24px; } .survey.swedish, .card-body[data-location-name*="Swedish"] .survey.providence { display: none; } .card-body[data-location-name*="Swedish"] .survey.swedish { display: inline-block; } /* CSS for search bar placeholder */ #searchBox .hover-placeholder { color: #9A9A9A; font-size: 18px; font-weight: normal; font-family: Barlow; position: absolute; pointer-events: none; left: 30px; top: 0px; transition: 0.2s ease all; -moz-transition: 0.2s ease all; -webkit-transition: 0.2s ease all; } @media screen { .dropdown-menu .search-wrap input::placeholder { color: #9A9A9A; } } #query:focus ~ .hover-placeholder, #query:not(:placeholder-shown) ~ .hover-placeholder { top: -15px; left: 30px; font-size: 12px; color: #515151; } .dpo-Providence .prov-svg, .dpo-JudeMedicalCenter .prov-svg, .dpo-MissionHeritage .prov-svg, .dpo-JudeHeritage .prov-svg, .dpo-JosephHeritage .prov-svg, .dpo-MaryHighDesert .prov-svg, .dpo-Facey .prov-svg, .dpo-JosephHospital .prov-svg, .dpo-Swedish .swed-svg, .dpo-PacificMedicalCenters .pacmed-svg, .dpo-Covenant .cov-svg, .dpo-CovenantMedicalGroup .cov-svg, .dpo-GraceClinics .grace-svg, .dpo-GraceClinic .grace-svg, .dpo-Kadlec .kadlec-svg{ display: inline; width: 24px; } .survey.swedish, .card-body[data-location-name*="Swedish"] .survey.providence { display: none; } .card-body[data-location-name*="Swedish"] .survey.swedish { display: inline-block; } /* Tablet Footer update */ @media screen and (min-width: 768px) and (max-width: 999.98px) { .footer-section-wrapper { margin: 0 24px; } #wm_footer .contact-btn { max-width: 25%; text-align: center; margin-bottom: 20px; } #footNote { padding: 28px 0; } #topFooter { padding-bottom: 28px; } } </style><title>Find a Doctor | Providence</title> <meta charset="utf-8"> <met |
| 2 |
Info
On line 1, column 111983 et="utf-8"> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <meta name="amp-google-client-id-api" content="googleanalytics"> <link rel="shortcut icon" type="image/ico" href="https://amp-providers.providence.org/7772/psj-icon.ico"> <link href="https://fonts.googleapis.com/css?family=Barlow:400,500,700&amp;display=swap" rel="stylesheet"> <style amp-boilerplate="">body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate="">body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript> <script async="" src="https://cdn.ampproject.org/v0.js"></script><meta name="description" content="Online Scheduling"> <link rel="canonical" href="https://www.providence.org/doctors"> <script async="" custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></script> <script async="" custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></script> <script async="" custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></script> <script async="" custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></script> <script src="~~assetOrigin~~/7914/2022-10-05_1330/js/linker.js" type="text/javascript" async=""></script> <meta name="amp-script-src" content="sha384-L3jywGcc3E1ACUxTtFt6BNXeVZqcPyjENkqAcS9U6rbMR2huRbFlYktyfnIwoBld"> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin=""> <link href="https://fonts.googleapis.com/css2?family=Lora&amp;display=swap" rel="stylesheet"> <script defer="" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCBMxL0BbkB81x980QosjGg02FtV6ZoFXs&amp;loading=async&amp;callback=mapRequirement&amp;v=3.55"></script> <script async="" src="https://cdn.ampproject.org/v0.js"></script> <script async="" custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script> <script async="" custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></script> <script async="" custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></script> </head><body><!-- Womp Search Results Page --><div id="womp_17723"> <amp-state id="wheelhouse" src="https://tealium.wheelhousedmg.com/i.gif?tealium_vid=CLIENT_ID(AMP_ECID_GOOGLE)"></amp-state> <amp-list id="tealium_analytics" src="amp-script:analyticsFuncs.data" layout="fixed-height" height="1px" width="auto" items="." single-item="" binding="always" data-no-nudge=""> <template type="amp-mustache"> <amp-analytics id="tealium_collect"> <script type="application/json"> { "vars": { "account": "providence", "profile": "main", "datasource": "neli68", "visitor_id": "CLIENT_ID(AMP_ECID_GOOGLE)", "gclidParam": "$IF(QUERY_PARAM(gclid), QUERY_PARAM(gclid), QUERY_PARAM(u_gclid))", "gclidCookie": "{{gclid}}", "gclid": "$IF(${gclidCookie}, ${gclidCookie}, ${gclidParam})", "hitTimestamp": "TIMESTAMP_ISO" }, "transport": { "beacon": false, "xhrpost": false, "image": true }, "requests": { "base_url": "https://pc-providence-collect.tealiumiq.com/vdata/i.gif", "base": "${base_url}?${tealium}&amp;${dom}&amp;${datetime}&amp;tealium_event=${tealium_event}&amp;tealium_event_type=${tealium_event_type}", "tealium": "tealium_account=${account}&amp;tealium_profile=${profile}&amp;datasource=${datasource}&amp;tealium_vid=${visitor_id}", "dom": "dom.viewport_width=${viewportWidth}&amp;dom.viewport_height=${viewportHeight}", "datetime": "tz=${timezone}&amp;lang=${browserLanguage}", "pageview": "${base}&amp;screen_size=${screenWidth}x${screenHeight}", "event": "${base}&amp;scroll_y=${scrollTop}&amp;scroll_x=${scrollLeft}" }, "extraUrlParams": { "hitTimestamp": "${hitTimestamp}", "clientID": "{{gaId}}", "user_agent": "${userAgent}", "utag_profile_version": "${profile} | ${datasource}", "ut.visitor_id": "${visitor_id}", "dom.title": "${title}", "dom.url": "${sourceUrl}", "dom.domain": "${sourceHost}", "dom.pathname": "${sourcePath}", "dom_title": "${title}", "dom_url": "${sourceUrl}", "dom_domain": "${sourceHost}", "dom_pathname": "${sourcePath}", "event_url": "${sourceHost}${sourcePath}", "experience_platform": "Web - AMP", "brand": "Providence", "web_auth_state": "Undetermined", "pageType": "~~pageType~~", "page_subtype": "~~page_subtype~~", "internalUser": "{{ internalUser }}", "userloc_state": "{{ state }}", "pcid": "${ampState(wheelhouse._pcid)}", "tealium_trace_id": "{{ traceId }}", "send_to_as": "true", "gclid": "${gclid}" }, "linkers": { "_gl": { "enabled": true, "destinationDomains": [ "*.nolink.wompmobile.com" ], "ids": { "_ga": "CLIENT_ID(_ga)" }, "proxyOnly": false } }, "cookies": { "enabled": true, "amp_pvid": { "value": "LINKER_PARAM(amp_pvid, pid)" } ,"_ga": { "value": "LINKER_PARAM(_gl, _ga)" } }, "triggers": { "reservationEnabledResults": { "on": "visible", "request": "event", "selector": "[data-track-reserve]", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Directory Journeys", "event_action": "Same Day Care Directory", "event_label": "Reservation Enabled Results" } }, "expressCareVirtualEnabled": { "on": "visible", "request": "event", "selector": "[data-ec-virtual-enabled]", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Directory Journeys", "event_action": "Same Day Care Directory", "event_label": "ExpressCare Virtual Enabled" } }, "timeslotClick": { "on": "click", "selector": "[data-vars-booking]", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Directory Journeys", "event_action": "Provider Directory", "event_label": "Timeslot Clicks" } }, "reservationClick": { "on": "click", "selector": "[data-track-reserve]", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Directory Journeys", "event_action": "Same Day Care Directory", "event_label": "Reservation Clicks" } }, "expresscareVirtualClick": { "on": "click", "selector": "[data-track-start]", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Directory Journeys", "event_action": "~~pageType~~", "event_label": "ExpressCare Virtual Clicks" } }, "phoneLinkClick": { "on": "click", "selector": "[data-vars-phone-link]", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Direct Contact", "event_action": "Phone Click-to-Call", "event_label": "${phoneLink}" } }, "locationAndMapClicks": { "on": "click", "selector": ".info .placename", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Key Engagements", "event_action": "Maps &amp; Directions Click", "event_label": "${eventLabel}" } }, "checkInsuranceVisible": { "on": "visible", "request": "event", "selector": ".check-insurance", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Personalization", "event_action": "Check Insurance Load", "event_label": "Insurance Check" } }, "checkInsuranceClick": { "on": "click", "selector": ".check-insurance", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Personalization", "event_action": "Check Insurance Click", "event_label": "Insurance Check" } }, "editInsuranceClick": { "on": "click", "selector": ".insurance-link", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Personalization", "event_action": "Edit Insurance Selection Click", "event_label": "Insurance Check" } }, "doesNotAcceptInsuranceVisible": { "on": "visible", "request": "event", "selector": ".does-not-accept-insurance", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Personalization", "event_action": "Does Not Accept Insurance Notification Visible", "event_label": "Insurance Check" } }, "acceptsInsuranceVisible": { "on": "visible", "request": "event", "selector": ".accepts-insurance", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Personalization", "event_action": "Accepts Insurance Notification", "event_label": "Insurance Check" } }, "insuranceClick": { "on": "click", "selector": ".insurance_suggestion", "request": "event", "vars": { "tealium_event": "womp_directory_event", "tealium_event_type": "event" }, "extraUrlParams": { "event_category": "Personalization", "event_action": "Insurance Selection Click", "event_label": "${eventLabel}" } } } } </script> </amp-analytics> <amp-analytics id="wheelhouse_collect"> <script type="application/json"> { "vars": { "account": "providence", "profile": "main", "datasource": "yerpj7", "visitor_id": "CLIENT_ID(AMP_ECID_GOOGLE)", "gclidParam": "$IF(QUERY_PARAM(gclid), QUERY_PARAM(gclid), QUERY_PARAM(u_gclid))", "gclidCookie": "{{gclid}}", "gclid": "$IF(${gclidCookie}, ${gclidCookie}, ${gclidParam})" }, "extraUrlParams": { "dom_domain": "${sourceHost}", "dom_pathname": "${sourcePath}", "dom_title": "${title}", "dom_url": "${sourceUrl}", "dom_viewport_height": "${viewportHeight}", "dom_viewport_width": "${viewportWidth}", "dom.domain": "${sourceHost}", "dom.pathname": "${sourcePath}", "dom.title": "${title}", "dom.url": "${sourceUrl}", "dom.viewport_height": "${viewportHeight}", "dom.viewport_width": "${viewportWidth}", "gclid": "${gclid}", "lang": "${browserLanguage}", "page_platform": "amp", "piwik_id": "${ampState(wheelhouse.piwik_vid)}", "tealium_account": "${account}", "tealium_profile": "${profile}", "tealium_vid": "${visitor_id}", "send_to_as": "true", "timestamp": "${timestamp}", "tz": "${timezone}" }, "transport": { "beacon": true, "xhrpost": false, "useBody": true, "image": false }, "requests": { "base_url": "https://tealium.wheelhousedmg.com/i.gif" }, "triggers": { "getDirectionsLabelClick": { "on": "click", "selector": ".info .placename", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Outbound Links", "event_action": "Maps &amp; Directions", "event_label": "${eventLabel}" } }, "trackTelephoneClicks": { "on": "click", "selector": "a[href^='tel:']", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Contact", "event_action": "Phone", "event_label": "${eventLabel}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackEmailClicks": { "on": "click", "selector": "a[href^='mailto:']", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Contact", "event_action": "Email", "event_label": "${eventLabel}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackApptClicks": { "on": "click", "selector": "[data-track-booking]:not([data-double-click])", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Schedule Appointment", "event_label": "${eventLabel}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackApptClicks2": { "on": "click", "selector": "[data-vars-booking]", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Schedule Appointment", "event_action": "${booking}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackSpecialtyClicks": { "on": "click", "selector": "[data-track-specialty]", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Schedule Appointment", "event_action": "Booking Start", "event_label": "${eventLabel}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackStartClicks": { "on": "click", "selector": "[data-track-start]", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Schedule Appointment", "event_action": "Start Your Visit Now", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackReserveClicks": { "on": "click", "selector": "[data-track-reserve]", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Schedule Appointment", "event_action": "Reserve My Spot", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackReserveWidgetStart": { "on": "click", "selector": ".reserveSpotFormCheckboxBtn", "request": "base_url", "extraUrlParams": { "event_category": "Reservations", "event_action": "Reservation Form Start", "event_label": "${eventLabel}" } }, "trackNoSearchResultsPwa": { "on": "click", "selector": "[data-no-search-results]", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Null Results", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackInsuranceSelectionProvider": { "on": "click", "selector": ".find_insurance a", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Patient Insurance Information", "event_label": "${eventLabel}", "cd64": "${eventLabel}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackInsuranceFilterSelection": { "on": "click", "selector": ".insurance_suggestions a", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Insurance Coverage Check Filter Provider Selected", "event_label": "${eventLabel}" } }, "trackInsuranceFilterCoverageMobile": { "on": "visible", "selector": ".find_insurance .no_insurance", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Insurance Coverage Check Filter Coverage Status", "event_label": "${eventLabel}" }, "visibilitySpec": { "repeat": true } }, "trackInsuranceFilterCoverageDesktop": { "on": "visible", "selector": ".desktop-insurance .no_insurance", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Insurance Coverage Check Filter Coverage Status", "event_label": "${eventLabel}" }, "visibilitySpec": { "repeat": true } }, "trackInsuranceFilterCoverageFound": { "on": "visible", "selector": ".find_insurance .insurance_found", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Insurance Coverage Check Filter Coverage Status", "event_label": "${eventLabel}" }, "visibilitySpec": { "repeat": true } }, "trackAcceptingNewPatientsToggle": { "on": "click", "selector": "button[data-type='clinicVisits']", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "acceptingNewPatients", "event_label": "true", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackOnlineBookingAvailableToggle": { "on": "click", "selector": "label[for='onlineBookingAvailable']", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Online Scheduling", "event_label": "${ampState(filterState.onlineBookingAvailableAnalytics)}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "getDirectionsClick": { "on": "click", "selector": ".aBtnDirections", "request": "event", "vars": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Get Directions", "event_label": "${locationName}" } }, "changeLanguage": { "on": "click", "selector": "button[data-vars-lang]", "request": "event", "vars": { "tealium_event": "amp_event", "event_category": "Provider Search", "event_action": "Change Language", "event_label": "${lang}" } }, "myVideoPause": { "on": "video-pause", "selector": "#philosophy_of_care_video", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "YouTube", "event_action": "Paused Video", "event_label": "${page_title}" } }, "recentlyViewedProviderWidgetLoad": { "on": "visible", "selector": "#recommendedDoctors .provider", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Recently Viewed Provider Widget", "event_action": "Provider Widget Loaded", "event_label": "${page_title}" } }, "trackRecentlyViewedProviderCardClick": { "on": "click", "selector": "#recommendedDoctors .provider", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Recently Viewed Provider Widget", "event_action": "Provider Widget Card Clicked", "event_label": "${page_title}" } } ,"trackTopFooterClicks": { "on": "click", "selector": "#topFooter a", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Navigation - Footer - Top", "event_action": "Primary", "event_label": "${eventLabel}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } }, "trackBottomFooterClicks": { "on": "click", "selector": "#footNote a", "request": "base_url", "extraUrlParams": { "tealium_event": "amp_event", "event_category": "Navigation - Footer - Bottom", "event_action": "Primary", "event_label": "${eventLabel}", "scroll_x": "${scrollLeft}", "scroll_y": "${scrollTop}" } } } } </script> </amp-analytics> </template> </amp-list> <amp-script id="analyticsFuncs" script="amp-list-source-script" nodom=""></amp-script> <script id="amp-list-source-script" type="text/plain" target="amp-script"> const data = async() => { /** <!-- childReplacement analyticsOrigin --> **/ const origin = window.location.origin.includes('preview-providence') || window.location.origin.includes('staging') ? 'https://providenceomnistaging.azureedge.net' : 'https://providenceomni.azureedge.net'; /** <!-- end childReplacement analyticsOrigin --> **/ const traceId = await fetch(`${origin}/cookie/GetCookieValue/trace_id`, { credentials: "include", method: "GET", mode: "cors" }) .then(res => res.json()) .then(res => res.value.length ? res.value : 'Not Set') .catch((err) => { console.log(err); let fromLocalStorage = localStorage.getItem('trace_id'); if (fromLocalStorage) { return fromLocalStorage; }; return "Undetermined"; }); const gclid = await fetch(`${origin}/cookie/GetCookieValue/u_gclid`, { credentials: "include", method: "GET", mode: "cors" }) .then(res => res.json()) .then(res => res.value.length ? res.value : false) .catch((err) => { console.log(err); return ''; }); const pixel = await fetch("https://s3-us-west-1.amazonaws.com/providence-internal/analytics.gif") .then((res) => res.status == 200 ? "True" : "False") .catch(() => "False"); const state = await fetch('https://wompservices.wompmobile.com/geoip/') .then(res => res.json()) .then(res => res.state ? res.state : "Undefined") .catch(() => { return "Undefined"; }); const gaCookie = await fetch(`${origin}/cookie/GetCookieValue/_ga`, { credentials: "include", method: "GET", mode: "cors" }) .then(res => res.json()) .then(res => res.value) .catch(() => ''); return { internalUser: pixel, state: state, traceId: traceId, gaId: gaCookie, gclid: gclid }; }; exportFunction('data', data); </script> <input type="checkbox" id="fullsearch" class="hidden"> <input type="checkbox" id="showmap" class="hidden"> <header class="bg-white"> <div id="wmHeader" class="onesite wmHeader"> <div id="topheader"> <nav> <ul> <li class="geo-locator"> <input type="checkbox" name="geo-locator-ck" id="geo-locator-ck" title="Current Location" aria-label="Current Location"> <label for="geo-locator-ck" class="nav-item" id="geoLocatorLabel" title="Current Location" aria-label="Current Location"> <svg aria-hidden="true" focusable="false" class="input-icon" role="presentation" width="17" height="22" viewBox="0 0 17 22" fill="#00338E" xmlns="http://www.w3.org/2000/svg"> <path d="M7.69102 21.0763C1.63693 12.4368 0.513184 11.5501 0.513184 8.375C0.513184 4.02574 4.09489 0.5 8.51318 0.5C12.9315 0.5 16.5132 4.02574 16.5132 8.375C16.5132 11.5501 15.3894 12.4368 9.33535 21.0763C8.93806 21.6413 8.08827 21.6412 7.69102 21.0763ZM8.51318 11.6562C10.3541 11.6562 11.8465 10.1872 11.8465 8.375C11.8465 6.56281 10.3541 5.09375 8.51318 5.09375C6.67223 5.09375 5.17985 6.56281 5.17985 8.375C5.17985 10.1872 6.67223 11.6562 8.51318 11.6562Z"> </path> </svg> <span>Detecting...</span> </label> <div id="geoLocatorMenu"> <h3>Provide Your Location</h3> <p>We'll use your location to find clinics, hospitals and doctors closest to you.</p> <form id="geoLocator-form" action="https://www.providence.org/doctors/search" method="get" target="_top"> <div class="typeahead__query"> <input type="search" id="geoLocatorInput" title="globalGeoInputLabel" aria-label="Set Your Location" placeholder="Zip code or city" autocomplete="off" max="100" class="textInput"> <div class="suggestion" id="geoLocatorSuggestions"> <label id="geoUseCurrentLocation" class="useLocation"> <svg viewBox="0 0 19 19" fill="#00338E"> <path d="M16.0823 0.200195L1.45728 6.9502C-0.230225 7.72363 0.332275 10.2197 2.12524 10.2197H8.31274V16.4072C8.31274 18.2002 10.8088 18.7627 11.5823 17.0752L18.3323 2.4502C18.8948 1.11426 17.4182 -0.362305 16.0823 0.200195Z"> </path> </svg> Use my current location </label> </div> </div> </form> <label for="geo-locator-ck" id="geoLocatorClear" title="Exit Current Location Menu" aria-label="Exit Current Location Menu"> <span class="screenreader">Exit Current Location Menu</span> <svg class="icon icon-cross" id="icon-cross" viewBox="0 0 32 32"> <path d="M31.708 25.708c-0-0-0-0-0-0l-9.708-9.708 9.708-9.708c0-0 0-0 0-0 0.105-0.105 0.18-0.227 0.229-0.357 0.133-0.356 0.057-0.771-0.229-1.057l-4.586-4.586c-0.286-0.286-0.702-0.361-1.057-0.229-0.13 0.048-0.252 0.124-0.357 0.228 0 0-0 0-0 0l-9.708 9.708-9.708-9.708c-0-0-0-0-0-0-0.105-0.104-0.227-0.18-0.357-0.228-0.356-0.133-0.771-0.057-1.057 0.229l-4.586 4.586c-0.286 0.286-0.361 0.702-0.229 1.057 0.049 0.13 0.124 0.252 0.229 0.357 0 0 0 0 0 0l9.708 9.708-9.708 9.708c-0 0-0 0-0 0-0.104 0.105-0.18 0.227-0.229 0.357-0.133 0.355-0.057 0.771 0.229 1.057l4.586 4.586c0.286 0.286 0.702 0.361 1.057 0.229 0.13-0.049 0.252-0.124 0.357-0.229 0-0 0-0 0-0l9.708-9.708 9.708 9.708c0 0 0 0 0 0 0.105 0.105 0.227 0.18 0.357 0.229 0.356 0.133 0.771 0.057 1.057-0.229l4.586-4.586c0.286-0.286 0.362-0.702 0.229-1.057-0.049-0.13-0.124-0.252-0.229-0.357z"> </path> </svg> </label> </div> </li> <li id="default-submenu"> <ul id="onesite-submenu"> <li class="desktoponly navbar-pipe-wrapper"> <span class="navbar-pipe"></span> </li> <li class="desktoponly"><a href="https://mychartor.providence.org/mychart/default.asp?mode=stdfile&amp;option=login-help">MyChart</a> </li> <li class="desktoponly"><a title="Pay My Bill" aria-label="Pay my bill" href="https://www.providence.org/obp">Pay My Bill</a></li> <li class="desktoponly"><a title="Careers" aria-label="Careers top navigation link" href="https://providence.jobs/" target="_blank">Careers</a></li> <li> <div class="account-wrapper"> <div class="my-account verified hidden"> <input type="checkbox" name="menuoptns" id="d_submenu6"> <label for="d_submenu6"> <svg class="account" aria-hidden="true" role="img"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#account-icon"></use> </svg> <span id="mychart-user"></span> <svg class="chevron-down-icon"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chevron-down"></use> </svg> </label> <ul class="submenu" data-submenu-name="My Account" id="account-menu"> </ul> </div> <div class="my-account unverified"> <a href="https://providence.org/care-dashboard" class="account-btn"> <svg class="account" aria-hidden="true" role="img"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#account-icon"></use> </svg> My Account </a> </div> </div> </li> </ul> </li> </ul> </nav> </div> <input type="checkbox" value="menu" name="menuoptns" id="menu" aria-label="Menu Toggle"> <label for="menu" class="overlay menu-overlay" title="Menu Overlay" aria-label="Menu Overlay"> <span class="screenreader">Menu Overlay</span> </label> <div id="header"> <a class="logo onesite" href="https://www.providence.org"> <span class="screenreader">Providence Home</span> <img alt="Providence Logo" src="https://amp-providers.providence.org/7772/Providence-Logo-SVG.svg" width="200" height="38"> </a> <div class="mobile-menu-list"> <label for="site-search" class="search mobileonly" title="Search Button" aria-label="Search Button"> <span class="screenreader">Search Button</span> <svg class="search-icon pointer-events-none"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search"></use> </svg> </label> <label for="menu" class="menu"> <svg> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#menu-icon"></use> </svg> </label> <label for="menu" class="close-menu"> <svg> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#close"></use> </svg> </label> </div> </div> <nav class="main-nav"> <ul id="top-nav"> <li> <a title="Find a Doctor" href="https://www.providence.org/doctors" data-vars-event-label="Find a Doctor">Find a Doctor </a> </li> <li> <a title="Locations" href="https://www.providence.org/locations" data-vars-event-label="Locations">Locations </a> </li> <li> <input type="checkbox" name="services-submenu" id="services-input" title="Toggle Services Submenu"> <label tabindex="0" for="services-input" class="services-input-label"> <a href="https://www.providence.org/services" title="Services" data-vars-event-label="Services" data-vars-event-action="Services">Services</a> <span class="toggle-close mobileonly"> <svg class="chevron-up-icon"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chevron-up"></use> </svg> </span> <span class="toggle-open mobileonly"> <svg class="chevron-down-icon"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chevron-down"></use> </svg> </span> </label> <ul class="submenu" data-submenu-name="Services"> <li class="item0 even first"> <a href="https://www.providence.org/services/cancer" data-vars-event-label="Cancer" data-vars-event-action="Services">Cancer</a> </li> <li class="item1 odd"> <a href="https://www.providence.org/services/heart-and-vascular" data-vars-event-label="Heart &amp; Vascular" data-vars-event-action="Services">Heart &amp; Vascular</a> </li> <li class="item2 even"> <a href="https://www.providence.org/services/neuroscience" data-vars-event-label="Neurosciences" data-vars-event-action="Services">Neurosciences</a> </li> <li class="item3 odd"> <a href="https://www.providence.org/services/orthopedics" data-vars-event-label="Orthopedics" data-vars-event-action="Services">Orthopedics</a> </li> <li class="item4 odd"> <a href="https://www.providence.org/services/primary-care" data-vars-event-label="Primary Care" data-vars-event-action="Services">Primary Care</a> </li> <li class="item5 even"> <a href="https://www.providence.org/services/pediatrics" data-vars-event-label="Pediatrics" data-vars-event-action="Services">Pediatrics</a> </li> <li class="item6 odd"> <a href="https://www.providence.org/services/maternity" data-vars-event-label="Maternity" data-vars-event-action="Services">Maternity</a> </li> <li class="item7 odd"> <a href="https://www.providence.org/locations?postal=&amp;lookup=94ffc90a6880468d9e6b2f25c1f15bd4&amp;lookupvalue=ExpressCare+and+Urgent+Care&amp;page=1&amp;radius=&amp;term=" data-vars-event-label="Urgent Care" data-vars-event-action="Services">Urgent Care</a> </li> <li class="item8 even last"> <a href="https://www.providence.org/services" title="View all" data-vars-event-label="View all" data-vars-event-action="Services">View all</a> </li> </ul> </li> <li> <input type="checkbox" name="get-care-submenu" id="get-care-input" title="Toggle Get Care Submenu"> <label tabindex="0" for="get-care-input" class="get-care-input-label"> Get Care <span class="toggle-close mobileonly"> <svg class="chevron-up-icon"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chevron-up"></use> </svg> </span> <span class="toggle-open mobileonly"> <svg class="chevron-down-icon"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chevron-down"></use> </svg> </span> </label> <ul class="submenu" data-submenu-name="Get Care"> <li class="item0 even first"> <a href="https://www.providence.org/doctors" data-vars-event-label="See a Doctor" data-vars-event-action="Get Care">See a Doctor</a> </li> <li class="item1 odd"> <a href="https://www.providence.org/our-services/urgent-care" data-vars-event-label="Same Day Care" data-vars-event-action="Get Care">Same Day Care</a> </li> <li class="item2 even"> <a href="https://www.providence.org/lp/virtual-care-options" data-vars-event-label="Get Care Virtually" data-vars-event-action="Get Care">Get Care Virtually</a> </li> <li class="item3 odd last"> <a rel="noopener noreferrer" href="https://mychartor.providence.org/mychart/Authentication/Login?mode=stdfile&amp;option=login-help" target="_blank" data-vars-event-label="Schedule with MyChart" data-vars-event-action="Get Care">Schedule with MyChart </a> </li> </ul> </li> <li> <input type="checkbox" name="billing-support-submenu" id="billing-support-input" title="Toggle Billing Support Submenu"> <label tabindex="0" for="billing-support-input" class="billing-support-input-label"> Billing Support <span class="toggle-close mobileonly"> <svg class="chevron-up-icon"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chevron-up"></use> </svg> </span> <span class="toggle-open mobileonly"> <svg class="chevron-down-icon"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chevron-down"></use> </svg> </span> </label> <ul class="submenu last" data-submenu-name="Billing Support"> <li class="item0 even first"> <a title="Online Bill Pay" href="https://www.providence.org/obp/ca/bill-pay" data-vars-event-label="Online Bill Pay" data-vars-event-action="Online Bill Pay">Online Bill Pay</a> </li> <li class="item1 odd"> <a title="Help Paying Your Bill" href="https://www.providence.org/obp/ca/financial-assistance" data-vars-event-label="Help Paying Your Bill" data-vars-event-action="Help Paying Your Bill">Help Paying Your Bill</a> </li> <li class="item2 even"> <a title="Understanding Your Medical Bill" href="https://www.providence.org/obp/ca/about-your-bill" data-vars-event-label="Understanding Your Medical Bill" data-vars-event-action="Understanding Your Medical Bill">Understanding Your Medical Bill</a> </li> <li class="item3 odd last"> <a title="Get a Price Estimate" href="https://www.providence.org/obp/ca/pricing-transparency" target="_blank" data-vars-event-label="Get a Price Estimate" data-vars-event-action="Get a Price Estimate">Get a Price Estimate</a> </li> </ul> </li> <li class="mobileonly"> <a title="Pay My Bill" data-vars-event-label="Pay My Bill" href="https://www.providence.org/obp">Pay My Bill</a> </li> <li class="mobileonly"> <a title="MyChart" data-vars-event-label="MyChart" href="https://mychartor.providence.org/mychart/default.asp?mode=stdfile&amp;option=login-help">MyChart</a> </li> <li class="mobileonly"><a title="Careers" aria-label="Careers top navigation link" href="https://providence.jobs/" target="_blank">Careers</a></li> </ul> </nav> <span class="search-container"> <input type="checkbox" value="search" name="menuoptns" id="site-search" aria-labelledby="site-search-id" aria-label="Menu Options" title="Menu Options"> <label for="site-search" class="overlay search-clear" aria-label="Menu Clear"> <span class="screenreader">Menu Clear</span> </label> <div class="site-search"> <div class="search-form"> <div class="typeahead"> <a id="search-link" href="https://www.providence.org/search-results" [href]="'https://www.providence.org/search-results?' + searchTerm"> <butt |
| 3 |
Error
On line 1, column 118880 archTerm"> <button type="submit" aria-label="Search Button" title="Search Button"> <span class="screenreader">Search Button</span> <svg class="search-icon pointer-events-none"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search"></use> </svg> </button> </a> <input id="search-input" autofocus="" on="input-throttled:AMP.setState({ searchTerm: event.value })" type="search" placeholder="Search..." title="Search Field" aria-label="Search Field"> <label for="site-search" class="search-clear-label" aria-label="Menu Clear"> <svg> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#close"></use> </svg> </label> <div class="suggestions"></div> </div> </div> </div> <label for="site-search" class="search desktoponly" id="site-search-id" title="Search" aria-label="Search"> <svg> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search"></use> </svg> </label> </span> </div> <div class="container search-filters"> <div class="row"> <div class="col-xs-12"> <div id="searchBox"> <div class="input-group mb-3 input-hover"> <label for="query"> <svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg> </label> <input type="text" class="form-control textInput" placeholder=" " aria-label="Search box" spellcheck="true" name="q" id="query" autocomplete="off"> <label class="hover-placeholder">Search by name, specialty, symptom, or condition, etc.</label> <label id="qClear" for="queryClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <button id="search-submit">Submit</button> </div> </div> <div id="filterBy">Filter by:</div> <div class="col-xs-12" id="visitTypeFilters"> <div class="btn-group" role="group" aria-label="Basic example" id="visitTypeButtons"> <button type="button" class="btn active visitType hidden" data-type="all"> All Providers </button> <button type="button" class="btn visitType" data-type="clinicVisits"> <span class="toggle"></span> Accepting New Patients </button> <button type="button" class="btn visitType onlineScheduling" data-type="onlineScheduling"> <span class="toggle"></span> Online Scheduling </button> <button type="button" class="btn visitType videoVisits" data-type="videoVisits"> <span class="toggle"></span> Offers Video Visit </button> </div> <div class="dropdown facet mt-1" id="Availability"> <button class="btn dropdown-toggle" type="button" id="AvailabilityLabel"> Availability <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div id="availability-dropdown-menu" class="dropdown-menu" aria-labelledby="AvailabilityLabel"> <ul> <li> <small class="dc location-label">Time of Day</small> <ul id="timesList" class="filtered-list"> <li> <a tabindex="0" class="dropdown-item dc time-value active" id="any"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Anytime</a> <a tabindex="0" class="dropdown-item dc time-value" id="morning"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Morning</a> <a tabindex="0" class="dropdown-item dc time-value" id="afternoon"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Afternoon</a> </li> </ul> <select id="time-selector" class="hidden"> <option value="any">Any Time</option> <option value="morning">Morning</option> <option value="afternoon">Afternoon</option> </select> </li> <li class="availability-divider"></li> <li> <small class="dc location-label">Days</small> <ul id="daysList" class="filtered-list"> <li> <a tabindex="0" class="dropdown-item dc time-value active" id="any-day"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Any day</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Monday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Monday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Tuesday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Tuesday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Wednesday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Wednesday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Thursday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Thursday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Friday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Friday</a> </li> </ul> <select id="day-selector" class="hidden"> <option value="any-day">Any Day</option> <option value="specific-day">Specific Days</option> </select> <div id="availability-days-checkboxes" class="hidden"> </div> </li> </ul> </div> </div> </div> <div class="col-xs-12" id="advancedFilters"> <div id="advancedFilters"> <div |
| 4 |
Info
On line 1, column 118853 archTerm"> <button type="submit" aria-label="Search Button" title="Search Button"> <span class="screenreader">Search Button</span> <svg class="search-icon pointer-events-none"> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search"></use> </svg> </button> </a> <input id="search-input" autofocus="" on="input-throttled:AMP.setState({ searchTerm: event.value })" type="search" placeholder="Search..." title="Search Field" aria-label="Search Field"> <label for="site-search" class="search-clear-label" aria-label="Menu Clear"> <svg> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#close"></use> </svg> </label> <div class="suggestions"></div> </div> </div> </div> <label for="site-search" class="search desktoponly" id="site-search-id" title="Search" aria-label="Search"> <svg> <use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search"></use> </svg> </label> </span> </div> <div class="container search-filters"> <div class="row"> <div class="col-xs-12"> <div id="searchBox"> <div class="input-group mb-3 input-hover"> <label for="query"> <svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg> </label> <input type="text" class="form-control textInput" placeholder=" " aria-label="Search box" spellcheck="true" name="q" id="query" autocomplete="off"> <label class="hover-placeholder">Search by name, specialty, symptom, or condition, etc.</label> <label id="qClear" for="queryClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <button id="search-submit">Submit</button> </div> </div> <div id="filterBy">Filter by:</div> <div class="col-xs-12" id="visitTypeFilters"> <div class="btn-group" role="group" aria-label="Basic example" id="visitTypeButtons"> <button type="button" class="btn active visitType hidden" data-type="all"> All Providers </button> <button type="button" class="btn visitType" data-type="clinicVisits"> <span class="toggle"></span> Accepting New Patients </button> <button type="button" class="btn visitType onlineScheduling" data-type="onlineScheduling"> <span class="toggle"></span> Online Scheduling </button> <button type="button" class="btn visitType videoVisits" data-type="videoVisits"> <span class="toggle"></span> Offers Video Visit </button> </div> <div class="dropdown facet mt-1" id="Availability"> <button class="btn dropdown-toggle" type="button" id="AvailabilityLabel"> Availability <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div id="availability-dropdown-menu" class="dropdown-menu" aria-labelledby="AvailabilityLabel"> <ul> <li> <small class="dc location-label">Time of Day</small> <ul id="timesList" class="filtered-list"> <li> <a tabindex="0" class="dropdown-item dc time-value active" id="any"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Anytime</a> <a tabindex="0" class="dropdown-item dc time-value" id="morning"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Morning</a> <a tabindex="0" class="dropdown-item dc time-value" id="afternoon"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Afternoon</a> </li> </ul> <select id="time-selector" class="hidden"> <option value="any">Any Time</option> <option value="morning">Morning</option> <option value="afternoon">Afternoon</option> </select> </li> <li class="availability-divider"></li> <li> <small class="dc location-label">Days</small> <ul id="daysList" class="filtered-list"> <li> <a tabindex="0" class="dropdown-item dc time-value active" id="any-day"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Any day</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Monday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Monday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Tuesday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Tuesday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Wednesday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Wednesday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Thursday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Thursday</a> <a tabindex="0" class="dropdown-item dc time-value day" id="Friday"> <svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg> <svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Friday</a> </li> </ul> <select id="day-selector" class="hidden"> <option value="any-day">Any Day</option> <option value="specific-day">Specific Days</option> </select> <div id="availability-days-checkboxes" class="hidden"> </div> </li> </ul> </div> </div> </div> <div class="col-xs-12" id="advancedFilters"> <div |
| 5 |
Info
On line 1, column 123857 dFilters"> <div class="dropdown facet mt-1" id="InsuranceAccepted"> <button class="btn dropdown-toggle" type="button" id="InsuranceAcceptedLabel"> Insurance <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div class="dropdown-menu" aria-labelledby="InsuranceAcceptedLabel"></div> </div> <div class="dropdown facet mt-1" id="PrimarySpecialties"> <button class="btn dropdown-toggle" type="button" id="PrimarySpecialtiesLabel"> Specialty <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div class="dropdown-menu" aria-labelledby="PrimarySpecialtiesLabel"></div> </div> <div class="dropdown facet mt-1" id="ProviderOrganization"> <button class="btn dropdown-toggle" type="button" id="MedicalGroupsLabel">Medical Group <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span></button> <div class="dropdown-menu" aria-labelledby="MedicalGroupsLabel"></div> </div> <div class="dropdown facet mt-1" id="Gender"> <button class="btn dropdown-toggle" type="button"> Gender <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div class="dropdown-menu" aria-labelledby="GenderLabel"></div> </div> <div class="dropdown facet mt-1" id="Languages"> <button class="btn dropdown-toggle" type="button" id="LanguagesLabel"> Language <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div class="dropdown-menu" aria-labelledby="LanguagesLabel"></div> </div> <div class="dropdown organization mt-1" id="organization" style="display: none;"> <button class="btn dropdown-toggle" type="button" id="OrganizationLabel"> Organization: <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div class="dropdown-menu" aria-labelledby="OrganizationLabel"> <ul> <li> <a class="dropdown-item facet-value" data-organization="pacmed"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Pacific Medical</a> </li> <li> <a class="dropdown-item facet-value" data-organization="providence"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>Providence</a> </li> </ul> </div> </div> <div class="dropdown location mt-1" id="Location"> <button class="btn dropdown-toggle" type="button" id="LocationLabel"> Location: Detecting... <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span> </button> <div class="dropdown-menu" aria-labelledby="LocationLabel"> <ul> <li> <div class="dropdown-item flex flex-columns"> <div><label for="searchRadius">Within</label><input type="number" id="searchRadius" class="dc textInput"><span>miles of:</span></div> <span class="dc location-label area-label">City, State, or Zip Code</span> <amp-state id="locationState"> <script type="application/json"> { "validZip": 1 } </script> </amp-state> <input type="text" name="location" class="dc textInput" [class]="(locationState.validZip != 0 ? 'dc textInput' : 'dc textInput invalid')" id="custom-location"> <div |
| 6 |
Info
On line 1, column 123942 location"> <div class="hidden" [class]="(locationState.validZip != 0 ? 'hidden' : 'tool_tip')">Invali |
| 7 |
Info
On line 1, column 126253 ol_tip')">Invalid Location Entered</div> <span class="dc location-label">Clinic</span> <select class="desktop-only" id="location-selection"> <option>--Selected a location</option> </select> <div class="mobile-only" id="locationSelection"> <input type="checkbox" id="locationSelectionInput" class="hidden" name="locationSelectionInput" value=""> <label id="locationSelectionLabel" for="locationSelectionInput"> <span id="locationSelectionText">Select a Clinic</span> <span class="dd-svg"> <svg width="12" height="7" class="loc-prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg> <svg width="12" height="8" class="loc-prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg> </span> </label> <div id="mobile-location-selection" class="dropdown-menu mobile-only"></div> </div> <button class="btn-primary" id="updateLocation"> Update </button> <div> <input type="checkbox" id="switch" class="dc"> <label for="switch" class="useLocation dc"> <span id="switchLabel" class="useLocation dc">Toggle</span> <small class="useLocation dc" style="padding: 0 5px">Use my current location</small> </label> </div> <div> <input type="checkbox" id="switchDistanceSort" class="dc"> <label for="switchDistanceSort" class="distanceSort dc"> <span id="switchDistanceSortLabel" class="distanceSort dc">Toggle</span> <small class="distanceSort dc" style="padding: 0 5px">Sort clinics by distance</small> </label> </div> </div> </li> </ul> </div> </div> </div> </div> <div class="pos-rel" id="filterWrapper"> <div class="col-xs-12 badges" id="filter-buttons"> </div> <button class="btn btn-link mt-1" id="clearFilters">Clear All Filters</button> </div> <div class="col-xs-12" id="mobileSearchWrapper"> <button class="mobile-only" id="search-submit2">Submit</button> <label for="fullsearch" class="btn mobile-only close-menu">Close</label> </div> </div> <amp-state id="insuranceState"> <script type="application/json"> { "insurance_q": "" } </script> </amp-state> <form id="setInsuranceSection" class="hidden" method="GET" action="https://www.providence.org/integrationapi/SetInsuranceSelection" action-xhr="https://www.providence.org/integrationapi/SetInsuranceSelection" target="_top"> <input type="text" name="plan" value="" [value]="insurance_q"> <inpu |
| 8 |
Error
On line 1, column 346834 urance_q"> <input type="submit" value="Submit"> </form> </div></header> <main> <section id="map" class="show-map"> <div id="virtual-only" style=" width: 100%; height: 100%; "> <picture style="float: right;width: auto;height: 100%;background: white;"> <source media="(min-width: 1000px)" srcset="https://az690879.vo.msecnd.net/7914/DexCare_VirtualProviders_lg.png"> <img src="https://az690879.vo.msecnd.net/7914/DexCare_VirtualProviders_sm.png" alt="Virtual Visit Options" style="width: auto;max-height: 400px;margin: 0 auto;display: block;"> </picture> </div> <div id="wm_map"></div> </section> <section class="container" id="results-wrapper"> <div class="row"> <div class="col-xs-12"> <div class="topbtns flex"> <label for="fullsearch" class="btn">Filter Results</label> <label for="showmap" class="togglebtn btn" id="maptoggle"><span></span>Map</label> </div> </div> <div class="col-xs-12 col-md-6 py-3"> <div id="results"> <div class="loader"></div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-md-6" id="pagination"></div> </div> </section> </main> <script> function clearInput(id) { var elem = document.getElementById(id); var event = new Event('input', { bubbles: true, cancelable: true, }); elem.value = ''; elem.dispatchEvent(event); if(id="query"){ document.getElementById("searchBox").classList.remove('is-dirty'); } } </script> <script id="searchScripts" type="text/javascript"> function mapRequirement() { /* google's javascript api requires a callback */ console.log('Google Map Loaded'); window.mapLoaded = true; } sessionStorage.removeItem("online_scheduling_filter"); const DAYS = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; /** <!-- childReplacement provOmniLoginStatus --> **/ let myChartLoggedIn = false; async function checkLoginStatus() { let geolocator = document.querySelector('#geoLocatorInput'); const myAccountWrapper = document.querySelector('#topheader .account-wrapper'); const provUrl = siteSearch.isStaging ? 'https://preview-providence.provhealth.org' : 'https://www.providence.org'; return fetch(`${provUrl}/identitynav/getjson`, { credentials: "include" }) .then(resp => resp.json()) .then(resp => { myChartLoggedIn = true; if(myAccountWrapper) myAccountWrapper.classList.remove('hidden'); if(resp && resp.children.length > 0) { /* User verified Populate & stop hiding user dropdown menu */ const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(loginButton) loginButton.classList.add('hidden'); if(myAccountDiv) myAccountDiv.classList.remove('hidden'); let myChartUrl = ''; /* Set username labels */ if(myAccountDiv) { const userLabel = myAccountDiv.querySelector('#mychart-user'); if(userLabel) userLabel.innerHTML = resp.text; const homeItem = resp.children.filter(l => l.text === "MyChart Home")[0]; if(homeItem) { /* Remove '/home' from mychart url */ myChartUrl = homeItem.href.slice(0, -5); } /* Populate dropdown menu */ const accountMenu = document.querySelector('#account-menu'); accountMenu.innerHTML = ''; resp.children.forEach(link => { const menuItem = document.createElement('li'); const menuItemLink = document.createElement('a'); menuItemLink.classList.add('mychart'); menuItemLink.href = link.href; menuItemLink.innerText = link.text; if(menuItemLink.href.includes('mychart')) { /* From Dustin B: if the user does not have an active session on the MyChart side, going directly to a deep link will present them with the login screen. This is not ideal, since we already know they have an active session with the external identity provider by virtue of being logged in to the Providence website. Unfortunately, MyChart does not have a mechanism to automatically obtain that session and redirect them to the deep-linked page. To get around this, we have a script on our side that overrides the click event on any MyChart links. */ menuItemLink.addEventListener('click', click => { click.preventDefault(); click.stopImmediatePropagation(); /* In a new tab, open MyChart link that automatically obtains the session and returns the user to the MyChart home page */ let other = window.open(`${myChartUrl}/Authentication/SAML/Login?idp=SSO`, "_blank"); if(other !== null && menuItemLink.getAttribute('href').toLowerCase().indexOf('/home') === -1) { /* Listen for a message from MyChart */ window.addEventListener("message", function(e) { /* Verify the sender and message */ if (myChartUrl.toLowerCase().indexOf(e.origin.toLowerCase()) === 0 && e.data === "Hello from MyChart") { /* Redirect the user to the deep link */ other.location = menuItemLink.getAttribute("href"); } }); } }); } menuItem.appendChild(menuItemLink); if(accountMenu) accountMenu.appendChild(menuItem); /* Show account menu now that it's populated */ myAccountDiv.classList.remove('hidden'); }); } } else if(resp.method === "POST") { myChartLoggedIn = false; /* remove account form if geo locator set to WA if(!geolocator || /, wa/gi.test(geolocator.value.toLowerCase())) { if(myAccountWrapper) myAccountWrapper.classList.add('hidden'); return; }*/ /* Update login post action url */ const myAccountLogin = document.querySelector('.my-account.unverified'); myAccountLogin.classList.remove('hidden'); const myAccountLoginAnchor = document.querySelector('#accountLogin'); myAccountLoginAnchor.addEventListener('click', e => { e.preventDefault(); fetch(resp.action, { method: "post" }); }); /*myAccountLogin.innerHTML = ''; const loginForm = document.createElement('form'); loginForm.setAttribute('action', resp.action); loginForm.setAttribute('method', "post"); loginForm.innerHTML = '<button type="submit" class="account-btn"><svg class="account" aria-hidden="true" role="img"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#account-icon"></use></svg>My Account</button>'; myAccountLogin.append(loginForm);*/ } else { const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(myAccountDiv) myAccountDiv.classList.add('hidden'); if(loginButton) { loginButton.classList.remove('hidden'); myAccountWrapper.classList.remove('hidden'); }; } }).catch(e => { console.error("Failed to receive users MyChart identity status", e); const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(myAccountDiv) myAccountDiv.classList.add('hidden'); if(loginButton) { loginButton.classList.remove('hidden'); myAccountWrapper.classList.remove('hidden'); }; }); } /** <!-- end childReplacement provOmniLoginStatus --> **/ class Search { constructor(fetchUrl) { this.fetchUrl = fetchUrl; }; form = document.querySelector("#search-submit"); form2 = document.querySelector("#search-submit2"); input = document.querySelector("#query"); autocomplete = document.querySelector('#autocomplete'); textInputs = document.querySelectorAll(".textInput"); resultsEl = document.querySelector("#results"); paginationEl = document.querySelector("#pagination"); mapEl = document.querySelector('#map'); showMapEl = document.querySelector('#showmap'); headerEl = document.querySelector('header'); distanceInput = document.querySelector("#distance"); logo = document.querySelector('#header-logo'); isStaging = /staging/gi.test(window.location.hostname); apiSub = this.isStaging ? 'womphealthdevapi' : 'womphealthapi'; /* if user passes query param hos=true, we removed the IsClinic=true filter param from our call womphealth api. This allows Providence to link into the directory and closely match the sitecore provider listings by displaying ALL providers at a location, even if that provider only has the location listed as type HOS */ hosFlag = false; helpers = { async fetchWithTimeout(resource, options = {}) { const { timeout = 1000 } = options; const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); const response = await fetch(resource, { ...options, signal: controller.signal }); clearTimeout(id); return response; }, getCID: () => { let cid = localStorage.getItem('cid'); /* If we DON'T have a CID saved to localStorage... */ if (!cid || !cid.length) { /* If crypto's randomUUID func is available, create a CID with it */ if (typeof crypto?.randomUUID == 'function') { cid = crypto.randomUUID(); } /* Else, go old school... */ else { cid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /* Save the new cid to localStorage */ localStorage.setItem('cid', cid); } /* Send CID to the requestor */ return cid; }, getAvailability(state) { const availableDays = state?.filters?.AvailableDays === undefined ? [0, 1, 2, 3, 4, 5, 6] : Object.entries(state?.filters?.AvailableDays ?? {}).filter(([day, value]) => !state.specificDays || value.active).map(([day,]) => DAYS.indexOf(day)); return (availableDays.length === 0 ? [0, 1, 2, 3, 4, 5, 6] : availableDays).join(","); }, slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); }, debounce(func, delay = 200){ let timeout = null; return function(){ let context = this; let args = arguments; clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args) }, delay); }; }, getCookie(name) { if (name == "trace_id" && localStorage.getItem('trace_id')) { return localStorage.getItem('trace_id'); } const match = document.cookie.match(new RegExp(`(?=${name}=)([^;]*)`)); if (match) { return match[0].split('=')[1]; } }, reportToWheelhouse: async (data) => { try { /* Grabbing URL params from the window's current location */ let tempParams = new URLSearchParams(window.location.search); /* Save the Google ID for Wheelhouse lookups to session storage */ let ampEcidGoogle = sessionStorage.getItem('ampEcidGoogle'); if (!ampEcidGoogle) { ampEcidGoogle = this.helpers.getCookie('AMP_ECID_GOOGLE'); sessionStorage.setItem('ampEcidGoogle', ampEcidGoogle) } let wheelhouse = sessionStorage.getItem('wheelhouseData'); if (!wheelhouse) { const data = await fetch(`https://tealium.wheelhousedmg.com/i.gif?tealium_vid=${ampEcidGoogle}&__amp_source_origin=${encodeURIComponent(window.location.origin)}`) .then(res => res.json()); sessionStorage.setItem('wheelhouseData', JSON.stringify(data)); wheelhouse = data; } else { wheelhouse = JSON.parse(wheelhouse); }; let gclid = this.helpers.getCookie('u_gclid'); if (!gclid) { for (let p of ['gclid', 'u_gclid']) { /* If the linkers are set in the URL, use that value */ const tempParam = tempParams.get(p); if (tempParam) { gclid = tempParam; /* If we find a param, we don't need to continue looping through possible params. */ continue; } } } let piwik_vid = wheelhouse.piwik_vid; let tealium_vid = wheelhouse.tealium_vid; let dt = new Date(); let timezoneOffset = dt.getTimezoneOffset(); let tData = { dom_domain:window.location.host, dom_pathname:window.location.pathname, dom_title: window.document.title, dom_url: window.location.href, dom_viewport_height: window.innerHeight, dom_viewport_width: window.innerWidth, "dom.domain": window.location.host, "dom.pathname": window.location.pathname, "dom.title": window.document.title, "dom.url": window.location.href, "dom.viewport_height": window.innerHeight, "dom.viewport_width": window.innerWidth, lang: window.navigator.language, page_platform: "amp", piwik_id: piwik_vid, tealium_account: "providence", tealium_profile: "main", tealium_vid: tealium_vid, timestamp: Date.now(), tz: timezoneOffset, "send_to_as": "true", tealium_event:"amp_event" }; if (gclid) { tData.gclid = gclid; } for (const d of data) { tData[d.key] = d.value; }; let tealiumUrl = `https://tealium.wheelhousedmg.com/i.gif`; return fetch(tealiumUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(tData) }) .then(resp => resp.json()) .then(json => { console.log('wheelhouse beacon resp: ', json); let providers = document.querySelectorAll(".provider-link, .schedule-appt, .sched-dis"); let providerSlots = document.querySelectorAll("directory-timeslots"); const linkers = { _gl: { type: "cookie", enabled: true, encode: true, overwrite: false, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ], id: '_ga', linkerParams: ['_gl', '_ga'] }, gclid: { type: "cookie", enabled: true, encode: false, createCookie: true, overwrite: true, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ], id: 'u_gclid', linkerParams: ['gclid', 'u_gclid'] } }; const template = { type: "custom", enabled: true, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ] }; linkers._pcid = {...template, id: "pcid", value: `${json._pcid}`}; for(let i = 0;i < providers.length; i++) { providers[i].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); providers[i].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } function onVisible(element, callback) { new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if(entry.intersectionRatio > 0) { callback(element); observer.disconnect(); } }); }).observe(element); if(!callback) return new Promise(r => callback=r); } /* Wait for shadowroot to load */ for(let i = 0;i < providerSlots.length; i++) { onVisible(providerSlots[i], () => { let slotLinks = providerSlots[i].shadowRoot.querySelectorAll(".schedule-appt, .sched-dis"); for(let j = 0;j < slotLinks.length; j++) { slotLinks[j].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); slotLinks[j].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } }); } /* Get initial shadowroot load, sometimes the Intersection observer isn't catching it */ setTimeout(() => { for(let i = 0;i < providerSlots.length; i++) { let slotLinks = providerSlots[i].shadowRoot.querySelectorAll(".schedule-appt, .sched-dis"); for(let j = 0;j < slotLinks.length; j++) { slotLinks[j].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); slotLinks[j].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } } }, 2000); }) .catch(error => { console.log('wheelhouse beacon error: ', error); }); } catch (ex) { console.log('Error: failed to report to wheelhouse; ' + ex); } }, autoNudgeAmpElements: (enable) => { if (!enable) { /* removeEventListener will not complain if scrollCallback is undefined */ window.removeEventListener('scroll', this.helpers.autoNudgeAmpElements.scrollCallback); return; } if (!this.helpers.autoNudgeAmpElements.scrollCallback) { this.helpers.autoNudgeAmpElements.scrollCallback = this.helpers.debounce(function () { /* Always grab fresh set of elements on scroll callback in case new ones were dynamically added to the body */ const ampElements = [].concat( /*Array.from(document.getElementsByTagName('amp-list')),*/ Array.from(document.getElementsByTagName('amp-img')) ); /* Look for AMP elements in the viewport */ ampElements.forEach((ampElm) => { if (this.helpers.isInViewport(ampElm)) { /* Give AMP a chance to handle the lazy-load on its own before we force layout */ setTimeout(() => { this.helpers.nudgeAmpElementLayout(ampElm); }, 50); } }); }, 50).bind(this); } /* Trigger on leading edge, before a scroll event occurs */ this.helpers.autoNudgeAmpElements.scrollCallback(); window.addEventListener('scroll', this.helpers.autoNudgeAmpElements.scrollCallback); }, nudgeAmpElementLayout: (ampElement) => { if (!ampElement) return; if (ampElement.hasAttribute('data-no-nudge')) return; if (ampElement['__AMP__RESOURCE'] && ampElement['__AMP__RESOURCE'].isLayoutPending && ampElement['__AMP__RESOURCE'].isLayoutPending() && ampElement['__AMP__RESOURCE'].startLayout) { try { console.log('Manually called layout for ' + ampElement.tagName.toLowerCase()); ampElement['__AMP__RESOURCE'].layoutScheduled(Date.now()); ampElement['__AMP__RESOURCE'].startLayout(); } catch (ex) { console.error('Unable to start layout on AMP element\n', ampElement, ex); } } }, isInViewport: (element) => { if (!element) return false; const winLeft = 0; const winRight = window.innerWidth; const winTop = 0; const winBottom = window.innerHeight; /* Hopefully this won't ever happen */ if (winRight <= winLeft || winBottom <= winTop) return false; /* Only consider non-hidden elements */ if (!(element.offsetWidth || element.offsetHeight || element.getClientRects().length)) return false; /* Check that some part of the element is in the viewport */ const rect = element.getBoundingClientRect(); return rect.top <= winBottom && rect.bottom >= winTop && rect.left <= winRight && rect.right >= winLeft; }, trackWheelhouseEvents: async () => { try { const { filters, query, facets, previousQuery, useMyLocationClicked, updatedLocation } = this.state; const { reportToWheelhouse } = this.helpers; const { scrollLeft, scrollTop } = document.body; const { Gender, Languages, InsuranceAccepted, PracticeGroup } = facets; const getActiveFilters = (name) => { return facets[name] .reduce((arr, v) => { if (!filters[name]) return []; return filters[name][v]?.active ? [...arr, v] : arr; }, []); }; const createEventBody = (category, action, label, extraVals = []) => { const body = [ {key: "tealium_event", value: "amp_event"}, {key: "scroll_x", value: scrollLeft}, {key: "scroll_y", value: scrollTop}, {key: "event_category", value: category}, {key: "event_action", value: action}, {key: "event_label", value: label}, ...extraVals ]; return body; }; /* Send location set beacon */ if (useMyLocationClicked || updatedLocation) { this.state.updatedLocation = false; await reportToWheelhouse( createEventBody('Provider Search', 'location', 'set') ) } /* Send event for clicked "Use My Location" button */ if (useMyLocationClicked) { const isOn = document.querySelector('#switch')?.checked ? "on" : "off"; await reportToWheelhouse( createEventBody('Provider Search', 'useMyLocation', isOn) ); this.state.useMyLocationClicked = false; } /* Send analytic events for query or query removal */ /*if (query?.length) { await reportToWheelhouse( createEventBody('Provider Search', 'search-term', 'Search term set') ); } else if (previousQuery?.length) { await reportToWheelhouse( createEventBody('Provider Search', 'search-term-removed', 'Search term not set') ); }*/ /* Create the gender event, if a gender is active on search filters */ if (Gender.length) { const genders = getActiveFilters('Gender'); if (genders.length) { await reportToWheelhouse( createEventBody('Provider Search', 'gender', genders.join(',')) ); } } /* Create the languages event, if a language is active on search filters */ if (Languages.length) { const languages = getActiveFilters('Languages'); if (languages.length) { await reportToWheelhouse( createEventBody('Provider Search', 'language', languages.join(',')) ); } } /* Create the insurances event, if an insurance is active on search filters */ if (InsuranceAccepted.length) { const insurances = getActiveFilters('InsuranceAccepted'); if (insurances.length) { await reportToWheelhouse( createEventBody( 'Provider Search', 'Patient Insurance Information', insurances.join(','), [{ key: "cd64", value: insurances.join(',') }] ) ); } } /* Create the practice group event, if a medical group is active on search filters */ if (PracticeGroup.length) { const practiceGroups = getActiveFilters('PracticeGroup'); if (practiceGroups.length) { await reportToWheelhouse( createEventBody( 'Provider Search', 'medical-group', practiceGroups.join(',') ) ); } } } catch (err) { console.error(err); } }, trackWompEvent: async (data) => { const { ec, el, ea } = data; const { pathname, search } = window.location; await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthAnalytics?brand=${this.state.filters.brand}&t=event&ec=${ec}&ea=${ea}&el=${el}&cid=${this.helpers.getCID()}&dp=${encodeURIComponent(pathname + search)}`) .then(res => res.json()) .then(res => console.log(res)); }, createPixel: (params = {}) => { const wheelHouseDataRaw = document.querySelector('#wheelhouseJson'); const wheelhouseJson = wheelHouseDataRaw ? JSON.parse(wheelHouseDataRaw.innerText) : {}; const { pageType, page_subtype } = this.state; const { origin, pathname, href, host } = window.location; const { referrer, title } = document; const timestamp = new Date().toISOString(); const clientId = this.helpers.getCookie('_ga') || 'no client id'; const ECID = this.helpers.getCookie('AMP_ECID_GOOGLE') || 'no client id'; const baseUrl = "https://pc-providence-collect.tealiumiq.com/vdata/i.gif"; const account = "providence"; const profile = "main"; const datasource = "neli68"; let gclid = this.helpers.getCookie('u_gclid') || false; if (!gclid) { let tempParams = new URLSearchParams(window.location.search); for (let p of ['u_gclid', 'gclid']) { /* If the linkers are set in the URL, use that value */ const tempParam = tempParams.get(p); if (tempParam) { gclid = tempParam; /* If we find a param, we don't need to continue looping through possible params. */ continue; } } }; let fromAmpScript = {}; try { fromAmpScript = JSON.parse(document.querySelector('amp-analytics#tealium_collect script').innerHTML).extraUrlParams; } catch (e) { console.error(`Could not parse amp-script JSON. Error: ${e}`); }; const isInternal = fromAmpScript.internalUser || 'False'; let traceId = fromAmpScript.tealium_trace_id || 'Not Set'; if(traceId === 'Not Set') { let tealiumTraceId = getCookieInvoca("tealium_trace_id"); if(tealiumTraceId){ traceId = tealiumTraceId; } } const parameters = { /* Tealium defaults */ tealium_account: account, tealium_profile: profile, datasource: datasource, tealium_vid: ECID, /* Variables sent with every view and event */ hitTimestamp: timestamp, clientID: clientId, /* sessionID: 9999, This should be a cookie? */ user_agent: navigator.userAgent, utag_profile_version: `${profile} | ${datasource}`, "ut.visitor_id": ECID, "dom.title": title, "dom.url": href, "dom.domain": host, "dom.pathname": pathname, dom_title: title, dom_url: href, dom_domain: host, dom_pathname: pathname, event_url: origin + pathname, experience_platform: "Web - AMP", /** <!-- childReplacement brandTrack --> **/ brand: "Providence", /** <!-- end childReplacement brandTrack --> **/ web_auth_state: "Undetermined", pageType, page_subtype, state: this.state.filters.userState, tealium_event: 'womp_directory_screen_view', tealium_event_type: 'view', internalUser: isInternal, "send_to_as": "true", "search_location_filter_within": 'Not Set', /* location_partner: "NEED DATA", */ /* Event specific parameters passed into function */ ...params }; try { let searchParams = new URLSearchParams(window.location.search); if(searchParams.get('campaignid')) { parameters['campaignid'] = searchParams.get('campaignid'); } if(searchParams.get('tacticid')) { parameters['tacticid'] = searchParams.get('tacticid'); } if(searchParams.get('offersetid')) { parameters['offersetid'] = searchParams.get('offersetid'); } if(searchParams.get('channel')) { parameters['channel'] = searchParams.get('channel'); } } catch (error) { console.log(error); } try { let dist = this.state.filters.distance; if(!isNaN(dist) && dist) { parameters.search_location_filter_within = dist; console.log("dist: dist " + dist); } } catch (error) { console.log("Search radius is not set."); } if (traceId) { parameters['tealium_trace_id'] = traceId; } if (wheelhouseJson?.pcid) { parameters.pcid = wheelhouseJson.pcid; } if (gclid) { parameters.gclid = gclid; } const pixelUrl = baseUrl + Object.keys(parameters).reduce((str, key, i) => { const prefix = i === 0 ? '?' : '&'; return str + prefix + key + '=' + encodeURIComponent(parameters[key]); }, ''); return pixelUrl; }, /* Takes a pixel URL and adds it to the DOM for tracking */ trackIt: (data) => { let pixels = Array.isArray(data) ? data.reduce((str, url) => str + `<img src="${url}" />`, '') : `<img src="${data}" />`; /* 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', ` <div id="pixels-wrapper" style="display: none;"> ${pixels} </div> `); }; }, /* 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(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) => { let validZip = true; if (res.results[0]) { let resultIndex; try { resultIndex = res.results.findIndex(address => address.address_components.findIndex(ac => ac.long_name.toLowerCase().includes('united states') ) > -1 ); } catch (ex) { console.log(ex); resultIndex = -1; } if (resultIndex > -1) { AMP.setState({locationState: {validZip: 1}}); const geo = res.results[resultIndex].geometry.location; coords = { latitude: geo.lat, longitude: geo.lng, }; console.log(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 { validZip = false; } } else { siteSearch.helpers.getGoogleLocationFallback(); } if (!validZip) { console.log('invalid zip'); AMP.setState({locationState: {validZip: 0}}); return; } }); }; }, getGoogleLocationFallback: (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?components=postal_code%3A${ value }%7Ccountry%3AUS&key=AIzaSyC9aTi6UWN30wj-e4uJHJ5j0y6szBTQ4BY` ) .then((res) => res.json()) .then((res) => { let validZip = true; if (res.results[0]) { let resultIndex; try { resultIndex = res.results.findIndex(address => address.address_components.findIndex(ac => ac.long_name.toLowerCase().includes('united states') ) > -1 ); } catch (ex) { console.log(ex); resultIndex = -1; } if (resultIndex > -1) { AMP.setState({locationState: {validZip: 1}}); const geo = res.results[resultIndex].geometry.location; coords = { latitude: geo.lat, longitude: geo.lng, }; console.log(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 { validZip = false; } } else { validZip = false; } if (!validZip) { console.log('invalid zip'); 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); }; }, 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} <svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg>`; */ this.state.filters.location = newLoc; /** <!-- childReplacement provOmniGeoLocatorShowPosition --> **/ const zipParts = res.results[0].address_components.filter((x) => x.types .join("") .match( /postal_code/gi ) ); const zip = zipParts[0].long_name; const fullLocation = this.state.filters.location + ' ' + zip; const headerLocationLabel = document.querySelector('#geoLocatorLabel span'); headerLocationLabel.innerHTML = fullLocation; document.querySelector('#geoLocatorInput').value = fullLocation; checkLoginStatus(); /** <!-- end childReplacement provOmniGeoLocatorShowPosition --> **/ }); this.state.filters = { ...this.state.filters, coordinates: { latitude, longitude, }, locationType: "user", }; this.handleSubmit(); }, showError: (error) => { console.log("Error when trying to get users location:"); console.log(error.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}<br />${City}, ${Region} ${PostalCode}`; } else if (type == 'providerCard') { formattedAddress = data; let zip = formattedAddress.split(',').pop(); let cityState = formattedAddress.split(',').slice(0,2).join(',').trim(); formattedAddress = formattedAddress.replace(cityState, ''); formattedAddress = formattedAddress.replace(zip, ''); formattedAddress = formattedAddress.slice(2); formattedAddress = `${formattedAddress} ${cityState},${zip}`; /* Following inserts comma before Suite text if Suite text is present. */ if (formattedAddress.toLowerCase().indexOf("suite") >= 0) { let suiteIndex = formattedAddress.toLowerCase().indexOf("suite"); let potentiallyComma = suiteIndex - 2; if (formattedAddress[potentiallyComma] != ",") { formattedAddress = formattedAddress.slice(0, potentiallyComma + 1) + ', ' + formattedAddress.slice(suiteIndex); } } } return formattedAddress; }, formatPhoneNumber: (num, format = 'default') => { /* Filter only numbers from the input */ const cleaned = ('' + num).replace(/\D/g, ''); /* Check if the input is of correct */ const match = cleaned.match(/^(1|)?(\d{3})(\d{3})(\d{4})$/); if (match) { /* Remove the matched extension code. Change this to format for any country code.*/ const intlCode = (match[1] ? '' : ''); switch(format) { case 'hyphenated': return `${intlCode}${match[2]}-${match[3]}-${match[4]}`; default: return `${intlCode}(${match[2]}) ${match[3]}-${match[4]}`; }; }; return null; }, viewportLessThan: (px = 1000) => { return window.innerWidth < px; }, resolveLocation: (data) => { const hasZip = data.zip && data.zip.length > 1; const hasCity = data.city && data.city.length > 1; const hasState = data.state && data.state.length > 1; const hasNeighborhood = data.neighborhood && data.neighborhood.length > 1; const hasCounty = data.county && data.county.length > 1; if (hasNeighborhood) { return data.neighborhood + (hasState ? `, ${data.state}` : ''); } else if (hasZip) { return data.zip; } else if (hasCity && hasState) { return `${data.city}, ${data.state}`; } else if (hasCounty) { return data.county + (hasState ? `, ${data.state}` : ''); } else { return 'Seattle, WA'; } }, pushHistory: async (queryParams = '') => { const { filters, query, pageType, page_subtype, defaultLoc, currentLoc, pushHistory } = this.state; const { createPixel, trackIt, reportToWheelhouse } = this.helpers; const trackingPixels = []; let search_match = "Not Set"; try { let match_filters = this.state.results.info.facets.PrimarySpecialties; if(match_filters.length > 0) { search_match = match_filters.toString(); } else if(query && query.length) { search_match = "No Match"; } /* Potentially return all Matches */ /* Possibly problematic for searches such as "Joe has Cancer" */ /*let matches = this.state.results.info.matches; if(matches.length > 0) { search_match = matches.toString(); } */ } catch (error) { if(query && query.length) { search_match = "No Match"; } console.log("Search did not match PrimarySpecialties."); } /* Set all the search event params for Tealium */ const searchTrackingParams = { search_type: this.state.pageInit ? 'Passive' : 'Active', search_term: `${search_match}`, medical_group_filter: /** <!-- childReplacement medGroupTealiumParam --> **/ Object.values(filters.ProviderOrganization).some(po => po.active) ? 'Set' : 'Not Set', /** <!-- end childReplacement medGroupTealiumParam --> **/ new_patient_availability_filter: filters.clinicVisits ? 'Set' : 'Not Set', telehealth_availability_filter: filters.videoVisits ? 'Set' : 'Not Set', online_scheduling_filter: filters.onlineScheduling ? 'Set' : 'Not Set', search_location_filter: defaultLoc !== currentLoc ? 'Set' : 'Default', event_category: 'Directory Journeys', event_action: 'Provider Directory', event_label: 'Search / Filter', tealium_event: 'womp_directory_event', tealium_event_type: 'event' }; /* Create a new URLSearchParams object from the provided query string, as well as a URL object from the current page location */ const apiUrl = new URLSearchParams(queryParams); const url = new URL(window.location); const curUrl = new URLSearchParams(window.location.search); /* Set the referrer in state to the current URL. */ this.state.referrer = url; /* If the referrer is set on document, use that. If not, use what we've saved to state. */ const referrer = document.referrer ? document.referrer : this.state.referrer.href; /* WheelhouseDMG tracking */ await reportToWheelhouse([ { key: "tealium_event", value: "amp_screen_view" }, { key: "dom_referrer", value: referrer }, { key: "dom.referrer", value: referrer }, { key: "screen_width", value: `${window.innerWidth}x${window.innerHeight}`} ]); /* Create the pageview pixel and push it to our pixels array */ const pageView = createPixel({ "dom.referrer": referrer, "dom_referrer": referrer }); trackingPixels.push(pageView); /* Loop through the params we want to add and determine if they should be in the URL pushed to history state */ const paramsToAdd = [ "brand", "languages", "LocationsOnly", "gender", "insuranceaccepted", "primaryspecialties", /** <!-- childReplacement providerOrgParamToAdd --> **/ "ProviderOrganization", /** <!-- end childReplacement providerOrgParamToAdd --> **/ "page", "sortby", "visittypes", "query", "locationname", "userlocation", "practicegroup", "LocationsOnly", "time", "days", "Tier", "distance" ]; for (let param of paramsToAdd) { const { pageType, page_subtype, filters, query, currentLoc } = this.state; const { visitType, InsuranceAccepted, PrimarySpecialties, LocationName, PracticeGroup, LocationsOnly, tier, distance } = filters; /** <!-- childReplacement providerOrgGetFilter --> **/ let ProviderOrganization = filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetFilter --> **/ let val, keys; switch (param) { case "Tier": url.searchParams.delete("tier"); if(!tier) { url.searchParams.delete("Tier"); continue; } val = tier; break; case "time": val = this.state.filters.AvailableTime ?? "any"; break; case "days": if(!this.state.specificDays) { url.searchParams.delete("days"); continue; } val = this.helpers.getAvailability(this.state); break; case "LocationsOnly": if(!LocationsOnly) { continue; } val = LocationsOnly; break; case "query": val = query; break; case "visittypes": keys = Object.keys(visitType).filter(key => visitType[key]?.active === true && visitType[key]?.auto != true); if (keys.length) { /* Create query string from keys */ val = keys.reduce((str, key) => str.length ? str + `,${key}` : key, ''); /* Set value for clinic visits in pixel */ if (visitType['clinicVisits']?.active === true || (visitType['clinicVisits']?.active === false && ![null, undefined].includes(visitType.clinicVisits))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['new_patient_availability_filter'] = 'Set'; }else if(curUrl.get("visittypes").indexOf('clinicVisits') > -1) { searchTrackingParams['new_patient_availability_filter'] = 'Default'; }else { searchTrackingParams['new_patient_availability_filter'] = 'Set'; } } else { searchTrackingParams['new_patient_availability_filter'] = 'Not Set'; } /* Set value for virtual visits in pixel */ if (visitType['videoVisits']?.active === true || (visitType['videoVisits']?.active === false && ![null, undefined].includes(visitType.videoVisits))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['telehealth_availability_filter'] = 'Set'; }else if(curUrl.get("visittypes").indexOf('videoVisits') > -1) { searchTrackingParams['telehealth_availability_filter'] = 'Default'; }else { searchTrackingParams['telehealth_availability_filter'] = 'Set'; } } else { searchTrackingParams['telehealth_availability_filter'] = 'Not Set'; } /* Set value for virtual visits in pixel */ if (visitType['onlineScheduling']?.active === true || (visitType['onlineScheduling']?.active === false && ![null, undefined].includes(visitType.onlineScheduling))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['online_scheduling_filter'] = 'Set'; sessionStorage.setItem("online_scheduling_filter", "Set"); }else if(curUrl.get("visittypes").indexOf('clinicVisits%2ConlineScheduling') > -1) { if(sessionStorage.getItem("online_scheduling_filter") === null) { searchTrackingParams['online_scheduling_filter'] = 'Default'; } else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } }else if(curUrl.get("visittypes").indexOf('onlineScheduling') > -1) { if(sessionStorage.getItem("online_scheduling_filter") === null) { searchTrackingParams['online_scheduling_filter'] = 'Default'; } else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } }else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } } else { searchTrackingParams['online_scheduling_filter'] = 'Not Set'; } } else { /* Remove the visitTypes param from the query string */ url.searchParams.delete(param); /* Set the values for virtual and clinic visits on the tracking pixel */ searchTrackingParams['new_patient_availability_filter'] = 'Not Set'; searchTrackingParams['telehealth_availability_filter'] = 'Not Set'; searchTrackingParams['online_scheduling_filter'] = 'Not Set'; /* Break out of the loops current iteration */ continue; }; break; case "brand": case "page": val = this.state.filters[param]; break; case "locationname": if (LocationName) { val = LocationName; break; } else { url.searchParams.delete(param); continue; } case "distance": if (distance) { val = distance; break; } else { url.searchParams.delete(param); continue; } case "userlocation": val = currentLoc; break; case "gender": case "insuranceaccepted": case "languages": let lookup = { languages: 'provider_language_filter', gender: 'provider_gender_filter', insuranceaccepted: 'insurance_filter' }; const tracker = lookup[param]; const filter = this.state.filters[ param === 'gender' ? 'Gender' : param === 'languages' ? 'Languages' : 'InsuranceAccepted' ]; /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(filter).filter(f => filter[f]?.active === true && filter[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); searchTrackingParams[tracker] = "Set"; } else { searchTrackingParams[tracker] = "Not Set"; url.searchParams.delete(param); continue; } break; case "primaryspecialties": /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(PrimarySpecialties).filter(f => PrimarySpecialties[f]?.active === true && PrimarySpecialties[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); } else { url.searchParams.delete(param); continue; } break; /** <!-- childReplacement providerOrgParsing --> **/ case "ProviderOrganization": /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(ProviderOrganization).filter(f => ProviderOrganization[f]?.active === true && ProviderOrganization[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); } else { url.searchParams.delete(param); continue; } param = 'providerorganization'; break; /** <!-- end childReplacement providerOrgParsing --> **/ case "sortby": if (apiUrl.get(param)) { val = apiUrl.get(param); } else { url.searchParams.delete(param); /* Break out of the loops current iteration */ continue; }; break; case "practicegroup": if (apiUrl.get(param)) { val = apiUrl.get(param); }else if(apiUrl.get("PracticeGroup")) { val = apiUrl.get("PracticeGroup"); } else { url.searchParams.delete(param); continue; } break; default: break; }; url.searchParams.set(param, val); }; if (window.history.pushState) { window.history.pushState('Find a Doctor','',url); } else { window.history.replaceState({}, 'Find a Doctor', url); this.state.pushHistory = true; } /* Push the current url to the window history, if it's not the initial load if (pushHistory) { window.history.pushState({}, "Womp Health Demo", url); } else { window.history.replaceState({}, "Womp Health Demo", url); this.state.pushHistory = true; }*/ /* Create pagination links based on url state */ this.paginate(); /* Create our tracking pixel for the search event and add it to the pixels array */ const searchEvent = createPixel(searchTrackingParams); trackingPixels.push(searchEvent); /* If the page has timeslots, create an event for Timeslot Enabled Results */ if (document.querySelectorAll('timeslots-widget, directory-timeslots').length > 0) { const timeslotEnabled = createPixel({ event_category: 'Directory Journeys', event_action: 'Provider Directory', event_label: 'Timeslot Enabled Results', tealium_event: 'womp_directory_event', tealium_event_type: 'event' }); trackingPixels.push(timeslotEnabled); }; /* Add the created pixels to the DOM to track the events */ trackIt(trackingPixels); /* Set this to false. Mainly being used for handling the search box update in state. */ this.state.pageInit = false; /* Track autocomplete if search was initated from the autocomplete el and if the window variable is true */ if (window.wmFromAutoComplete === true) { autocomplete.helpers.trackAutocomplete(this.state.query); window.wmFromAutoComplete = false; } await this.helpers.trackWheelhouseEvents(); }, getSlotsType: () => { const { visitType } = this.state.filters; if (!(visitType.clinicVisits?.active && visitType.videoVisits?.active)) { let tmp = Object.keys(visitType).filter(key => visitType[key]?.active); if (tmp.length) { return tmp[0].replace('Visits', ''); }; } return 'all'; }, /* Determine what, if any, timeslots to show for a provider */ checkForTimeslots: (NewClinicVisit, NewVideoVisit, DaysUntilNextAppt) => { let hasTimeslots = false; if (DaysUntilNextAppt <= 30 && (NewClinicVisit == 1 || NewVideoVisit == 1)) { hasTimeslots = true; } return hasTimeslots; }, createProviderLink: (omni) => { let providerLink = `/doctors/profile/${omni.ProfileUrl}`; if (/swedish/gi.test(location.origin) && omni.ProviderUniqueUrlSwedish) { providerLink = omni.ProviderUniqueUrlSwedish.replace(/https:\/\/[a-z\.]*\.org/gi, ''); } if (/pacmed|pacificmedicalcenters/gi.test(location.origin) && omni.ProviderUniqueUrlPacmed) { providerLink = omni.ProviderUniqueUrlPacmed.replace(/https:\/\/[a-z\.]*\.org/gi, ''); } if (/providence/gi.test(location.origin) && omni.ProviderUniqueUrlOnesite) { providerLink = omni.ProviderUniqueUrlOnesite; } return providerLink; } }; state = {}; map = { lookup: {}, markers: {}, locations: {}, infoWindow: false, promises: [], selectedNpi: false, selectedLocation: false, bounds: false, icons: {}, toggleVirtual: (isVirtual = false) => { if (isVirtual) { document.querySelector('main').classList.add('no-locations'); document.querySelector('#map').classList.remove('show-map'); } else { document.querySelector('main').classList.remove('no-locations'); document.querySelector('#map').classList.add('show-map'); }; }, selectProvider: npi => { const w = window.innerWidth; const npiCard = document.querySelector(`#npi${npi}`); /* Saving some time by not modifying the DOM if the NPI hasn't changed since last click */ if (npi !== this.map.selectedNpi || !npiCard.classList.contains('selected')) { /* Remove the selected classs from the currently selected Npi */ if (this.map.selectedNpi) { document.querySelector(`#npi${this.map.selectedNpi}`).classList.remove('selected'); }; /* Highight card for selected Npi */ this.map.selectedNpi = npi; npiCard.classList.add('selected'); }; /* We'll scroll to the card regardless, just in case the user changed scroll position since last click */ if (w < 1000) { this.resultsEl.scrollBy({ top: 0, /* 32px is the amount of spacing between the viewports left edge and the selected provider card */ /* only exception is the very first card (has 16px), but this still works for that edge case */ left: npiCard.getBoundingClientRect().left - 32, behavior: 'smooth' }); } else { this.helpers.scrollToTarget(npiCard, 30); }; }, createMarkers: () => { this.map.bounds = new google.maps.LatLngBounds(); try { /* Sets Map bounds to the searched for geoIP */ var ePoint1 = new google.maps.LatLng(this.state.results.geoip.coordinates.lat + 0.01, this.state.results.geoip.coordinates.lng + 0.01); var ePoint2 = new google.maps.LatLng(this.state.results.geoip.coordinates.lat - 0.01, this.state.results.geoip.coordinates.lng - 0.01); this.map.bounds.extend(ePoint1); this.map.bounds.extend(ePoint2); } catch (error) { console.log("Error: Could not get geoIP coordinates"); } const { locations, el, setMapOnAll, lookup } = this.map; const { createPixel, trackIt } = this.helpers; const ids = Object.keys(locations); const byUUID = {}; const selectFields = [ "id", "Region", "City", "PostalCode", "Address", "Name", "GeocodedCoordinate", "AddressId" ]; const encodedFields = encodeURIComponent(selectFields.join(',')); const encodedIds = encodeURIComponent(ids?.join(',')); /* ids?.length && uses short-circuit evalutaion to skip the fetch call if ids is null or not an array for any reason */ /*Using womphealthapi temp because weird data in staging ${this.apiSub} : 'womphealthdevapi' : 'womphealthapi'; */ ids?.length && fetch(`https://womphealthapi.azurewebsites.net/api/WompHealthData?brand=${this.state.filters.brand}&data=locations&ids=${encodedIds}&select=${encodedFields}`) .then(res => res.json()) .then(res => { res.forEach((loc, i) => { if (!loc) { console.debug(`Location information not available for id=${ids[i]}`); return; } const { pageType, page_subtype } = this.state; const { GeocodedCoordinate, City, Region, PostalCode, Address, Name } = loc; const uuid = loc.AddressId; lookup[loc.id] = uuid; if (!byUUID[uuid]) { byUUID[uuid] = loc; } const coordinates = { lat: loc.GeocodedCoordinate.coordinates[1], lng: loc.GeocodedCoordinate.coordinates[0] }; const coords = new google.maps.LatLng(coordinates.lat, coordinates.lng); const marker = new google.maps.Marker({ position: coords, map: el, icon: this.map.icons.default }); marker.icon.url = 'https://pacmed.azureedge.net/7914/[email protected]'; marker.addListener('mouseover', () => { try { marker.setIcon(this.map.icons.selected); } catch (error) { console.log(error); } }); marker.addListener('mouseout', () => { try { if(this.map.selectedMarker !== marker) { marker.setIcon(this.map.icons.default); } } catch (error) { console.log(error); } }); marker.addListener('click', () => { try { const isMobile = this.helpers.viewportLessThan(); this.map.resetMarkerIcon(); if (this.map.infoWindow) this.map.infoWindow.close(); this.map.selectedMarker = marker; this.map.selectedLocation = uuid; marker.setIcon(this.map.icons.selected); if (!isMobile) { /* Track pin clicks that open a location's info window */ trackIt(createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Click', event_label: Name, tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); /* Remove the selected class from the currently selected Npi if it's for another location */ if (this.map.selectedNpi && !this.map.locations[loc.id].some(curr => curr.Npi === this.map.selectedNpi)) { document.querySelector(`#npi${this.map.selectedNpi}`).classList.remove('selected'); }; const FormattedAddress = this.helpers.formatAddress('infoWindow', { City: City, Region: Region, PostalCode: PostalCode, Address: Address }); let providers = []; for (const [key, value] of Object.entries(this.map.lookup)) { if (this.map.locations[key] === undefined) { continue; } if (value && value == uuid) { providers = [...providers, ...this.map.locations[key]] }; }; const contentString = /*html*/`<div class="poi-info-window" data-location="${uuid}"> <div> ${FormattedAddress} <h3 style="margin: 8px 0 8px 0; font-size: 16px;">Providers</h3> ${providers.reduce((all, curr) => { const { Npi, virtual, ImageUrl, Name, ProviderTitle } = curr; const providerHtml = `<div class="provider" onclick="siteSearch.map.selectProvider(${Npi})"> <div class="provider-thumbnail-wrapper ${virtual ? 'hasVirtual' : ''}"> <div class="provider-thumbnail" style="background-image: url('${ImageUrl}')"></div> ${virtual ? `<div class="virtualVisits infoWindow"> <svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit"></use></svg> </div>` : ''} </div> <div class="provider-info"> <h4>${Name}</h4> <div class="provider-specialty">${ProviderTitle ? ProviderTitle : '<br />'}</div> </div> </div>`; return all + providerHtml; }, '')} ${!providers.length ? '<p>No providers found as this location</p>' : ''} </div> </div>`; /* Set and open info window */ const infoWindow = new google.maps.InfoWindow({ content: contentString }); this.map.infoWindow = infoWindow; this.map.infoWindow.open(el, marker); } else { this.map.el.setCenterWithOffset(marker.getPosition(), 0, 150); }; } catch (err) { console.log(err); throw err; } }); this.map.markers[uuid] = marker; try { /* Keep the map within the first 172.5 miles from the center */ if(i == 0 || ((this.map.bounds.Lh.hi - 2.5 < coordinates.lat && this.map.bounds.Lh.hi + 2.5 > coordinates.lat) && (this.map.bounds.ci.hi - 2.5 < coordinates.lng && this.map.bounds.ci.hi + 2.5 > coordinates.lng) )) { /* extend the bounds to include each marker's position */ this.map.bounds.extend(coords); const { bounds } = this.map; if (bounds.getNorthEast().equals(bounds.getSouthWest())) { var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01); var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01); this.map.bounds.extend(extendPoint1); this.map.bounds.extend(extendPoint2); }; } } catch (error) { console.log(error); try { if(i == 0 || ((this.map.bounds.La.hi - 2.5 < coordinates.lat && this.map.bounds.La.hi + 2.5 > coordinates.lat) && (this.map.bounds.eb.hi - 2.5 < coordinates.lng && this.map.bounds.eb.hi + 2.5 > coordinates.lng) )) { /* extend the bounds to include each marker's position */ this.map.bounds.extend(coords); const { bounds } = this.map; if (bounds.getNorthEast().equals(bounds.getSouthWest())) { var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01); var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01); this.map.bounds.extend(extendPoint1); this.map.bounds.extend(extendPoint2); }; } } catch (error) { console.log(error); } } this.map.el.fitBounds(this.map.bounds); this.map.setMapOnAll(this.map.el); }); }) .catch(err => console.log(err)); }, deleteMarkers: () => { if (this.map.markers) { this.map.setMapOnAll(null); this.map.infoWindows = {}; this.map.markers = {}; }; }, resetMarkerIcon: () => { const { markers, icons, selectedMarker } = this.map; if (selectedMarker) { selectedMarker.setIcon(icons.default); }; }, setOffset: () => { try { let windowWidth = window.innerWidth; if(windowWidth <= 960) { let top = this.helpers.pxInViewport(this.headerEl); let topOffset = document.getElementById('results-wrapper').offsetHeight + 60; this.mapEl.style.top = `${top}px`; this.mapEl.style.height = `calc(100vh - ${topOffset}px)`; } } catch (error) { console.log(error); let top = this.helpers.pxInViewport(this.headerEl); this.mapEl.style.top = `${top}px`; this.mapEl.style.height = `calc(100vh - ${top}px)`; } }, setMapOnAll: map => { const { markers, bounds } = this.map; let vals = Object.values(markers); for (let val of vals) { val.setMap(map); } }, build: () => { this.helpers.waitForGlobal('mapLoaded', () => { console.log('found google'); /* Add a prototype function to allow px offsets for map center */ google.maps.Map.prototype.setCenterWithOffset = function(latlng, offsetX = 0, offsetY = 0) { var map = this; var ov = new google.maps.OverlayView(); ov.onAdd = function() { var proj = this.getProjection(); var aPoint = proj.fromLatLngToContainerPixel(latlng); aPoint.x = aPoint.x+offsetX; aPoint.y = aPoint.y+offsetY; map.panTo(proj.fromContainerPixelToLatLng(aPoint)); }; ov.draw = function() {}; ov.setMap(this); }; /* Create the position object for centering the map and populate it with the coordinates we passed in. */ const position = { lat: 39.50, lng: -98.35 }; /* Set icons object */ this.map.icons = { default: { url: "https://pacmed.azureedge.net/7914/[email protected]", scaledSize: new google.maps.Size(24, 35), origin: new google.maps.Point(0,0), anchor: new google.maps.Point(12, 35) }, selected: { url: "https://pacmed.azureedge.net/7914/[email protected]", scaledSize: new google.maps.Size(24, 35), origin: new google.maps.Point(0,0), anchor: new google.maps.Point(12, 35) } }; /* Initialize the map on the page */ this.map.el = new google.maps.Map(document.getElementById("wm_map"), { mapTypeId: google.maps.MapTypeId.ROADMAP, gestureHandling: "greedy", disableDefaultUI: true, }); console.log('map initialized'); const {el: map} = this.map; /* POI marker visibility settings */ const styles = { default: [], hide: [ { featureType: "poi.business", stylers: [{ visibility: "off" }], }, { featureType: "poi.medical", stylers: [{ visibility: "off" }], }, { featureType: "transit", elementType: "labels.icon", stylers: [{ visibility: "off" }], } ], }; /* Update the map to show location data */ map.setOptions({ styles: styles["hide"] }); /* Set map offset from top of screen */ const w = window.innerWidth; /* Track Scroll */ /*google.maps.event.addListener(map, 'dragend', () => { let firstMapEvent = sessionStorage.getItem("fme"); if(!firstMapEvent || firstMapEvent === 'zoom load') { siteSearch.helpers.trackIt(siteSearch.helpers.createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Interaction', event_label: 'Scroll', tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); sessionStorage.setItem("fme", "occured"); } });*/ /* Track Zoom */ /*google.maps.event.addListener(map, 'zoom_changed', () => { let firstMapEvent = sessionStorage.getItem("fme"); if(!firstMapEvent) { sessionStorage.setItem("fme", "zoom load");} else if(firstMapEvent === 'zoom load') { siteSearch.helpers.trackIt(siteSearch.helpers.createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Interaction', event_label: 'Zoom', tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); sessionStorage.setItem("fme", "occured"); } });*/ google.maps.event.addListener(map, 'click', () => { const selectedNpi = document.querySelector(`#npi${this.map.selectedNpi}`); if (selectedNpi) { selectedNpi.classList.remove('selected'); } if (this.map.infoWindow) { this.map.infoWindow.close(); this.map.resetMarkerIcon(); } }); }); } }; MapOldQueryParams = function () { /* Post migration to ProvOmni, providence will still have old links to providence search, using old query params. https://app.asana.com/0/1204100916165836/1205550979625263/f Handle query term replacement to map old providence query params to new ProvOmni params */ let queryString = document.location.search; if (queryString.includes('search-term')) { queryString = queryString.replace('search-term', 'query'); } if (queryString.includes('provider-organization')) { queryString = queryString.replace('provider-organization', 'providerorganization'); } if (queryString.includes('language=')) { queryString = queryString.replace('language=', 'languages='); } if (queryString.includes('insurance=')) { queryString = queryString.replace('insurance=', 'insuranceaccepted='); } if (queryString.includes('&location=')) { queryString = queryString.replace('&location=', '&userlocation='); } let acceptsNew = false; let offersVideo = false; if (queryString.includes('acceptingNewPatients=on')) { acceptsNew = true; queryString = queryString.replace('acceptingNewPatients=on', ''); } if (queryString.includes('virtualCare=on')) { offersVideo = true; queryString = queryString.replace('virtualCare=on', '') } if (acceptsNew && offersVideo) { queryString += '&visittypes=clinicVisits%252CvideoVisits'; } else if (acceptsNew) { queryString += '&visittypes=clinicVisits'; } else if (offersVideo) { queryString += '&visittypes=videoVisits'; } /* if the query string changed, refresh the page */ if (document.location.search != queryString) { document.location.search = queryString } }; containsDisclaimerSearch = (query) => { this.state.disclaimerSearch = false; /* reset */ const regex = new RegExp(this.state.disclaimerSearchKeywords.join( "|" ), "i"); /* true if query or input value includes a disclaimer keyword */ this.state.disclaimerSearch = regex.test(query) || regex.test(this.input.value); }; containsExcludeKeywords = (query) => { this.state.excludeSearch = false; /* reset */ const regex = new RegExp(this.state.excludeSearchKeywords.join( "|" ), "i"); /* true if query or input value includes an exclude keyword */ const exclude = regex.test(query) || regex.test(this.input.value); return exclude; }; handleSubmit = async (e, { ignoreElements = [] } = {}) => { this.MapOldQueryParams(); this.resultsEl.innerHTML = "<div class='loader'></div>"; this.paginationEl.style.display = "none"; this.map.locations = {}; this.map.selectedNpi = false; this.map.selectedLocation = false; this.map.selectedMarker = false; this.map.deleteMarkers(); try { let allDirtyE = document.querySelectorAll(".is-dirty"); allDirtyE.forEach((element) => { element.classList.remove('is-dirty'); }); } catch (error) { console.log(""); } try { if(this.state.query !== '') { document.getElementById("searchBox").classList.add("is-dirty"); } } catch (error) { console.log(error); } document .querySelectorAll(".dropdown, #searchBox") .forEach((drop) => { if(!ignoreElements.includes(drop.id)) { drop.classList.remove("active"); } }); document.querySelector('#fullsearch').checked = false; if (!this.state.pageInit) { this.state.previousQuery = this.state.query; this.state.query = this.input.value; }; /* Exclusions and disclaimers */ if (this.containsExcludeKeywords(this.state.query || '')) { this.state.excludeSearch = true; this.state.query = ''; this.input.value = ''; } this.containsDisclaimerSearch(this.state.query || ''); /* Destructing state */ const { query, geolocation, filters, isTest, autoApplied } = this.state; const { formatPhoneNumber, formatAddress, showError, showPosition, slugify, trackIt, createPixel } = this.helpers; /* Update previousSearch state */ if (query.length) { let fromLocal = localStorage.getItem('previousSearches'); let fromSession = sessionStorage.getItem('previousSearches'); /* If localStorage already existed, parse the string ot JSON. If not, create an empty array */ fromLocal !== null ? fromLocal = JSON.parse(fromLocal) : fromLocal = []; /* If sessionStorage already existed, parse the string ot JSON. If not, create an empty array */ fromSession !== null ? fromSession = JSON.parse(fromSession) : fromSession = []; /* Append the newest query to the array of previous searches */ let prevSearchesLocal = [...new Set([query, ...fromLocal])]; let prevSearchesSession = [...new Set([query, ...fromSession])]; /* Write the combined array back to localStorage */ localStorage.setItem('previousSearches', JSON.stringify(prevSearchesLocal.slice(0,5))); /* Write the combined array back to sessionStorage for analytics */ sessionStorage.setItem('previousSearches', JSON.stringify(prevSearchesSession)); }; /* Remove autofilters from what is being sent to the API */ if (autoApplied && autoApplied.length) { for (const filter of autoApplied) { const inState = filters[filter.name]; /* If we actually have this value in state, then set active to false */ if (inState && inState[filter.value]) { inState[filter.value].active = false; } } } /* Destructing filters */ const { previousPostalCode, distance, brand, Languages, Gender, InsuranceAccepted, PrimarySpecialties, visitType, coordinates, LocationId, PracticeGroupId, LocationsOnly, } = filters; let LocationName = filters.LocationName; let PracticeGroup = filters.PracticeGroup; /** <!-- childReplacement providerOrgGetFilter --> **/ let ProviderOrganization = filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetFilter --> **/ /* If certain terms are in search, set clinicVisits to true */ if (/(family medicine|family doctor|internal medicine|family care)/gi.test(query)) { const shouldSkipAutoFilter = query != this.state.previousQuery ? false : /(family medicine|family doctor|internal medicine|family care)/gi.test(this.state.previousQuery); if (!shouldSkipAutoFilter) { if (!filters.visitType) { filters.visitType = { clinicVisits: { active: true } } } else { filters.visitType = {...filters.visitType, clinicVisits: { active: true }} }; } }; /* If certain terms are in search, set videoVisits to true */ if (/(video|virtual)/gi.test(query)) { if (!filters.visitType) { filters.visitType = { videoVisits: { active: true } }; } else { filters.visitType = { ...filters.visitType, videoVisits: { active: true } }; }; }; /* If certain requirements are met and search terms exists, turn on 'near me' location toggle */ if (navigator.geolocation && !this.state.nearme && /near me/gi.test(query)) { this.state.nearme = true; navigator.geolocation.getCurrentPosition(showPosition, showError); return; }; /* Return to page one when new filters are applied */ if(!this.state.filters.init) { this.state.filters.page = 1; } /* Some variables for our API request */ const post = { search: query, top: 20, skip: (this.state.filters.page - 1) * 20, highlightPreTag: "", highlightPostTag: "", }; /* Starting point for building out the request params + filters */ let queryParams = `&top=${post.top}&skip=${post.skip}`; /* Check for a default search (provided in before-render when a slug is present in URL) */ const defaultSearch = document.querySelector('#defaultSearch'); const slugText = document.querySelector('#slugText'); if(this.state.filters.init && defaultSearch) { const slugSearchTerm = defaultSearch.querySelector('#slugSearchTerm'); const slugLocation = defaultSearch.querySelector('#slugLocation'); const searchString = slugSearchTerm.innerText; const locationString = slugLocation.innerText; this.state.filters.location = locationString; this.state.query = searchString; post.search = searchString; this.input.value = searchString; window.sessionStorage.setItem('previousQuery', searchString); } else if(!this.state.filters.init && slugText) { /* If additional searches are performed, remove the slug & slug description text */ slugText.remove(); } /* Add visit type, if applicable */ /* const types = Object.keys(visitType).filter(type => visitType[type] === true); if (types.length > 0) { queryParams += types.reduce((str, type) => str + `&${type}=1`, ""); }; */ if (visitType.clinicVisits?.active == true) { queryParams += '&AcceptingNewPatients=1'; } if (visitType.videoVisits?.active == true) { queryParams += '&VirtualCare=1'; } /* Grab languages, if applicable */ const languages = Object.keys(Languages).filter(lang => lang && Languages[lang]?.active === true); if (languages.length > 0) { queryParams += languages.reduce((str, lang, i) => i === 0 ? str + lang : `${str},${lang}`, "&Languages="); }; /* Grab genders, if applicable */ let genders = Object.keys(Gender).filter(gender => Gender[gender]?.active === true); let genderVals = ["Female", "Male", "Non-binary"]; for (const [key, value] of Object.entries(genders)) { if(!genderVals.includes(value)) { delete Gender[value]; let gendersCk = value.charAt(0).toUpperCase() + value.toLowerCase().slice(1); if(!genderVals.includes(gendersCk)) { genders.splice(key, 1); } } } if (genders.length > 0) { queryParams += genders.reduce((str, gender, i) => i === 0 ? str + gender.charAt(0).toUpperCase() + gender.toLowerCase().slice(1) : `${str},${gender.charAt(0).toUpperCase() + gender.toLowerCase().slice(1)}`, "&Gender="); for (const [key, value] of Object.entries(this.state.filters.Gender)) { if(!genderVals.includes(key)) { delete this.state.filters.Gender[key]; } } }; try { /* Check for preselected filters */ await this.helpers.fetchWithTimeout(`https://www.providence.org/integrationapi/getinsuranceselectionjson`, { timeout: 2000 }) .then(res => res.json()) .then(res => { console.log("getinsuranceselectionjson " + res.insurance); if(res.insurance !== null) { queryParams += `&InsuranceAccepted=${res.insurance}`; localStorage.setItem('omniSearchInsurance', res.insurance); } }); } catch (err) { console.error('Insurance selection API not responding:\n' + err); } /* Grab insurances accepted, if applicable */ const insurances = Object.keys(InsuranceAccepted).filter(ins => InsuranceAccepted[ins]?.active === true); if (insurances.length > 0) { let reduced = insurances.reduce((str, ins, i) => i === 0 ? str + ins : `${str},${ins}`, ""); if(reduced.indexOf(',') !== -1){ let currentInsurance = localStorage.getItem('omniSearchInsurance'); if(reduced.split(",")[0] === currentInsurance) { reduced = reduced.split(",").pop(); } else { reduced = reduced.substring(0, reduced.indexOf(',')); } } queryParams += `&InsuranceAccepted=${reduced}`; localStorage.setItem('omniSearchInsurance', reduced); try { await this.helpers.fetchWithTimeout("https://www.providence.org/integrationapi/SetInsuranceSelection?plan=" + reduced, { timeout: 2000 }); } catch (err) { console.error('Insurance selection API not responding.'); } }; /** <!-- childReplacement providerOrgQueryParams --> **/ /* Get medical organizations for ProvidenceOmni */ let organizations = Object.keys(ProviderOrganization).filter(spec => ProviderOrganization[spec]?.active === true); try { for (var i = 0; i < organizations.length; i++) { if(organizations[i] === 'Providence Affiliated Physicians') { let org = []; for (var j = 0; j < organizations.length; j++) { /* Check if valid medical organization */ if(this.state.facets.ProviderOrganization.includes(organizations[j])) { org.push(organizations[j]); } } let po = this.searchParams.get("ProviderOrganization").replaceAll(", ", "!"); po = po.split(','); for (var j = 0; j < po.length; j++) { po[j] = po[j].replace("!", ", "); /* Check if valid medical organization */ if(!this.state.facets.ProviderOrganization.includes(po[j])) { po.splice(j, 1); } } organizations = Array.from([...new Set([...org, ...po])]); break; } } } catch (error) { console.log(error); } /* Check if valid medical organization */ let orgs = []; for (var j = 0; j < organizations.length; j++) { /* Check if valid medical organization */ if(this.state.facets.ProviderOrganization.includes(organizations[j])) { orgs.push(organizations[j]); } } organizations = orgs; if (organizations.length > 0) { const encodedOrganizations = encodeURIComponent(organizations.reduce((str, spec, i) => i === 0 ? str + spec : `${str},${spec}`, "")); queryParams += `&ProviderOrganization=${encodedOrganizations}`; } /** <!-- end childReplacement providerOrgQueryParams --> **/ /* Grab primary specialties, if applicable */ const specialties = Object.keys(PrimarySpecialties).filter(spec => PrimarySpecialties[spec]?.active === true); if (specialties.length > 0) { queryParams += specialties.reduce((str, spec, i) => i === 0 ? str + spec : `${str},${spec}`, "&PrimarySpecialties="); }; /* Set filter for LocationName */ if (LocationName && LocationName.length) { LocationName = encodeURIComponent(LocationName); queryParams += `&LocationName=${LocationName}`; } /* Set filter for distance */ if (distance && distance.length) { queryParams += `&distance=${distance}`; } /* Set filter for PracticeGroup */ if (PracticeGroup && PracticeGroup.length) { PracticeGroup = encodeURIComponent(PracticeGroup); queryParams += `&PracticeGroup=${PracticeGroup}`; } /* Set filter for LocationId */ if (LocationId && LocationId.length) { queryParams += `&LocationId=${this.state.filters.LocationId}`; } /* Set filter for PracticeGroupId */ if (PracticeGroupId && PracticeGroupId.length) { queryParams += `&PracticeGroupId=${this.state.filters.PracticeGroupId}`; } /* Not 100% necessary but it double-checks that we do indeed have coordinates before adding them to the request. */ if (this.state.filters.location) { queryParams += `&location=${this.state.filters.location}`; } else if (this.state.currentLoc) { queryParams += `&location=${this.state.currentLoc}`; } /* If sortby is not false, set param */ if (this.state.filters.sortby) { queryParams += `&sortby=${this.state.filters.sortby}`; }; /* Create the user id if not present */ if (!localStorage.getItem('cid')) { localStorage.setItem('cid', crypto.randomUUID()); }; queryParams += `&cid=${localStorage.getItem('cid')}`; /* Set locations=true if LocationsOnly is set */ if(this.state.filters.LocationsOnly) { queryParams += `&locations=${this.state.filters.LocationsOnly}`; } if(this.state.specificDays && this.state.filters.AvailableDays) { queryParams += `&days=${Object.entries(this.state.filters.AvailableDays).filter(x => x[1].active).map(x => DAYS.indexOf(x[0])).join(",")}`; } if(this.state.filters.AvailableTime) { queryParams += `&time=${this.state.filters.AvailableTime}`; } if(this.state.filters.tier) { queryParams += `&Tier=${this.state.filters.tier}`; } /* Provider cards in the directory should not list their hospital locations as the "primary" address. This occurs because we index a provider record for each location for a provider. We can now filter out those records with IsClinic=true. */ /** <!-- childReplacement isClinicOmniQueryParam --> **/ queryParams += '&IsClinic=true'; /** <!-- end childReplacement isClinicOmniQueryParam --> **/ let userQueryParams = document.location.search; if (!this.hosFlag) { if (userQueryParams.includes('hos=true')) { /* The hos=true flag has not been raised and the param exists, so we must not filter out HOS locations. */ siteSearch.hosFlag = true; queryParams = queryParams.replace('&IsClinic=true', ''); } } else if (userQueryParams.includes('hos=true')) { /* If the HOS flag has been raised, that means a hos=true search has already been performed once, so we want to remove it from the url query params since it is no longer in use. It is only used for the initial search, then discarded for future searches */ userQueryParams = userQueryParams.replace('hos=true', ''); if (history.pushState) { window.history.pushState({}, "Find a Doctor | Providence", location.origin + location.pathname + userQueryParams); } } let degree = this.searchParams.get('degree'); if(degree !== null) { queryParams += `&Degrees=${degree.toUpperCase()}`; } /* Add a \ (%5C) to allow for , in queryParams, example - ProviderOrganization */ queryParams = queryParams.replaceAll('%2C%20','%5C%2C%20'); /*Change queryParams lowercase to uppercase for certain values to get results*/ const qStrings = [ 'Gender', 'InsuranceAccepted', 'Languages', 'LocationId', 'LocationName', 'PracticeGroup', 'PracticeGroupId', 'PrimarySpecialties', 'ProviderOrganization', 'userLocation', 'visitTypes' ]; const searchParams = new URLSearchParams(window.location.search); let qStringsLength = qStrings.length; for (let i = 0; i < qStringsLength; i++) { /*replace with correct queryParams*/ queryParams = queryParams.replace(qStrings[i].toLowerCase(), qStrings[i]); /*if not in queryParams check if in url*/ if(searchParams.get(qStrings[i]) && !(queryParams.includes(qStrings[i]) || queryParams.includes(qStrings[i].toLowerCase())) ) { queryParams += `&${qStrings[i]}=${searchParams.get(qStrings[i]).replaceAll(', ','%5C%2C%20').replaceAll('&','%26')}`; }else if(searchParams.get(qStrings[i].toLowerCase()) && !(queryParams.includes(qStrings[i]) || queryParams.includes(qStrings[i].toLowerCase())) ) { queryParams += `&${qStrings[i]}=${searchParams.get(qStrings[i].toLowerCase()).replaceAll(', ','%5C%2C%20').replaceAll('&','%26')}`; } } /* Sending the actual request */ await fetch( `${this.fetchUrl}&type=search&brand=${brand}&search=${post.search}${queryParams}${isTest ? '&test=true' : ''}` ) .then((res) => res.json()) .then((res) => { this.state.results = res; }) .then(() => { const { results, query, filters } = this.state; /* If coordinates are returned from the API, save them to state */ if (this.state.results.geoip) { const { geoip } = this.state.results; const { lat, lng } = geoip.coordinates; this.state.currentLoc = this.helpers.resolveLocation(geoip); localStorage.setItem('omniSearchLocation', this.state.currentLoc); queryParams += `&userLocation=${this.state.currentLoc}`; if (!this.state.defaultLoc) { this.state.defaultLoc = this.state.currentLoc; }; this.state.filters.coordinates = { latitude: lat, longitude: lng }; }; /* Update local storage with previous searches */ const terms = query.split(/\s+/); let previousTerms = window.sessionStorage.getItem('previousQuery'); previousTerms = previousTerms && previousTerms.length ? previousTerms.split(/\s+/) : []; let inserted = []; let deleted = []; for (let term of terms) { if (!previousTerms.includes(term)) { inserted.push(term); }; }; for (let term of previousTerms) { if (!terms.includes(term)) { deleted.push(term); }; }; window.sessionStorage.setItem('previousQuery', this.state.query); /* If we get an error from the API, write the error to the page and stop the rest of the function from running. */ if (results.error) { this.resultsEl.innerHTML = `<p>${this.state.results.error.message}</p>`; 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("filter-buttons"); filtersElem.innerHTML = ""; const filtersWrapper = 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")) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log("Couldn't remove filter"); } try { const searchRadiusVal = siteSearch.state.filters.distance; if (searchRadiusVal) { document.querySelector('#searchRadius').value = searchRadiusVal; } if (searchRadiusVal?.length) { let filterText = `Within ${searchRadiusVal} miles`; if (searchRadiusVal == 1) { filterText = `Within ${searchRadiusVal} mile`; } let distanceFilterEl = { facets: [{ name: "distance", value: filterText }], value: filterText }; siteSearch.state.results.info.filters.push(distanceFilterEl); } for (let filter of siteSearch.state.results.info.filters) { try { if (filter.facets[0].name === "IsClinic" || filter.facets[0].name === "Degrees") { continue; } } catch {} let filterButton = document.createElement("span"); let filterText = filter.value; /* LocationId becomes LocationName */ if ( 'info' in this.state.results && 'locations' in this.state.results.info && this.state.results.info.locations.length && this.state.results.info.locations.some((loc) => loc.id == filterText) ) { filterText = this.state.results.info.locations.find((loc) => loc.id == filterText)?.name; } filterButton.classList.add("filter-button"); filterButton.innerHTML = `${filterText} <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; 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 (e) { throw(e); } } /* 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 }]; } } try { if(dataFacets.length === 1) { filterButton.classList.add(dataFacets[0].name); } } catch (error) { console.log(error); } /* Set the data-facets attribute */ filterButton.setAttribute('data-facets', JSON.stringify(dataFacets)); /* Create an event listener to handle clicks, removing filters */ filterButton.addEventListener('click', e => { const { target } = e; const { dataset } = target; if (dataset.text) { let theRegEx = new RegExp(dataset.text, "gi"); window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } else if(target.textContent) { let theRegEx = new RegExp(target.textContent, "gi"); window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } const facets = JSON.parse(dataset.facets); let clearedInsurance = false; let clearInsurancePromise; for (let facet of facets) { const { name, value } = facet; const state = this.state.filters[name]; if (state) { if (typeof state === 'string') { this.state.filters[name] = false; } else if (typeof state === 'object') { state[value].active = false; /* Catch weird values that have ',' in them */ try { let csv = value.split(","); for (let v of csv) { state[v].active = false; } } catch (error) { console.log("Error removing cvs filter."); } } } if (name == "InsuranceAccepted") { let lsInsurance = localStorage.getItem('omniSearchInsurance') || false; if (lsInsurance) { lsInsurance = lsInsurance.split(',').filter(ins => { ins != value }); lsInsurance.length ? localStorage.setItem('omniSearchInsurance', lsInsurance.join(',')) : localStorage.removeItem('omniSearchInsurance'); clearedInsurance = true; clearInsurancePromise = fetch("https://www.providence.org/integrationapi/clearinsuranceselection"); } } else if (name == "distance") { let searchRadiusEl = document.querySelector('#searchRadius'); searchRadiusEl.value = ''; } } 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(); if (history.pushState) { window.history.replaceState({}, "Find a Doctor", url); } } catch (error) { console.log("Error updating url"); } if (clearedInsurance) { Promise.allSettled([clearInsurancePromise]).then(()=>this.handleSubmit()); } else { this.handleSubmit(); } if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } }); /* Add the filter button to the DOM */ filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } /* Add badges for Virtual and New Patient visit type selections */ if (this.state.filters.visitType) { const { clinicVisits, onlineScheduling, videoVisits } = this.state.filters.visitType; const hasClinicVisits = clinicVisits && clinicVisits.active; const hasOnlineScheduling = onlineScheduling && onlineScheduling.active; const hasVideoVisits = videoVisits && videoVisits.active; const handleVisitFiltersClick = e => { const { value } = e.target.dataset; const { filters } = this.state; const state = filters.visitType; if (state) { state[value].active = false; } if (value == 'onlineScheduling') { if (filters.visitType['clinicVisits'].active != document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.contains('active')) { document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.toggle("active"); } document.querySelectorAll("#daysList a").forEach(day => { siteSearch.updateDaySelectorStateAllDays(day, false) }); if (state) { state['clinicVisits'].active = false; } } try { if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log(error); } this.handleSubmit(); }; handleVisitFiltersClick.bind(this); if (hasClinicVisits) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Accepting New Patients <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "clinicVisits"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } if (hasOnlineScheduling) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Online Scheduling <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "onlineScheduling"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } if (hasVideoVisits) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Video Visits <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "videoVisits"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } /* Create an event listener to handle clicks, removing filters */ document.querySelectorAll('.visit-filter').forEach(handleVisitFiltersClick); } } catch (e) { console.log(`Facets failed to load`); } 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 = `<link rel="canonical" href="https://${baseUrl}${window.location.pathname}?page=${this.state.filters.page}">`; 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 = ""; let secondaryLocationIds = []; if (this.state.description) { resultsHtml += `<p style="margin-bottom: 1em; margin-top: 0;"><small>${this.state.description}</small></p>`; 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 += `<div class="search-type" style="display: flex; flex-wrap: wrap; margin: 10px 0;"> <h4 style="margin: 0 6px 0 0;">Search type(s):</h4> <p style="margin: 0;">${results.info.type.join(', ')}</p> </div>`; } /* Pretty self explanatory: if the results count is zero, tell the user there are no results */ if (count == 0) { this.resultsEl.innerHTML = "No results"; /* Set map to center on Bellevue */ this.map.el.setCenterWithOffset(new google.maps.LatLng(47.659159, -122.183832), 0, 0); return; } /* Otherwise, let's display the total number of results */ else { try { if(results.geoip.zip !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.zip}</span></p>`; } else if(results.geoip.city !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.city}</span></p>`; } else if(results.geoip.county !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.county}</span></p>`; } else if(results.geoip.state !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.state}</span></p>`; }else { resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results</p>`; } } catch (error) { resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results</p>`; console.log(error); } } this.state.disclaimerSearch ? resultsHtml += '<h5>This page is intended to provide the most comprehensive information available regarding services offered in your area. Some of the services listed might not be offered at Providence facilities or in Providence affiliated practices. Providence is not responsible for services offered by providers on an independent basis.</h5>' : ''; /* 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 { ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, PrimarySpecialties, ProviderTitle, Addresses, PracticeGroup, LocationNames, LocationName, Rating, virtual, distance, distances, Npi, id, clinicVisits, NewClinicVisit, NewVideoVisit, GeocodedCoordinate, LocationId, LocationIds, RatingCount, ReviewCount, Degrees, Phones, ProfileUrl, AcceptingNewPatients, VirtualCare, SjhScheduleProviderNum, AllowsOpenSchedule, AllowsDirectSchedule, LocationsIsClinic, acceptingNewPatients, AppointmentRequestUrl, DaysUntilNextAppt, DaysUntilFollowUpAppt, Tier, Networks } = result; let ProviderOrganization = result.ProviderOrganization; const searchFeatures = result['@search.features']; const width = (150 / ImageHeight) * ImageWidth; const badges = result.badges ? Object.values(result.badges) : []; const supportsOdhp = (acceptingNewPatients == 1 && AllowsOpenSchedule == 1) || AllowsDirectSchedule == 1; const onlineBooking = supportsOdhp || SjhScheduleProviderNum ? true : false; let AcceptingClinicVisits = AcceptingNewPatients == 1 ? `<div class="new-patients mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg></span> <span class="anp">Accepting New Patients</span> </div>` : ""; const hasTimeslots = this.helpers.checkForTimeslots(NewClinicVisit, NewVideoVisit, DaysUntilNextAppt); /* Determining the description to show. If we have a Provider Title, use that. When no provider title, check for a primary specialty. If none, show no text. */ let descr = ""; if (PrimarySpecialties?.length) { descr = PrimarySpecialties.join(', '); } else if (ProviderTitle?.length) { descr = ProviderTitle; } else { descr = ""; }; this.map.deleteMarkers(); for (const location of LocationIds) { let isClinic; try { let iOfLoc = LocationIds.indexOf(location); isClinic = LocationsIsClinic[iOfLoc]; } catch (ex) { console.log(`Failed to determine if location is CLI for provider: Npi=${Npi}, LocationId=${location}; ` + ex); isClinic = true; } if (isClinic) { if (!this.map.locations[location]) { this.map.locations[location] = []; } this.map.locations[location].push({ Npi, ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, ProviderTitle: descr, Rating, PracticeGroup, virtual }); } } /* If we have badges for matched, create element HTML for it */ let badgesEls = ""; if (badges.length) { badges.forEach((badge) => {if (!badge.includes('PracticeGroupId')) {badgesEls += `<span>${badge}</span>`}}); } /* If a location name is present in the results, choose the proper text and create element HTML for it */ let locationName = ""; if (LocationName) { locationName = /*html*/`<h4 class="location-n"><span class="dd-svg"><svg class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span>${LocationName}</h4>`; } else if (LocationNames.length) { if (result["@search.highlights"] && result["@search.highlights"].LocationNames) { locationName = /*html*/`<h4 class="location-n"><span class="dd-svg"><svg class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span>${result["@search.highlights"].LocationNames[0]}</h4>`; }; }; let locationNames = false; if (LocationNames.length > 1) { locationNames = LocationNames.filter(loc => loc !== locationName); }; /* Loop through the LocationNames and Addresses arrays. If these are present, then build out the locs array for listing addresses on the provider detail cards */ let locs = []; let primaryLocation = false; let secondaryLocations = false; /* Formats address */ if (LocationNames.length && Addresses.length) { for (let i = 0; i < LocationNames.length; i++) { if (Addresses[i]) { locs.push({ name: LocationNames[i], address: formatAddress('providerCard', Addresses[i]), phone: Phones[i] }); } } /* Code for identifying clinics vs hospitals */ for (let i = 0; i < locs.length; i++) { locs[i].isClinic = result.LocationsIsClinic[i]; locs[i].distance = result.distances[i]; } let hasClinic = false; let tempLocArr = []; if (locs.length > 1) { locs.forEach(item => { if (item.isClinic == true) { hasClinic = true; } }); if (hasClinic) { for (let i = 0; i < locs.length; i++) { if (locs[i].isClinic) { tempLocArr.push(locs[i]); } } locs = tempLocArr; } } /* Locations should be sorted by distance Some providers have multiple locations within the same physical location (https://www.providence.org/doctors/general-surgery/mt/missoula/deron-ludwig-1245310200) In those cases, we want to display the first location in the list */ if (hasClinic && locs.length > 1) { locs = locs.sort((loc1, loc2) => loc1.distance - loc2.distance); } else { locs = locs.sort((a) => a.name === LocationName ? -1 : 1); } primaryLocation = locs[0]; secondaryLocations = locs.length > 1 ? locs.slice(1) : false; }; /* If a practice group is present in the result, choose the proper text and create element HTML for it */ let practiceGroup = ""; if (PracticeGroup.length) { if (result["@search.highlights"] && result["@search.highlights"].PracticeGroup) { practiceGroup = /*html*/`<small class="practice-group">${result["@search.highlights"].PracticeGroup[0]}</small>`; }; }; /* We only want to display review/rating data for providers that have "PHS Reviews Eligible Providers" listed as a network */ let rating = ""; if (Networks?.includes('PHS Reviews Eligible Providers')) { /* Create star rating for provider, if rating is present */ if (Rating) { let ratingFull = ""; let lastStar = parseInt(Rating.toString().slice(2,3)) * 1.6; if(Rating > 4) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3"></span> <span class="full-s fs-4"></span> <span class="full-s fs-5" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 3) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3"></span> <span class="full-s fs-4" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 2) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 1) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } if(ReviewCount > 0) { rating = /*html*/`<div class="reviews hide-zero-average_rating${Rating}"> <span class="stars s${Rating}"> <span class="empty"> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> </span> <span class="full"> ${ratingFull} </span> </span> <span class="stats"> ${Math.round(Rating * 100) / 100} </span> <a class="survey providence" href="https://www.providence.org/about/our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> <a class="survey swedish" href="https://www.swedish.org/about/about-our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> </div> <div class="rating_comment_count"><span>${RatingCount} Ratings</span> | <span>${ReviewCount} Reviews</span></div>`; } else { rating = /*html*/`<div class="reviews hide-zero-average_rating${Rating}"> <span class="stars s${Rating}"> <span class="empty"> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> </span> <span class="full"> ${ratingFull} </span> </span> <span class="stats"> ${Math.round(Rating * 100) / 100} </span> <a class="survey" href="https://www.providence.org/about/our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> </div> <div class="rating_comment_count"><span>${RatingCount} Ratings</span></div>`; } } } /* If provider offers virtual visits, create virtual visit html */ let offersVirtual = ""; let virtualBadge = ""; if(VirtualCare && AcceptingNewPatients == 1) { AcceptingClinicVisits = ` <div class="vv-and-patients desktop-only"> <div class="virtualVisits"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div> <div class="new-patients mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg></span> <span class="anp">Accepting New Patients</span> </div> </div> <div class="virtualVisits mobile-only mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div> <div class="new-patients mobile-only mobile-map-hide"> <svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg> <span class="anp">Accepting New Patients</span> </div> `; } else if (VirtualCare) { offersVirtual = /*html*/ `<div class="virtualVisits"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div>`; virtualBadge = /*html*/`<div class="virtualVisits infoWindow mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span> </div>` }; /* If a distance from ZIP exists, include that in the provider card. */ let distanceEl = ""; if (distance) { distanceEl = /*html*/`<div class="distance">${parseFloat(Number(distance).toFixed(1))} miles away</div>`; }; let name = Name; if (Degrees.length) { name = `${Name}, ${Degrees.join(', ')}`; }; let providerLink = this.helpers.createProviderLink(result); let wheelhouseData = sessionStorage.getItem('wheelhouseData'); if (!wheelhouseData) { try { const wheelHouseDataRaw = document.querySelector('#wheelhouseJson'); wheelhouseData = wheelHouseDataRaw ? JSON.parse(wheelHouseDataRaw.innerText) : {}; } catch (error) { console.log("Couldn't get wheelhouse data"); } } else{ wheelhouseData = JSON.parse(wheelhouseData); } const locName = primaryLocation?.name ? primaryLocation.name : LocationName ? LocationName : ''; const providerPhone = primaryLocation?.phone ?? Phones[0]; if (/swedish/gi.test(location.origin) && result.ProviderUniqueUrlSwedish) { /* use kyruus profile URL as source of truth for link to to profile */ providerLink = result.ProviderUniqueUrlSwedish; /* some providers still have old profile URL per request from Anita, overwrite with correct www. URL */ if (providerLink.includes('schedule.swedish.org')) { providerLink = providerLink.replace('schedule.swedish.org', 'www.swedish.org'); } } if (!/virtual/gi.test(locName)) { withLocations += 1; }; /* Determine whether or not to display the 90 days fallback message when timeslots are not available */ const shouldShowAlert = () => { return false; /* - We don't care about followup appointments anymore, only new patient availability if (DaysUntilFollowUpAppt && DaysUntilFollowUpAppt >=0 && DaysUntilFollowUpAppt <= 90) { return true; } else */ if (DaysUntilNextAppt && DaysUntilNextAppt >=0 && DaysUntilNextAppt <= 90) { return true; } else { return false; } }; const wompTrackData = { ec: '', el: `${name}, ${i+1}`, ea: JSON.stringify(result['@search.features']) }; let medicalGroupsAndAffiliations = ''; /** <!-- childReplacement provOmniMedicalAffiliations --> **/ try { ProviderOrganization = ProviderOrganization.filter(org => org.toLowerCase() != 'providence affiliated provider'); if (ProviderOrganization.length === 1) { if (ProviderOrganization[0].toLowerCase() === 'providence') { medicalGroupsAndAffiliations = ''; } else { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[0]} </div> </div>`; } } else if (ProviderOrganization.length > 1) { if (ProviderOrganization[0].toLowerCase() === 'providence') { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[1]} </div> </div>`; } else { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[0]} </div> </div>`; } } } catch (ex) { console.log('Failed to display provider org: ' + ex); } /** <!-- end childReplacement provOmniMedicalAffiliations --> **/ let distanceSec = ""; try { if (distances) { distanceSec = /*html*/`<div class="distance">${parseFloat(Number(distances[0]).toFixed(1))} miles away</div>`; }; } catch (error) { console.log("Couldn't get distances"); } let brandedLocNames = ["Providence", "Jude Medical Center", "Mission Heritage", "Jude Heritage", "Joseph Heritage", "Mary High Desert", "Facey", "Joseph Hospital", "Santa Rosa Memorial", "Petaluma Valley Hospital", "Sacred Heart Medical", "Saint Johns Health Cancer", "Doctors of Saint", "Pavilion for Women", "Swedish", "Pacific Medical Centers", "Covenant", "Grace Clinics", "Grace Clinic", "Kadlec"]; let addPO = true; let dpo = ""; if(ProviderOrganization?.length > 0) { /** See provOmniMedicalAffiliations, interesting case where we use the second PO if there are 2 and the first is providence **/ if (ProviderOrganization.length > 1 && ProviderOrganization[0].toLowerCase() === 'providence') { dpo = `dpo-${ProviderOrganization[1].replace(/\s+/g, '')}`; } else { dpo = `dpo-${ProviderOrganization[0].replace(/\s+/g, '')}`; } for (var i = 0; i < brandedLocNames.length; i++) { if(locName.indexOf(brandedLocNames[i]) > -1){ addPO = false; break; } } } else { addPO = false; } let matchedLocName = locName; let matchedLocAdd = primaryLocation.address; try { if (secondaryLocations.length > 0){ for (let i = 0; i < secondaryLocations.length; i++) { /* check if secondary location matches what we are searching for */ if (this.searchParams.get('locationname').includes(secondaryLocations[i].name)) { let secondName = locName; let secondAdd = primaryLocation.address; matchedLocName = secondaryLocations[i].name; matchedLocAdd = secondaryLocations[i].address; secondaryLocations[i].name = secondName; secondaryLocations[i].address = secondAdd; } } } } catch (error) { console.log("couldn't find secondary location: " + error); } resultsHtml += /*html*/ `<div id="npi${Npi}" class="card card-body provider${addPO ? ` ${dpo}` : ''}" data-location="${LocationId}" data-npi="${Npi}" data-position="${i+1}" data-location-name="${locName}"> <div class="provider-image"> <a href="${providerLink}" class="provider-link"> <amp-img src="${ImageSourceUrl || ImageUrl}" alt="Image of ${name}" width="${ImageWidth ? width : 150}" height="150" layout="${ImageWidth ? "intrinsic" : "fill"}" ></amp-img> ${virtualBadge} </a> </div> <div class="provider-info"> <a href="${providerLink}" class="provider-link"> <h3 class="color-headings">${name}</h3> </a> <p class="resultDescription">${descr}</p> ${rating} ${offersVirtual} ${AcceptingClinicVisits} ${isTest && Tier ? `<div class="tier"><span>Tier: ${Tier}</span></div>` : ''} </div> <div class="locations-and-timeslots mobile-map-hide"> ${isTest && ProviderOrganization?.length ? '<div id="test__organizations"><h4>Organizations</h4>' + ProviderOrganization.reduce((html, curr) => html + `<p>${curr}</p>`, '') + '</div>' : '' } ${isTest && searchFeatures && Object.keys(searchFeatures).length ? '<div id="test__organizations"><h4>Search Features</h4>' + Object.keys(searchFeatures).reduce((html, key) => html + `<p>${key}: ${searchFeatures[key]?.similarityScore.toFixed(3)}</p>`, '') + '</div>' : '' } <div class="read-more-wrap"> <div class="location-name ${matchedLocName}"> <span class="hide-content"> <svg width="24" height="24" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#providence-icon"></use></svg> <svg width="24" height="24" class="swed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#swedish-icon"></use></svg> <svg width="16" height="24" class="pacmed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pacmed-icon"></use></svg> <svg width="24" height="24" class="cov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#covenant-icon"></use></svg> <svg width="24" height="24" class="grace-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grace-icon"></use></svg> <svg width="20" height="24" class="kadlec-svg" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#kadlec-icon"></use></svg> </span> <h4 class="location-n ${matchedLocName}"> ${matchedLocName} </h4> </div> <div class="address"> <div class="hov-ul">${primaryLocation?.address ? matchedLocAdd : ''}</div> ${distanceEl} </div> ${providerPhone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(providerPhone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(providerPhone, 'hyphenated')}">${formatPhoneNumber(providerPhone)}</a>` : '' } ${medicalGroupsAndAffiliations} <div class="badges">${badgesEls}</div> ${hasTimeslots ? /** <!-- childReplacement searchPageTimeslotsWidget --> **/ /*html*/` <directory-timeslots provider="${Npi}" provider-link="${providerLink}" address="${primaryLocation.address.split(',')[0] || ''}" staging="${this.isStaging}" days="${this.helpers.getAvailability(this.state)}" time="${this.state.filters.AvailableTime}" ></directory-timeslots>` /** <!-- end childReplacement searchPageTimeslotsWidget --> **/ : '' } </div> </div> <div class="remaining-data mobile-map-hide"> ${!onlineBooking && AppointmentRequestUrl?.length ? `<div> <a class="btn-primary r-consult" style="padding: 5px 15px 7px; display: inline-block; margin-top: 0.5rem;" href="${AppointmentRequestUrl}" target="_blank"> <small>Request a Consultation</small></a> </div>` : '' } ${!hasTimeslots && shouldShowAlert() ? /*html*/` <div> <span class="fp-return-patients mychart-wa-mt"> Return Patients <a href="https://mychartwa.providence.org/mychart/Authentication/Login" target="_top">schedule with MyChart</a> </span> </div>` : '' } <input type="checkbox" class="read-more-state" id="provider${Npi}" /> ${secondaryLocations.length ? /*html*/` <div class="read-more-target"> ${secondaryLocations.reduce((html, curr) => { return html + /*html*/ `<div class="additional-location" data-location-name="curr.name" data-location=""> <div class="location-name ${curr.name}"> <span class="hide-content"> <svg width="24" height="24" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#providence-icon"></use></svg> <svg width="24" height="24" class="swed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#swedish-icon"></use></svg> <svg width="16" height="24" class="pacmed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pacmed-icon"></use></svg> <svg width="24" height="24" class="cov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#covenant-icon"></use></svg> <svg width="24" height="24" class="grace-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grace-icon"></use></svg> <svg width="20" height="24" class="kadlec-svg" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#kadlec-icon"></use></svg> </span> <h4 class="location-n ${curr.name}"> ${curr.name} </h4> </div> <div class="address"> <div class="hov-ul">${curr.address}</div> </div> ${distanceSec} <span id="${Npi}-${curr.name}"> <div class="phone"> <small>${curr.phone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(curr.phone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(curr.phone, 'hyphenated')}">${formatPhoneNumber(curr.phone)}</a>` : ''}</small> </div> </span> </div>` }, '')} </div> ` : ''} ${secondaryLocations.length ? `<div> <label for="provider${Npi}" class="read-more-trigger"> <span class="loc-wrap"> <svg class="expand" width="14" height="14" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chev-down"></use></svg> <svg class="compress" width="14" height="2" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chev-up"></use></svg> </span> ${secondaryLocations.length} <span class="more-location">More</span> <span class="less-location">Less</span> Location${secondaryLocations.length === 1 ? '' : 's'} </label></div>` : ''} </div> </div>`; try { for (let i = 0; i < secondaryLocations.length; i++) { secondaryLocationIds.push(LocationIds[LocationNames.indexOf(secondaryLocations[i].name)]); }; } catch (error) { console.log(error); } }); } if (results.info.locations) { /* Check if clinic locations are stored as object or string */ let clinicNamesAlpha; if (results.info.locations.length && results.info.locations[0].name) { let greenListedLocs = ['providence', 'mission heritage', 'st. jude heritage', 'st. joseph heritage', 'st. mary high desert', 'facey', 'st. joseph hospital', 'st. jude medical center', 'covenant', 'swedish', 'pacific medical centers', 'kadlec', 'grace clinics', 'st. jude', 'grace clinic', 'oregon clinic']; document.CurrentClinics = results.info.locations.filter(loc => greenListedLocs.some(gLoc => loc.name.toLowerCase().includes(gLoc)) ); let alphaSortedClinics = document.CurrentClinics.sort((locA, locB) => { return locA.name.toLowerCase().localeCompare(locB.name.toLowerCase()); }); clinicNamesAlpha = alphaSortedClinics.map(loc => loc.name); } else { clinicNamesAlpha = results.info.locations; } document.querySelector('#location-selection').innerHTML = clinicNamesAlpha.reduce((html, name) => { return html + `<option value="${name}" ${name === this.state.filters.LocationName ? 'selected' : ''}>${name}</option>`; }, `<option class="clinic-value" value="">Select a Clinic</option>`); document.querySelector('#mobile-location-selection').innerHTML = clinicNamesAlpha.reduce((html, name) => { return html + `<li><a tabindex="0" class="dropdown-item dc facet-value loc-sel">${name}</a></li>`; }, `<li><a tabindex="0" class="dropdown-item dc facet-value loc-sel">Select a Clinic</a></li>`); } else { document.querySelector('#custom-location + small.dc')?.remove(); document.querySelector('#location-selection')?.remove(); document.querySelector('#mobile-location-selection')?.remove(); } /* We want to make sure the corrected text from search response is always in the search box. If there's been a spelling correction, update the input value */ let decodedQ = query; try { decodedQ = decodeURIComponent(query); } catch { console.log(`Search query "${query}" is not a valid URI.`); } if (decodedQ !== results.info.search) { resultsHtml = /*html*/ `<div> <h5 class="results-text">Showing results for <strong>${results.info.search}</strong></h5> </div> ${resultsHtml}`; this.state.query = results.info.search; this.input.value = results.info.search; }; if (!decodedQ?.length) { this.input.value = ""; } if (results.info.any) { resultsHtml = /*html*/ `<div> <h5 class="results-text" data-no-search-results>There are no exact matches for ${ query.length ? `"${decodeURIComponent(this.state.query)}"` : "your search" }. Here are some similar results:</h5> </div> ${resultsHtml}`; }; /* If an exclude search keyword was used */ if (this.state.excludeSearch) { resultsHtml = /*html*/ `<div> <h5 class="results-text" data-no-search-results>There are no results for this search. Here are some similar results:</h5> </div> ${resultsHtml}`; } /* Update the HTML for our results container to show new results */ this.resultsEl.innerHTML = resultsHtml; try { let addLocs = document.getElementsByClassName("additional-location"); for (let i = 0; i < addLocs.length; i++) { addLocs[i].setAttribute("data-location", secondaryLocationIds[i]); } } catch (error) { console.log("Couldn't add data-location to additional location: " + error); } if (LocationsOnly || withLocations > 0 ) { this.map.toggleVirtual(false); } else { this.map.toggleVirtual(true); } }) .then(() => { if (this.state.filters.visitType) { queryParams += `&visittypes=${Object.keys(this.state.filters.visitType).join(',')}`; }; /* Set show the proper filters as having been selected */ this.displayFilters(); this.map.createMarkers(); this.helpers.pushHistory(queryParams); }) .then(async () => { const res = this.state.results; /* Query secondary locations for timeslot availability */ /* Populate array with KyruusId-LocationIds */ const parsedIds = []; /* Check each provider for multiple locations & create necessary timeslots */ for(let i = 0; i < res.results.length; i++) { let promises = []; const { LocationIds, LocationId, KyruusId } = res.results[i]; if(LocationIds.length > 1) { /* If more than 2 locations, loop through each location and check for availability */ for(let j = 0; j < LocationIds.length; j++) { /* Skip primary location because its timeslot data was already returned from omni */ if(LocationId === LocationIds[j]) { continue; } else { parsedIds.push(`${KyruusId}-${LocationIds[j]}`); } } } } const encodedIds = encodeURIComponent(parsedIds?.join(',')); const whDataUrl = `https://${this.apiSub}.azurewebsites.net/api/WompHealthData?`; const selectFields = [ "NewClinicVisit", "NewVideoVisit", "DaysUntilNextAppt", "Npi", "LocationName", "ProfileUrl", "ProviderUniqueUrlSwedish", "ProviderUniqueUrlPacmed", "ProviderUniqueUrlOnesite", "Phones" ]; const encodedFields = encodeURIComponent(selectFields.join(',')); /* parsedIds?.length && uses short-circuit evalutaion to skip the fetch call if ids is null or not an array for any reason */ parsedIds?.length && fetch(`${whDataUrl}&brand=${brand}&data=providers&ids=${encodedIds}&select=${encodedFields}`).then((res) => res.json()).then(res => { res.forEach((prov, i) => { if (!prov) { console.debug(`Location information not available for id=${parsedIds[i]}`); return; } const hasTimeslots = this.helpers.checkForTimeslots(prov.NewClinicVisit, prov.NewVideoVisit, prov.DaysUntilNextAppt); if (hasTimeslots) { const providerLink = this.helpers.createProviderLink(prov); const locTimeslotWrapper = document.getElementById(prov.Npi + "-" + prov.LocationName); if (locTimeslotWrapper) { locTimeslotWrapper.innerHTML = /*html*/` <timeslots-widget phone="${this.helpers.formatPhoneNumber(prov.Phones[0])}" provider="${prov.Npi}" type="${this.state.slotsType}" color="#3572A9" days-ahead="30" more-appointments-url="${providerLink}#aptType" ></timeslots-widget> `; } } }); }).catch(err => { console.error(err); }); fireInvoca(); }); }; renderLocations = (locations) => { if(!Array.isArray(locations)) { throw new Error(`Expected array, got ${typeof locations}`); } let resultHTML = ""; locations.forEach(location => { if(location.type !== "location") { throw new Error(`Expected location, got ${location.type ?? "<no type>"}`) } const { ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, PrimarySpecialties, ProviderTitle, Addresses, Address, PracticeGroup, LocationNames, LocationName, Rating, virtual, distance, Npi, id, clinicVisits, NewClinicVisit, NewVideoVisit, GeocodedCoordinate, RatingCount, Degrees, Phones, ProfileUrl, AcceptingNewPatients, VirtualCare, Phone } = location; const { formatPhoneNumber } = this.helpers; /* If we have badges for matched, create element HTML for it */ const badges = location.badges ? Object.values(location.badges) : []; let badgesEls = badges.reduce((acc, badge) => acc + `<span>${badge}</span>`, ""); /* TODO map the locations better */ if (!this.map.locations[id]) { this.map.locations[id] = []; } /* Determine the image to show for the location */ let locationImageSrc = 'https://az690879.vo.msecnd.net/7772/clinic-placeholder.jpg'; if (ImageSourceUrl?.length) { locationImageSrc = ImageSourceUrl; } else if (ImageUrl?.length) { locationImageSrc = ImageUrl; } /* Build the card for the location */ resultHTML += ` <div id=${id} class="card card-body" data-location="${id}"> <div class="provider-image"> <a href="#"> <amp-img src="${locationImageSrc}" width="120" height="120"></amp-img> </a> </div> <div class="provider-info"> <a href="#"> <h3 class="color-headings">${Name}</h3> </a> <div class="read-more-wrap"> <div class="address"> <div class="hov-ul">${Address}</div> </div> ${Phone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(Phone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(Phone, 'hyphenated')}">${formatPhoneNumber(Phone)}</a>` : ''} </div> <div class="badges">${badgesEls}</div> </div> </div> `; }); return resultHTML; }; getLocation = (e) => { /** <!-- childReplacement provOmniGeoLocatorLoadingLocation --> **/ const headerLocationLabel = document.querySelector('#geoLocatorLabel span'); headerLocationLabel.innerHTML = 'Detecting...'; /** <!-- end childReplacement provOmniGeoLocatorLoadingLocation --> **/ const LocationLabel = document.querySelector("#LocationLabel"); LocationLabel.innerHTML = `Location: Detecting... <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(siteSearch.helpers.showPosition, siteSearch.helpers.showError, { timeout: 10000 }); }; }; createClickListener = async () => { const { trackWompEvent } = this.helpers; document.body.addEventListener("click", async (e) => { const clearActiveDropdowns = () => document .querySelectorAll(".dropdown, #searchBox") .forEach((drop) => drop.classList.remove("active")); const el = e.target; const parent = el.parentNode; const { filters, query } = this.state; /* If this is an autocomplete... */ if (parent.id == "autocomplete") { this.input.value = el.innerText; this.state.query = el.innerText; this.handleSubmit(); return; }; if (el.id == "LocationLabel") { setTimeout(() => { if (document.querySelector('.dropdown.location.active')) { const el = document.querySelector('#custom-location'); el.select(); } }, 100); } /** <!-- childReplacement provOmniGeoLocatorClickEvent --> **/ /* Providence specific geolocation toggle in the top nav */ if (el.id == 'geoLocatorInput') { document.querySelector('#geoLocatorSuggestions').style.display = 'block'; el.select(); } if (el.id == 'geoLocatorClear') { document.querySelector('#geoLocatorSuggestions').style.display = 'none'; if(window.innerWidth < 1000) { document.querySelector('main').style.setProperty('z-index','auto'); } } /** <!-- end childReplacement provOmniGeoLocatorClickEvent --> **/ /* If user is clicking the 'update location' button then... */ if (el.id == "updateLocation") { const locNameVal = document.querySelector('#location-selection').value; if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } const searchRadiusVal = document.querySelector('#searchRadius').value; /* ∞ */ if (searchRadiusVal?.length) { this.state.filters.distance = searchRadiusVal; } else { this.state.filters.distance = ''; } this.helpers.getGoogleLocation(); }; if(el.classList.contains("search-clear-label")) { document.querySelector("#search-input").value = ""; document.querySelector("#search-link").href= "https://www.providence.org/search-results"; } if (el.classList.contains("visitType")) { const type = el.dataset.type; const btnEls = document.querySelectorAll(".visitType"); let submitViaOnlineScheduling = false; switch (type) { case "all": /* If something other than 'all' is selected... */ if (filters.visitType) { this.state.filters.visitType = false; btnEls.forEach((el) => el.classList.remove("active")); el.classList.add("active"); break; /* else, just exit the function */ } else { return; } default: /* Remove the active stage on 'All' */ btnEls[0].classList.remove('active'); /* If we don't have an active filter, create the object */ if (!filters.visitType) { filters.visitType = {}; } if (type == "onlineScheduling") { /* Set the value for this visit type to the opposite of previous value */ filters.visitType["clinicVisits"] = { active: !filters.visitType[type]?.active }; filters.visitType[type] = { active: !filters.visitType[type]?.active }; /* Toggle the active state */ el.classList.toggle("active"); if (filters.visitType[type].active != document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.contains('active')) { document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.toggle("active"); } document.querySelectorAll("#daysList a").forEach(day => { siteSearch.updateDaySelectorStateAllDays(day, filters.visitType[type].active) }); submitViaOnlineScheduling = true; } else { /* Set the value for this visit type to the opposite of previous value */ filters.visitType[type] = { active: !filters.visitType[type]?.active }; /* Toggle the active state */ el.classList.toggle("active"); } /* Highlight 'All' if both video and clinic visits are unselected */ if (Object.keys(filters.visitType).every(key => filters.visitType[key]?.active == false)) { btnEls[0].classList.add('active'); } break; }; try { if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log(error); } if (submitViaOnlineScheduling) { this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); } else { this.handleSubmit(); } }; /* Don't close the dropdown when clicking in a dropdown menu or input */ if(el.classList.contains("dropdown-menu") || el.id === "insuranceInput" || el.id === "specialtyInput") { return; } /* If we're clicking the body of the document and a dropdown is active, remove active state */ if ( document.querySelector(".dropdown.active") && !( el.closest('.dropdown') || parent.classList.contains("dropdown") || el.classList.contains("dropdown-item") || el.classList.contains("dc") ) ) { clearActiveDropdowns(); }; /* If we're clicking the body of the document and search is active, remove active state */ if(document.querySelector("#searchBox.active")&& !(el.closest('#searchBox') || parent.parentNode.id === 'searchBox')) { document.querySelector("#searchBox.active").classList.remove("active"); } /* If clicking in a dropdown... */ if (parent.classList.contains("dropdown")) { if (!parent.classList.contains("active")) { clearActiveDropdowns(); el.parentNode.classList.add("active"); /* Auto focus on the input element if one exists */ const toggleInput = el.parentNode.querySelector("input"); if (toggleInput) { toggleInput.focus(); } } else { el.parentNode.classList.remove("active"); }; return; /* This prevents further logic from running unnecessarily */ }; /* If clicking in an element inside a dropdown... */ if (parent.classList.contains("dropdown-toggle")) { if (!parent.parentNode.classList.contains("active")) { clearActiveDropdowns(); parent.parentNode.classList.add("active"); } else { parent.parentNode.classList.remove("active"); }; return; /* This prevents further logic from running unnecessarily */ }; /* If clicking the search bar */ if (parent.classList.contains("input-hover")) { if (!parent.parentNode.classList.contains("active")) { clearActiveDropdowns(); parent.parentNode.classList.add("active"); } else { parent.parentNode.classList.remove("active"); } } if(el.id === 'location-selection') { let locNameVal = document.querySelector('#location-selection').value; if(locNameVal !== this.state.filters.LocationName && locNameVal !== "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } else if(el.id === 'locationSelection') { let locNameVal = document.querySelector('#locationSelection').value; if(locNameVal !== this.state.filters.LocationName && locNameVal === "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } /* Mobile location selection for the clinic selection */ if(el.classList.contains("loc-sel")) { let locNameVal = el.text; if(locNameVal !== this.state.filters.LocationName && locNameVal !== "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } /* Handle dropdown logic */ if (el.classList.contains("facet-value")) { if(el.classList.contains("loc-sel")) { if(el.text !== "Select a Clinic") { document.getElementById("location-selection").value = el.text; }else { document.getElementById("location-selection").value = ""; } document.getElementById("locationSelectionText").textContent = el.text; document.getElementById("locationSelectionInput").checked = false; return; } const toggleEl = el.closest('.dropdown.facet, .dropdown.sort'); const filterID = toggleEl.id; const isActive = el.classList.contains("active"); if (filterID === "sortby") { if (el.innerText == "Relevance") { this.state.filters.sortby = false; } else { this.state.filters.sortby = el.dataset.sort; }; toggleEl.querySelector( "button" ).innerHTML = `Sort by: ${el.innerText} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; toggleEl.classList.remove("active"); trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); this.handleSubmit(); return; }; if (filterID === "organization") { this.state.brand = el.dataset.organization; this.state.filters.brand = this.state.brand; toggleEl.querySelector( "button" ).innerHTML = `Organization: ${el.innerText} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; toggleEl.classList.remove("active"); trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); this.handleSubmit(); return; }; if (isActive) { /* If this was autoapplied, we save the text string that matched the filter and remove it from the query when clicked */ if (this.state.filters[filterID][el.innerText].text) { let theRegEx = new RegExp(this.state.filters[filterID][el.innerText].text, "gi"); /* Update our session storage to keep track of our previous query */ window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } this.state.filters[filterID][el.innerText] = { active: false }; } else { this.state.filters[filterID][el.innerText] = { active: true }; trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); el.classList.add("active"); }; this.handleSubmit(); }; /* Clear filters click handling */ if (el.id == "clearFilters") { let clearInsurancePromise = fetch("https://www.providence.org/integrationapi/clearinsuranceselection"); let searchRadiusEl = document.querySelector('#searchRadius'); searchRadiusEl.value = ''; const filtersWrapper = document.getElementById("filterWrapper"); /* Remove user entered queries from state and the input el */ this.state.query = ""; this.input.value = ""; this.state.specificDays = false; /* Clear filters */ this.state.filters = { ...this.state.filters, Gender: {}, InsuranceAccepted: {}, Languages: {}, PrimarySpecialties: {}, /** <!-- childReplacement providerOrgClearFilter --> **/ ProviderOrganization: {}, /** <!-- end childReplacement providerOrgClearFilter --> **/ page: 1, LocationName: false, PracticeGroup: '', LocationId: '', distance: '', location: false, AvailableDays: { Sunday: { active: false }, Monday: { active: false }, Tuesday: { active: false }, Wednesday: { active: false }, Thursday: { active: false }, Friday: { active: false }, Saturday: { active: false }, }, AvailableTime: "any", visitType: false }; this.state.currentLoc = false; /* Remove Query Params*/ let url = new URL(window.location); url.search = ""; if (history.pushState) { window.history.replaceState({}, "Find a Doctor", url); this.state.pushHistory = true; } /* Clear localStorage of retained insurance and location filters */ localStorage.removeItem('omniSearchInsurance'); localStorage.removeItem('omniSearchLocation'); document.querySelector("#custom-location").value = ''; this.populateDaytimeSelectorFromState(); Promise.allSettled([clearInsurancePromise]).then(()=>this.handleSubmit()); filtersWrapper.classList.remove("hasFilter"); }; /* Pagination clicks */ if (e.target.classList.contains("page-link")) { this.state.filters.page = e.target.dataset.page; }; /* Geolcation clicks */ if (e.target.classList.contains("useLocation")) { const isOn = document.querySelector('#switch')?.checked; if (!isOn) { this.getLocation(e); } else { this.helpers.getGoogleLocation(); } this.state.useMyLocationClicked = true; return; }; /* toggle between sorting clinics in location dropdown by alpha or distance */ if (e.target.classList.contains("distanceSort")) { const isOn = document.querySelector('#switchDistanceSort')?.checked; let clinicNames; if (!isOn) { if (document.CurrentClinics.length && document.CurrentClinics[0].distance) { clinicNames = document.CurrentClinics.sort((locA, locB) => locA.distance - locB.distance).map(loc => loc.name) } else { clinicNames = document.CurrentClinics; } } else { if (document.CurrentClinics.length && document.CurrentClinics[0].name) { let sortedClinics = document.CurrentClinics.sort((locA, locB) => { return locA.name.toLowerCase().localeCompare(locB.name.toLowerCase()); }); clinicNames = sortedClinics.map(loc => loc.name); } else { clinicNames = document.CurrentClinics; } } document.querySelector('#location-selection').innerHTML = clinicNames.reduce((html, name) => { return html + `<option value="${name}">${name}</option>`; }, `<option value="">--Select a clinic</option>`); return; } /* Map clicks */ if (e.target.classList.contains('gm-ui-hover-effect') && e.target.tagName == "BUTTON") { const selectedNpi = document.querySelector(`#npi${this.map.selectedNpi}`); if (selectedNpi) { selectedNpi.classList.remove('selected'); }; if (this.map.selectedMarker) { this.map.resetMarkerIcon(); }; }; /* Provider card clicks */ var setInfoWindow = el => { var { location, npi } = el.dataset; let additionalLoc = ""; /* Provider card click - Additional Location */ if (e.target.classList.contains("additional-location")) { additionalLoc = e.target.getAttribute("data-location"); }else if(e.target.parentElement.classList.contains("additional-location")) { additionalLoc = e.target.parentElement.getAttribute("data-location"); }else if(e.target.parentElement.parentElement.classList.contains("additional-location")) { additionalLoc = e.target.parentElement.parentElement.getAttribute("data-location"); } if(additionalLoc) {location = additionalLoc;} var id = this.map.lookup[location]; google.maps.event.trigger(this.map.markers[id], 'click'); this.map.selectProvider(npi); }; const closestEl = e.target.closest('.card.provider'); if (closestEl) { if (e.target.tagName !== "TIMESLOTS-WIDGET" && e.target.tagName !== "DIRECTORY-TIMESLOTS" && !e.target.closest('a')) { setInfoWindow(closestEl); } const linkEl = e.target.closest('a'); if (linkEl) { const index = Number(closestEl.dataset.position); const provider = this.state.results.providers[index - 1]; const hasTimeslots = closestEl.querySelector('timeslots-widget') ? 1 : 0; const defaultVals = { ea: `${provider.Name}, ${index}, timeslots: ${hasTimeslots}`, el: JSON.stringify(provider['@search.features']) }; if (linkEl.classList.contains('provider-link')) { /* Prevent routing until analytics tracking finishes */ e.preventDefault(); /* Send beacon */ await this.helpers.trackWompEvent({ ec: 'Provider Click', ...defaultVals }) .catch(err => console.error(err)); /* Route to link */ window.location = linkEl.href; } if (linkEl.classList.contains('phoneLink')) { this.helpers.trackWompEvent({ ec: "Direct Contact", ea: "Phone Click-to-Call", el: linkEl.dataset.varsPhoneLink }); this.helpers.trackWompEvent({ ec: 'Provider Click-To-Call', ...defaultVals }) } } } }); }; createChangeListeners = () => { this.showMapEl.addEventListener('change', e => { if (e.target.checked) { this.map.el.fitBounds(this.map.bounds); } else { /* allows amp-imgs to load when map view is toggled off after a search was performed and elements are scrolled into view vertically after being outside of the viewport horizontally */ this.helpers.autoNudgeAmpElements(true); } }); }; displayFilters = () => { const { Gender, Languages, InsuranceAccepted, PrimarySpecialties, visitType, brand, location, sortby, locationType, distance } = this.state.filters; /** <!-- childReplacement providerOrgGetDisplayFilter --> **/ let ProviderOrganization = this.state.filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetDisplayFilter --> **/ const { geoip, info } = this.state.results; /* Grab the object for the filterable facets */ let facets = this.state.facets; const facetSet = { Gender, Languages, InsuranceAccepted, distance, /** <!-- childReplacement providerOrgFacetSet --> **/ ProviderOrganization, /** <!-- end childReplacement providerOrgFacetSet --> **/ PrimarySpecialties }; /* Listen for input changes inside of dropdowns and filter list items based on the input value */ function createFilterInputListener(facet, keys, input, list) { const filterInput = document.getElementById(input); const filterList = document.getElementById(list); filterInput.addEventListener('input', e => { const regex = new RegExp(e.target.value, 'i'); let active = 0; let filteredHtml = ''; let total = 0; for(let key of keys) { let isActive = false; if (facet[key]?.active === true) { active++; isActive = true; }; if (!regex.test(key)) continue; total++; filteredHtml += /*html*/ `<li> <a class="dropdown-item dc facet-value${isActive ? " active" : ""}" ><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } /* Hide the list when no list items match input */ filterList.style.display = total > 0 ? "block" : "none"; filterList.innerHTML = filteredHtml; }); fireInvoca(); } /* We'll use this to remove auto-applied filters at search time */ const autoApplied = []; /* Iterate over the facets and set filter displays */ try{ for (let facet in facetSet) { /* We want to make sure that the facet exists in the facets obj before proceeding */ if (facets[facet]) { facets[facet].forEach((val) => { const filter = info.facets[facet]; let isSelected = false; if (filter && filter.includes(val)) { isSelected = true; }; this.state.filters[facet][val] = { ...this.state.filters[facet][val], active: isSelected }; }); } }; } catch { console.log(`Facets failed to load`); } for (const f of info.filters) { const { facets: fs } = f; if (f.text?.length && fs && fs.length) { console.log(f); fs.forEach(filter => { autoApplied.push({ name: filter.name, value: filter.value }); }); } } /* Update autoapplied filters in state */ this.state.autoApplied = autoApplied; /* For each of the filter dropdowns we've added, update the UL to show the available filters */ document.querySelectorAll(".dropdown.facet").forEach((dropdown) => { const facet = this.state.filters[dropdown.id]; if (facet) { /* Use blocklists to explicitly remove specific filter values For example, providence requested we remove multiple provider orgs from the "Medical Group" dropdown */ let blocklists = { 'ProviderOrganization': [ "Providence Affiliated Physicians", " Los Angeles County", " Mission", " St. Joseph", " St. Jude", " St. Mary", /** <!-- childReplacement provomniProviderOrgBlocklist --> **/ 'Providence', 'Providence Affiliated Physicians, Los Angeles', 'Axminster Medical Group & Providence Care Network Providers', 'Covenant Health Partners Affiliated', 'Covenant Rural Health Clinics', 'Facey Affiliated Provider', 'PacMed AdvantAge Health Center', 'Providence Affiliated Provider', 'Providence Medical Group', 'Providence Medical Institute', 'Saint John\'s Physician Partners Affiliated Providers', 'St. Joseph Health Medical Group', 'Providence Care Network', 'Providence Medical Associates' /** <!-- end childReplacement provomniProviderOrgBlocklist --> **/ ] }; let isFiltered = false; let html = "<ul>"; let keys = Object.keys(facet); keys = keys.sort(); let active = 0; /* Add a text input and a list id to lists which can be filtered by text */ if(dropdown.id === "InsuranceAccepted") { html = /*html*/ `<div class="search-wrap"> <button id="insuranceInputClear" class="hidden" onclick="clearInput('insuranceInput')"></button> <label for="insuranceInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='insuranceInput' placeholder='Search insurance' /> <label for="insuranceInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='insuranceList'> ` } else if(dropdown.id === "PrimarySpecialties") { html = /*html*/ `<div class="search-wrap"> <button id="specialtyInputClear" class="hidden" onclick="clearInput('specialtyInput')"></button> <label for="specialtyInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='specialtyInput' placeholder='Search specialty' /> <label for="specialtyInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='specialtiesList'>` } else if(dropdown.id === "ProviderOrganization") { html = /*html*/ `<div class="search-wrap"> <button id="organizationInputClear" class="hidden" onclick="clearInput('organizationInput')"></button> <label for="organizationInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='organizationInput' placeholder='Search medical group' /> <label for="organizationInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='organizationList'>` } else if(dropdown.id === "Languages") { html = /*html*/ `<div class="search-wrap"> <button id="languageInputClear" class="hidden" onclick="clearInput('languageInput')"></button> <label for="languageInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='languageInput' placeholder='Search languages' /> <label for="languageInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='languagesList'>` } /* If we have a blocklist for our filter, then remove blocked values before generating UI */ if (dropdown.id in blocklists) { let allowedKeys = keys.filter(function(key) { return !blocklists[dropdown.id].includes(key); }); keys = allowedKeys; } for (let key of keys) { let isActive = false; if (facet[key]?.active === true) { active++; isActive = true; }; if(dropdown.id ==="Gender"){ const genderVals = ["Female", "Male", "Non-binary"]; if(genderVals.includes(key)) { html += /*html*/ `<li> <a tabindex="0" class="dropdown-item dc facet-value${isActive ? " active" : ""}"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } } else { html += /*html*/ `<li> <a tabindex="0" class="dropdown-item dc facet-value${isActive ? " active" : ""}"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } }; html += '</ul>'; dropdown.lastElementChild.innerHTML = html; if (dropdown.id === "InsuranceAccepted") { createFilterInputListener(facet, keys, "insuranceInput", "insuranceList"); } else if(dropdown.id === "PrimarySpecialties") { createFilterInputListener(facet, keys, "specialtyInput", "specialtiesList"); } else if(dropdown.id === "ProviderOrganization") { createFilterInputListener(facet, keys, "organizationInput", "organizationList"); } else if(dropdown.id === "Languages") { createFilterInputListener(facet, keys, "languageInput", "languagesList"); } if (active > 0) { dropdown.querySelector("button").classList.add("filtered"); } else { dropdown.querySelector("button").classList.remove("filtered"); }; } }); /* Set query in search box */ if (this.state.query.length) { document.querySelector("#query").value = decodeURIComponent( this.state.query ); }; /* Set location information in location dropdown */ if (this.state.currentLoc?.length) { try { document.querySelector( "#LocationLabel" ).innerHTML = `Location: ${this.state.currentLoc} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; document.querySelector( 'input[name="location"]' ).value = this.state.currentLoc; /** <!-- childReplacement provOmniGeoLocatorInputUpdate --> **/ try { this.textInputs.forEach(input => { if (['custom-location'].includes(input.id)) { if(document.querySelector('#geoLocatorLabel span').textContent.indexOf(this.state.currentLoc) > -1) { input.value = document.querySelector('#geoLocatorLabel span').textContent; }else { input.value = this.state.currentLoc; } } if (['geoLocatorInput'].includes(input.id)) { checkLoginStatus(); } }); } catch (ex) { console.log(ex); } /** <!-- end childReplacement provOmniGeoLocatorInputUpdate --> **/ } catch (err) { console.error(err); } } /* Set the visit type toggle */ document.querySelectorAll('#visitTypeFilters button').forEach(button => { const type = button.dataset.type; const { visitType } = this.state.filters; if (!visitType) { type === 'all' ? button.classList.add('active') : button.classList.remove('active'); } else { type === 'all' ? button.classList.remove('active') : visitType[type]?.active ? button.classList.add('active') : button.classList.remove('active'); } }); }; paginate = () => { const { filters } = this.state; const { pages, page } = filters; let start; let html = ``; if(pages === 1 && page === pages) { html = /*html*/`<button id="queryClear" class="hidden" onclick="clearInput('query')"></button> <div class="btn-group single-page" role="group" aria-label="Results pages">`; } else { html = /*html*/`<button id="queryClear" class="hidden" onclick="clearInput('query')"></button> <div class="btn-group" role="group" aria-label="Results pages">`; } /* Let's make sure we display 5 results any time there are 5 or more pages */ if (page <= 4) { start = 1; } else if (pages - page < 4) { start = page - (4 - (pages - page)); } else { start = page - 2; }; const firstPage = new URL(document.location); firstPage.searchParams.set('page', 1); var nextPageUrl = new URL(document.location); var previousPageUrl = new URL(document.location); try { nextPageUrl.searchParams.set('page', filters.page + 1); previousPageUrl.searchParams.set('page', filters.page - 1); } catch (error) { console.log("Error setting pagination"); nextPageUrl.searchParams.set('page', 1); previousPageUrl.searchParams.set('page', 1); } if (start > 1) { html += start > 2 ? /*html*/ `<a href="${previousPageUrl.href}" class="previous-page"> <svg width="12" height="22" class="page-svg arrow-left-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-left-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-inactive"></use></svg> </a> <a class="btn page-link" data-page="1" href="${firstPage.href}">1</a> <span class="btn page-link ellipsis ellipsis-prev">...</span>` : ``; }; const ithPage = new URL(document.location); let mobileHide = ""; for (let i = start; i <= start + 4; i++) { if(pages > 4 && i > start + 1 && i < start + 4) { mobileHide = " mobile-hide" } else { mobileHide = ""; } if (i > pages) break; ithPage.searchParams.set('page', i); if(i === 1) { html += /*html*/ `<a href="${previousPageUrl.href}" class="previous-page"> <svg width="12" height="22" class="page-svg arrow-left-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-left-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-inactive"></use></svg> </a>`; } html += /*html*/ `<a class="btn${page == i ? " active" : ""} page-link ${mobileHide}" data-page="${i}" href="${ithPage.href}">${i}</a>`; if (i == start + 4 && i < pages) { ithPage.searchParams.set('page', pages); html += /*html*/ `<span class="btn page-link ellipsis">...</span>`; if (i < pages + 1) { ithPage.searchParams.set('page', pages); html += /*html*/`<a class="btn page-link${mobileHide} no-m" data-page="${pages}" href="${ithPage.href}">${pages}</a>`; }; }; }; html += `<a href="${nextPageUrl.href}" class="next-page"> <svg width="12" height="22" class="page-svg arrow-right-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-right-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-right-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-right-inactive"></use></svg> </a></div>`; this.paginationEl.innerHTML = html; this.paginationEl.style.display = "flex"; try { this.paginationEl.classList.remove("first-page"); } catch (error) { } try { this.paginationEl.classList.remove("last-page"); } catch (error) { } try { this.paginationEl.classList.remove("no-pages"); } catch (error) { } if(pages === 0) { this.paginationEl.classList.add(`no-pages`); this.paginationEl.style.display = "none"; } else if(filters.page === pages){ this.paginationEl.classList.add(`last-page`); } else if(filters.page === 1) { this.paginationEl.classList.add(`first-page`); } try { let windowWidth = window.innerWidth; if(windowWidth <= 960) { let top = document.getElementById('results-wrapper').offsetHeight + 55; this.mapEl.style.height = `calc(100vh - ${top}px)`; } } catch (error) { console.log(error); } }; addWindowListeners = () => { window.addEventListener('resize', this.helpers.debounce(() => { return; const w = window.innerWidth; if (w < 1000) { if (this.mapEl.style.top !== '78px') { this.logo.style.width = '85px'; this.logo.style.height = '50px'; this.mapEl.style.top = '78px'; this.mapEl.style.height = 'calc(100vh - 78px)'; }; } else { const h = document.querySelector('header'); this.logo.style.width = '170px'; this.logo.style.height = '99px'; this.mapEl.style.top = `${h.clientHeight}px`; this.mapEl.style.height = `calc(100vh - ${h.clientHeight}px)`; }; }, 100)); window.onpopstate = (event) => { this.searchParams = prepURLSearchParams(); this.init(); }; window.addEventListener('scroll', () => this.map.setOffset()); }; populateTimeFromState = () => { const activeDays = Object.entries(this.state.filters.AvailableDays).filter(x => this.state.specificDays && x[1].active).map(x => x[0]); let labelText = "Availability "; const label = document.querySelector("#AvailabilityLabel"); if(activeDays.length) { if(activeDays.length !== 7) { labelText += ": " + activeDays.map(day => { switch(day) { case "Monday": return "M"; case "Tuesday": return "T"; case "Wednesday": return "W"; case "Thursday": return "Th"; case "Friday": return "F"; case "Saturday": return "S"; case "Sunday": return "Su"; default: return ""; } }).join(", ") + " "; } } if(this.state.filters.AvailableTime !== "any") { let time = this.state.filters.AvailableTime.split(""); time[0] = time[0].toUpperCase(); labelText += ": " + time.join("") + " "; } labelText += `<span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; label.innerHTML = labelText; }; populateDaytimeSelectorFromState = () => { const timeSelector = document.querySelector("#time-selector"); const daySelector = document.querySelector("#day-selector"); const checkboxes = document.querySelector("#availability-days-checkboxes"); const dayCheckboxes = document.querySelectorAll(".day-checkbox"); timeSelector.value = this.state.filters.AvailableTime; if(this.state.specificDays) { daySelector.value = "specific-day"; checkboxes.classList.remove("hidden"); } else { daySelector.value = "any-day"; checkboxes.classList.add("hidden"); } dayCheckboxes.forEach(checkbox => { const state = this.state.filters.AvailableDays[checkbox.innerHTML].active; if(!state) { checkbox.classList.remove("active"); } else { checkbox.classList.add("active"); } }); const activeDays = Object.entries(this.state.filters.AvailableDays).filter(x => this.state.specificDays && x[1].active).map(x => x[0]); let labelText = "Availability "; const label = document.querySelector("#AvailabilityLabel"); if(activeDays.length) { if(activeDays.length !== 7) { labelText += ": " + activeDays.map(day => { switch(day) { case "Monday": return "M"; case "Tuesday": return "T"; case "Wednesday": return "W"; case "Thursday": return "Th"; case "Friday": return "F"; case "Saturday": return "S"; case "Sunday": return "Su"; default: return ""; } }).join(", ") + " "; } } if(this.state.filters.AvailableTime !== "any") { let time = this.state.filters.AvailableTime.split(""); time[0] = time[0].toUpperCase(); labelText += ": " + time.join("") + " "; } labelText += `<span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; label.innerHTML = labelText; }; updateTimeSelectorState = (time) => { document.querySelectorAll("#timesList .active").forEach(e => { e.classList.remove("active"); }); time.classList.add("active"); this.state.filters.AvailableTime = time.id; this.populateTimeFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; updateDaySelectorState = (day) => { let autoChecked = false; if(day.classList.contains("active") && day.id !== "any-day"){ this.state.filters.AvailableDays[day.id].active = false; } if(!day.classList.contains("active")){ day.classList.add("active"); if(day.id !== "any-day") { this.state.filters.AvailableDays[day.id].active = true; } } else if (day.id !== "any-day") { day.classList.remove("active"); /* If this was the last checked box being unchecked, we need to automatically check the "any days" box */ if (!document.querySelectorAll("#daysList .day.active").length) { try { document.querySelector("#any-day").classList.add("active"); this.state.specificDays = false; autoChecked = true; } catch (error) { console.log(error); } } } if (!autoChecked) { if (day.id !== "any-day") { this.state.specificDays = true; try { document.querySelector("#any-day").classList.remove("active"); } catch (error) { console.log(error); } } else { this.state.specificDays = false; document.querySelectorAll("#daysList .day.active").forEach(e => { e.classList.remove("active"); }); this.state.filters.AvailableDays["Monday"].active = false; this.state.filters.AvailableDays["Tuesday"].active = false; this.state.filters.AvailableDays["Wednesday"].active = false; this.state.filters.AvailableDays["Thursday"].active = false; this.state.filters.AvailableDays["Friday"].active = false; } } this.populateTimeFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; updateDaySelectorStateAllDays = (day, check) => { if(day.id == "any-day") { if (check && day.classList.contains("active")) { this.state.specificDays = true; day.classList.remove("active"); } else { if (!check && !day.classList.contains("active")) { this.state.specificDays = false; day.classList.add("active"); } } } else { if(day.classList.contains("active") && !check) { day.classList.remove("active"); this.state.filters.AvailableDays[day.id].active = false; } else if(!day.classList.contains("active") && check) { day.classList.add("active"); this.state.filters.AvailableDays[day.id].active = true; } } this.populateTimeFromState(); }; updateDayTimeSelectorState = () => { const timeSelector = document.querySelector("#time-selector"); this.state.filters.AvailableTime = timeSelector.value; const daySelector = document.querySelector("#day-selector"); this.state.specificDays = (daySelector.value === "specific-day"); const checkboxes = document.querySelector("#availability-days-checkboxes"); this.populateDaytimeSelectorFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; /** <!-- childReplacement provOmniSearchFunctions --> **/ /** * @description Convert a JavaScript object into a serialized string * @param {object} obj - JavaScript object capable of serialization * @param {boolean} usePlusSpace - Use '+' instead of '%20' to represent spaces * @returns {string} - Serialized string * * This converts an object into a URL query or URL encoded form body. * The returned string is not prepended by a question mark. * Example with usePlusSpace is truthy: * { a: '', b: '1 2', c: '1 2' } --> 'a&b=1+2&c=1+2' * Example when usePlusSpace is falsy: * { a: '', b: '1 2', c: '1 2' } --> 'a&b=1%202&c=1%202' */ serializeObject(obj, usePlusSpace) { return Object.keys(obj).map((key) => { let value = encodeURIComponent(obj[key] || ''); if (usePlusSpace) value = value.replace(/%20/g, '+'); return encodeURIComponent(key) + '=' + value; }).join('&'); } xhrProvidenceGeolocationAutocomplete(location) { return new Promise((resolve, reject) => { let geocodeUrl; if (this.isStaging) { geocodeUrl = 'https://providencekyruusstaging.azurewebsites.net/api/geolocation/api/autocomplete/'+location+'?take=5'; }else{ geocodeUrl = 'https://providencekyruus.azurewebsites.net/api/geolocation/api/autocomplete/'+location+'?take=5'; } const xhr = new XMLHttpRequest(); xhr.open('GET', geocodeUrl, true); xhr.withCredentials = true; xhr.setRequestHeader("traceparent","00-7c12f8055a29436f95167ba40787dcae-92e09794d75a4f19-01"); xhr.setRequestHeader("x-requested-with","XMLHttpRequest"); if (this.isStaging) { xhr.setRequestHeader("RequestVerificationToken", "E0-x1LbtEMXUSEIfOvRQXj9EY-u2bR1VJpBe-z7eQIzN8eoy0QgjmInrnmLtlJwyuM7x6CdjAD5Q_GREZr0A_WR1uGWT4rJ1QgGUkznTSZo1:DG_76BAidglwME5Li0a2saKhFlCIpsNYFT3iawY9SwQFpiFD-1gwWlOgHFyFANWTe4re6YgvEMiDOW-Y8gGuxTBBqMs8hiwguwKZ3102tF41"); }else{ xhr.setRequestHeader("RequestVerificationToken", "3brXwene3ShS4TLB4IT1rG9vLrzdUgU88z9FHag9cLeRr1rekAkPtkmUkAUv2miRgl8Qpp0HzxxrETfJ6-l7Z3V8Fd0pyZ6nB1RovFdJb041:6E51pGciBcnM_aG91aokWULU6JNKdIR7o7uULQ7B6xAEYUxY2GavtVGNc9qyRnOGwkASFWxBQ12fgXS6n53NTRiIY27SrDAh6js-bSVaOjg1"); } xhr.responseType = 'json'; xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { /* IE compatibility */ const json = typeof xhr.response === 'string' ? JSON.parse(xhr.response) : xhr.response; if (json && json.success) resolve(json); else reject('Could not guess user location from zipcode'); } else { reject('xhrProvidenceGeolocationAutocomplete did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(); }) .then((data) => { let results = []; results.push("location-value " + location); data.results.forEach((loc) => { results.push(loc.city + ', ' + loc.state + ' ' + loc.zip); }); return results; }) .catch((error) => { console.error('Unable to fetch typeahead results for geolocator\n', error); return []; }); } handleGeoUpdate(event) { event.preventDefault(); let location = event.submitter.value; if(location) { let loc1 = location.split(","); let loc2 = loc1[1].split(" "); let city = loc1[0]; let state = loc2[1]; let zip = loc2[2]; document.querySelector('#geoLocatorLabel span').textContent = location; document.querySelector('#geo-locator-ck').checked = false; document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'none'); this.setGeoProvLocation(zip, city, state); /*Set user Entered zipcode cookie; important to providence // 24 hours */ const setCookie = (id, value, maxage, path, domain, secure) => { let cookieString = id + "=" + value; cookieString += path ? "; path=" + path : "; path=/"; if (maxage !== undefined) cookieString += "; max-age=" + maxage; if (domain) cookieString += "; domain=" + domain; if (secure) cookieString += "; secure"; document.cookie = cookieString; }; setCookie('UserEnteredLocationGeoCoordinates-v3', `{"City":"${city}","Latitude":"undefined","Longitude":"undefined","PostalCode":"${zip}","StateCode":"${state}","Regions":[],"Version":1}`, 86400); /* localstorage user entered zipcode is only used by us */ localStorage.setItem('UserEnteredLocationGeoCoordinates-v3', `{"City":"${city}","Latitude":"undefined","Longitude":"undefined","PostalCode":"${zip}","StateCode":"${state}","Regions":[],"Version":1}`); siteSearch.helpers.getGoogleLocation("geoLocatorSuggestions input:first-of-type"); } /*Set input value to selected value*/ document.querySelector('#geoLocatorInput').value = event.submitter.value; /*Remove options*/ let inputs = document.querySelectorAll('#geoLocatorSuggestions input'); for (let i = 0; i < inputs.length; i++) { inputs[i].remove(); } } /* Set the userlocation on Providence's end */ setGeoProvLocation(postalcodeSaved, citySaved, stateSaved) { return new Promise((resolve, reject) => { const body = { scController: 'DetectUserLocation', scAction: 'SetUserLocation', postalcode: postalcodeSaved, city: citySaved, state: stateSaved }; let geocodeUrl; if (this.isStaging) { geocodeUrl = 'https://preview-providence.provhealth.org/locations'; }else{ geocodeUrl = 'https://www.providence.org/locations'; } const xhr = new XMLHttpRequest(); xhr.open('POST', geocodeUrl); xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest'); xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded; charset=UTF-8'); xhr.setRequestHeader('Authorization', 'Bearer ' + '7i9p5na47e3xgxz2bk472jgyfm989tt5'); xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { checkLoginStatus(); /*console.log("Providence geolocation updated - setGeoProvLocation()");*/ } else { reject('setGeoProvLocation did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(this.serializeObject(body)); }); } guessUserLocation() { return new Promise((resolve, reject) => { const geocodeUrl = 'https://wompservices.wompmobile.com/geoip'; const xhr = new XMLHttpRequest(); xhr.open('GET', geocodeUrl); xhr.responseType = 'json'; xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { /* IE compatibility */ const json = typeof xhr.response === 'string' ? JSON.parse(xhr.response) : xhr.response; if (json && json.success) resolve(json); else reject('Could not guess user location'); } else { reject('guessUserLocation did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(); }); } updateGeoTypeahead(event, currentTarget) { let typeaheadList = document.querySelector('#geoLocator-form .suggestion'); if (!typeaheadList) return console.error('Unable to locate typeahead list for typeahead update'); typeaheadList.querySelectorAll('input').forEach(e => e.remove()); this.xhrProvidenceGeolocationAutocomplete(event.target.value) .then((results) => { /* only add the results if they match the current typeahead */ if(document.getElementById('geoLocatorInput').value === results[0].replace('location-value ','')) { /* Remove first validation value */ results.shift(); results.forEach((loc) => { let btn = document.createElement('input'); btn.value = loc; btn.type = "submit"; btn.style.border = "none"; btn.style.textAlign = "left"; typeaheadList.append(btn); }); } }); } /** <!-- end childReplacement provOmniSearchFunctions --> **/ init = async () => { this.searchParams = prepURLSearchParams(); /** <!-- childReplacement provOmniGeoTypeahead --> **/ function getCookie(name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); if (match) { return match[2]; } } try{ /*Add geo locator to utility nav if cookie is set*/ let userGeoCookie = getCookie("UserEnteredLocationGeoCoordinates-v3"); let geoCookie = getCookie("GeoIpLocationGeoCoordinates-v3"); let geoStorage = localStorage.getItem('UserEnteredLocationGeoCoordinates-v3'); if(userGeoCookie || geoCookie || geoStorage){ const userGeoObj = JSON.parse(userGeoCookie || geoCookie || geoStorage); if(document.querySelector('#topheader nav')){ let location = userGeoObj.City + ", " + userGeoObj.StateCode + " " + userGeoObj.PostalCode; let span = document.querySelector('#geoLocatorLabel span'); if(span){ span.textContent = location; document.querySelector('#geoLocatorInput').value = location; } const customStyles = document.querySelector('style'); let css = ` #topheader label#geoLocatorLabel { color: inherit; pointer-events: all; } label#geoLocatorLabel { display: block; } `; if (customStyles) { customStyles.innerHTML += css; } } }else{ this.guessUserLocation() .then((userLocation) => { if (userLocation.city && userLocation.zip && userLocation.state) { let location = userLocation.city + ", " + userLocation.state + " " + userLocation.zip; let label = document.querySelector('#geoLocatorLabel'); let span = document.querySelector('#geoLocatorLabel span'); span.textContent = location; document.querySelector('#geoLocatorInput').value = location; const customStyles = document.querySelector('style'); let css = ` #topheader label#geoLocatorLabel { color: inherit; pointer-events: all; } label#geoLocatorLabel { display: block; } `; if (customStyles) { customStyles.innerHTML += css; } } }) .catch(error => console.log(error)); } } catch (error) { console.error('Add Geo Locator failed\n', error); } /* Geolocator autocomplete */ const geolocator = document.querySelector('#geoLocatorInput'); if (geolocator) { const typeaheadList = document.querySelector('#geoLocator-form .suggestion'); /* Workaround for event.currentTarget === null when debounced is used See: https://github.com/jashkenas/underscore/issues/1905 */ let debouncedUpdateGeoTypeahead = this.helpers.debounce(this.updateGeoTypeahead, 250).bind(this); geolocator.addEventListener('input', function (event) { debouncedUpdateGeoTypeahead(event, event.currentTarget); document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'block'); }); geolocator.addEventListener('click', function (event) { if(document.querySelector('#geoLocator-form .suggestion').style.display !== "block"){ /*Select input on click so that the user can overwrite saved location*/ this.setSelectionRange(0, this.value.length); document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'block'); } }); let geoLocatorLabel = document.querySelector('#geoLocatorLabel'); geoLocatorLabel.addEventListener('click', function (event) { document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'none'); if(window.innerWidth < 1000) { document.querySelector('main').style.setProperty('z-index','0'); } }); } /* Catch geolocator form submission */ let geoForm = document.querySelector('#geoLocator-form'); if(geoForm){ geoForm.addEventListener('submit', this.handleGeoUpdate.bind(this)); } let geoUseCurrentLocation = document.querySelector('#geoUseCurrentLocation'); if(geoUseCurrentLocation) { geoUseCurrentLocation.addEventListener('click', () => { document.querySelector('#geo-locator-ck').checked = false; }); } /** <!-- end childReplacement provOmniGeoTypeahead --> **/ /* Create cookies for linker params, if they don't already exist */ if (typeof cookieLinkerParams != "undefined") cookieLinkerParams(); this.state = { pageInit: true, pageType: 'Provider Directory', page_subtype: 'Provider Search Results', query: "", /** <!-- childReplacement brandColor --> **/ color: "#00338e", /** <!-- end childReplacement brandColor --> **/ disclaimerSearch: false, disclaimerSearchKeywords: [ 'transgender surgery', 'IVF', 'vasectomy', ], excludeSearch: false, excludeSearchKeywords: [ 'abortion', 'physician assisted suicide', 'physician-assisted suicide', 'euthanasia', 'euthenasia' ], pushHistory: false, specificDays: false, filters: { previousPostalCode: false, distance: this.searchParams.get("distance") || false, page: Number(this.searchParams.get("page")) || 1, coordinates: false, Gender: {}, InsuranceAccepted: {}, PrimarySpecialties: {}, /** <!-- childReplacement providerOrgState --> **/ ProviderOrganization: {}, /** <!-- end childReplacement providerOrgState --> **/ Languages: {}, availability: "all", visitType: false, /** <!-- childReplacement brandState --> **/ brand: this.searchParams.get("brand") || "providence", /** <!-- end childReplacement brandState --> **/ sortby: false, init: true, locationType: false, location: false, LocationsOnly: ((this.searchParams.get("LocationsOnly") ?? "false") === "true"), AvailableDays: { Sunday: { active: false }, Monday: { active: false }, Tuesday: { active: false }, Wednesday: { active: false }, Thursday: { active: false }, Friday: { active: false }, Saturday: { active: false }, }, AvailableTime: "any", tier: this.searchParams.get("Tier") || this.searchParams.get("tier") }, results: false, }; this.map.build(); this.map.setOffset(); /* Create day-time selector */ /* populate day-time selector state from URL if it exists */ if(this.searchParams.get("time")) { const time = this.searchParams.get("time").trim(); switch(time) { case "any": case "morning": case "afternoon": this.state.filters.AvailableTime = time; default: break; } } if(this.searchParams.get("days")) { const days = this.searchParams.get("days").split(",").map(x => +x); this.state.specificDays = true; days.forEach(n => { const day = DAYS[n]; if(day && this.state.filters.AvailableDays[day]) { this.state.filters.AvailableDays[day].active = true; } }) } document.querySelectorAll("#timesList a").forEach(time => { time.addEventListener('click', () => this.updateTimeSelectorState(time)); }); document.querySelectorAll("#daysList a").forEach(day => { day.addEventListener('click', () => this.updateDaySelectorState(day)); }); document.querySelector("#time-selector").addEventListener("change", () => { this.updateDayTimeSelectorState(); }); document.querySelector("#day-selector").addEventListener("change", () => { this.updateDayTimeSelectorState(); }); let dayTimeHTML = ""; DAYS.forEach((day, i) => { if(i === 0 || i === 6) { return; } const isActive = this.state.filters.AvailableDays[day].active; dayTimeHTML += ` <a class="day-checkbox dropdown-item omni-checkbox ${isActive ? " active" : ""}" id="${day}-checkbox">${day}</a> `; }); document.querySelector("#availability-days-checkboxes").innerHTML = dayTimeHTML; document.querySelectorAll(".day-checkbox").forEach(checkbox => { checkbox.addEventListener("click", e => { const state = this.state.filters.AvailableDays[checkbox.innerHTML].active; this.state.filters.AvailableDays[checkbox.innerHTML].active = !state; this.updateDayTimeSelectorState(); }) }); document.querySelector("#day-selector").value = "any-day"; this.populateDaytimeSelectorFromState(); /* end day-time selector */ const { searchParams, state, map, helpers } = this; const { createPixel, trackIt, trackFromEmitted } = helpers; const lsInsurance = localStorage.getItem('omniSearchInsurance') || false; const lsLocation = localStorage.getItem('omniSearchLocation') || false; const loopable = ['gender', 'languages', 'insuranceaccepted', 'primaryspecialties'/** <!-- childReplacement providerOrgLoopable --> **/ , "ProviderOrganization" /** <!-- end childReplacement providerOrgLoopable --> **/]; const isMobile = this.helpers.viewportLessThan(); let tmp; document.addEventListener('timeslot-clicked', event => trackFromEmitted('timeslot', event)); document.addEventListener('phone-clicked', event => trackFromEmitted('phone', event)); /* if (isMobile) { this.logo.style.width = '85px'; this.logo.style.height = '50px'; }; */ let hasInsuranceParam = false; const isTest = searchParams.get('test'); if (isTest && isTest == "true") { this.state.isTest = true; } /* Loop through params that can have more than one value */ for (let param of loopable) { if (searchParams.get(param)) { if (param === 'insuranceaccepted') { hasInsuranceParam = true; } tmp = searchParams.get(param).split(","); tmp.forEach((val) => { switch(param) { case 'gender': param = 'Gender'; break; case 'languages': param = 'Languages'; break; case 'primaryspecialties': param = 'PrimarySpecialties'; break; case 'insuranceaccepted': param = 'InsuranceAccepted'; break; default: } state.filters[param][val] = { active: true }; }); }; } if (lsInsurance && !hasInsuranceParam) { const split = lsInsurance.split(','); for (let ins of split) { if (!this.state.filters.InsuranceAccepted) { this.state.filters.InsuranceAccepted = {}; } this.state.filters.InsuranceAccepted[ins] = { active: true } } } /* Set query if not empty */ if (searchParams.get("query")) { window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = decodeURIComponent(searchParams.get('query')); } else { window.sessionStorage.setItem('previousQuery', ''); }; /* If a PracticeGroup param is present, then set state */ if (searchParams.get('practicegroup')) { this.state.filters.PracticeGroup = searchParams.get('practicegroup'); } /* If a userlocation param is present, then set state */ if (searchParams.get('userlocation')) { this.state.filters.location = searchParams.get('userlocation'); this.state.currentLoc = searchParams.get('userlocation'); } else if (lsLocation) { this.state.filters.location = lsLocation } /* If there is a search param of LocationName and it has a length, set that to state */ if (searchParams.get('locationname')) { this.state.filters.LocationName = searchParams.get('locationname'); } /* If there is a search param of distance and it has a length, set that to state */ if (searchParams.get('distance')) { this.state.filters.distance = searchParams.get('distance'); } /* Check search params for LocationId and PracticeGroupId, and set state accordingly */ if (searchParams.get('locationid')) { this.state.filters.LocationId = searchParams.get('locationid'); } if (searchParams.get('practicegroupid')) { this.state.filters.PracticeGroupId = searchParams.get('practicegroupid'); } await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthSearchFacets?brand=` + this.state.filters.brand) .then(res => res.json()) .then(res => { this.state.facets = res["facets"]; }); /* Check for / apply visit type filters */ let visitTypes = searchParams.get('visittypes'); if (visitTypes) { visitTypes = visitTypes.split(','); document.querySelector('button[data-type="all"]').classList.remove('active'); this.state.filters.visitType = {}; for (let type of visitTypes) { document.querySelector(`button[data-type="${type}"]`).classList.add('active'); this.state.filters.visitType[type] = { active: true }; } } const userState = await fetch('https://wompservices.wompmobile.com/geoip/') .then(res => res.json()) .then(res => res.state ? res.state : "Undefined") .catch(() => { return "Undefined"; }); this.state.filters.userState = userState; let brands = { pacmed: "Pacific Medical", providence: "Providence", swedish: "Swedish" }; document.getElementById("OrganizationLabel").innerHTML = `Organization: ${brands[this.state.filters.brand]} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; /* Handle sort order */ const sortby = searchParams.get("sortby"); if (sortby) { switch (sortby) { case "GeocodedCoordinate": if (searchParams.get("userlocation")) { siteSearch.state.filters.sortby = "GeocodedCoordinate"; const sortByLabel = document.querySelector("#SortByLabel"); sortByLabel.innerHTML = `Sort by: Distance <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; } else { siteSearch.state.filters.sortby = false; }; break; default: siteSearch.state.filters.sortby = sortby; break; }; }; this.handleSubmit(); }; }; const prepURLSearchParams = () => { const url = new URL(window.location); const searchParams = new URLSearchParams(url.search); const qStrings = [ 'gender', 'insuranceaccepted', 'languages', 'locationid', 'locationname', 'practicegroup', 'practicegroupid', 'primaryspecialties', 'providerorganization', 'userlocation', 'visittypes' ]; /* convert certain param labels to lowercase */ searchParams.forEach((value, key) => { const match = qStrings.find((str) => { return str === key.toLowerCase(); }); if (match) { searchParams.delete(key); searchParams.set(match, value); } }); url.search = searchParams.toString(); window.history.pushState('Find a Doctor','',url); return searchParams; }; const siteSearch = new Search( `https://${/staging/gi.test(window.location.hostname) ? 'womphealthdevapi' : 'womphealthapi'}.azurewebsites.net/api/WompHealthSearch?` ); siteSearch.textInputs.forEach((el) => { if (el.id === 'searchRadius') { el.addEventListener("keydown", function (e) { if (e.keyCode === 13) { const searchRadiusVal = document.querySelector('#searchRadius').value; if (searchRadiusVal?.length) { siteSearch.state.filters.distance = searchRadiusVal; } else { siteSearch.state.filters.distance = ''; } siteSearch.handleSubmit(); } }); } if (el.id === 'query') { el.addEventListener("keydown", function (e) { if (e.keyCode === 13) siteSearch.handleSubmit(); }); }; if (el.id === "custom-location") { el.addEventListener("keydown", function(e) { if (e.keyCode === 13) { siteSearch.helpers.getGoogleLocation(); } }); } el.addEventListener("change", function (e) { const { id, value } = e.target; if (id === "query") { return; } else { let val = value?.length ? value : false; siteSearch.state.filters[id] = val; }; }); }); siteSearch.form.addEventListener("click", siteSearch.handleSubmit); siteSearch.form2.addEventListener("click", siteSearch.handleSubmit); siteSearch.createClickListener(); siteSearch.createChangeListeners(); siteSearch.addWindowListeners(); siteSearch.init(); try { let mtoggle = localStorage.getItem("maptoggle"); if(mtoggle !== null) { document.getElementById("showmap").checked = mtoggle; } document.getElementById("maptoggle").addEventListener("click", function(e){ localStorage.setItem("maptoggle", !document.getElementById("showmap").checked); }); } catch (error) { console.log(error); } class Autocomplete { constructor(args) { this.useHistory = args.useHistory || false; this.param = args.param; this.src = args.src; this.id = args.id; this.localStorageKey = args.localStorageKey; this.shouldSubmit = args.shouldSubmit || true; this.minChars = args.minChars || 3; this.isStaging = /staging/gi.test(window.location.hostname); this.apiSub = this.isStaging ? 'womphealthdevapi' : 'womphealthapi'; /** <!-- childReplacement autocompleteBrand --> **/ this.brand = 'providence'; /** <!-- end childReplacement autocompleteBrand --> **/ this.state = { initValue: '' }; /* Automatically call the init script for the autocomplete */ this.init(); this.helpers.trackAutocomplete = this.helpers.trackAutocomplete.bind(this); }; helpers = { debounce(func, delay = 200) { let timeout = null; return function(){ let context = this; let args = arguments; if(document.getElementById('searchBox').classList.length === 0) { document.getElementById("searchBox").classList.add("is-dirty"); if(document.getElementById('mobileSearchWrapper').classList.length === 1) { document.getElementById("mobileSearchWrapper").classList.add("is-dirty"); } } clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args) }, delay); }; }, slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); }, removeSelected() { document.querySelectorAll('div[data-autocomplete].selected').forEach(div => div.classList.remove('selected')); }, fetchSuggestions: async(val) => { const { src, param, helpers, autocompleteEl } = this; const { slugify } = helpers; const results = await fetch(`${src}&${param}=${val}`).then(res => res.json()) .then(res => { /* If the length is 0, there are no results to the search */ if (res.value.length == 0) { autocompleteEl.innerHTML = '<small>No suggestions</small>'; return 'success'; }; /* Map out the returned results */ const tmp = res.value.reduce((html, r) => r?.text ? html + `<div id="${slugify(r.text)}" data-autocomplete>${r.text}</div>` : html, ''); autocompleteEl.innerHTML = tmp; return 'success'; }); return results; }, getCID: () => { let cid = localStorage.getItem('cid'); /* If we DON'T have a CID saved to localStorage... */ if (!cid || !cid.length) { /* If crypto's randomUUID func is available, create a CID with it */ if (typeof crypto?.randomUUID == 'function') { cid = crypto.randomUUID(); } /* Else, go old school... */ else { cid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /* Save the new cid to localStorage */ localStorage.setItem('cid', cid); } /* Send CID to the requestor */ return cid; }, trackAutocomplete: async (str) => { const data = JSON.stringify({ text: this.state.initValue, autocomplete: str }); const { pathname, search } = window.location; await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthAnalytics?brand=${this.brand}&t=event&ec=search&ea=autocomplete&el=${data}&cid=${this.helpers.getCID()}&dp=${encodeURIComponent(pathname + search)}`) .then(res => res.json()) .then(res => console.log(res)); } }; addListener = { input: () => { const { src, param, helpers, autocompleteEl, getPreviousSearches, minChars, state, inputEl } = this; const { debounce, slugify, fetchSuggestions } = helpers; inputEl.addEventListener('input', debounce(async function(e) { const val = inputEl.value; /* This is the text that the user input */ state.initValue = val; /* If more than minChars characters are input, start querying the API */ if (val.length >= minChars) { await fetchSuggestions(val); } /* Otherwise, show previous and suggested searches */ else { getPreviousSearches(); }; })); }, key: () => { const { debounce, removeSelected, trackAutocomplete } = this.helpers; const { inputEl, autocompleteEl, state } = this; inputEl.addEventListener('keydown', debounce(function(e) { const selected = document.querySelector('div[data-autocomplete].selected'); let nextSibling = false; let prevSibling = false; if (selected) { nextSibling = selected.nextElementSibling ? selected.nextElementSibling : false; prevSibling = selected.previousElementSibling ? selected.previousElementSibling : false; if (prevSibling && !prevSibling.hasAttribute('data-autocomplete')) { prevSibling = false; }; }; const { key } = e; switch(key) { case 'ArrowDown': if (nextSibling) { removeSelected(); nextSibling.classList.add('selected'); inputEl.value = nextSibling.innerText; } else if (!selected) { const first = document.querySelector('div[data-autocomplete]'); inputEl.value = first.innerText; first.classList.add('selected'); }; break; case 'ArrowUp': if (prevSibling) { removeSelected(); prevSibling.classList.add('selected'); inputEl.value = prevSibling.innerText; } else { if (selected) { removeSelected(); inputEl.value = state.initValue; }; }; break; case 'Enter': if (selected) { /* trackAutocomplete(selected.innerText); */ } autocompleteEl.style.display = 'none'; inputEl.blur(); window.wmFromAutoComplete = true; break; default: break; } }, 100)); }, clicks: () => { const { autocompleteEl, inputEl, shouldSubmit, id, getPreviousSearches, helpers } = this; const { removeSelected, fetchSuggestions } = helpers; document.body.addEventListener('click', (e) => { const { target } = e; /* If it has data-autocomplete, it's an autocomplete option. Update the autocomplete */ if (target.hasAttribute('data-autocomplete')) { removeSelected(); /* helpers.trackAutocomplete(target.innerText); */ target.classList.add('selected'); inputEl.value = e.target.innerText; autocompleteEl.style.display = "none"; if (shouldSubmit) { inputEl.dispatchEvent(new KeyboardEvent('keydown',{'key':'Enter'})); }; } /* Else, if the id matches the current id, then its the auto complete element. Run default logic. */ else if (target.id == id) { if (target.value.length > 0) { fetchSuggestions(target.value); } else { getPreviousSearches(); }; } /* Otherwise, it's a click outside autocomplete. Hide the autocomplete options. */ else { autocompleteEl.style.display = "none"; }; }); /* If the input is focused on, show the autocomplete box */ inputEl.addEventListener('focus', (e) => { autocompleteEl.style.display = 'block'; }); } }; checkRequiredArgs = () => { const requiredArgs = ["id", "src", "param"]; let missing = false; const missingRequiredArgs = requiredArgs.some(arg => { const tmp = !this[arg]; missing = arg; return tmp; }); if (missingRequiredArgs) { console.error(`Please add key "${missing}" to autocomplete options object`); } else if (this.id == "autocomplete") { console.error('Input id cannot be "autocomplete". Please choose another id.'); return false; }; return !missingRequiredArgs; }; getPreviousSearches = async () => { const { src, param, helpers, autocompleteEl, useHistory, localStorageKey } = this; const { slugify } = helpers; let theHtml = await fetch(`${src}&${param}=`).then(res => res.json()) .then(res => { if (!res.value?.length) { return ''; } const tmp = res.value.reduce((html, r) => r?.text ? html + `<div id="${slugify(r.text)}" data-autocomplete>${r.text}</div>` : html, ''); return `<h5>Suggested Searches</h5>${tmp}`; }); /* If we are using histor AND a localStorageKey is set */ if (useHistory && localStorageKey) { this.previousSearches = JSON.parse(localStorage.getItem(localStorageKey)); const { previousSearches } = this; theHtml += '<div class="availability-divider"></div><h5>Previous Searches</h5>'; /* Make sure the data we have in an array before populated the autocomplete */ if (Array.isArray(previousSearches) && previousSearches.length) { theHtml += previousSearches.reduce((html, search, i) => { const id = this.helpers.slugify(search) + 1; const el = `<div id="${id}" data-autocomplete>${search}</div>`; return html + el; }, ''); } else { theHtml += '<small>No previous searches</small>'; }; autocompleteEl.innerHTML = theHtml; }; }; addAutocomplete = () => { this.inputEl = document.querySelector("#" + this.id); this.inputEl.insertAdjacentHTML('afterend', '<div id="autocomplete" style="display: none;"></div>'); this.autocompleteEl = document.querySelector("#autocomplete"); const { inputEl, id, addListener } = this; for (let listener of Object.values(addListener)) { listener(); }; }; init() { const { checkRequiredArgs, addAutocomplete } = this; if (!checkRequiredArgs()) return; addAutocomplete(); }; }; const autocomplete = new Autocomplete({ id: 'query', src: `https://${/staging/gi.test(window.location.hostname) ? 'womphealthdevapi' : 'womphealthapi'}.azurewebsites.net/api/WompHealthSearch?type=autocomplete&brand=pacmed&top=5&skip=0`, param: 'search', useHistory: true, localStorageKey: 'previousSearches', minChars: 3 }); </script> <script id="invocaDecoration" type="text/javascript"> var internalUserError = ''; const isInternalUser = fetch("https://s3-us-west-1.amazonaws.com/providence-internal/analytics.gif") .then((res) => { return res.status == 200 ? true : false; }) .catch((error) => { console.log(error); internalUserError = "true"; return false; }); function getCookieInvoca(name) { const match = document.cookie.match(new RegExp(`(?=${name}=)([^;]*)`)); if (match) { return match[0].split('=')[1]; } } function hasUrlParam(param, de) { return (param ? `${param}` : `${de}`); } function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } function fireInvoca() { let url = window.location.href; const invocaCookie = getCookieInvoca("invoca_session"); let invocaHasOutbound; let invocaConfigPhone; if( (internalUserError === "true" || !isInternalUser) && invocaCookie !== undefined) { let invocaCookieJSON; if(invocaCookie.charAt(0) === '%'){ invocaCookieJSON = JSON.parse(decodeURIComponent(invocaCookie)); } else { invocaCookieJSON = JSON.parse(invocaCookie); } if(invocaCookieJSON) { let hgcrmAgency = hasUrlParam(invocaCookieJSON.session.hgcrm_agency, "client"), hgcrmCampaignid = hasUrlParam(invocaCookieJSON.session.campaignid, "607"), hgcrmChannel = hasUrlParam(invocaCookieJSON.session.hgcrm_channel, "email"), hgcrmSource = hasUrlParam(invocaCookieJSON.session.hgcrm_source, "mh"), hgcrmTacticid = hasUrlParam(invocaCookieJSON.session.hgcrm_tacticid, "15688"), hgcrmTrackingsetid = hasUrlParam(invocaCookieJSON.session.offersetid, "22561"), landingPage = hasUrlParam(invocaCookieJSON.session.landing_page, ""); landingPage = landingPage.split('?')[0].replace("https://",""); invocaHasOutbound = `landing_page=${landingPage}&hgcrm_channel=${hgcrmChannel}&hgcrm_source=${hgcrmSource}&campaignid=${hgcrmCampaignid}&hgcrm_tacticid=${hgcrmTacticid}&offersetid=${hgcrmTrackingsetid}`; invocaConfigPhone = `calling_page=${encodeURIComponent(url)}&calling_url=${encodeURIComponent(url)}&${invocaHasOutbound}&advertiser_campaign_id_from_network=2&network_id=1758&tealium_vid=AMP_STATE(wheelhouse.tealium_vid)`; waitForElm('#results .provider').then((elm) => { /*Replace Phone Number*/ let phones = document.getElementsByClassName("phoneLink"); for (i = 0; i < phones.length; ++i) { let tel = phones[i]; let telNum = tel.href.replace("tel:+1-","").replace("tel:","").replaceAll("-","").trim(); if(tel) { tel.insertAdjacentHTML( "beforebegin", ` <amp-list data-no-nudge class="callTrackingList" id="tel1" src="https://tealium.wheelhousedmg.com/i.gif?tealium_vid=CLIENT_ID(AMP_ECID_GOOGLE)" layout="fixed-height" height="19px" width="auto" items="." single-item binding="always" template="callTrackTemplate${i}" > <div placeholder> ${tel.outerHTML} </div> </amp-list> <template type="amp-mustache" id="callTrackTemplate${i}"> <amp-call-tracking class="callTracking" config="https://pnapi.invoca.net/amp?invoca_id=AMP_STATE(wheelhouse.tealium_vid)&_pcid=AMP_STATE(wheelhouse._pcid)&${invocaConfigPhone}&invoca_detected_destination=${telNum}"> ${tel.outerHTML} </amp-call-tracking> </template> ` ); tel.remove(); } } var script = document.createElement('script'); script.setAttribute("custom-element", 'amp-call-tracking'); script.src = 'https://cdn.ampproject.org/v0/amp-call-tracking-0.1.js'; script.setAttribute("async", "true"); document.head.appendChild(script); }); /*Decorate Outbound Links*/ let outboundInvocaLinks; let v; if(url.indexOf("pacmed") > -1 || url.indexOf("pacificmedicalcenters") > -1) { outboundInvocaLinks = document.body.querySelectorAll("a[href*='pacificmedicalcenters']"); for (i = 0; i < outboundInvocaLinks.length; ++i) { if(outboundInvocaLinks[i].href.indexOf("?") > -1) { v = outboundInvocaLinks[i].href + "&" + invocaHasOutbound; } else { v = outboundInvocaLinks[i].href + "?" + invocaHasOutbound; } outboundInvocaLinks[i].href = v; } } else if(url.indexOf("swedish") > -1) { outboundInvocaLinks = document.body.querySelectorAll("a[href*='swedish']"); for (i = 0; i < outboundInvocaLinks.length; ++i) { if(outboundInvocaLinks[i].href.indexOf("?") > -1) { v = outboundInvocaLinks[i].href + "&" + invocaHasOutbound; } else { v = outboundInvocaLinks[i].href + "?" + invocaHasOutbound; } outboundInvocaLinks[i].href = v; } } } } } fireInvoca(); </script> <amp-list src="amp-state:wheelhouse" layout="fixed-height" height="1" single-item="" items="." style="z-index: -100"> <template type="amp-mustache"> <div id="wheelhouseJson" style="width: 1px; height: 1px; overflow: hidden;"> { "pcid": "{{ _pcid }}", "traceId": "{{ trace_id }}" } </div> </template> </amp-list> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,500,700&amp;display=swap" rel="stylesheet"> <div id="wm_footer" class="wm_footer section"> <div class="footer-bs-wrap" id="topFooter"> <div class="footer-section-wrapper"> <div class="d-col large-col"> <div class="mission-and-values"> <div class="mission"> <h3>OUR MISSION</h3> <p>As expressions of God's healing love, witnessed through the ministry of Jesus, we are steadfast in serving all, especially those who are poor and vulnerable.</p> </div> <div class="values"> <h3>OUR VALUES</h3> <p>Compassion, Dignity, Justice, Excellence, Integrity. <a class="underline" href="https://www.providence.org/about/our-mission">Learn more about our mission, vision and values.</a></p> </div> </div> <a class="btn white contact-btn" aria-label="Contact us at Providence" href="https://www.providence.org/about/contactus">Contact Us</a> </div> <div class="more-links"> <div class="d-col"> <h3>ACROSS PROVIDENCE</h3> <ul> <li><a href="https://www.providence.org/about">About Us</a></li> <li><a href="https://www.providence.org/about/health-equity" aria-label="Health Equity footer link">Health Equity</a></li> <li><a href="https://www.providence.org/about/initiatives" aria-label="Initiatives at Providence">Initiatives</a></li> <li><a href="https://www.collaboratingforbetterhealthcare.com/c/a/62g1OpEWK4O9ZckbK4vI08">Help Us Improve Health Care</a></li> <li><a href="https://www.providence.org/about/annual-report" aria-label="Annual Report to Our Communities footer link">Annual Reports to Our Community</a></li> <li><a href="https://www.providence.org/about/annual-report/reports/chna-and-chip-reports" aria-label="Community Health Needs Assessments and Improvement Plans footer link">Community Health Needs Assessments and Improvement Plans</a></li> <li><a href="https://www.providence.org/doctors" aria-label="Our Doctors footer link">Our Doctors</a></li> <li><a href="https://www.providence.org/business/nursing-institute" aria-label="Our Nurses footer link">Our Nurses</a></li> </ul> </div> <div class="d-col"> <h3>RESOURCES</h3> <ul> <li><a href="https://www.providence.org/for-employees" aria-label="For Care givers (our employees)" title="For Care givers (our employees)">For Caregivers (our employees)</a></li> <li><a href="https://psjhealth.jobs/" aria-label="Career Opportunities footer link">Career Opportunities</a></li> <li><a href="https://psdconnect.org/partners/providence" aria-label="Physician &amp; Provider Careers footer link">Physician &amp; Provider Careers</a></li> <li><a href="https://www.providence.org/business/supplier-information" aria-label="Supplier Portal footer link">Supplier Portal</a></li> <li><a href="https://psjhcrmwebsites.microsoftcrmportals.com/home" aria-label="Classes and Events footer link">Classes and Events</a></li> <li><a href="https://www.providence.org/obp" aria-label="Online Bill Pay &amp; Financial Assistance footer link">Online Bill Pay &amp; Financial Assistance</a></li> <li><a href="https://www.providence.org/obp/get-a-price-estimate" aria-label="Get a Price Estimate footer link">Get a Price Estimate</a></li> <li><a href="https://www.providence.org/obp/pricing-transparency">Pricing Transparency</a></li> <li><a href="https://blog.providence.org/" aria-label="Read our blog footer link">Read our Blog</a></li> </ul> </div> </div> </div> </div> <div id="footNote" class="wm_footer section"> <div class="footer-bs-wrap"> <div id="fnWrap" class="footer-section-wrapper"> <div class="d-col social"> <h3>Connect with us</h3> <div> <a href="https://www.facebook.com/providence" title="Check us out on Facebook" target="_blank"> <span class="screenreader">Check us out on Facebook</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/facebook.png" alt="Facebook"></amp-img> </a> <a href="https://twitter.com/providence" title="Check us out on Twitter" target="_blank"> <span class="screenreader">Check us out on Twitter</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/twitter.png" alt="Twitter"></amp-img> </a> <a href="https://www.linkedin.com/company/providencest.josephhealth" title="Check us out on LinkedIn" target="_blank"> <span class="screenreader">Check us out on LinkedIn</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/linkedin.png" alt="LinkedIn"></amp-img> </a> <a href="https://www.instagram.com/providencehealthsystem/" title="Check us out on Instagram" target="_blank"> <span class="screenreader">Check us out on Instagram</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/instagram.png" alt="Instgram"></amp-img> </a> <a href="https://www.youtube.com/channel/UCCJ8wEkiHBHkVD8Jb7sbIBg/featured" title="Check us out on YouTube" target="_blank"> <span class="screenreader">Check us out on YouTube</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/youtube.png" alt="YouTube"></amp-img> </a> </div> </div> <div class="d-col legal"> <div class="footer-info"> <a href="https://www.providence.org/utility-pages/disclaimer">Disclaimer</a> <span>|</span> <a href="https://www.providence.org/utility-pages/privacy-policy">Terms of Use &amp; Privacy Policy</a> <span>|</span> <a href="https://www.providence.org/utility-pages/nondiscrimination-statement">Notice of Nondiscrimination &amp; Communication Assistance</a> <span>|</span> <a href="https://www.providence.org/utility-pages/notice-of-privacy-practices">Notice of Privacy Practices</a> </div> <span class="copyright">©2024 Providence. All rights reserved.</span> </div> </div> </div> </div> </div> <svg hidden="" class="hidden" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <symbol id="icon-building-o" viewBox="0 0 22 28"> <title>Building Icon</title> <desc>Building Icon</desc> <path d="M6 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 26h6v-24h-18v24h6v-3.5c0-0.266 0.234-0.5 0.5-0.5h5c0.266 0 0.5 0.234 0.5 0.5v3.5zM22 1v26c0 0.547-0.453 1-1 1h-20c-0.547 0-1-0.453-1-1v-26c0-0.547 0.453-1 1-1h20c0.547 0 1 0.453 1 1z"></path> </symbol> <symbol id="icon-building-hospital" viewBox="0 0 448 512"> <title>Hospital</title> <desc>Hospital</desc> <path fill="currentColor" d="M128 244v-40c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v40c0 6.627-5.373 12-12 12h-40c-6.627 0-12-5.373-12-12zm140 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm-76 84v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm76 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm180 124v36H0v-36c0-6.627 5.373-12 12-12h19.5V85.035C31.5 73.418 42.245 64 55.5 64H144V24c0-13.255 10.745-24 24-24h112c13.255 0 24 10.745 24 24v40h88.5c13.255 0 24 9.418 24 21.035V464H436c6.627 0 12 5.373 12 12zM79.5 463H192v-67c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v67h112.5V112H304v24c0 13.255-10.745 24-24 24H168c-13.255 0-24-10.745-24-24v-24H79.5v351zM266 64h-26V38a6 6 0 0 0-6-6h-20a6 6 0 0 0-6 6v26h-26a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h26v26a6 6 0 0 0 6 6h20a6 6 0 0 0 6-6V96h26a6 6 0 0 0 6-6V70a6 6 0 0 0-6-6z"></path> </symbol> <symbol id="icon-shield" viewBox="0 0 24 24"> <title>Information shield</title> <desc>Information shield</desc> <path d="M12 20.862c-1.184-0.672-4.42-2.695-6.050-5.549-0.079-0.138-0.153-0.276-0.223-0.417-0.456-0.911-0.727-1.878-0.727-2.896v-6.307l7-2.625 7 2.625v6.307c0 1.018-0.271 1.985-0.726 2.897-0.070 0.14-0.145 0.279-0.223 0.417-1.631 2.854-4.867 4.876-6.050 5.549zM12.447 22.894c0 0 4.989-2.475 7.34-6.589 0.096-0.168 0.188-0.34 0.276-0.515 0.568-1.135 0.937-2.408 0.937-3.79v-7c0-0.426-0.267-0.79-0.649-0.936l-8-3c-0.236-0.089-0.485-0.082-0.702 0l-8 3c-0.399 0.149-0.646 0.527-0.649 0.936v7c0 1.382 0.369 2.655 0.938 3.791 0.087 0.175 0.179 0.346 0.276 0.515 2.351 4.114 7.34 6.589 7.34 6.589 0.292 0.146 0.62 0.136 0.894 0z"></path> </symbol> <symbol id="icon-info-circle" viewBox="0 0 24 28"> <title>Information Circle</title> <desc>Information Circle Icon</desc> <path d="M16 21.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-1.5v-8c0-0.281-0.219-0.5-0.5-0.5h-5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h1.5v5h-1.5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h7c0.281 0 0.5-0.219 0.5-0.5zM14 7.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-3c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h3c0.281 0 0.5-0.219 0.5-0.5zM24 14c0 6.625-5.375 12-12 12s-12-5.375-12-12 5.375-12 12-12 12 5.375 12 12z"></path> </symbol> <symbol id="icon-check" viewBox="0 0 28 28"> <path d="M26.109 8.844c0 0.391-0.156 0.781-0.438 1.062l-13.438 13.438c-0.281 0.281-0.672 0.438-1.062 0.438s-0.781-0.156-1.062-0.438l-7.781-7.781c-0.281-0.281-0.438-0.672-0.438-1.062s0.156-0.781 0.438-1.062l2.125-2.125c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l4.594 4.609 10.25-10.266c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l2.125 2.125c0.281 0.281 0.438 0.672 0.438 1.062z"></path> </symbol> <symbol id="icon-chevron-down" viewBox="0 0 28 28"> <path d="M26.297 12.625l-11.594 11.578c-0.391 0.391-1.016 0.391-1.406 0l-11.594-11.578c-0.391-0.391-0.391-1.031 0-1.422l2.594-2.578c0.391-0.391 1.016-0.391 1.406 0l8.297 8.297 8.297-8.297c0.391-0.391 1.016-0.391 1.406 0l2.594 2.578c0.391 0.391 0.391 1.031 0 1.422z"></path> </symbol> <symbol id="search" viewBox="0 0 19 21"> <title>Search</title> <desc>Search Icon</desc> <path d="M17.693 20.533a1 1 0 0 1-.75-.34l-3.86-4.37a8.53 8.53 0 0 1-12.92-5.59 8.57 8.57 0 1 1 16.43 1.35 1 1 0 1 1-1.87-.71 6.57 6.57 0 0 0-6-8.88 6.46 6.46 0 0 0-5.16 2.32 6.57 6.57 0 0 0 8.5 9.8 2 2 0 0 1 2.53.42l3.86 4.37a1 1 0 0 1-.75 1.66l-.01-.03z"></path> </symbol> <symbol id="close" viewBox="0 0 320 512"> <title>Close</title> <desc>Close icon</desc> <path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path> </symbol> <symbol id="check" viewBox="0 0 21 21"> <title>Check</title> <desc>Checkmark</desc> <path d="M11 21c-4.525.02-8.527-3.06-10-7.5C.173 9.096 2.166 4.456 6 1.833 10.165.038 15.153.82 18.5 4.333c2.914 3.144 3.391 8.17.833 11.667-.137.748-.65.863-.833.833-.59-.46-.706-.973-.833-1.666C20.303 11.874 19.48 7.146 16 4.333 12.79 1.9 8.002 2.23 5.167 5.167 2.093 8.346 1.928 13.142 4.333 16c3.07 3.65 7.823 4.308 11.667 1.667-.08.088.236.066.833 0-.063.326.114.589 0 .833.155.348.017.633 0 .833C14.961 20.406 13.025 21 11 21zm-.772-6.667h-.034a.824.824 0 0 1-.586-.24L7.955 12.43a.834.834 0 0 1 .345-1.439.823.823 0 0 1 .82.268l1 1.005 2.727-3.432a.824.824 0 0 1 1.433.205.835.835 0 0 1-.145.834l-3.304 4.155a.825.825 0 0 1-.603.307z"></path> </symbol> <symbol id="heart-empty" viewBox="0 0 15 13"> <title>Empty Heart</title> <desc>Empty Heart Icon</desc> <path d="M4.115 1.733c-.63 0-1.225.26-1.674.734-.957 1.006-.957 2.643 0 3.65l4.166 4.056c.001.003.004.004.006.006l.75.74 4.936-4.825c.935-.984.935-2.621-.022-3.627a2.329 2.329 0 0 0-3.348 0c-.408.429-.658 1-.707 1.609a.867.867 0 0 1-1.728-.003 2.63 2.63 0 0 0-.705-1.606 2.297 2.297 0 0 0-1.674-.734M7.359 13a.862.862 0 0 1-.609-.25L1.207 7.333c-1.606-1.687-1.606-4.396-.022-6.06A4.015 4.015 0 0 1 4.115 0c1.11 0 2.15.452 2.93 1.272.112.118.217.242.314.371.097-.129.203-.253.314-.37A4.015 4.015 0 0 1 10.603 0c1.11 0 2.15.452 2.93 1.272 1.583 1.665 1.583 4.374 0 6.04l-5.568 5.441a.864.864 0 0 1-.606.247"></path> </symbol> <symbol id="heart-full" viewBox="0 0 15 13"> <title>Full Heart</title> <desc>Full Heart Icon</desc> <path d="M5.936 11.457L7.5 13l6.4-6.254c1.467-1.543 1.467-4.045 0-5.589a3.627 3.627 0 0 0-5.313 0 4.03 4.03 0 0 0-1.088 2.466 4.02 4.02 0 0 0-1.086-2.466 3.628 3.628 0 0 0-5.313 0c-1.467 1.544-1.467 4.046 0 5.59l3.275 3.199 1.561 1.511z"></path> </symbol> <symbol id="people" viewBox="0 0 20 18"> <title>People</title> <desc>People Icon</desc> <path d="M6.477 17.002H2.429A2.429 2.429 0 0 1 0 14.573v-2.59A2.494 2.494 0 0 1 1.514 9.78a12.476 12.476 0 0 1 6.55-.712.82.82 0 0 1-.26 1.619c-1.91-.3-3.867-.085-5.667.623a.899.899 0 0 0-.485.697v2.566c0 .447.362.81.81.81h4.047a.81.81 0 0 1 0 1.619h-.032zm3.238-13.31a3.643 3.643 0 1 0-3.643 3.595 3.619 3.619 0 0 0 3.643-3.595zm-1.619 0a2.024 2.024 0 1 1-2.024-1.976 2 2 0 0 1 2.024 1.976zm11.335 11.424v-2.72a2.21 2.21 0 0 0-1.344-1.96 10.42 10.42 0 0 0-7.683 0 2.21 2.21 0 0 0-1.344 1.96v3.796a.81.81 0 1 0 1.619 0v-3.789a.615.615 0 0 1 .316-.445 8.817 8.817 0 0 1 6.477 0 .615.615 0 0 1 .315.445v2.72a.275.275 0 0 1-.283.268h-4.55a.81.81 0 1 0 0 1.62h4.574a1.895 1.895 0 0 0 1.903-1.895zM17 5.295a2.834 2.834 0 1 0-2.833 2.793 2.817 2.817 0 0 0 2.834-2.785v-.008zm-1.618 0a1.214 1.214 0 1 1-1.215-1.174 1.198 1.198 0 0 1 1.215 1.182v-.008z"></path> </symbol> <symbol id="chat" viewBox="0 0 19 19"> <title>Chat</title> <desc>Chat Icon</desc> <path d="M4.524 19a.905.905 0 0 1-.643-1.547l3.086-3.085a2.714 2.714 0 0 1 1.918-.797H16c.5 0 .905-.405.905-.904V2.714c0-.5-.405-.904-.905-.904H2.714c-.5 0-.904.405-.904.904v9.953c0 .5.405.904.904.904h.905a.905.905 0 1 1 0 1.81h-.905A2.714 2.714 0 0 1 0 12.667V2.714C0 1.215 1.215 0 2.714 0H16c1.5 0 2.714 1.215 2.714 2.714v9.953c0 1.499-1.215 2.714-2.714 2.714H8.894a.905.905 0 0 0-.643.262L5.166 18.73c-.17.172-.4.27-.642.271zm8.762-13.662c0-.5-.405-.905-.905-.905H5.429a.905.905 0 1 0 0 1.81h6.952c.5 0 .905-.405.905-.905zm-3.62 3.62c0-.5-.404-.906-.904-.906H5.429a.905.905 0 0 0 0 1.81h3.333c.5 0 .905-.405.905-.905z"></path> </symbol> <symbol id="phone" viewBox="0 0 24 24"> <title>Phone</title> <desc>Phone Icon</desc> <path d="M17.87 17.113l1.544 1.544c.374.374.58.87.58 1.397 0 .53-.206 1.024-.58 1.398l-2.25 2.25a.98.98 0 0 1-.657.287A15.963 15.963 0 0 1 4.66 19.341 15.958 15.958 0 0 1 .011 7.493a.983.983 0 0 1 .286-.656l2.251-2.251a1.98 1.98 0 0 1 2.795 0L6.887 6.13a3.982 3.982 0 0 1 1 3.965l-.958 3.188a.977.977 0 0 1-1.222.658.971.971 0 0 1-.581-.474.974.974 0 0 1-.076-.747l.958-3.188A2.023 2.023 0 0 0 5.5 7.517L3.955 5.973 1.962 7.947a13.814 13.814 0 0 0 4.084 10.007c2.68 2.679 6.207 4.152 10.008 4.083l1.973-1.972a.014.014 0 0 0 0-.02L16.482 18.5a2.024 2.024 0 0 0-2.014-.507l-3.188.957a.985.985 0 0 1-1.22-.657.973.973 0 0 1 .074-.747c.124-.23.331-.4.583-.475l3.188-.958a3.984 3.984 0 0 1 3.965 1zM14.024 12A2.027 2.027 0 0 0 12 9.975a.981.981 0 0 1 0-1.96A3.99 3.99 0 0 1 15.986 12a.982.982 0 0 1-1.962 0zM12 4.007c4.407 0 7.993 3.585 7.993 7.993a.981.981 0 0 1-1.962 0A6.038 6.038 0 0 0 12 5.968a.981.981 0 0 1 0-1.961zM12 0c6.617 0 12 5.383 12 12 0 .54-.44.981-.98.981a.982.982 0 0 1-.981-.981c0-5.535-4.504-10.039-10.04-10.039A.981.981 0 0 1 12 0z"></path> </symbol> <symbol id="fax" viewBox="0 0 28 28"> <title>Fax</title> <desc>Fax Icon</desc> <path d="M4.5 6c1.375 0 2.5 1.125 2.5 2.5v17c0 1.375-1.125 2.5-2.5 2.5h-2c-1.375 0-2.5-1.125-2.5-2.5v-17c0-1.375 1.125-2.5 2.5-2.5h2zM26 8.547c1.188 0.688 2 1.984 2 3.453v12c0 2.203-1.797 4-4 4h-13.5c-1.375 0-2.5-1.125-2.5-2.5v-24c0-0.828 0.672-1.5 1.5-1.5h10.5c0.828 0 1.984 0.484 2.562 1.062l2.375 2.375c0.578 0.578 1.062 1.734 1.062 2.562v2.547zM14.5 24v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM14.5 20v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM14.5 16v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM18.5 24v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM18.5 20v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM18.5 16v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM22.5 24v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM22.5 20v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM22.5 16v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM24 10v-4h-2.5c-0.828 0-1.5-0.672-1.5-1.5v-2.5h-10v8h14z"></path> </symbol> <symbol id="pin" viewBox="0 0 17 22"> <title>Location</title> <desc>Location Icon</desc> <path d="M8.25 11a3.666 3.666 0 1 1 0-7.334 3.666 3.666 0 0 1 0 7.333zm0-5.5a1.833 1.833 0 1 0 0 3.666 1.833 1.833 0 0 0 0-3.666zm.513 16.343a.917.917 0 0 0 .247-1.274C2.237 10.486 1.833 9.78 1.833 7.333c0-2.21 1.705-5.5 6.416-5.5 4.712 0 6.417 3.29 6.417 5.5 0 2.025-.175 2.75-4.73 9.597a.917.917 0 0 0 1.522 1.017c4.427-6.636 5.04-7.929 5.04-10.614C16.499 4.5 14.263 0 8.25 0 2.237 0 0 4.5 0 7.333c0 3.061.632 4.097 7.489 14.262a.917.917 0 0 0 1.274.248z"></path> </symbol> <symbol id="calendar" viewBox="0 0 34 39"> <title>Calendar</title> <desc>Calendar Icon</desc> <path d="M26.953 36.098c2.592 0 4.702-2.175 4.702-4.846V14.439H2.345v16.813c0 2.671 2.111 4.846 4.704 4.846h19.904zM26.97 3.61C30.848 3.61 34 6.845 34 10.824V31.29c0 3.978-3.152 7.215-7.03 7.215H7.03C3.154 38.505 0 35.268 0 31.29V10.824C0 6.845 3.154 3.61 7.03 3.61c.641 0 1.16.532 1.16 1.19 0 .656-.519 1.189-1.16 1.189-2.597 0-4.712 2.168-4.712 4.835v1.312h29.364v-1.312c0-2.667-2.114-4.835-4.711-4.835-.641 0-1.16-.533-1.16-1.19 0-.657.519-1.19 1.16-1.19zM13.922 6.016c-.588 0-1.025-.539-1.025-1.203s.437-1.203 1.025-1.203h6.156c.587 0 1.025.539 1.025 1.203s-.438 1.203-1.025 1.203h-6.156zM25.793 21.66c.668 0 1.173.539 1.173 1.203s-.505 1.203-1.173 1.203H8.207c-.668 0-1.173-.539-1.173-1.203s.505-1.203 1.173-1.203h17.586zm-4.69 4.813c.629 0 1.173.539 1.173 1.203s-.544 1.203-1.173 1.203h-8.206c-.629 0-1.173-.539-1.173-1.203s.544-1.203 1.173-1.203h8.206zM10.552 9.626c-.649 0-1.173-.545-1.173-1.203v-7.22C9.38.546 9.903 0 10.552 0c.648 0 1.172.546 1.172 1.203v7.22c0 .658-.524 1.203-1.172 1.203zm12.896 0c-.648 0-1.172-.545-1.172-1.203v-7.22C22.276.546 22.8 0 23.448 0c.649 0 1.173.546 1.173 1.203v7.22c0 .658-.524 1.203-1.173 1.203z"></path> </symbol> <symbol id="chevron-down" viewBox="0 0 20 11"> <title>Down</title> <desc>Menu Toggle Arrow</desc> <path d="M9.758 8.898L17.81.847a1 1 0 1 1 1.414 1.414l-8.427 8.428a1 1 0 0 1-1.039.236 1 1 0 0 1-1.038-.236L.293 2.26A1 1 0 0 1 1.707.847l8.051 8.051z"></path> </symbol> <symbol id="chevron-down-green" viewBox="0 0 20 11"> <title>Down</title> <desc>Menu Toggle Arrow</desc> <path d="M9.758 8.898L17.81.847a1 1 0 1 1 1.414 1.414l-8.427 8.428a1 1 0 0 1-1.039.236 1 1 0 0 1-1.038-.236L.293 2.26A1 1 0 0 1 1.707.847l8.051 8.051z" fill="#00338e"></path> </symbol> <symbol id="chevron-up" viewBox="0 0 16 16"> <title>Up</title> <desc>Menu Toggle Arrow</desc> <path fill-rule="evenodd" d="M7.646 4.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1-.708.708L8 5.707l-5.646 5.647a.5.5 0 0 1-.708-.708l6-6z" fill="#000"></path> </symbol> <symbol id="twitter" viewBox="0 0 512 512"> <title>Twitter</title> <desc>Twitter Icon</desc> <path d="M459.37 151.716c.325 4.548.325 9.097.325 13.645 0 138.72-105.583 298.558-298.558 298.558-59.452 0-114.68-17.219-161.137-47.106 8.447.974 16.568 1.299 25.34 1.299 49.055 0 94.213-16.568 130.274-44.832-46.132-.975-84.792-31.188-98.112-72.772 6.498.974 12.995 1.624 19.818 1.624 9.421 0 18.843-1.3 27.614-3.573-48.081-9.747-84.143-51.98-84.143-102.985v-1.299c13.969 7.797 30.214 12.67 47.431 13.319-28.264-18.843-46.781-51.005-46.781-87.391 0-19.492 5.197-37.36 14.294-52.954 51.655 63.675 129.3 105.258 216.365 109.807-1.624-7.797-2.599-15.918-2.599-24.04 0-57.828 46.782-104.934 104.934-104.934 30.213 0 57.502 12.67 76.67 33.137 23.715-4.548 46.456-13.32 66.599-25.34-7.798 24.366-24.366 44.833-46.132 57.827 21.117-2.273 41.584-8.122 60.426-16.243-14.292 20.791-32.161 39.308-52.628 54.253z"></path> </symbol> <symbol id="facebook" viewBox="0 0 320 512"> <title>Facebook</title> <desc>Facebook Icon</desc> <path d="M279.14 288l14.22-92.66h-88.91v-60.13c0-25.35 12.42-50.06 52.24-50.06h40.42V6.26S260.43 0 225.36 0c-73.22 0-121.08 44.38-121.08 124.72v70.62H22.89V288h81.39v224h100.17V288z"></path> </symbol> <symbol id="youtube" viewBox="0 0 576 512"> <title>YouTube</title> <desc>YouTube Icon</desc> <path d="M549.655 124.083c-6.281-23.65-24.787-42.276-48.284-48.597C458.781 64 288 64 288 64S117.22 64 74.629 75.486c-23.497 6.322-42.003 24.947-48.284 48.597-11.412 42.867-11.412 132.305-11.412 132.305s0 89.438 11.412 132.305c6.281 23.65 24.787 41.5 48.284 47.821C117.22 448 288 448 288 448s170.78 0 213.371-11.486c23.497-6.321 42.003-24.171 48.284-47.821 11.412-42.867 11.412-132.305 11.412-132.305s0-89.438-11.412-132.305zm-317.51 213.508V175.185l142.739 81.205-142.739 81.201z"></path> </symbol> <symbol id="quote" viewBox="0 0 20 16"> <title>Quote</title> <desc>Quote Symbol</desc> <path d="M20 3.718c-2.766.29-4.128 1.658-4.046 3.895h3.055v8.08H11V9.104C11 3.055 14.22.86 19.34.693L20 3.718zm-11 0c-2.766.29-4.128 1.658-4.004 3.895H8.05v8.08H0V9.104C0 3.055 3.22.86 8.38.693L9 3.718z"></path> </symbol> <symbol id="hospital" viewBox="0 0 22 28"> <title>Hospital</title> <desc>Hospital Icon</desc> <path d="M6 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 26h6v-18h-4v0.5c0 0.828-0.672 1.5-1.5 1.5h-7c-0.828 0-1.5-0.672-1.5-1.5v-0.5h-4v18h6v-3.5c0-0.266 0.234-0.5 0.5-0.5h5c0.266 0 0.5 0.234 0.5 0.5v3.5zM14 7.5v-5c0-0.266-0.234-0.5-0.5-0.5h-1c-0.266 0-0.5 0.234-0.5 0.5v1.5h-2v-1.5c0-0.266-0.234-0.5-0.5-0.5h-1c-0.266 0-0.5 0.234-0.5 0.5v5c0 0.266 0.234 0.5 0.5 0.5h1c0.266 0 0.5-0.234 0.5-0.5v-1.5h2v1.5c0 0.266 0.234 0.5 0.5 0.5h1c0.266 0 0.5-0.234 0.5-0.5zM22 7v20c0 0.547-0.453 1-1 1h-20c-0.547 0-1-0.453-1-1v-20c0-0.547 0.453-1 1-1h5v-4.5c0-0.828 0.672-1.5 1.5-1.5h7c0.828 0 1.5 0.672 1.5 1.5v4.5h5c0.547 0 1 0.453 1 1z"></path> </symbol> <symbol id="clinic" viewBox="0 0 26 26"> <title>Clinic</title> <desc>Clinic Icon</desc> <path d="m12.875 0c-0.15625 0.015625-0.30469 0.070312-0.4375 0.15625l-12 8c-0.46484 0.3125-0.59375 0.94141-0.28125 1.4062s0.94141 0.59375 1.4062 0.28125l11.438-7.625 11.438 7.625c0.46484 0.3125 1.0938 0.18359 1.4062-0.28125s0.18359-1.0938-0.28125-1.4062l-12-8c-0.20312-0.13281-0.44531-0.1875-0.6875-0.15625zm0.125 4.5-12 7.5v11c0 1.6562 1.3438 3 3 3h18c1.6562 0 3-1.3438 3-3v-11zm-2 6.5h4v4h4v4h-4v4h-4v-4h-4v-4h4z"></path> </symbol> <symbol id="icon-earth" viewBox="0 0 32 32"> <title>Earth</title> <desc>Earth Icon</desc> <path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 30c-1.967 0-3.84-0.407-5.538-1.139l7.286-8.197c0.163-0.183 0.253-0.419 0.253-0.664v-3c0-0.552-0.448-1-1-1-3.531 0-7.256-3.671-7.293-3.707-0.188-0.188-0.442-0.293-0.707-0.293h-4c-0.552 0-1 0.448-1 1v6c0 0.379 0.214 0.725 0.553 0.894l3.447 1.724v5.871c-3.627-2.53-6-6.732-6-11.489 0-2.147 0.484-4.181 1.348-6h3.652c0.265 0 0.52-0.105 0.707-0.293l4-4c0.188-0.188 0.293-0.442 0.293-0.707v-2.419c1.268-0.377 2.61-0.581 4-0.581 2.2 0 4.281 0.508 6.134 1.412-0.13 0.109-0.256 0.224-0.376 0.345-1.133 1.133-1.757 2.64-1.757 4.243s0.624 3.109 1.757 4.243c1.139 1.139 2.663 1.758 4.239 1.758 0.099 0 0.198-0.002 0.297-0.007 0.432 1.619 1.211 5.833-0.263 11.635-0.014 0.055-0.022 0.109-0.026 0.163-2.541 2.596-6.084 4.208-10.004 4.208z"></path> </symbol> <symbol id="icon-shield" viewBox="0 0 24 24"> <titl |
| 9 |
Info
On line 1, column 329428 urance_q"> <input type="submit" value="Submit"> </form> </div></header> <main> <section id="map" class="show-map"> <div id="virtual-only" style=" width: 100%; height: 100%; "> <picture style="float: right;width: auto;height: 100%;background: white;"> <source media="(min-width: 1000px)" srcset="https://az690879.vo.msecnd.net/7914/DexCare_VirtualProviders_lg.png"> <img src="https://az690879.vo.msecnd.net/7914/DexCare_VirtualProviders_sm.png" alt="Virtual Visit Options" style="width: auto;max-height: 400px;margin: 0 auto;display: block;"> </picture> </div> <div id="wm_map"></div> </section> <section class="container" id="results-wrapper"> <div class="row"> <div class="col-xs-12"> <div class="topbtns flex"> <label for="fullsearch" class="btn">Filter Results</label> <label for="showmap" class="togglebtn btn" id="maptoggle"><span></span>Map</label> </div> </div> <div class="col-xs-12 col-md-6 py-3"> <div id="results"> <div class="loader"></div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-md-6" id="pagination"></div> </div> </section> </main> <script> function clearInput(id) { var elem = document.getElementById(id); var event = new Event('input', { bubbles: true, cancelable: true, }); elem.value = ''; elem.dispatchEvent(event); if(id="query"){ document.getElementById("searchBox").classList.remove('is-dirty'); } } </script> <script id="searchScripts" type="text/javascript"> function mapRequirement() { /* google's javascript api requires a callback */ console.log('Google Map Loaded'); window.mapLoaded = true; } sessionStorage.removeItem("online_scheduling_filter"); const DAYS = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; /** <!-- childReplacement provOmniLoginStatus --> **/ let myChartLoggedIn = false; async function checkLoginStatus() { let geolocator = document.querySelector('#geoLocatorInput'); const myAccountWrapper = document.querySelector('#topheader .account-wrapper'); const provUrl = siteSearch.isStaging ? 'https://preview-providence.provhealth.org' : 'https://www.providence.org'; return fetch(`${provUrl}/identitynav/getjson`, { credentials: "include" }) .then(resp => resp.json()) .then(resp => { myChartLoggedIn = true; if(myAccountWrapper) myAccountWrapper.classList.remove('hidden'); if(resp && resp.children.length > 0) { /* User verified Populate & stop hiding user dropdown menu */ const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(loginButton) loginButton.classList.add('hidden'); if(myAccountDiv) myAccountDiv.classList.remove('hidden'); let myChartUrl = ''; /* Set username labels */ if(myAccountDiv) { const userLabel = myAccountDiv.querySelector('#mychart-user'); if(userLabel) userLabel.innerHTML = resp.text; const homeItem = resp.children.filter(l => l.text === "MyChart Home")[0]; if(homeItem) { /* Remove '/home' from mychart url */ myChartUrl = homeItem.href.slice(0, -5); } /* Populate dropdown menu */ const accountMenu = document.querySelector('#account-menu'); accountMenu.innerHTML = ''; resp.children.forEach(link => { const menuItem = document.createElement('li'); const menuItemLink = document.createElement('a'); menuItemLink.classList.add('mychart'); menuItemLink.href = link.href; menuItemLink.innerText = link.text; if(menuItemLink.href.includes('mychart')) { /* From Dustin B: if the user does not have an active session on the MyChart side, going directly to a deep link will present them with the login screen. This is not ideal, since we already know they have an active session with the external identity provider by virtue of being logged in to the Providence website. Unfortunately, MyChart does not have a mechanism to automatically obtain that session and redirect them to the deep-linked page. To get around this, we have a script on our side that overrides the click event on any MyChart links. */ menuItemLink.addEventListener('click', click => { click.preventDefault(); click.stopImmediatePropagation(); /* In a new tab, open MyChart link that automatically obtains the session and returns the user to the MyChart home page */ let other = window.open(`${myChartUrl}/Authentication/SAML/Login?idp=SSO`, "_blank"); if(other !== null && menuItemLink.getAttribute('href').toLowerCase().indexOf('/home') === -1) { /* Listen for a message from MyChart */ window.addEventListener("message", function(e) { /* Verify the sender and message */ if (myChartUrl.toLowerCase().indexOf(e.origin.toLowerCase()) === 0 && e.data === "Hello from MyChart") { /* Redirect the user to the deep link */ other.location = menuItemLink.getAttribute("href"); } }); } }); } menuItem.appendChild(menuItemLink); if(accountMenu) accountMenu.appendChild(menuItem); /* Show account menu now that it's populated */ myAccountDiv.classList.remove('hidden'); }); } } else if(resp.method === "POST") { myChartLoggedIn = false; /* remove account form if geo locator set to WA if(!geolocator || /, wa/gi.test(geolocator.value.toLowerCase())) { if(myAccountWrapper) myAccountWrapper.classList.add('hidden'); return; }*/ /* Update login post action url */ const myAccountLogin = document.querySelector('.my-account.unverified'); myAccountLogin.classList.remove('hidden'); const myAccountLoginAnchor = document.querySelector('#accountLogin'); myAccountLoginAnchor.addEventListener('click', e => { e.preventDefault(); fetch(resp.action, { method: "post" }); }); /*myAccountLogin.innerHTML = ''; const loginForm = document.createElement('form'); loginForm.setAttribute('action', resp.action); loginForm.setAttribute('method', "post"); loginForm.innerHTML = '<button type="submit" class="account-btn"><svg class="account" aria-hidden="true" role="img"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#account-icon"></use></svg>My Account</button>'; myAccountLogin.append(loginForm);*/ } else { const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(myAccountDiv) myAccountDiv.classList.add('hidden'); if(loginButton) { loginButton.classList.remove('hidden'); myAccountWrapper.classList.remove('hidden'); }; } }).catch(e => { console.error("Failed to receive users MyChart identity status", e); const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(myAccountDiv) myAccountDiv.classList.add('hidden'); if(loginButton) { loginButton.classList.remove('hidden'); myAccountWrapper.classList.remove('hidden'); }; }); } /** <!-- end childReplacement provOmniLoginStatus --> **/ class Search { constructor(fetchUrl) { this.fetchUrl = fetchUrl; }; form = document.querySelector("#search-submit"); form2 = document.querySelector("#search-submit2"); input = document.querySelector("#query"); autocomplete = document.querySelector('#autocomplete'); textInputs = document.querySelectorAll(".textInput"); resultsEl = document.querySelector("#results"); paginationEl = document.querySelector("#pagination"); mapEl = document.querySelector('#map'); showMapEl = document.querySelector('#showmap'); headerEl = document.querySelector('header'); distanceInput = document.querySelector("#distance"); logo = document.querySelector('#header-logo'); isStaging = /staging/gi.test(window.location.hostname); apiSub = this.isStaging ? 'womphealthdevapi' : 'womphealthapi'; /* if user passes query param hos=true, we removed the IsClinic=true filter param from our call womphealth api. This allows Providence to link into the directory and closely match the sitecore provider listings by displaying ALL providers at a location, even if that provider only has the location listed as type HOS */ hosFlag = false; helpers = { async fetchWithTimeout(resource, options = {}) { const { timeout = 1000 } = options; const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); const response = await fetch(resource, { ...options, signal: controller.signal }); clearTimeout(id); return response; }, getCID: () => { let cid = localStorage.getItem('cid'); /* If we DON'T have a CID saved to localStorage... */ if (!cid || !cid.length) { /* If crypto's randomUUID func is available, create a CID with it */ if (typeof crypto?.randomUUID == 'function') { cid = crypto.randomUUID(); } /* Else, go old school... */ else { cid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /* Save the new cid to localStorage */ localStorage.setItem('cid', cid); } /* Send CID to the requestor */ return cid; }, getAvailability(state) { const availableDays = state?.filters?.AvailableDays === undefined ? [0, 1, 2, 3, 4, 5, 6] : Object.entries(state?.filters?.AvailableDays ?? {}).filter(([day, value]) => !state.specificDays || value.active).map(([day,]) => DAYS.indexOf(day)); return (availableDays.length === 0 ? [0, 1, 2, 3, 4, 5, 6] : availableDays).join(","); }, slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); }, debounce(func, delay = 200){ let timeout = null; return function(){ let context = this; let args = arguments; clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args) }, delay); }; }, getCookie(name) { if (name == "trace_id" && localStorage.getItem('trace_id')) { return localStorage.getItem('trace_id'); } const match = document.cookie.match(new RegExp(`(?=${name}=)([^;]*)`)); if (match) { return match[0].split('=')[1]; } }, reportToWheelhouse: async (data) => { try { /* Grabbing URL params from the window's current location */ let tempParams = new URLSearchParams(window.location.search); /* Save the Google ID for Wheelhouse lookups to session storage */ let ampEcidGoogle = sessionStorage.getItem('ampEcidGoogle'); if (!ampEcidGoogle) { ampEcidGoogle = this.helpers.getCookie('AMP_ECID_GOOGLE'); sessionStorage.setItem('ampEcidGoogle', ampEcidGoogle) } let wheelhouse = sessionStorage.getItem('wheelhouseData'); if (!wheelhouse) { const data = await fetch(`https://tealium.wheelhousedmg.com/i.gif?tealium_vid=${ampEcidGoogle}&__amp_source_origin=${encodeURIComponent(window.location.origin)}`) .then(res => res.json()); sessionStorage.setItem('wheelhouseData', JSON.stringify(data)); wheelhouse = data; } else { wheelhouse = JSON.parse(wheelhouse); }; let gclid = this.helpers.getCookie('u_gclid'); if (!gclid) { for (let p of ['gclid', 'u_gclid']) { /* If the linkers are set in the URL, use that value */ const tempParam = tempParams.get(p); if (tempParam) { gclid = tempParam; /* If we find a param, we don't need to continue looping through possible params. */ continue; } } } let piwik_vid = wheelhouse.piwik_vid; let tealium_vid = wheelhouse.tealium_vid; let dt = new Date(); let timezoneOffset = dt.getTimezoneOffset(); let tData = { dom_domain:window.location.host, dom_pathname:window.location.pathname, dom_title: window.document.title, dom_url: window.location.href, dom_viewport_height: window.innerHeight, dom_viewport_width: window.innerWidth, "dom.domain": window.location.host, "dom.pathname": window.location.pathname, "dom.title": window.document.title, "dom.url": window.location.href, "dom.viewport_height": window.innerHeight, "dom.viewport_width": window.innerWidth, lang: window.navigator.language, page_platform: "amp", piwik_id: piwik_vid, tealium_account: "providence", tealium_profile: "main", tealium_vid: tealium_vid, timestamp: Date.now(), tz: timezoneOffset, "send_to_as": "true", tealium_event:"amp_event" }; if (gclid) { tData.gclid = gclid; } for (const d of data) { tData[d.key] = d.value; }; let tealiumUrl = `https://tealium.wheelhousedmg.com/i.gif`; return fetch(tealiumUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(tData) }) .then(resp => resp.json()) .then(json => { console.log('wheelhouse beacon resp: ', json); let providers = document.querySelectorAll(".provider-link, .schedule-appt, .sched-dis"); let providerSlots = document.querySelectorAll("directory-timeslots"); const linkers = { _gl: { type: "cookie", enabled: true, encode: true, overwrite: false, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ], id: '_ga', linkerParams: ['_gl', '_ga'] }, gclid: { type: "cookie", enabled: true, encode: false, createCookie: true, overwrite: true, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ], id: 'u_gclid', linkerParams: ['gclid', 'u_gclid'] } }; const template = { type: "custom", enabled: true, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ] }; linkers._pcid = {...template, id: "pcid", value: `${json._pcid}`}; for(let i = 0;i < providers.length; i++) { providers[i].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); providers[i].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } function onVisible(element, callback) { new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if(entry.intersectionRatio > 0) { callback(element); observer.disconnect(); } }); }).observe(element); if(!callback) return new Promise(r => callback=r); } /* Wait for shadowroot to load */ for(let i = 0;i < providerSlots.length; i++) { onVisible(providerSlots[i], () => { let slotLinks = providerSlots[i].shadowRoot.querySelectorAll(".schedule-appt, .sched-dis"); for(let j = 0;j < slotLinks.length; j++) { slotLinks[j].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); slotLinks[j].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } }); } /* Get initial shadowroot load, sometimes the Intersection observer isn't catching it */ setTimeout(() => { for(let i = 0;i < providerSlots.length; i++) { let slotLinks = providerSlots[i].shadowRoot.querySelectorAll(".schedule-appt, .sched-dis"); for(let j = 0;j < slotLinks.length; j++) { slotLinks[j].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); slotLinks[j].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } } }, 2000); }) .catch(error => { console.log('wheelhouse beacon error: ', error); }); } catch (ex) { console.log('Error: failed to report to wheelhouse; ' + ex); } }, autoNudgeAmpElements: (enable) => { if (!enable) { /* removeEventListener will not complain if scrollCallback is undefined */ window.removeEventListener('scroll', this.helpers.autoNudgeAmpElements.scrollCallback); return; } if (!this.helpers.autoNudgeAmpElements.scrollCallback) { this.helpers.autoNudgeAmpElements.scrollCallback = this.helpers.debounce(function () { /* Always grab fresh set of elements on scroll callback in case new ones were dynamically added to the body */ const ampElements = [].concat( /*Array.from(document.getElementsByTagName('amp-list')),*/ Array.from(document.getElementsByTagName('amp-img')) ); /* Look for AMP elements in the viewport */ ampElements.forEach((ampElm) => { if (this.helpers.isInViewport(ampElm)) { /* Give AMP a chance to handle the lazy-load on its own before we force layout */ setTimeout(() => { this.helpers.nudgeAmpElementLayout(ampElm); }, 50); } }); }, 50).bind(this); } /* Trigger on leading edge, before a scroll event occurs */ this.helpers.autoNudgeAmpElements.scrollCallback(); window.addEventListener('scroll', this.helpers.autoNudgeAmpElements.scrollCallback); }, nudgeAmpElementLayout: (ampElement) => { if (!ampElement) return; if (ampElement.hasAttribute('data-no-nudge')) return; if (ampElement['__AMP__RESOURCE'] && ampElement['__AMP__RESOURCE'].isLayoutPending && ampElement['__AMP__RESOURCE'].isLayoutPending() && ampElement['__AMP__RESOURCE'].startLayout) { try { console.log('Manually called layout for ' + ampElement.tagName.toLowerCase()); ampElement['__AMP__RESOURCE'].layoutScheduled(Date.now()); ampElement['__AMP__RESOURCE'].startLayout(); } catch (ex) { console.error('Unable to start layout on AMP element\n', ampElement, ex); } } }, isInViewport: (element) => { if (!element) return false; const winLeft = 0; const winRight = window.innerWidth; const winTop = 0; const winBottom = window.innerHeight; /* Hopefully this won't ever happen */ if (winRight <= winLeft || winBottom <= winTop) return false; /* Only consider non-hidden elements */ if (!(element.offsetWidth || element.offsetHeight || element.getClientRects().length)) return false; /* Check that some part of the element is in the viewport */ const rect = element.getBoundingClientRect(); return rect.top <= winBottom && rect.bottom >= winTop && rect.left <= winRight && rect.right >= winLeft; }, trackWheelhouseEvents: async () => { try { const { filters, query, facets, previousQuery, useMyLocationClicked, updatedLocation } = this.state; const { reportToWheelhouse } = this.helpers; const { scrollLeft, scrollTop } = document.body; const { Gender, Languages, InsuranceAccepted, PracticeGroup } = facets; const getActiveFilters = (name) => { return facets[name] .reduce((arr, v) => { if (!filters[name]) return []; return filters[name][v]?.active ? [...arr, v] : arr; }, []); }; const createEventBody = (category, action, label, extraVals = []) => { const body = [ {key: "tealium_event", value: "amp_event"}, {key: "scroll_x", value: scrollLeft}, {key: "scroll_y", value: scrollTop}, {key: "event_category", value: category}, {key: "event_action", value: action}, {key: "event_label", value: label}, ...extraVals ]; return body; }; /* Send location set beacon */ if (useMyLocationClicked || updatedLocation) { this.state.updatedLocation = false; await reportToWheelhouse( createEventBody('Provider Search', 'location', 'set') ) } /* Send event for clicked "Use My Location" button */ if (useMyLocationClicked) { const isOn = document.querySelector('#switch')?.checked ? "on" : "off"; await reportToWheelhouse( createEventBody('Provider Search', 'useMyLocation', isOn) ); this.state.useMyLocationClicked = false; } /* Send analytic events for query or query removal */ /*if (query?.length) { await reportToWheelhouse( createEventBody('Provider Search', 'search-term', 'Search term set') ); } else if (previousQuery?.length) { await reportToWheelhouse( createEventBody('Provider Search', 'search-term-removed', 'Search term not set') ); }*/ /* Create the gender event, if a gender is active on search filters */ if (Gender.length) { const genders = getActiveFilters('Gender'); if (genders.length) { await reportToWheelhouse( createEventBody('Provider Search', 'gender', genders.join(',')) ); } } /* Create the languages event, if a language is active on search filters */ if (Languages.length) { const languages = getActiveFilters('Languages'); if (languages.length) { await reportToWheelhouse( createEventBody('Provider Search', 'language', languages.join(',')) ); } } /* Create the insurances event, if an insurance is active on search filters */ if (InsuranceAccepted.length) { const insurances = getActiveFilters('InsuranceAccepted'); if (insurances.length) { await reportToWheelhouse( createEventBody( 'Provider Search', 'Patient Insurance Information', insurances.join(','), [{ key: "cd64", value: insurances.join(',') }] ) ); } } /* Create the practice group event, if a medical group is active on search filters */ if (PracticeGroup.length) { const practiceGroups = getActiveFilters('PracticeGroup'); if (practiceGroups.length) { await reportToWheelhouse( createEventBody( 'Provider Search', 'medical-group', practiceGroups.join(',') ) ); } } } catch (err) { console.error(err); } }, trackWompEvent: async (data) => { const { ec, el, ea } = data; const { pathname, search } = window.location; await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthAnalytics?brand=${this.state.filters.brand}&t=event&ec=${ec}&ea=${ea}&el=${el}&cid=${this.helpers.getCID()}&dp=${encodeURIComponent(pathname + search)}`) .then(res => res.json()) .then(res => console.log(res)); }, createPixel: (params = {}) => { const wheelHouseDataRaw = document.querySelector('#wheelhouseJson'); const wheelhouseJson = wheelHouseDataRaw ? JSON.parse(wheelHouseDataRaw.innerText) : {}; const { pageType, page_subtype } = this.state; const { origin, pathname, href, host } = window.location; const { referrer, title } = document; const timestamp = new Date().toISOString(); const clientId = this.helpers.getCookie('_ga') || 'no client id'; const ECID = this.helpers.getCookie('AMP_ECID_GOOGLE') || 'no client id'; const baseUrl = "https://pc-providence-collect.tealiumiq.com/vdata/i.gif"; const account = "providence"; const profile = "main"; const datasource = "neli68"; let gclid = this.helpers.getCookie('u_gclid') || false; if (!gclid) { let tempParams = new URLSearchParams(window.location.search); for (let p of ['u_gclid', 'gclid']) { /* If the linkers are set in the URL, use that value */ const tempParam = tempParams.get(p); if (tempParam) { gclid = tempParam; /* If we find a param, we don't need to continue looping through possible params. */ continue; } } }; let fromAmpScript = {}; try { fromAmpScript = JSON.parse(document.querySelector('amp-analytics#tealium_collect script').innerHTML).extraUrlParams; } catch (e) { console.error(`Could not parse amp-script JSON. Error: ${e}`); }; const isInternal = fromAmpScript.internalUser || 'False'; let traceId = fromAmpScript.tealium_trace_id || 'Not Set'; if(traceId === 'Not Set') { let tealiumTraceId = getCookieInvoca("tealium_trace_id"); if(tealiumTraceId){ traceId = tealiumTraceId; } } const parameters = { /* Tealium defaults */ tealium_account: account, tealium_profile: profile, datasource: datasource, tealium_vid: ECID, /* Variables sent with every view and event */ hitTimestamp: timestamp, clientID: clientId, /* sessionID: 9999, This should be a cookie? */ user_agent: navigator.userAgent, utag_profile_version: `${profile} | ${datasource}`, "ut.visitor_id": ECID, "dom.title": title, "dom.url": href, "dom.domain": host, "dom.pathname": pathname, dom_title: title, dom_url: href, dom_domain: host, dom_pathname: pathname, event_url: origin + pathname, experience_platform: "Web - AMP", /** <!-- childReplacement brandTrack --> **/ brand: "Providence", /** <!-- end childReplacement brandTrack --> **/ web_auth_state: "Undetermined", pageType, page_subtype, state: this.state.filters.userState, tealium_event: 'womp_directory_screen_view', tealium_event_type: 'view', internalUser: isInternal, "send_to_as": "true", "search_location_filter_within": 'Not Set', /* location_partner: "NEED DATA", */ /* Event specific parameters passed into function */ ...params }; try { let searchParams = new URLSearchParams(window.location.search); if(searchParams.get('campaignid')) { parameters['campaignid'] = searchParams.get('campaignid'); } if(searchParams.get('tacticid')) { parameters['tacticid'] = searchParams.get('tacticid'); } if(searchParams.get('offersetid')) { parameters['offersetid'] = searchParams.get('offersetid'); } if(searchParams.get('channel')) { parameters['channel'] = searchParams.get('channel'); } } catch (error) { console.log(error); } try { let dist = this.state.filters.distance; if(!isNaN(dist) && dist) { parameters.search_location_filter_within = dist; console.log("dist: dist " + dist); } } catch (error) { console.log("Search radius is not set."); } if (traceId) { parameters['tealium_trace_id'] = traceId; } if (wheelhouseJson?.pcid) { parameters.pcid = wheelhouseJson.pcid; } if (gclid) { parameters.gclid = gclid; } const pixelUrl = baseUrl + Object.keys(parameters).reduce((str, key, i) => { const prefix = i === 0 ? '?' : '&'; return str + prefix + key + '=' + encodeURIComponent(parameters[key]); }, ''); return pixelUrl; }, /* Takes a pixel URL and adds it to the DOM for tracking */ trackIt: (data) => { let pixels = Array.isArray(data) ? data.reduce((str, url) => str + `<img src="${url}" />`, '') : `<img src="${data}" />`; /* 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', ` <div id="pixels-wrapper" style="display: none;"> ${pixels} </div> `); }; }, /* 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(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) => { let validZip = true; if (res.results[0]) { let resultIndex; try { resultIndex = res.results.findIndex(address => address.address_components.findIndex(ac => ac.long_name.toLowerCase().includes('united states') ) > -1 ); } catch (ex) { console.log(ex); resultIndex = -1; } if (resultIndex > -1) { AMP.setState({locationState: {validZip: 1}}); const geo = res.results[resultIndex].geometry.location; coords = { latitude: geo.lat, longitude: geo.lng, }; console.log(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 { validZip = false; } } else { siteSearch.helpers.getGoogleLocationFallback(); } if (!validZip) { console.log('invalid zip'); AMP.setState({locationState: {validZip: 0}}); return; } }); }; }, getGoogleLocationFallback: (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?components=postal_code%3A${ value }%7Ccountry%3AUS&key=AIzaSyC9aTi6UWN30wj-e4uJHJ5j0y6szBTQ4BY` ) .then((res) => res.json()) .then((res) => { let validZip = true; if (res.results[0]) { let resultIndex; try { resultIndex = res.results.findIndex(address => address.address_components.findIndex(ac => ac.long_name.toLowerCase().includes('united states') ) > -1 ); } catch (ex) { console.log(ex); resultIndex = -1; } if (resultIndex > -1) { AMP.setState({locationState: {validZip: 1}}); const geo = res.results[resultIndex].geometry.location; coords = { latitude: geo.lat, longitude: geo.lng, }; console.log(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 { validZip = false; } } else { validZip = false; } if (!validZip) { console.log('invalid zip'); 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); }; }, 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} <svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg>`; */ this.state.filters.location = newLoc; /** <!-- childReplacement provOmniGeoLocatorShowPosition --> **/ const zipParts = res.results[0].address_components.filter((x) => x.types .join("") .match( /postal_code/gi ) ); const zip = zipParts[0].long_name; const fullLocation = this.state.filters.location + ' ' + zip; const headerLocationLabel = document.querySelector('#geoLocatorLabel span'); headerLocationLabel.innerHTML = fullLocation; document.querySelector('#geoLocatorInput').value = fullLocation; checkLoginStatus(); /** <!-- end childReplacement provOmniGeoLocatorShowPosition --> **/ }); this.state.filters = { ...this.state.filters, coordinates: { latitude, longitude, }, locationType: "user", }; this.handleSubmit(); }, showError: (error) => { console.log("Error when trying to get users location:"); console.log(error.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}<br />${City}, ${Region} ${PostalCode}`; } else if (type == 'providerCard') { formattedAddress = data; let zip = formattedAddress.split(',').pop(); let cityState = formattedAddress.split(',').slice(0,2).join(',').trim(); formattedAddress = formattedAddress.replace(cityState, ''); formattedAddress = formattedAddress.replace(zip, ''); formattedAddress = formattedAddress.slice(2); formattedAddress = `${formattedAddress} ${cityState},${zip}`; /* Following inserts comma before Suite text if Suite text is present. */ if (formattedAddress.toLowerCase().indexOf("suite") >= 0) { let suiteIndex = formattedAddress.toLowerCase().indexOf("suite"); let potentiallyComma = suiteIndex - 2; if (formattedAddress[potentiallyComma] != ",") { formattedAddress = formattedAddress.slice(0, potentiallyComma + 1) + ', ' + formattedAddress.slice(suiteIndex); } } } return formattedAddress; }, formatPhoneNumber: (num, format = 'default') => { /* Filter only numbers from the input */ const cleaned = ('' + num).replace(/\D/g, ''); /* Check if the input is of correct */ const match = cleaned.match(/^(1|)?(\d{3})(\d{3})(\d{4})$/); if (match) { /* Remove the matched extension code. Change this to format for any country code.*/ const intlCode = (match[1] ? '' : ''); switch(format) { case 'hyphenated': return `${intlCode}${match[2]}-${match[3]}-${match[4]}`; default: return `${intlCode}(${match[2]}) ${match[3]}-${match[4]}`; }; }; return null; }, viewportLessThan: (px = 1000) => { return window.innerWidth < px; }, resolveLocation: (data) => { const hasZip = data.zip && data.zip.length > 1; const hasCity = data.city && data.city.length > 1; const hasState = data.state && data.state.length > 1; const hasNeighborhood = data.neighborhood && data.neighborhood.length > 1; const hasCounty = data.county && data.county.length > 1; if (hasNeighborhood) { return data.neighborhood + (hasState ? `, ${data.state}` : ''); } else if (hasZip) { return data.zip; } else if (hasCity && hasState) { return `${data.city}, ${data.state}`; } else if (hasCounty) { return data.county + (hasState ? `, ${data.state}` : ''); } else { return 'Seattle, WA'; } }, pushHistory: async (queryParams = '') => { const { filters, query, pageType, page_subtype, defaultLoc, currentLoc, pushHistory } = this.state; const { createPixel, trackIt, reportToWheelhouse } = this.helpers; const trackingPixels = []; let search_match = "Not Set"; try { let match_filters = this.state.results.info.facets.PrimarySpecialties; if(match_filters.length > 0) { search_match = match_filters.toString(); } else if(query && query.length) { search_match = "No Match"; } /* Potentially return all Matches */ /* Possibly problematic for searches such as "Joe has Cancer" */ /*let matches = this.state.results.info.matches; if(matches.length > 0) { search_match = matches.toString(); } */ } catch (error) { if(query && query.length) { search_match = "No Match"; } console.log("Search did not match PrimarySpecialties."); } /* Set all the search event params for Tealium */ const searchTrackingParams = { search_type: this.state.pageInit ? 'Passive' : 'Active', search_term: `${search_match}`, medical_group_filter: /** <!-- childReplacement medGroupTealiumParam --> **/ Object.values(filters.ProviderOrganization).some(po => po.active) ? 'Set' : 'Not Set', /** <!-- end childReplacement medGroupTealiumParam --> **/ new_patient_availability_filter: filters.clinicVisits ? 'Set' : 'Not Set', telehealth_availability_filter: filters.videoVisits ? 'Set' : 'Not Set', online_scheduling_filter: filters.onlineScheduling ? 'Set' : 'Not Set', search_location_filter: defaultLoc !== currentLoc ? 'Set' : 'Default', event_category: 'Directory Journeys', event_action: 'Provider Directory', event_label: 'Search / Filter', tealium_event: 'womp_directory_event', tealium_event_type: 'event' }; /* Create a new URLSearchParams object from the provided query string, as well as a URL object from the current page location */ const apiUrl = new URLSearchParams(queryParams); const url = new URL(window.location); const curUrl = new URLSearchParams(window.location.search); /* Set the referrer in state to the current URL. */ this.state.referrer = url; /* If the referrer is set on document, use that. If not, use what we've saved to state. */ const referrer = document.referrer ? document.referrer : this.state.referrer.href; /* WheelhouseDMG tracking */ await reportToWheelhouse([ { key: "tealium_event", value: "amp_screen_view" }, { key: "dom_referrer", value: referrer }, { key: "dom.referrer", value: referrer }, { key: "screen_width", value: `${window.innerWidth}x${window.innerHeight}`} ]); /* Create the pageview pixel and push it to our pixels array */ const pageView = createPixel({ "dom.referrer": referrer, "dom_referrer": referrer }); trackingPixels.push(pageView); /* Loop through the params we want to add and determine if they should be in the URL pushed to history state */ const paramsToAdd = [ "brand", "languages", "LocationsOnly", "gender", "insuranceaccepted", "primaryspecialties", /** <!-- childReplacement providerOrgParamToAdd --> **/ "ProviderOrganization", /** <!-- end childReplacement providerOrgParamToAdd --> **/ "page", "sortby", "visittypes", "query", "locationname", "userlocation", "practicegroup", "LocationsOnly", "time", "days", "Tier", "distance" ]; for (let param of paramsToAdd) { const { pageType, page_subtype, filters, query, currentLoc } = this.state; const { visitType, InsuranceAccepted, PrimarySpecialties, LocationName, PracticeGroup, LocationsOnly, tier, distance } = filters; /** <!-- childReplacement providerOrgGetFilter --> **/ let ProviderOrganization = filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetFilter --> **/ let val, keys; switch (param) { case "Tier": url.searchParams.delete("tier"); if(!tier) { url.searchParams.delete("Tier"); continue; } val = tier; break; case "time": val = this.state.filters.AvailableTime ?? "any"; break; case "days": if(!this.state.specificDays) { url.searchParams.delete("days"); continue; } val = this.helpers.getAvailability(this.state); break; case "LocationsOnly": if(!LocationsOnly) { continue; } val = LocationsOnly; break; case "query": val = query; break; case "visittypes": keys = Object.keys(visitType).filter(key => visitType[key]?.active === true && visitType[key]?.auto != true); if (keys.length) { /* Create query string from keys */ val = keys.reduce((str, key) => str.length ? str + `,${key}` : key, ''); /* Set value for clinic visits in pixel */ if (visitType['clinicVisits']?.active === true || (visitType['clinicVisits']?.active === false && ![null, undefined].includes(visitType.clinicVisits))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['new_patient_availability_filter'] = 'Set'; }else if(curUrl.get("visittypes").indexOf('clinicVisits') > -1) { searchTrackingParams['new_patient_availability_filter'] = 'Default'; }else { searchTrackingParams['new_patient_availability_filter'] = 'Set'; } } else { searchTrackingParams['new_patient_availability_filter'] = 'Not Set'; } /* Set value for virtual visits in pixel */ if (visitType['videoVisits']?.active === true || (visitType['videoVisits']?.active === false && ![null, undefined].includes(visitType.videoVisits))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['telehealth_availability_filter'] = 'Set'; }else if(curUrl.get("visittypes").indexOf('videoVisits') > -1) { searchTrackingParams['telehealth_availability_filter'] = 'Default'; }else { searchTrackingParams['telehealth_availability_filter'] = 'Set'; } } else { searchTrackingParams['telehealth_availability_filter'] = 'Not Set'; } /* Set value for virtual visits in pixel */ if (visitType['onlineScheduling']?.active === true || (visitType['onlineScheduling']?.active === false && ![null, undefined].includes(visitType.onlineScheduling))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['online_scheduling_filter'] = 'Set'; sessionStorage.setItem("online_scheduling_filter", "Set"); }else if(curUrl.get("visittypes").indexOf('clinicVisits%2ConlineScheduling') > -1) { if(sessionStorage.getItem("online_scheduling_filter") === null) { searchTrackingParams['online_scheduling_filter'] = 'Default'; } else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } }else if(curUrl.get("visittypes").indexOf('onlineScheduling') > -1) { if(sessionStorage.getItem("online_scheduling_filter") === null) { searchTrackingParams['online_scheduling_filter'] = 'Default'; } else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } }else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } } else { searchTrackingParams['online_scheduling_filter'] = 'Not Set'; } } else { /* Remove the visitTypes param from the query string */ url.searchParams.delete(param); /* Set the values for virtual and clinic visits on the tracking pixel */ searchTrackingParams['new_patient_availability_filter'] = 'Not Set'; searchTrackingParams['telehealth_availability_filter'] = 'Not Set'; searchTrackingParams['online_scheduling_filter'] = 'Not Set'; /* Break out of the loops current iteration */ continue; }; break; case "brand": case "page": val = this.state.filters[param]; break; case "locationname": if (LocationName) { val = LocationName; break; } else { url.searchParams.delete(param); continue; } case "distance": if (distance) { val = distance; break; } else { url.searchParams.delete(param); continue; } case "userlocation": val = currentLoc; break; case "gender": case "insuranceaccepted": case "languages": let lookup = { languages: 'provider_language_filter', gender: 'provider_gender_filter', insuranceaccepted: 'insurance_filter' }; const tracker = lookup[param]; const filter = this.state.filters[ param === 'gender' ? 'Gender' : param === 'languages' ? 'Languages' : 'InsuranceAccepted' ]; /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(filter).filter(f => filter[f]?.active === true && filter[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); searchTrackingParams[tracker] = "Set"; } else { searchTrackingParams[tracker] = "Not Set"; url.searchParams.delete(param); continue; } break; case "primaryspecialties": /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(PrimarySpecialties).filter(f => PrimarySpecialties[f]?.active === true && PrimarySpecialties[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); } else { url.searchParams.delete(param); continue; } break; /** <!-- childReplacement providerOrgParsing --> **/ case "ProviderOrganization": /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(ProviderOrganization).filter(f => ProviderOrganization[f]?.active === true && ProviderOrganization[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); } else { url.searchParams.delete(param); continue; } param = 'providerorganization'; break; /** <!-- end childReplacement providerOrgParsing --> **/ case "sortby": if (apiUrl.get(param)) { val = apiUrl.get(param); } else { url.searchParams.delete(param); /* Break out of the loops current iteration */ continue; }; break; case "practicegroup": if (apiUrl.get(param)) { val = apiUrl.get(param); }else if(apiUrl.get("PracticeGroup")) { val = apiUrl.get("PracticeGroup"); } else { url.searchParams.delete(param); continue; } break; default: break; }; url.searchParams.set(param, val); }; if (window.history.pushState) { window.history.pushState('Find a Doctor','',url); } else { window.history.replaceState({}, 'Find a Doctor', url); this.state.pushHistory = true; } /* Push the current url to the window history, if it's not the initial load if (pushHistory) { window.history.pushState({}, "Womp Health Demo", url); } else { window.history.replaceState({}, "Womp Health Demo", url); this.state.pushHistory = true; }*/ /* Create pagination links based on url state */ this.paginate(); /* Create our tracking pixel for the search event and add it to the pixels array */ const searchEvent = createPixel(searchTrackingParams); trackingPixels.push(searchEvent); /* If the page has timeslots, create an event for Timeslot Enabled Results */ if (document.querySelectorAll('timeslots-widget, directory-timeslots').length > 0) { const timeslotEnabled = createPixel({ event_category: 'Directory Journeys', event_action: 'Provider Directory', event_label: 'Timeslot Enabled Results', tealium_event: 'womp_directory_event', tealium_event_type: 'event' }); trackingPixels.push(timeslotEnabled); }; /* Add the created pixels to the DOM to track the events */ trackIt(trackingPixels); /* Set this to false. Mainly being used for handling the search box update in state. */ this.state.pageInit = false; /* Track autocomplete if search was initated from the autocomplete el and if the window variable is true */ if (window.wmFromAutoComplete === true) { autocomplete.helpers.trackAutocomplete(this.state.query); window.wmFromAutoComplete = false; } await this.helpers.trackWheelhouseEvents(); }, getSlotsType: () => { const { visitType } = this.state.filters; if (!(visitType.clinicVisits?.active && visitType.videoVisits?.active)) { let tmp = Object.keys(visitType).filter(key => visitType[key]?.active); if (tmp.length) { return tmp[0].replace('Visits', ''); }; } return 'all'; }, /* Determine what, if any, timeslots to show for a provider */ checkForTimeslots: (NewClinicVisit, NewVideoVisit, DaysUntilNextAppt) => { let hasTimeslots = false; if (DaysUntilNextAppt <= 30 && (NewClinicVisit == 1 || NewVideoVisit == 1)) { hasTimeslots = true; } return hasTimeslots; }, createProviderLink: (omni) => { let providerLink = `/doctors/profile/${omni.ProfileUrl}`; if (/swedish/gi.test(location.origin) && omni.ProviderUniqueUrlSwedish) { providerLink = omni.ProviderUniqueUrlSwedish.replace(/https:\/\/[a-z\.]*\.org/gi, ''); } if (/pacmed|pacificmedicalcenters/gi.test(location.origin) && omni.ProviderUniqueUrlPacmed) { providerLink = omni.ProviderUniqueUrlPacmed.replace(/https:\/\/[a-z\.]*\.org/gi, ''); } if (/providence/gi.test(location.origin) && omni.ProviderUniqueUrlOnesite) { providerLink = omni.ProviderUniqueUrlOnesite; } return providerLink; } }; state = {}; map = { lookup: {}, markers: {}, locations: {}, infoWindow: false, promises: [], selectedNpi: false, selectedLocation: false, bounds: false, icons: {}, toggleVirtual: (isVirtual = false) => { if (isVirtual) { document.querySelector('main').classList.add('no-locations'); document.querySelector('#map').classList.remove('show-map'); } else { document.querySelector('main').classList.remove('no-locations'); document.querySelector('#map').classList.add('show-map'); }; }, selectProvider: npi => { const w = window.innerWidth; const npiCard = document.querySelector(`#npi${npi}`); /* Saving some time by not modifying the DOM if the NPI hasn't changed since last click */ if (npi !== this.map.selectedNpi || !npiCard.classList.contains('selected')) { /* Remove the selected classs from the currently selected Npi */ if (this.map.selectedNpi) { document.querySelector(`#npi${this.map.selectedNpi}`).classList.remove('selected'); }; /* Highight card for selected Npi */ this.map.selectedNpi = npi; npiCard.classList.add('selected'); }; /* We'll scroll to the card regardless, just in case the user changed scroll position since last click */ if (w < 1000) { this.resultsEl.scrollBy({ top: 0, /* 32px is the amount of spacing between the viewports left edge and the selected provider card */ /* only exception is the very first card (has 16px), but this still works for that edge case */ left: npiCard.getBoundingClientRect().left - 32, behavior: 'smooth' }); } else { this.helpers.scrollToTarget(npiCard, 30); }; }, createMarkers: () => { this.map.bounds = new google.maps.LatLngBounds(); try { /* Sets Map bounds to the searched for geoIP */ var ePoint1 = new google.maps.LatLng(this.state.results.geoip.coordinates.lat + 0.01, this.state.results.geoip.coordinates.lng + 0.01); var ePoint2 = new google.maps.LatLng(this.state.results.geoip.coordinates.lat - 0.01, this.state.results.geoip.coordinates.lng - 0.01); this.map.bounds.extend(ePoint1); this.map.bounds.extend(ePoint2); } catch (error) { console.log("Error: Could not get geoIP coordinates"); } const { locations, el, setMapOnAll, lookup } = this.map; const { createPixel, trackIt } = this.helpers; const ids = Object.keys(locations); const byUUID = {}; const selectFields = [ "id", "Region", "City", "PostalCode", "Address", "Name", "GeocodedCoordinate", "AddressId" ]; const encodedFields = encodeURIComponent(selectFields.join(',')); const encodedIds = encodeURIComponent(ids?.join(',')); /* ids?.length && uses short-circuit evalutaion to skip the fetch call if ids is null or not an array for any reason */ /*Using womphealthapi temp because weird data in staging ${this.apiSub} : 'womphealthdevapi' : 'womphealthapi'; */ ids?.length && fetch(`https://womphealthapi.azurewebsites.net/api/WompHealthData?brand=${this.state.filters.brand}&data=locations&ids=${encodedIds}&select=${encodedFields}`) .then(res => res.json()) .then(res => { res.forEach((loc, i) => { if (!loc) { console.debug(`Location information not available for id=${ids[i]}`); return; } const { pageType, page_subtype } = this.state; const { GeocodedCoordinate, City, Region, PostalCode, Address, Name } = loc; const uuid = loc.AddressId; lookup[loc.id] = uuid; if (!byUUID[uuid]) { byUUID[uuid] = loc; } const coordinates = { lat: loc.GeocodedCoordinate.coordinates[1], lng: loc.GeocodedCoordinate.coordinates[0] }; const coords = new google.maps.LatLng(coordinates.lat, coordinates.lng); const marker = new google.maps.Marker({ position: coords, map: el, icon: this.map.icons.default }); marker.icon.url = 'https://pacmed.azureedge.net/7914/[email protected]'; marker.addListener('mouseover', () => { try { marker.setIcon(this.map.icons.selected); } catch (error) { console.log(error); } }); marker.addListener('mouseout', () => { try { if(this.map.selectedMarker !== marker) { marker.setIcon(this.map.icons.default); } } catch (error) { console.log(error); } }); marker.addListener('click', () => { try { const isMobile = this.helpers.viewportLessThan(); this.map.resetMarkerIcon(); if (this.map.infoWindow) this.map.infoWindow.close(); this.map.selectedMarker = marker; this.map.selectedLocation = uuid; marker.setIcon(this.map.icons.selected); if (!isMobile) { /* Track pin clicks that open a location's info window */ trackIt(createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Click', event_label: Name, tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); /* Remove the selected class from the currently selected Npi if it's for another location */ if (this.map.selectedNpi && !this.map.locations[loc.id].some(curr => curr.Npi === this.map.selectedNpi)) { document.querySelector(`#npi${this.map.selectedNpi}`).classList.remove('selected'); }; const FormattedAddress = this.helpers.formatAddress('infoWindow', { City: City, Region: Region, PostalCode: PostalCode, Address: Address }); let providers = []; for (const [key, value] of Object.entries(this.map.lookup)) { if (this.map.locations[key] === undefined) { continue; } if (value && value == uuid) { providers = [...providers, ...this.map.locations[key]] }; }; const contentString = /*html*/`<div class="poi-info-window" data-location="${uuid}"> <div> ${FormattedAddress} <h3 style="margin: 8px 0 8px 0; font-size: 16px;">Providers</h3> ${providers.reduce((all, curr) => { const { Npi, virtual, ImageUrl, Name, ProviderTitle } = curr; const providerHtml = `<div class="provider" onclick="siteSearch.map.selectProvider(${Npi})"> <div class="provider-thumbnail-wrapper ${virtual ? 'hasVirtual' : ''}"> <div class="provider-thumbnail" style="background-image: url('${ImageUrl}')"></div> ${virtual ? `<div class="virtualVisits infoWindow"> <svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit"></use></svg> </div>` : ''} </div> <div class="provider-info"> <h4>${Name}</h4> <div class="provider-specialty">${ProviderTitle ? ProviderTitle : '<br />'}</div> </div> </div>`; return all + providerHtml; }, '')} ${!providers.length ? '<p>No providers found as this location</p>' : ''} </div> </div>`; /* Set and open info window */ const infoWindow = new google.maps.InfoWindow({ content: contentString }); this.map.infoWindow = infoWindow; this.map.infoWindow.open(el, marker); } else { this.map.el.setCenterWithOffset(marker.getPosition(), 0, 150); }; } catch (err) { console.log(err); throw err; } }); this.map.markers[uuid] = marker; try { /* Keep the map within the first 172.5 miles from the center */ if(i == 0 || ((this.map.bounds.Lh.hi - 2.5 < coordinates.lat && this.map.bounds.Lh.hi + 2.5 > coordinates.lat) && (this.map.bounds.ci.hi - 2.5 < coordinates.lng && this.map.bounds.ci.hi + 2.5 > coordinates.lng) )) { /* extend the bounds to include each marker's position */ this.map.bounds.extend(coords); const { bounds } = this.map; if (bounds.getNorthEast().equals(bounds.getSouthWest())) { var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01); var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01); this.map.bounds.extend(extendPoint1); this.map.bounds.extend(extendPoint2); }; } } catch (error) { console.log(error); try { if(i == 0 || ((this.map.bounds.La.hi - 2.5 < coordinates.lat && this.map.bounds.La.hi + 2.5 > coordinates.lat) && (this.map.bounds.eb.hi - 2.5 < coordinates.lng && this.map.bounds.eb.hi + 2.5 > coordinates.lng) )) { /* extend the bounds to include each marker's position */ this.map.bounds.extend(coords); const { bounds } = this.map; if (bounds.getNorthEast().equals(bounds.getSouthWest())) { var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01); var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01); this.map.bounds.extend(extendPoint1); this.map.bounds.extend(extendPoint2); }; } } catch (error) { console.log(error); } } this.map.el.fitBounds(this.map.bounds); this.map.setMapOnAll(this.map.el); }); }) .catch(err => console.log(err)); }, deleteMarkers: () => { if (this.map.markers) { this.map.setMapOnAll(null); this.map.infoWindows = {}; this.map.markers = {}; }; }, resetMarkerIcon: () => { const { markers, icons, selectedMarker } = this.map; if (selectedMarker) { selectedMarker.setIcon(icons.default); }; }, setOffset: () => { try { let windowWidth = window.innerWidth; if(windowWidth <= 960) { let top = this.helpers.pxInViewport(this.headerEl); let topOffset = document.getElementById('results-wrapper').offsetHeight + 60; this.mapEl.style.top = `${top}px`; this.mapEl.style.height = `calc(100vh - ${topOffset}px)`; } } catch (error) { console.log(error); let top = this.helpers.pxInViewport(this.headerEl); this.mapEl.style.top = `${top}px`; this.mapEl.style.height = `calc(100vh - ${top}px)`; } }, setMapOnAll: map => { const { markers, bounds } = this.map; let vals = Object.values(markers); for (let val of vals) { val.setMap(map); } }, build: () => { this.helpers.waitForGlobal('mapLoaded', () => { console.log('found google'); /* Add a prototype function to allow px offsets for map center */ google.maps.Map.prototype.setCenterWithOffset = function(latlng, offsetX = 0, offsetY = 0) { var map = this; var ov = new google.maps.OverlayView(); ov.onAdd = function() { var proj = this.getProjection(); var aPoint = proj.fromLatLngToContainerPixel(latlng); aPoint.x = aPoint.x+offsetX; aPoint.y = aPoint.y+offsetY; map.panTo(proj.fromContainerPixelToLatLng(aPoint)); }; ov.draw = function() {}; ov.setMap(this); }; /* Create the position object for centering the map and populate it with the coordinates we passed in. */ const position = { lat: 39.50, lng: -98.35 }; /* Set icons object */ this.map.icons = { default: { url: "https://pacmed.azureedge.net/7914/[email protected]", scaledSize: new google.maps.Size(24, 35), origin: new google.maps.Point(0,0), anchor: new google.maps.Point(12, 35) }, selected: { url: "https://pacmed.azureedge.net/7914/[email protected]", scaledSize: new google.maps.Size(24, 35), origin: new google.maps.Point(0,0), anchor: new google.maps.Point(12, 35) } }; /* Initialize the map on the page */ this.map.el = new google.maps.Map(document.getElementById("wm_map"), { mapTypeId: google.maps.MapTypeId.ROADMAP, gestureHandling: "greedy", disableDefaultUI: true, }); console.log('map initialized'); const {el: map} = this.map; /* POI marker visibility settings */ const styles = { default: [], hide: [ { featureType: "poi.business", stylers: [{ visibility: "off" }], }, { featureType: "poi.medical", stylers: [{ visibility: "off" }], }, { featureType: "transit", elementType: "labels.icon", stylers: [{ visibility: "off" }], } ], }; /* Update the map to show location data */ map.setOptions({ styles: styles["hide"] }); /* Set map offset from top of screen */ const w = window.innerWidth; /* Track Scroll */ /*google.maps.event.addListener(map, 'dragend', () => { let firstMapEvent = sessionStorage.getItem("fme"); if(!firstMapEvent || firstMapEvent === 'zoom load') { siteSearch.helpers.trackIt(siteSearch.helpers.createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Interaction', event_label: 'Scroll', tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); sessionStorage.setItem("fme", "occured"); } });*/ /* Track Zoom */ /*google.maps.event.addListener(map, 'zoom_changed', () => { let firstMapEvent = sessionStorage.getItem("fme"); if(!firstMapEvent) { sessionStorage.setItem("fme", "zoom load");} else if(firstMapEvent === 'zoom load') { siteSearch.helpers.trackIt(siteSearch.helpers.createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Interaction', event_label: 'Zoom', tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); sessionStorage.setItem("fme", "occured"); } });*/ google.maps.event.addListener(map, 'click', () => { const selectedNpi = document.querySelector(`#npi${this.map.selectedNpi}`); if (selectedNpi) { selectedNpi.classList.remove('selected'); } if (this.map.infoWindow) { this.map.infoWindow.close(); this.map.resetMarkerIcon(); } }); }); } }; MapOldQueryParams = function () { /* Post migration to ProvOmni, providence will still have old links to providence search, using old query params. https://app.asana.com/0/1204100916165836/1205550979625263/f Handle query term replacement to map old providence query params to new ProvOmni params */ let queryString = document.location.search; if (queryString.includes('search-term')) { queryString = queryString.replace('search-term', 'query'); } if (queryString.includes('provider-organization')) { queryString = queryString.replace('provider-organization', 'providerorganization'); } if (queryString.includes('language=')) { queryString = queryString.replace('language=', 'languages='); } if (queryString.includes('insurance=')) { queryString = queryString.replace('insurance=', 'insuranceaccepted='); } if (queryString.includes('&location=')) { queryString = queryString.replace('&location=', '&userlocation='); } let acceptsNew = false; let offersVideo = false; if (queryString.includes('acceptingNewPatients=on')) { acceptsNew = true; queryString = queryString.replace('acceptingNewPatients=on', ''); } if (queryString.includes('virtualCare=on')) { offersVideo = true; queryString = queryString.replace('virtualCare=on', '') } if (acceptsNew && offersVideo) { queryString += '&visittypes=clinicVisits%252CvideoVisits'; } else if (acceptsNew) { queryString += '&visittypes=clinicVisits'; } else if (offersVideo) { queryString += '&visittypes=videoVisits'; } /* if the query string changed, refresh the page */ if (document.location.search != queryString) { document.location.search = queryString } }; containsDisclaimerSearch = (query) => { this.state.disclaimerSearch = false; /* reset */ const regex = new RegExp(this.state.disclaimerSearchKeywords.join( "|" ), "i"); /* true if query or input value includes a disclaimer keyword */ this.state.disclaimerSearch = regex.test(query) || regex.test(this.input.value); }; containsExcludeKeywords = (query) => { this.state.excludeSearch = false; /* reset */ const regex = new RegExp(this.state.excludeSearchKeywords.join( "|" ), "i"); /* true if query or input value includes an exclude keyword */ const exclude = regex.test(query) || regex.test(this.input.value); return exclude; }; handleSubmit = async (e, { ignoreElements = [] } = {}) => { this.MapOldQueryParams(); this.resultsEl.innerHTML = "<div class='loader'></div>"; this.paginationEl.style.display = "none"; this.map.locations = {}; this.map.selectedNpi = false; this.map.selectedLocation = false; this.map.selectedMarker = false; this.map.deleteMarkers(); try { let allDirtyE = document.querySelectorAll(".is-dirty"); allDirtyE.forEach((element) => { element.classList.remove('is-dirty'); }); } catch (error) { console.log(""); } try { if(this.state.query !== '') { document.getElementById("searchBox").classList.add("is-dirty"); } } catch (error) { console.log(error); } document .querySelectorAll(".dropdown, #searchBox") .forEach((drop) => { if(!ignoreElements.includes(drop.id)) { drop.classList.remove("active"); } }); document.querySelector('#fullsearch').checked = false; if (!this.state.pageInit) { this.state.previousQuery = this.state.query; this.state.query = this.input.value; }; /* Exclusions and disclaimers */ if (this.containsExcludeKeywords(this.state.query || '')) { this.state.excludeSearch = true; this.state.query = ''; this.input.value = ''; } this.containsDisclaimerSearch(this.state.query || ''); /* Destructing state */ const { query, geolocation, filters, isTest, autoApplied } = this.state; const { formatPhoneNumber, formatAddress, showError, showPosition, slugify, trackIt, createPixel } = this.helpers; /* Update previousSearch state */ if (query.length) { let fromLocal = localStorage.getItem('previousSearches'); let fromSession = sessionStorage.getItem('previousSearches'); /* If localStorage already existed, parse the string ot JSON. If not, create an empty array */ fromLocal !== null ? fromLocal = JSON.parse(fromLocal) : fromLocal = []; /* If sessionStorage already existed, parse the string ot JSON. If not, create an empty array */ fromSession !== null ? fromSession = JSON.parse(fromSession) : fromSession = []; /* Append the newest query to the array of previous searches */ let prevSearchesLocal = [...new Set([query, ...fromLocal])]; let prevSearchesSession = [...new Set([query, ...fromSession])]; /* Write the combined array back to localStorage */ localStorage.setItem('previousSearches', JSON.stringify(prevSearchesLocal.slice(0,5))); /* Write the combined array back to sessionStorage for analytics */ sessionStorage.setItem('previousSearches', JSON.stringify(prevSearchesSession)); }; /* Remove autofilters from what is being sent to the API */ if (autoApplied && autoApplied.length) { for (const filter of autoApplied) { const inState = filters[filter.name]; /* If we actually have this value in state, then set active to false */ if (inState && inState[filter.value]) { inState[filter.value].active = false; } } } /* Destructing filters */ const { previousPostalCode, distance, brand, Languages, Gender, InsuranceAccepted, PrimarySpecialties, visitType, coordinates, LocationId, PracticeGroupId, LocationsOnly, } = filters; let LocationName = filters.LocationName; let PracticeGroup = filters.PracticeGroup; /** <!-- childReplacement providerOrgGetFilter --> **/ let ProviderOrganization = filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetFilter --> **/ /* If certain terms are in search, set clinicVisits to true */ if (/(family medicine|family doctor|internal medicine|family care)/gi.test(query)) { const shouldSkipAutoFilter = query != this.state.previousQuery ? false : /(family medicine|family doctor|internal medicine|family care)/gi.test(this.state.previousQuery); if (!shouldSkipAutoFilter) { if (!filters.visitType) { filters.visitType = { clinicVisits: { active: true } } } else { filters.visitType = {...filters.visitType, clinicVisits: { active: true }} }; } }; /* If certain terms are in search, set videoVisits to true */ if (/(video|virtual)/gi.test(query)) { if (!filters.visitType) { filters.visitType = { videoVisits: { active: true } }; } else { filters.visitType = { ...filters.visitType, videoVisits: { active: true } }; }; }; /* If certain requirements are met and search terms exists, turn on 'near me' location toggle */ if (navigator.geolocation && !this.state.nearme && /near me/gi.test(query)) { this.state.nearme = true; navigator.geolocation.getCurrentPosition(showPosition, showError); return; }; /* Return to page one when new filters are applied */ if(!this.state.filters.init) { this.state.filters.page = 1; } /* Some variables for our API request */ const post = { search: query, top: 20, skip: (this.state.filters.page - 1) * 20, highlightPreTag: "", highlightPostTag: "", }; /* Starting point for building out the request params + filters */ let queryParams = `&top=${post.top}&skip=${post.skip}`; /* Check for a default search (provided in before-render when a slug is present in URL) */ const defaultSearch = document.querySelector('#defaultSearch'); const slugText = document.querySelector('#slugText'); if(this.state.filters.init && defaultSearch) { const slugSearchTerm = defaultSearch.querySelector('#slugSearchTerm'); const slugLocation = defaultSearch.querySelector('#slugLocation'); const searchString = slugSearchTerm.innerText; const locationString = slugLocation.innerText; this.state.filters.location = locationString; this.state.query = searchString; post.search = searchString; this.input.value = searchString; window.sessionStorage.setItem('previousQuery', searchString); } else if(!this.state.filters.init && slugText) { /* If additional searches are performed, remove the slug & slug description text */ slugText.remove(); } /* Add visit type, if applicable */ /* const types = Object.keys(visitType).filter(type => visitType[type] === true); if (types.length > 0) { queryParams += types.reduce((str, type) => str + `&${type}=1`, ""); }; */ if (visitType.clinicVisits?.active == true) { queryParams += '&AcceptingNewPatients=1'; } if (visitType.videoVisits?.active == true) { queryParams += '&VirtualCare=1'; } /* Grab languages, if applicable */ const languages = Object.keys(Languages).filter(lang => lang && Languages[lang]?.active === true); if (languages.length > 0) { queryParams += languages.reduce((str, lang, i) => i === 0 ? str + lang : `${str},${lang}`, "&Languages="); }; /* Grab genders, if applicable */ let genders = Object.keys(Gender).filter(gender => Gender[gender]?.active === true); let genderVals = ["Female", "Male", "Non-binary"]; for (const [key, value] of Object.entries(genders)) { if(!genderVals.includes(value)) { delete Gender[value]; let gendersCk = value.charAt(0).toUpperCase() + value.toLowerCase().slice(1); if(!genderVals.includes(gendersCk)) { genders.splice(key, 1); } } } if (genders.length > 0) { queryParams += genders.reduce((str, gender, i) => i === 0 ? str + gender.charAt(0).toUpperCase() + gender.toLowerCase().slice(1) : `${str},${gender.charAt(0).toUpperCase() + gender.toLowerCase().slice(1)}`, "&Gender="); for (const [key, value] of Object.entries(this.state.filters.Gender)) { if(!genderVals.includes(key)) { delete this.state.filters.Gender[key]; } } }; try { /* Check for preselected filters */ await this.helpers.fetchWithTimeout(`https://www.providence.org/integrationapi/getinsuranceselectionjson`, { timeout: 2000 }) .then(res => res.json()) .then(res => { console.log("getinsuranceselectionjson " + res.insurance); if(res.insurance !== null) { queryParams += `&InsuranceAccepted=${res.insurance}`; localStorage.setItem('omniSearchInsurance', res.insurance); } }); } catch (err) { console.error('Insurance selection API not responding:\n' + err); } /* Grab insurances accepted, if applicable */ const insurances = Object.keys(InsuranceAccepted).filter(ins => InsuranceAccepted[ins]?.active === true); if (insurances.length > 0) { let reduced = insurances.reduce((str, ins, i) => i === 0 ? str + ins : `${str},${ins}`, ""); if(reduced.indexOf(',') !== -1){ let currentInsurance = localStorage.getItem('omniSearchInsurance'); if(reduced.split(",")[0] === currentInsurance) { reduced = reduced.split(",").pop(); } else { reduced = reduced.substring(0, reduced.indexOf(',')); } } queryParams += `&InsuranceAccepted=${reduced}`; localStorage.setItem('omniSearchInsurance', reduced); try { await this.helpers.fetchWithTimeout("https://www.providence.org/integrationapi/SetInsuranceSelection?plan=" + reduced, { timeout: 2000 }); } catch (err) { console.error('Insurance selection API not responding.'); } }; /** <!-- childReplacement providerOrgQueryParams --> **/ /* Get medical organizations for ProvidenceOmni */ let organizations = Object.keys(ProviderOrganization).filter(spec => ProviderOrganization[spec]?.active === true); try { for (var i = 0; i < organizations.length; i++) { if(organizations[i] === 'Providence Affiliated Physicians') { let org = []; for (var j = 0; j < organizations.length; j++) { /* Check if valid medical organization */ if(this.state.facets.ProviderOrganization.includes(organizations[j])) { org.push(organizations[j]); } } let po = this.searchParams.get("ProviderOrganization").replaceAll(", ", "!"); po = po.split(','); for (var j = 0; j < po.length; j++) { po[j] = po[j].replace("!", ", "); /* Check if valid medical organization */ if(!this.state.facets.ProviderOrganization.includes(po[j])) { po.splice(j, 1); } } organizations = Array.from([...new Set([...org, ...po])]); break; } } } catch (error) { console.log(error); } /* Check if valid medical organization */ let orgs = []; for (var j = 0; j < organizations.length; j++) { /* Check if valid medical organization */ if(this.state.facets.ProviderOrganization.includes(organizations[j])) { orgs.push(organizations[j]); } } organizations = orgs; if (organizations.length > 0) { const encodedOrganizations = encodeURIComponent(organizations.reduce((str, spec, i) => i === 0 ? str + spec : `${str},${spec}`, "")); queryParams += `&ProviderOrganization=${encodedOrganizations}`; } /** <!-- end childReplacement providerOrgQueryParams --> **/ /* Grab primary specialties, if applicable */ const specialties = Object.keys(PrimarySpecialties).filter(spec => PrimarySpecialties[spec]?.active === true); if (specialties.length > 0) { queryParams += specialties.reduce((str, spec, i) => i === 0 ? str + spec : `${str},${spec}`, "&PrimarySpecialties="); }; /* Set filter for LocationName */ if (LocationName && LocationName.length) { LocationName = encodeURIComponent(LocationName); queryParams += `&LocationName=${LocationName}`; } /* Set filter for distance */ if (distance && distance.length) { queryParams += `&distance=${distance}`; } /* Set filter for PracticeGroup */ if (PracticeGroup && PracticeGroup.length) { PracticeGroup = encodeURIComponent(PracticeGroup); queryParams += `&PracticeGroup=${PracticeGroup}`; } /* Set filter for LocationId */ if (LocationId && LocationId.length) { queryParams += `&LocationId=${this.state.filters.LocationId}`; } /* Set filter for PracticeGroupId */ if (PracticeGroupId && PracticeGroupId.length) { queryParams += `&PracticeGroupId=${this.state.filters.PracticeGroupId}`; } /* Not 100% necessary but it double-checks that we do indeed have coordinates before adding them to the request. */ if (this.state.filters.location) { queryParams += `&location=${this.state.filters.location}`; } else if (this.state.currentLoc) { queryParams += `&location=${this.state.currentLoc}`; } /* If sortby is not false, set param */ if (this.state.filters.sortby) { queryParams += `&sortby=${this.state.filters.sortby}`; }; /* Create the user id if not present */ if (!localStorage.getItem('cid')) { localStorage.setItem('cid', crypto.randomUUID()); }; queryParams += `&cid=${localStorage.getItem('cid')}`; /* Set locations=true if LocationsOnly is set */ if(this.state.filters.LocationsOnly) { queryParams += `&locations=${this.state.filters.LocationsOnly}`; } if(this.state.specificDays && this.state.filters.AvailableDays) { queryParams += `&days=${Object.entries(this.state.filters.AvailableDays).filter(x => x[1].active).map(x => DAYS.indexOf(x[0])).join(",")}`; } if(this.state.filters.AvailableTime) { queryParams += `&time=${this.state.filters.AvailableTime}`; } if(this.state.filters.tier) { queryParams += `&Tier=${this.state.filters.tier}`; } /* Provider cards in the directory should not list their hospital locations as the "primary" address. This occurs because we index a provider record for each location for a provider. We can now filter out those records with IsClinic=true. */ /** <!-- childReplacement isClinicOmniQueryParam --> **/ queryParams += '&IsClinic=true'; /** <!-- end childReplacement isClinicOmniQueryParam --> **/ let userQueryParams = document.location.search; if (!this.hosFlag) { if (userQueryParams.includes('hos=true')) { /* The hos=true flag has not been raised and the param exists, so we must not filter out HOS locations. */ siteSearch.hosFlag = true; queryParams = queryParams.replace('&IsClinic=true', ''); } } else if (userQueryParams.includes('hos=true')) { /* If the HOS flag has been raised, that means a hos=true search has already been performed once, so we want to remove it from the url query params since it is no longer in use. It is only used for the initial search, then discarded for future searches */ userQueryParams = userQueryParams.replace('hos=true', ''); if (history.pushState) { window.history.pushState({}, "Find a Doctor | Providence", location.origin + location.pathname + userQueryParams); } } let degree = this.searchParams.get('degree'); if(degree !== null) { queryParams += `&Degrees=${degree.toUpperCase()}`; } /* Add a \ (%5C) to allow for , in queryParams, example - ProviderOrganization */ queryParams = queryParams.replaceAll('%2C%20','%5C%2C%20'); /*Change queryParams lowercase to uppercase for certain values to get results*/ const qStrings = [ 'Gender', 'InsuranceAccepted', 'Languages', 'LocationId', 'LocationName', 'PracticeGroup', 'PracticeGroupId', 'PrimarySpecialties', 'ProviderOrganization', 'userLocation', 'visitTypes' ]; const searchParams = new URLSearchParams(window.location.search); let qStringsLength = qStrings.length; for (let i = 0; i < qStringsLength; i++) { /*replace with correct queryParams*/ queryParams = queryParams.replace(qStrings[i].toLowerCase(), qStrings[i]); /*if not in queryParams check if in url*/ if(searchParams.get(qStrings[i]) && !(queryParams.includes(qStrings[i]) || queryParams.includes(qStrings[i].toLowerCase())) ) { queryParams += `&${qStrings[i]}=${searchParams.get(qStrings[i]).replaceAll(', ','%5C%2C%20').replaceAll('&','%26')}`; }else if(searchParams.get(qStrings[i].toLowerCase()) && !(queryParams.includes(qStrings[i]) || queryParams.includes(qStrings[i].toLowerCase())) ) { queryParams += `&${qStrings[i]}=${searchParams.get(qStrings[i].toLowerCase()).replaceAll(', ','%5C%2C%20').replaceAll('&','%26')}`; } } /* Sending the actual request */ await fetch( `${this.fetchUrl}&type=search&brand=${brand}&search=${post.search}${queryParams}${isTest ? '&test=true' : ''}` ) .then((res) => res.json()) .then((res) => { this.state.results = res; }) .then(() => { const { results, query, filters } = this.state; /* If coordinates are returned from the API, save them to state */ if (this.state.results.geoip) { const { geoip } = this.state.results; const { lat, lng } = geoip.coordinates; this.state.currentLoc = this.helpers.resolveLocation(geoip); localStorage.setItem('omniSearchLocation', this.state.currentLoc); queryParams += `&userLocation=${this.state.currentLoc}`; if (!this.state.defaultLoc) { this.state.defaultLoc = this.state.currentLoc; }; this.state.filters.coordinates = { latitude: lat, longitude: lng }; }; /* Update local storage with previous searches */ const terms = query.split(/\s+/); let previousTerms = window.sessionStorage.getItem('previousQuery'); previousTerms = previousTerms && previousTerms.length ? previousTerms.split(/\s+/) : []; let inserted = []; let deleted = []; for (let term of terms) { if (!previousTerms.includes(term)) { inserted.push(term); }; }; for (let term of previousTerms) { if (!terms.includes(term)) { deleted.push(term); }; }; window.sessionStorage.setItem('previousQuery', this.state.query); /* If we get an error from the API, write the error to the page and stop the rest of the function from running. */ if (results.error) { this.resultsEl.innerHTML = `<p>${this.state.results.error.message}</p>`; 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("filter-buttons"); filtersElem.innerHTML = ""; const filtersWrapper = 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")) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log("Couldn't remove filter"); } try { const searchRadiusVal = siteSearch.state.filters.distance; if (searchRadiusVal) { document.querySelector('#searchRadius').value = searchRadiusVal; } if (searchRadiusVal?.length) { let filterText = `Within ${searchRadiusVal} miles`; if (searchRadiusVal == 1) { filterText = `Within ${searchRadiusVal} mile`; } let distanceFilterEl = { facets: [{ name: "distance", value: filterText }], value: filterText }; siteSearch.state.results.info.filters.push(distanceFilterEl); } for (let filter of siteSearch.state.results.info.filters) { try { if (filter.facets[0].name === "IsClinic" || filter.facets[0].name === "Degrees") { continue; } } catch {} let filterButton = document.createElement("span"); let filterText = filter.value; /* LocationId becomes LocationName */ if ( 'info' in this.state.results && 'locations' in this.state.results.info && this.state.results.info.locations.length && this.state.results.info.locations.some((loc) => loc.id == filterText) ) { filterText = this.state.results.info.locations.find((loc) => loc.id == filterText)?.name; } filterButton.classList.add("filter-button"); filterButton.innerHTML = `${filterText} <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; 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 (e) { throw(e); } } /* 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 }]; } } try { if(dataFacets.length === 1) { filterButton.classList.add(dataFacets[0].name); } } catch (error) { console.log(error); } /* Set the data-facets attribute */ filterButton.setAttribute('data-facets', JSON.stringify(dataFacets)); /* Create an event listener to handle clicks, removing filters */ filterButton.addEventListener('click', e => { const { target } = e; const { dataset } = target; if (dataset.text) { let theRegEx = new RegExp(dataset.text, "gi"); window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } else if(target.textContent) { let theRegEx = new RegExp(target.textContent, "gi"); window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } const facets = JSON.parse(dataset.facets); let clearedInsurance = false; let clearInsurancePromise; for (let facet of facets) { const { name, value } = facet; const state = this.state.filters[name]; if (state) { if (typeof state === 'string') { this.state.filters[name] = false; } else if (typeof state === 'object') { state[value].active = false; /* Catch weird values that have ',' in them */ try { let csv = value.split(","); for (let v of csv) { state[v].active = false; } } catch (error) { console.log("Error removing cvs filter."); } } } if (name == "InsuranceAccepted") { let lsInsurance = localStorage.getItem('omniSearchInsurance') || false; if (lsInsurance) { lsInsurance = lsInsurance.split(',').filter(ins => { ins != value }); lsInsurance.length ? localStorage.setItem('omniSearchInsurance', lsInsurance.join(',')) : localStorage.removeItem('omniSearchInsurance'); clearedInsurance = true; clearInsurancePromise = fetch("https://www.providence.org/integrationapi/clearinsuranceselection"); } } else if (name == "distance") { let searchRadiusEl = document.querySelector('#searchRadius'); searchRadiusEl.value = ''; } } 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(); if (history.pushState) { window.history.replaceState({}, "Find a Doctor", url); } } catch (error) { console.log("Error updating url"); } if (clearedInsurance) { Promise.allSettled([clearInsurancePromise]).then(()=>this.handleSubmit()); } else { this.handleSubmit(); } if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } }); /* Add the filter button to the DOM */ filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } /* Add badges for Virtual and New Patient visit type selections */ if (this.state.filters.visitType) { const { clinicVisits, onlineScheduling, videoVisits } = this.state.filters.visitType; const hasClinicVisits = clinicVisits && clinicVisits.active; const hasOnlineScheduling = onlineScheduling && onlineScheduling.active; const hasVideoVisits = videoVisits && videoVisits.active; const handleVisitFiltersClick = e => { const { value } = e.target.dataset; const { filters } = this.state; const state = filters.visitType; if (state) { state[value].active = false; } if (value == 'onlineScheduling') { if (filters.visitType['clinicVisits'].active != document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.contains('active')) { document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.toggle("active"); } document.querySelectorAll("#daysList a").forEach(day => { siteSearch.updateDaySelectorStateAllDays(day, false) }); if (state) { state['clinicVisits'].active = false; } } try { if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log(error); } this.handleSubmit(); }; handleVisitFiltersClick.bind(this); if (hasClinicVisits) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Accepting New Patients <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "clinicVisits"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } if (hasOnlineScheduling) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Online Scheduling <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "onlineScheduling"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } if (hasVideoVisits) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Video Visits <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "videoVisits"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } /* Create an event listener to handle clicks, removing filters */ document.querySelectorAll('.visit-filter').forEach(handleVisitFiltersClick); } } catch (e) { console.log(`Facets failed to load`); } 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 = `<link rel="canonical" href="https://${baseUrl}${window.location.pathname}?page=${this.state.filters.page}">`; 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 = ""; let secondaryLocationIds = []; if (this.state.description) { resultsHtml += `<p style="margin-bottom: 1em; margin-top: 0;"><small>${this.state.description}</small></p>`; 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 += `<div class="search-type" style="display: flex; flex-wrap: wrap; margin: 10px 0;"> <h4 style="margin: 0 6px 0 0;">Search type(s):</h4> <p style="margin: 0;">${results.info.type.join(', ')}</p> </div>`; } /* Pretty self explanatory: if the results count is zero, tell the user there are no results */ if (count == 0) { this.resultsEl.innerHTML = "No results"; /* Set map to center on Bellevue */ this.map.el.setCenterWithOffset(new google.maps.LatLng(47.659159, -122.183832), 0, 0); return; } /* Otherwise, let's display the total number of results */ else { try { if(results.geoip.zip !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.zip}</span></p>`; } else if(results.geoip.city !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.city}</span></p>`; } else if(results.geoip.county !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.county}</span></p>`; } else if(results.geoip.state !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.state}</span></p>`; }else { resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results</p>`; } } catch (error) { resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results</p>`; console.log(error); } } this.state.disclaimerSearch ? resultsHtml += '<h5>This page is intended to provide the most comprehensive information available regarding services offered in your area. Some of the services listed might not be offered at Providence facilities or in Providence affiliated practices. Providence is not responsible for services offered by providers on an independent basis.</h5>' : ''; /* 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 { ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, PrimarySpecialties, ProviderTitle, Addresses, PracticeGroup, LocationNames, LocationName, Rating, virtual, distance, distances, Npi, id, clinicVisits, NewClinicVisit, NewVideoVisit, GeocodedCoordinate, LocationId, LocationIds, RatingCount, ReviewCount, Degrees, Phones, ProfileUrl, AcceptingNewPatients, VirtualCare, SjhScheduleProviderNum, AllowsOpenSchedule, AllowsDirectSchedule, LocationsIsClinic, acceptingNewPatients, AppointmentRequestUrl, DaysUntilNextAppt, DaysUntilFollowUpAppt, Tier, Networks } = result; let ProviderOrganization = result.ProviderOrganization; const searchFeatures = result['@search.features']; const width = (150 / ImageHeight) * ImageWidth; const badges = result.badges ? Object.values(result.badges) : []; const supportsOdhp = (acceptingNewPatients == 1 && AllowsOpenSchedule == 1) || AllowsDirectSchedule == 1; const onlineBooking = supportsOdhp || SjhScheduleProviderNum ? true : false; let AcceptingClinicVisits = AcceptingNewPatients == 1 ? `<div class="new-patients mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg></span> <span class="anp">Accepting New Patients</span> </div>` : ""; const hasTimeslots = this.helpers.checkForTimeslots(NewClinicVisit, NewVideoVisit, DaysUntilNextAppt); /* Determining the description to show. If we have a Provider Title, use that. When no provider title, check for a primary specialty. If none, show no text. */ let descr = ""; if (PrimarySpecialties?.length) { descr = PrimarySpecialties.join(', '); } else if (ProviderTitle?.length) { descr = ProviderTitle; } else { descr = ""; }; this.map.deleteMarkers(); for (const location of LocationIds) { let isClinic; try { let iOfLoc = LocationIds.indexOf(location); isClinic = LocationsIsClinic[iOfLoc]; } catch (ex) { console.log(`Failed to determine if location is CLI for provider: Npi=${Npi}, LocationId=${location}; ` + ex); isClinic = true; } if (isClinic) { if (!this.map.locations[location]) { this.map.locations[location] = []; } this.map.locations[location].push({ Npi, ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, ProviderTitle: descr, Rating, PracticeGroup, virtual }); } } /* If we have badges for matched, create element HTML for it */ let badgesEls = ""; if (badges.length) { badges.forEach((badge) => {if (!badge.includes('PracticeGroupId')) {badgesEls += `<span>${badge}</span>`}}); } /* If a location name is present in the results, choose the proper text and create element HTML for it */ let locationName = ""; if (LocationName) { locationName = /*html*/`<h4 class="location-n"><span class="dd-svg"><svg class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span>${LocationName}</h4>`; } else if (LocationNames.length) { if (result["@search.highlights"] && result["@search.highlights"].LocationNames) { locationName = /*html*/`<h4 class="location-n"><span class="dd-svg"><svg class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span>${result["@search.highlights"].LocationNames[0]}</h4>`; }; }; let locationNames = false; if (LocationNames.length > 1) { locationNames = LocationNames.filter(loc => loc !== locationName); }; /* Loop through the LocationNames and Addresses arrays. If these are present, then build out the locs array for listing addresses on the provider detail cards */ let locs = []; let primaryLocation = false; let secondaryLocations = false; /* Formats address */ if (LocationNames.length && Addresses.length) { for (let i = 0; i < LocationNames.length; i++) { if (Addresses[i]) { locs.push({ name: LocationNames[i], address: formatAddress('providerCard', Addresses[i]), phone: Phones[i] }); } } /* Code for identifying clinics vs hospitals */ for (let i = 0; i < locs.length; i++) { locs[i].isClinic = result.LocationsIsClinic[i]; locs[i].distance = result.distances[i]; } let hasClinic = false; let tempLocArr = []; if (locs.length > 1) { locs.forEach(item => { if (item.isClinic == true) { hasClinic = true; } }); if (hasClinic) { for (let i = 0; i < locs.length; i++) { if (locs[i].isClinic) { tempLocArr.push(locs[i]); } } locs = tempLocArr; } } /* Locations should be sorted by distance Some providers have multiple locations within the same physical location (https://www.providence.org/doctors/general-surgery/mt/missoula/deron-ludwig-1245310200) In those cases, we want to display the first location in the list */ if (hasClinic && locs.length > 1) { locs = locs.sort((loc1, loc2) => loc1.distance - loc2.distance); } else { locs = locs.sort((a) => a.name === LocationName ? -1 : 1); } primaryLocation = locs[0]; secondaryLocations = locs.length > 1 ? locs.slice(1) : false; }; /* If a practice group is present in the result, choose the proper text and create element HTML for it */ let practiceGroup = ""; if (PracticeGroup.length) { if (result["@search.highlights"] && result["@search.highlights"].PracticeGroup) { practiceGroup = /*html*/`<small class="practice-group">${result["@search.highlights"].PracticeGroup[0]}</small>`; }; }; /* We only want to display review/rating data for providers that have "PHS Reviews Eligible Providers" listed as a network */ let rating = ""; if (Networks?.includes('PHS Reviews Eligible Providers')) { /* Create star rating for provider, if rating is present */ if (Rating) { let ratingFull = ""; let lastStar = parseInt(Rating.toString().slice(2,3)) * 1.6; if(Rating > 4) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3"></span> <span class="full-s fs-4"></span> <span class="full-s fs-5" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 3) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3"></span> <span class="full-s fs-4" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 2) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 1) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } if(ReviewCount > 0) { rating = /*html*/`<div class="reviews hide-zero-average_rating${Rating}"> <span class="stars s${Rating}"> <span class="empty"> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> </span> <span class="full"> ${ratingFull} </span> </span> <span class="stats"> ${Math.round(Rating * 100) / 100} </span> <a class="survey providence" href="https://www.providence.org/about/our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> <a class="survey swedish" href="https://www.swedish.org/about/about-our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> </div> <div class="rating_comment_count"><span>${RatingCount} Ratings</span> | <span>${ReviewCount} Reviews</span></div>`; } else { rating = /*html*/`<div class="reviews hide-zero-average_rating${Rating}"> <span class="stars s${Rating}"> <span class="empty"> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> </span> <span class="full"> ${ratingFull} </span> </span> <span class="stats"> ${Math.round(Rating * 100) / 100} </span> <a class="survey" href="https://www.providence.org/about/our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> </div> <div class="rating_comment_count"><span>${RatingCount} Ratings</span></div>`; } } } /* If provider offers virtual visits, create virtual visit html */ let offersVirtual = ""; let virtualBadge = ""; if(VirtualCare && AcceptingNewPatients == 1) { AcceptingClinicVisits = ` <div class="vv-and-patients desktop-only"> <div class="virtualVisits"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div> <div class="new-patients mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg></span> <span class="anp">Accepting New Patients</span> </div> </div> <div class="virtualVisits mobile-only mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div> <div class="new-patients mobile-only mobile-map-hide"> <svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg> <span class="anp">Accepting New Patients</span> </div> `; } else if (VirtualCare) { offersVirtual = /*html*/ `<div class="virtualVisits"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div>`; virtualBadge = /*html*/`<div class="virtualVisits infoWindow mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span> </div>` }; /* If a distance from ZIP exists, include that in the provider card. */ let distanceEl = ""; if (distance) { distanceEl = /*html*/`<div class="distance">${parseFloat(Number(distance).toFixed(1))} miles away</div>`; }; let name = Name; if (Degrees.length) { name = `${Name}, ${Degrees.join(', ')}`; }; let providerLink = this.helpers.createProviderLink(result); let wheelhouseData = sessionStorage.getItem('wheelhouseData'); if (!wheelhouseData) { try { const wheelHouseDataRaw = document.querySelector('#wheelhouseJson'); wheelhouseData = wheelHouseDataRaw ? JSON.parse(wheelHouseDataRaw.innerText) : {}; } catch (error) { console.log("Couldn't get wheelhouse data"); } } else{ wheelhouseData = JSON.parse(wheelhouseData); } const locName = primaryLocation?.name ? primaryLocation.name : LocationName ? LocationName : ''; const providerPhone = primaryLocation?.phone ?? Phones[0]; if (/swedish/gi.test(location.origin) && result.ProviderUniqueUrlSwedish) { /* use kyruus profile URL as source of truth for link to to profile */ providerLink = result.ProviderUniqueUrlSwedish; /* some providers still have old profile URL per request from Anita, overwrite with correct www. URL */ if (providerLink.includes('schedule.swedish.org')) { providerLink = providerLink.replace('schedule.swedish.org', 'www.swedish.org'); } } if (!/virtual/gi.test(locName)) { withLocations += 1; }; /* Determine whether or not to display the 90 days fallback message when timeslots are not available */ const shouldShowAlert = () => { return false; /* - We don't care about followup appointments anymore, only new patient availability if (DaysUntilFollowUpAppt && DaysUntilFollowUpAppt >=0 && DaysUntilFollowUpAppt <= 90) { return true; } else */ if (DaysUntilNextAppt && DaysUntilNextAppt >=0 && DaysUntilNextAppt <= 90) { return true; } else { return false; } }; const wompTrackData = { ec: '', el: `${name}, ${i+1}`, ea: JSON.stringify(result['@search.features']) }; let medicalGroupsAndAffiliations = ''; /** <!-- childReplacement provOmniMedicalAffiliations --> **/ try { ProviderOrganization = ProviderOrganization.filter(org => org.toLowerCase() != 'providence affiliated provider'); if (ProviderOrganization.length === 1) { if (ProviderOrganization[0].toLowerCase() === 'providence') { medicalGroupsAndAffiliations = ''; } else { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[0]} </div> </div>`; } } else if (ProviderOrganization.length > 1) { if (ProviderOrganization[0].toLowerCase() === 'providence') { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[1]} </div> </div>`; } else { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[0]} </div> </div>`; } } } catch (ex) { console.log('Failed to display provider org: ' + ex); } /** <!-- end childReplacement provOmniMedicalAffiliations --> **/ let distanceSec = ""; try { if (distances) { distanceSec = /*html*/`<div class="distance">${parseFloat(Number(distances[0]).toFixed(1))} miles away</div>`; }; } catch (error) { console.log("Couldn't get distances"); } let brandedLocNames = ["Providence", "Jude Medical Center", "Mission Heritage", "Jude Heritage", "Joseph Heritage", "Mary High Desert", "Facey", "Joseph Hospital", "Santa Rosa Memorial", "Petaluma Valley Hospital", "Sacred Heart Medical", "Saint Johns Health Cancer", "Doctors of Saint", "Pavilion for Women", "Swedish", "Pacific Medical Centers", "Covenant", "Grace Clinics", "Grace Clinic", "Kadlec"]; let addPO = true; let dpo = ""; if(ProviderOrganization?.length > 0) { /** See provOmniMedicalAffiliations, interesting case where we use the second PO if there are 2 and the first is providence **/ if (ProviderOrganization.length > 1 && ProviderOrganization[0].toLowerCase() === 'providence') { dpo = `dpo-${ProviderOrganization[1].replace(/\s+/g, '')}`; } else { dpo = `dpo-${ProviderOrganization[0].replace(/\s+/g, '')}`; } for (var i = 0; i < brandedLocNames.length; i++) { if(locName.indexOf(brandedLocNames[i]) > -1){ addPO = false; break; } } } else { addPO = false; } let matchedLocName = locName; let matchedLocAdd = primaryLocation.address; try { if (secondaryLocations.length > 0){ for (let i = 0; i < secondaryLocations.length; i++) { /* check if secondary location matches what we are searching for */ if (this.searchParams.get('locationname').includes(secondaryLocations[i].name)) { let secondName = locName; let secondAdd = primaryLocation.address; matchedLocName = secondaryLocations[i].name; matchedLocAdd = secondaryLocations[i].address; secondaryLocations[i].name = secondName; secondaryLocations[i].address = secondAdd; } } } } catch (error) { console.log("couldn't find secondary location: " + error); } resultsHtml += /*html*/ `<div id="npi${Npi}" class="card card-body provider${addPO ? ` ${dpo}` : ''}" data-location="${LocationId}" data-npi="${Npi}" data-position="${i+1}" data-location-name="${locName}"> <div class="provider-image"> <a href="${providerLink}" class="provider-link"> <amp-img src="${ImageSourceUrl || ImageUrl}" alt="Image of ${name}" width="${ImageWidth ? width : 150}" height="150" layout="${ImageWidth ? "intrinsic" : "fill"}" ></amp-img> ${virtualBadge} </a> </div> <div class="provider-info"> <a href="${providerLink}" class="provider-link"> <h3 class="color-headings">${name}</h3> </a> <p class="resultDescription">${descr}</p> ${rating} ${offersVirtual} ${AcceptingClinicVisits} ${isTest && Tier ? `<div class="tier"><span>Tier: ${Tier}</span></div>` : ''} </div> <div class="locations-and-timeslots mobile-map-hide"> ${isTest && ProviderOrganization?.length ? '<div id="test__organizations"><h4>Organizations</h4>' + ProviderOrganization.reduce((html, curr) => html + `<p>${curr}</p>`, '') + '</div>' : '' } ${isTest && searchFeatures && Object.keys(searchFeatures).length ? '<div id="test__organizations"><h4>Search Features</h4>' + Object.keys(searchFeatures).reduce((html, key) => html + `<p>${key}: ${searchFeatures[key]?.similarityScore.toFixed(3)}</p>`, '') + '</div>' : '' } <div class="read-more-wrap"> <div class="location-name ${matchedLocName}"> <span class="hide-content"> <svg width="24" height="24" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#providence-icon"></use></svg> <svg width="24" height="24" class="swed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#swedish-icon"></use></svg> <svg width="16" height="24" class="pacmed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pacmed-icon"></use></svg> <svg width="24" height="24" class="cov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#covenant-icon"></use></svg> <svg width="24" height="24" class="grace-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grace-icon"></use></svg> <svg width="20" height="24" class="kadlec-svg" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#kadlec-icon"></use></svg> </span> <h4 class="location-n ${matchedLocName}"> ${matchedLocName} </h4> </div> <div class="address"> <div class="hov-ul">${primaryLocation?.address ? matchedLocAdd : ''}</div> ${distanceEl} </div> ${providerPhone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(providerPhone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(providerPhone, 'hyphenated')}">${formatPhoneNumber(providerPhone)}</a>` : '' } ${medicalGroupsAndAffiliations} <div class="badges">${badgesEls}</div> ${hasTimeslots ? /** <!-- childReplacement searchPageTimeslotsWidget --> **/ /*html*/` <directory-timeslots provider="${Npi}" provider-link="${providerLink}" address="${primaryLocation.address.split(',')[0] || ''}" staging="${this.isStaging}" days="${this.helpers.getAvailability(this.state)}" time="${this.state.filters.AvailableTime}" ></directory-timeslots>` /** <!-- end childReplacement searchPageTimeslotsWidget --> **/ : '' } </div> </div> <div class="remaining-data mobile-map-hide"> ${!onlineBooking && AppointmentRequestUrl?.length ? `<div> <a class="btn-primary r-consult" style="padding: 5px 15px 7px; display: inline-block; margin-top: 0.5rem;" href="${AppointmentRequestUrl}" target="_blank"> <small>Request a Consultation</small></a> </div>` : '' } ${!hasTimeslots && shouldShowAlert() ? /*html*/` <div> <span class="fp-return-patients mychart-wa-mt"> Return Patients <a href="https://mychartwa.providence.org/mychart/Authentication/Login" target="_top">schedule with MyChart</a> </span> </div>` : '' } <input type="checkbox" class="read-more-state" id="provider${Npi}" /> ${secondaryLocations.length ? /*html*/` <div class="read-more-target"> ${secondaryLocations.reduce((html, curr) => { return html + /*html*/ `<div class="additional-location" data-location-name="curr.name" data-location=""> <div class="location-name ${curr.name}"> <span class="hide-content"> <svg width="24" height="24" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#providence-icon"></use></svg> <svg width="24" height="24" class="swed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#swedish-icon"></use></svg> <svg width="16" height="24" class="pacmed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pacmed-icon"></use></svg> <svg width="24" height="24" class="cov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#covenant-icon"></use></svg> <svg width="24" height="24" class="grace-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grace-icon"></use></svg> <svg width="20" height="24" class="kadlec-svg" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#kadlec-icon"></use></svg> </span> <h4 class="location-n ${curr.name}"> ${curr.name} </h4> </div> <div class="address"> <div class="hov-ul">${curr.address}</div> </div> ${distanceSec} <span id="${Npi}-${curr.name}"> <div class="phone"> <small>${curr.phone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(curr.phone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(curr.phone, 'hyphenated')}">${formatPhoneNumber(curr.phone)}</a>` : ''}</small> </div> </span> </div>` }, '')} </div> ` : ''} ${secondaryLocations.length ? `<div> <label for="provider${Npi}" class="read-more-trigger"> <span class="loc-wrap"> <svg class="expand" width="14" height="14" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chev-down"></use></svg> <svg class="compress" width="14" height="2" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chev-up"></use></svg> </span> ${secondaryLocations.length} <span class="more-location">More</span> <span class="less-location">Less</span> Location${secondaryLocations.length === 1 ? '' : 's'} </label></div>` : ''} </div> </div>`; try { for (let i = 0; i < secondaryLocations.length; i++) { secondaryLocationIds.push(LocationIds[LocationNames.indexOf(secondaryLocations[i].name)]); }; } catch (error) { console.log(error); } }); } if (results.info.locations) { /* Check if clinic locations are stored as object or string */ let clinicNamesAlpha; if (results.info.locations.length && results.info.locations[0].name) { let greenListedLocs = ['providence', 'mission heritage', 'st. jude heritage', 'st. joseph heritage', 'st. mary high desert', 'facey', 'st. joseph hospital', 'st. jude medical center', 'covenant', 'swedish', 'pacific medical centers', 'kadlec', 'grace clinics', 'st. jude', 'grace clinic', 'oregon clinic']; document.CurrentClinics = results.info.locations.filter(loc => greenListedLocs.some(gLoc => loc.name.toLowerCase().includes(gLoc)) ); let alphaSortedClinics = document.CurrentClinics.sort((locA, locB) => { return locA.name.toLowerCase().localeCompare(locB.name.toLowerCase()); }); clinicNamesAlpha = alphaSortedClinics.map(loc => loc.name); } else { clinicNamesAlpha = results.info.locations; } document.querySelector('#location-selection').innerHTML = clinicNamesAlpha.reduce((html, name) => { return html + `<option value="${name}" ${name === this.state.filters.LocationName ? 'selected' : ''}>${name}</option>`; }, `<option class="clinic-value" value="">Select a Clinic</option>`); document.querySelector('#mobile-location-selection').innerHTML = clinicNamesAlpha.reduce((html, name) => { return html + `<li><a tabindex="0" class="dropdown-item dc facet-value loc-sel">${name}</a></li>`; }, `<li><a tabindex="0" class="dropdown-item dc facet-value loc-sel">Select a Clinic</a></li>`); } else { document.querySelector('#custom-location + small.dc')?.remove(); document.querySelector('#location-selection')?.remove(); document.querySelector('#mobile-location-selection')?.remove(); } /* We want to make sure the corrected text from search response is always in the search box. If there's been a spelling correction, update the input value */ let decodedQ = query; try { decodedQ = decodeURIComponent(query); } catch { console.log(`Search query "${query}" is not a valid URI.`); } if (decodedQ !== results.info.search) { resultsHtml = /*html*/ `<div> <h5 class="results-text">Showing results for <strong>${results.info.search}</strong></h5> </div> ${resultsHtml}`; this.state.query = results.info.search; this.input.value = results.info.search; }; if (!decodedQ?.length) { this.input.value = ""; } if (results.info.any) { resultsHtml = /*html*/ `<div> <h5 class="results-text" data-no-search-results>There are no exact matches for ${ query.length ? `"${decodeURIComponent(this.state.query)}"` : "your search" }. Here are some similar results:</h5> </div> ${resultsHtml}`; }; /* If an exclude search keyword was used */ if (this.state.excludeSearch) { resultsHtml = /*html*/ `<div> <h5 class="results-text" data-no-search-results>There are no results for this search. Here are some similar results:</h5> </div> ${resultsHtml}`; } /* Update the HTML for our results container to show new results */ this.resultsEl.innerHTML = resultsHtml; try { let addLocs = document.getElementsByClassName("additional-location"); for (let i = 0; i < addLocs.length; i++) { addLocs[i].setAttribute("data-location", secondaryLocationIds[i]); } } catch (error) { console.log("Couldn't add data-location to additional location: " + error); } if (LocationsOnly || withLocations > 0 ) { this.map.toggleVirtual(false); } else { this.map.toggleVirtual(true); } }) .then(() => { if (this.state.filters.visitType) { queryParams += `&visittypes=${Object.keys(this.state.filters.visitType).join(',')}`; }; /* Set show the proper filters as having been selected */ this.displayFilters(); this.map.createMarkers(); this.helpers.pushHistory(queryParams); }) .then(async () => { const res = this.state.results; /* Query secondary locations for timeslot availability */ /* Populate array with KyruusId-LocationIds */ const parsedIds = []; /* Check each provider for multiple locations & create necessary timeslots */ for(let i = 0; i < res.results.length; i++) { let promises = []; const { LocationIds, LocationId, KyruusId } = res.results[i]; if(LocationIds.length > 1) { /* If more than 2 locations, loop through each location and check for availability */ for(let j = 0; j < LocationIds.length; j++) { /* Skip primary location because its timeslot data was already returned from omni */ if(LocationId === LocationIds[j]) { continue; } else { parsedIds.push(`${KyruusId}-${LocationIds[j]}`); } } } } const encodedIds = encodeURIComponent(parsedIds?.join(',')); const whDataUrl = `https://${this.apiSub}.azurewebsites.net/api/WompHealthData?`; const selectFields = [ "NewClinicVisit", "NewVideoVisit", "DaysUntilNextAppt", "Npi", "LocationName", "ProfileUrl", "ProviderUniqueUrlSwedish", "ProviderUniqueUrlPacmed", "ProviderUniqueUrlOnesite", "Phones" ]; const encodedFields = encodeURIComponent(selectFields.join(',')); /* parsedIds?.length && uses short-circuit evalutaion to skip the fetch call if ids is null or not an array for any reason */ parsedIds?.length && fetch(`${whDataUrl}&brand=${brand}&data=providers&ids=${encodedIds}&select=${encodedFields}`).then((res) => res.json()).then(res => { res.forEach((prov, i) => { if (!prov) { console.debug(`Location information not available for id=${parsedIds[i]}`); return; } const hasTimeslots = this.helpers.checkForTimeslots(prov.NewClinicVisit, prov.NewVideoVisit, prov.DaysUntilNextAppt); if (hasTimeslots) { const providerLink = this.helpers.createProviderLink(prov); const locTimeslotWrapper = document.getElementById(prov.Npi + "-" + prov.LocationName); if (locTimeslotWrapper) { locTimeslotWrapper.innerHTML = /*html*/` <timeslots-widget phone="${this.helpers.formatPhoneNumber(prov.Phones[0])}" provider="${prov.Npi}" type="${this.state.slotsType}" color="#3572A9" days-ahead="30" more-appointments-url="${providerLink}#aptType" ></timeslots-widget> `; } } }); }).catch(err => { console.error(err); }); fireInvoca(); }); }; renderLocations = (locations) => { if(!Array.isArray(locations)) { throw new Error(`Expected array, got ${typeof locations}`); } let resultHTML = ""; locations.forEach(location => { if(location.type !== "location") { throw new Error(`Expected location, got ${location.type ?? "<no type>"}`) } const { ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, PrimarySpecialties, ProviderTitle, Addresses, Address, PracticeGroup, LocationNames, LocationName, Rating, virtual, distance, Npi, id, clinicVisits, NewClinicVisit, NewVideoVisit, GeocodedCoordinate, RatingCount, Degrees, Phones, ProfileUrl, AcceptingNewPatients, VirtualCare, Phone } = location; const { formatPhoneNumber } = this.helpers; /* If we have badges for matched, create element HTML for it */ const badges = location.badges ? Object.values(location.badges) : []; let badgesEls = badges.reduce((acc, badge) => acc + `<span>${badge}</span>`, ""); /* TODO map the locations better */ if (!this.map.locations[id]) { this.map.locations[id] = []; } /* Determine the image to show for the location */ let locationImageSrc = 'https://az690879.vo.msecnd.net/7772/clinic-placeholder.jpg'; if (ImageSourceUrl?.length) { locationImageSrc = ImageSourceUrl; } else if (ImageUrl?.length) { locationImageSrc = ImageUrl; } /* Build the card for the location */ resultHTML += ` <div id=${id} class="card card-body" data-location="${id}"> <div class="provider-image"> <a href="#"> <amp-img src="${locationImageSrc}" width="120" height="120"></amp-img> </a> </div> <div class="provider-info"> <a href="#"> <h3 class="color-headings">${Name}</h3> </a> <div class="read-more-wrap"> <div class="address"> <div class="hov-ul">${Address}</div> </div> ${Phone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(Phone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(Phone, 'hyphenated')}">${formatPhoneNumber(Phone)}</a>` : ''} </div> <div class="badges">${badgesEls}</div> </div> </div> `; }); return resultHTML; }; getLocation = (e) => { /** <!-- childReplacement provOmniGeoLocatorLoadingLocation --> **/ const headerLocationLabel = document.querySelector('#geoLocatorLabel span'); headerLocationLabel.innerHTML = 'Detecting...'; /** <!-- end childReplacement provOmniGeoLocatorLoadingLocation --> **/ const LocationLabel = document.querySelector("#LocationLabel"); LocationLabel.innerHTML = `Location: Detecting... <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(siteSearch.helpers.showPosition, siteSearch.helpers.showError, { timeout: 10000 }); }; }; createClickListener = async () => { const { trackWompEvent } = this.helpers; document.body.addEventListener("click", async (e) => { const clearActiveDropdowns = () => document .querySelectorAll(".dropdown, #searchBox") .forEach((drop) => drop.classList.remove("active")); const el = e.target; const parent = el.parentNode; const { filters, query } = this.state; /* If this is an autocomplete... */ if (parent.id == "autocomplete") { this.input.value = el.innerText; this.state.query = el.innerText; this.handleSubmit(); return; }; if (el.id == "LocationLabel") { setTimeout(() => { if (document.querySelector('.dropdown.location.active')) { const el = document.querySelector('#custom-location'); el.select(); } }, 100); } /** <!-- childReplacement provOmniGeoLocatorClickEvent --> **/ /* Providence specific geolocation toggle in the top nav */ if (el.id == 'geoLocatorInput') { document.querySelector('#geoLocatorSuggestions').style.display = 'block'; el.select(); } if (el.id == 'geoLocatorClear') { document.querySelector('#geoLocatorSuggestions').style.display = 'none'; if(window.innerWidth < 1000) { document.querySelector('main').style.setProperty('z-index','auto'); } } /** <!-- end childReplacement provOmniGeoLocatorClickEvent --> **/ /* If user is clicking the 'update location' button then... */ if (el.id == "updateLocation") { const locNameVal = document.querySelector('#location-selection').value; if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } const searchRadiusVal = document.querySelector('#searchRadius').value; /* ∞ */ if (searchRadiusVal?.length) { this.state.filters.distance = searchRadiusVal; } else { this.state.filters.distance = ''; } this.helpers.getGoogleLocation(); }; if(el.classList.contains("search-clear-label")) { document.querySelector("#search-input").value = ""; document.querySelector("#search-link").href= "https://www.providence.org/search-results"; } if (el.classList.contains("visitType")) { const type = el.dataset.type; const btnEls = document.querySelectorAll(".visitType"); let submitViaOnlineScheduling = false; switch (type) { case "all": /* If something other than 'all' is selected... */ if (filters.visitType) { this.state.filters.visitType = false; btnEls.forEach((el) => el.classList.remove("active")); el.classList.add("active"); break; /* else, just exit the function */ } else { return; } default: /* Remove the active stage on 'All' */ btnEls[0].classList.remove('active'); /* If we don't have an active filter, create the object */ if (!filters.visitType) { filters.visitType = {}; } if (type == "onlineScheduling") { /* Set the value for this visit type to the opposite of previous value */ filters.visitType["clinicVisits"] = { active: !filters.visitType[type]?.active }; filters.visitType[type] = { active: !filters.visitType[type]?.active }; /* Toggle the active state */ el.classList.toggle("active"); if (filters.visitType[type].active != document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.contains('active')) { document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.toggle("active"); } document.querySelectorAll("#daysList a").forEach(day => { siteSearch.updateDaySelectorStateAllDays(day, filters.visitType[type].active) }); submitViaOnlineScheduling = true; } else { /* Set the value for this visit type to the opposite of previous value */ filters.visitType[type] = { active: !filters.visitType[type]?.active }; /* Toggle the active state */ el.classList.toggle("active"); } /* Highlight 'All' if both video and clinic visits are unselected */ if (Object.keys(filters.visitType).every(key => filters.visitType[key]?.active == false)) { btnEls[0].classList.add('active'); } break; }; try { if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log(error); } if (submitViaOnlineScheduling) { this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); } else { this.handleSubmit(); } }; /* Don't close the dropdown when clicking in a dropdown menu or input */ if(el.classList.contains("dropdown-menu") || el.id === "insuranceInput" || el.id === "specialtyInput") { return; } /* If we're clicking the body of the document and a dropdown is active, remove active state */ if ( document.querySelector(".dropdown.active") && !( el.closest('.dropdown') || parent.classList.contains("dropdown") || el.classList.contains("dropdown-item") || el.classList.contains("dc") ) ) { clearActiveDropdowns(); }; /* If we're clicking the body of the document and search is active, remove active state */ if(document.querySelector("#searchBox.active")&& !(el.closest('#searchBox') || parent.parentNode.id === 'searchBox')) { document.querySelector("#searchBox.active").classList.remove("active"); } /* If clicking in a dropdown... */ if (parent.classList.contains("dropdown")) { if (!parent.classList.contains("active")) { clearActiveDropdowns(); el.parentNode.classList.add("active"); /* Auto focus on the input element if one exists */ const toggleInput = el.parentNode.querySelector("input"); if (toggleInput) { toggleInput.focus(); } } else { el.parentNode.classList.remove("active"); }; return; /* This prevents further logic from running unnecessarily */ }; /* If clicking in an element inside a dropdown... */ if (parent.classList.contains("dropdown-toggle")) { if (!parent.parentNode.classList.contains("active")) { clearActiveDropdowns(); parent.parentNode.classList.add("active"); } else { parent.parentNode.classList.remove("active"); }; return; /* This prevents further logic from running unnecessarily */ }; /* If clicking the search bar */ if (parent.classList.contains("input-hover")) { if (!parent.parentNode.classList.contains("active")) { clearActiveDropdowns(); parent.parentNode.classList.add("active"); } else { parent.parentNode.classList.remove("active"); } } if(el.id === 'location-selection') { let locNameVal = document.querySelector('#location-selection').value; if(locNameVal !== this.state.filters.LocationName && locNameVal !== "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } else if(el.id === 'locationSelection') { let locNameVal = document.querySelector('#locationSelection').value; if(locNameVal !== this.state.filters.LocationName && locNameVal === "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } /* Mobile location selection for the clinic selection */ if(el.classList.contains("loc-sel")) { let locNameVal = el.text; if(locNameVal !== this.state.filters.LocationName && locNameVal !== "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } /* Handle dropdown logic */ if (el.classList.contains("facet-value")) { if(el.classList.contains("loc-sel")) { if(el.text !== "Select a Clinic") { document.getElementById("location-selection").value = el.text; }else { document.getElementById("location-selection").value = ""; } document.getElementById("locationSelectionText").textContent = el.text; document.getElementById("locationSelectionInput").checked = false; return; } const toggleEl = el.closest('.dropdown.facet, .dropdown.sort'); const filterID = toggleEl.id; const isActive = el.classList.contains("active"); if (filterID === "sortby") { if (el.innerText == "Relevance") { this.state.filters.sortby = false; } else { this.state.filters.sortby = el.dataset.sort; }; toggleEl.querySelector( "button" ).innerHTML = `Sort by: ${el.innerText} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; toggleEl.classList.remove("active"); trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); this.handleSubmit(); return; }; if (filterID === "organization") { this.state.brand = el.dataset.organization; this.state.filters.brand = this.state.brand; toggleEl.querySelector( "button" ).innerHTML = `Organization: ${el.innerText} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; toggleEl.classList.remove("active"); trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); this.handleSubmit(); return; }; if (isActive) { /* If this was autoapplied, we save the text string that matched the filter and remove it from the query when clicked */ if (this.state.filters[filterID][el.innerText].text) { let theRegEx = new RegExp(this.state.filters[filterID][el.innerText].text, "gi"); /* Update our session storage to keep track of our previous query */ window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } this.state.filters[filterID][el.innerText] = { active: false }; } else { this.state.filters[filterID][el.innerText] = { active: true }; trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); el.classList.add("active"); }; this.handleSubmit(); }; /* Clear filters click handling */ if (el.id == "clearFilters") { let clearInsurancePromise = fetch("https://www.providence.org/integrationapi/clearinsuranceselection"); let searchRadiusEl = document.querySelector('#searchRadius'); searchRadiusEl.value = ''; const filtersWrapper = document.getElementById("filterWrapper"); /* Remove user entered queries from state and the input el */ this.state.query = ""; this.input.value = ""; this.state.specificDays = false; /* Clear filters */ this.state.filters = { ...this.state.filters, Gender: {}, InsuranceAccepted: {}, Languages: {}, PrimarySpecialties: {}, /** <!-- childReplacement providerOrgClearFilter --> **/ ProviderOrganization: {}, /** <!-- end childReplacement providerOrgClearFilter --> **/ page: 1, LocationName: false, PracticeGroup: '', LocationId: '', distance: '', location: false, AvailableDays: { Sunday: { active: false }, Monday: { active: false }, Tuesday: { active: false }, Wednesday: { active: false }, Thursday: { active: false }, Friday: { active: false }, Saturday: { active: false }, }, AvailableTime: "any", visitType: false }; this.state.currentLoc = false; /* Remove Query Params*/ let url = new URL(window.location); url.search = ""; if (history.pushState) { window.history.replaceState({}, "Find a Doctor", url); this.state.pushHistory = true; } /* Clear localStorage of retained insurance and location filters */ localStorage.removeItem('omniSearchInsurance'); localStorage.removeItem('omniSearchLocation'); document.querySelector("#custom-location").value = ''; this.populateDaytimeSelectorFromState(); Promise.allSettled([clearInsurancePromise]).then(()=>this.handleSubmit()); filtersWrapper.classList.remove("hasFilter"); }; /* Pagination clicks */ if (e.target.classList.contains("page-link")) { this.state.filters.page = e.target.dataset.page; }; /* Geolcation clicks */ if (e.target.classList.contains("useLocation")) { const isOn = document.querySelector('#switch')?.checked; if (!isOn) { this.getLocation(e); } else { this.helpers.getGoogleLocation(); } this.state.useMyLocationClicked = true; return; }; /* toggle between sorting clinics in location dropdown by alpha or distance */ if (e.target.classList.contains("distanceSort")) { const isOn = document.querySelector('#switchDistanceSort')?.checked; let clinicNames; if (!isOn) { if (document.CurrentClinics.length && document.CurrentClinics[0].distance) { clinicNames = document.CurrentClinics.sort((locA, locB) => locA.distance - locB.distance).map(loc => loc.name) } else { clinicNames = document.CurrentClinics; } } else { if (document.CurrentClinics.length && document.CurrentClinics[0].name) { let sortedClinics = document.CurrentClinics.sort((locA, locB) => { return locA.name.toLowerCase().localeCompare(locB.name.toLowerCase()); }); clinicNames = sortedClinics.map(loc => loc.name); } else { clinicNames = document.CurrentClinics; } } document.querySelector('#location-selection').innerHTML = clinicNames.reduce((html, name) => { return html + `<option value="${name}">${name}</option>`; }, `<option value="">--Select a clinic</option>`); return; } /* Map clicks */ if (e.target.classList.contains('gm-ui-hover-effect') && e.target.tagName == "BUTTON") { const selectedNpi = document.querySelector(`#npi${this.map.selectedNpi}`); if (selectedNpi) { selectedNpi.classList.remove('selected'); }; if (this.map.selectedMarker) { this.map.resetMarkerIcon(); }; }; /* Provider card clicks */ var setInfoWindow = el => { var { location, npi } = el.dataset; let additionalLoc = ""; /* Provider card click - Additional Location */ if (e.target.classList.contains("additional-location")) { additionalLoc = e.target.getAttribute("data-location"); }else if(e.target.parentElement.classList.contains("additional-location")) { additionalLoc = e.target.parentElement.getAttribute("data-location"); }else if(e.target.parentElement.parentElement.classList.contains("additional-location")) { additionalLoc = e.target.parentElement.parentElement.getAttribute("data-location"); } if(additionalLoc) {location = additionalLoc;} var id = this.map.lookup[location]; google.maps.event.trigger(this.map.markers[id], 'click'); this.map.selectProvider(npi); }; const closestEl = e.target.closest('.card.provider'); if (closestEl) { if (e.target.tagName !== "TIMESLOTS-WIDGET" && e.target.tagName !== "DIRECTORY-TIMESLOTS" && !e.target.closest('a')) { setInfoWindow(closestEl); } const linkEl = e.target.closest('a'); if (linkEl) { const index = Number(closestEl.dataset.position); const provider = this.state.results.providers[index - 1]; const hasTimeslots = closestEl.querySelector('timeslots-widget') ? 1 : 0; const defaultVals = { ea: `${provider.Name}, ${index}, timeslots: ${hasTimeslots}`, el: JSON.stringify(provider['@search.features']) }; if (linkEl.classList.contains('provider-link')) { /* Prevent routing until analytics tracking finishes */ e.preventDefault(); /* Send beacon */ await this.helpers.trackWompEvent({ ec: 'Provider Click', ...defaultVals }) .catch(err => console.error(err)); /* Route to link */ window.location = linkEl.href; } if (linkEl.classList.contains('phoneLink')) { this.helpers.trackWompEvent({ ec: "Direct Contact", ea: "Phone Click-to-Call", el: linkEl.dataset.varsPhoneLink }); this.helpers.trackWompEvent({ ec: 'Provider Click-To-Call', ...defaultVals }) } } } }); }; createChangeListeners = () => { this.showMapEl.addEventListener('change', e => { if (e.target.checked) { this.map.el.fitBounds(this.map.bounds); } else { /* allows amp-imgs to load when map view is toggled off after a search was performed and elements are scrolled into view vertically after being outside of the viewport horizontally */ this.helpers.autoNudgeAmpElements(true); } }); }; displayFilters = () => { const { Gender, Languages, InsuranceAccepted, PrimarySpecialties, visitType, brand, location, sortby, locationType, distance } = this.state.filters; /** <!-- childReplacement providerOrgGetDisplayFilter --> **/ let ProviderOrganization = this.state.filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetDisplayFilter --> **/ const { geoip, info } = this.state.results; /* Grab the object for the filterable facets */ let facets = this.state.facets; const facetSet = { Gender, Languages, InsuranceAccepted, distance, /** <!-- childReplacement providerOrgFacetSet --> **/ ProviderOrganization, /** <!-- end childReplacement providerOrgFacetSet --> **/ PrimarySpecialties }; /* Listen for input changes inside of dropdowns and filter list items based on the input value */ function createFilterInputListener(facet, keys, input, list) { const filterInput = document.getElementById(input); const filterList = document.getElementById(list); filterInput.addEventListener('input', e => { const regex = new RegExp(e.target.value, 'i'); let active = 0; let filteredHtml = ''; let total = 0; for(let key of keys) { let isActive = false; if (facet[key]?.active === true) { active++; isActive = true; }; if (!regex.test(key)) continue; total++; filteredHtml += /*html*/ `<li> <a class="dropdown-item dc facet-value${isActive ? " active" : ""}" ><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } /* Hide the list when no list items match input */ filterList.style.display = total > 0 ? "block" : "none"; filterList.innerHTML = filteredHtml; }); fireInvoca(); } /* We'll use this to remove auto-applied filters at search time */ const autoApplied = []; /* Iterate over the facets and set filter displays */ try{ for (let facet in facetSet) { /* We want to make sure that the facet exists in the facets obj before proceeding */ if (facets[facet]) { facets[facet].forEach((val) => { const filter = info.facets[facet]; let isSelected = false; if (filter && filter.includes(val)) { isSelected = true; }; this.state.filters[facet][val] = { ...this.state.filters[facet][val], active: isSelected }; }); } }; } catch { console.log(`Facets failed to load`); } for (const f of info.filters) { const { facets: fs } = f; if (f.text?.length && fs && fs.length) { console.log(f); fs.forEach(filter => { autoApplied.push({ name: filter.name, value: filter.value }); }); } } /* Update autoapplied filters in state */ this.state.autoApplied = autoApplied; /* For each of the filter dropdowns we've added, update the UL to show the available filters */ document.querySelectorAll(".dropdown.facet").forEach((dropdown) => { const facet = this.state.filters[dropdown.id]; if (facet) { /* Use blocklists to explicitly remove specific filter values For example, providence requested we remove multiple provider orgs from the "Medical Group" dropdown */ let blocklists = { 'ProviderOrganization': [ "Providence Affiliated Physicians", " Los Angeles County", " Mission", " St. Joseph", " St. Jude", " St. Mary", /** <!-- childReplacement provomniProviderOrgBlocklist --> **/ 'Providence', 'Providence Affiliated Physicians, Los Angeles', 'Axminster Medical Group & Providence Care Network Providers', 'Covenant Health Partners Affiliated', 'Covenant Rural Health Clinics', 'Facey Affiliated Provider', 'PacMed AdvantAge Health Center', 'Providence Affiliated Provider', 'Providence Medical Group', 'Providence Medical Institute', 'Saint John\'s Physician Partners Affiliated Providers', 'St. Joseph Health Medical Group', 'Providence Care Network', 'Providence Medical Associates' /** <!-- end childReplacement provomniProviderOrgBlocklist --> **/ ] }; let isFiltered = false; let html = "<ul>"; let keys = Object.keys(facet); keys = keys.sort(); let active = 0; /* Add a text input and a list id to lists which can be filtered by text */ if(dropdown.id === "InsuranceAccepted") { html = /*html*/ `<div class="search-wrap"> <button id="insuranceInputClear" class="hidden" onclick="clearInput('insuranceInput')"></button> <label for="insuranceInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='insuranceInput' placeholder='Search insurance' /> <label for="insuranceInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='insuranceList'> ` } else if(dropdown.id === "PrimarySpecialties") { html = /*html*/ `<div class="search-wrap"> <button id="specialtyInputClear" class="hidden" onclick="clearInput('specialtyInput')"></button> <label for="specialtyInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='specialtyInput' placeholder='Search specialty' /> <label for="specialtyInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='specialtiesList'>` } else if(dropdown.id === "ProviderOrganization") { html = /*html*/ `<div class="search-wrap"> <button id="organizationInputClear" class="hidden" onclick="clearInput('organizationInput')"></button> <label for="organizationInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='organizationInput' placeholder='Search medical group' /> <label for="organizationInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='organizationList'>` } else if(dropdown.id === "Languages") { html = /*html*/ `<div class="search-wrap"> <button id="languageInputClear" class="hidden" onclick="clearInput('languageInput')"></button> <label for="languageInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='languageInput' placeholder='Search languages' /> <label for="languageInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='languagesList'>` } /* If we have a blocklist for our filter, then remove blocked values before generating UI */ if (dropdown.id in blocklists) { let allowedKeys = keys.filter(function(key) { return !blocklists[dropdown.id].includes(key); }); keys = allowedKeys; } for (let key of keys) { let isActive = false; if (facet[key]?.active === true) { active++; isActive = true; }; if(dropdown.id ==="Gender"){ const genderVals = ["Female", "Male", "Non-binary"]; if(genderVals.includes(key)) { html += /*html*/ `<li> <a tabindex="0" class="dropdown-item dc facet-value${isActive ? " active" : ""}"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } } else { html += /*html*/ `<li> <a tabindex="0" class="dropdown-item dc facet-value${isActive ? " active" : ""}"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } }; html += '</ul>'; dropdown.lastElementChild.innerHTML = html; if (dropdown.id === "InsuranceAccepted") { createFilterInputListener(facet, keys, "insuranceInput", "insuranceList"); } else if(dropdown.id === "PrimarySpecialties") { createFilterInputListener(facet, keys, "specialtyInput", "specialtiesList"); } else if(dropdown.id === "ProviderOrganization") { createFilterInputListener(facet, keys, "organizationInput", "organizationList"); } else if(dropdown.id === "Languages") { createFilterInputListener(facet, keys, "languageInput", "languagesList"); } if (active > 0) { dropdown.querySelector("button").classList.add("filtered"); } else { dropdown.querySelector("button").classList.remove("filtered"); }; } }); /* Set query in search box */ if (this.state.query.length) { document.querySelector("#query").value = decodeURIComponent( this.state.query ); }; /* Set location information in location dropdown */ if (this.state.currentLoc?.length) { try { document.querySelector( "#LocationLabel" ).innerHTML = `Location: ${this.state.currentLoc} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; document.querySelector( 'input[name="location"]' ).value = this.state.currentLoc; /** <!-- childReplacement provOmniGeoLocatorInputUpdate --> **/ try { this.textInputs.forEach(input => { if (['custom-location'].includes(input.id)) { if(document.querySelector('#geoLocatorLabel span').textContent.indexOf(this.state.currentLoc) > -1) { input.value = document.querySelector('#geoLocatorLabel span').textContent; }else { input.value = this.state.currentLoc; } } if (['geoLocatorInput'].includes(input.id)) { checkLoginStatus(); } }); } catch (ex) { console.log(ex); } /** <!-- end childReplacement provOmniGeoLocatorInputUpdate --> **/ } catch (err) { console.error(err); } } /* Set the visit type toggle */ document.querySelectorAll('#visitTypeFilters button').forEach(button => { const type = button.dataset.type; const { visitType } = this.state.filters; if (!visitType) { type === 'all' ? button.classList.add('active') : button.classList.remove('active'); } else { type === 'all' ? button.classList.remove('active') : visitType[type]?.active ? button.classList.add('active') : button.classList.remove('active'); } }); }; paginate = () => { const { filters } = this.state; const { pages, page } = filters; let start; let html = ``; if(pages === 1 && page === pages) { html = /*html*/`<button id="queryClear" class="hidden" onclick="clearInput('query')"></button> <div class="btn-group single-page" role="group" aria-label="Results pages">`; } else { html = /*html*/`<button id="queryClear" class="hidden" onclick="clearInput('query')"></button> <div class="btn-group" role="group" aria-label="Results pages">`; } /* Let's make sure we display 5 results any time there are 5 or more pages */ if (page <= 4) { start = 1; } else if (pages - page < 4) { start = page - (4 - (pages - page)); } else { start = page - 2; }; const firstPage = new URL(document.location); firstPage.searchParams.set('page', 1); var nextPageUrl = new URL(document.location); var previousPageUrl = new URL(document.location); try { nextPageUrl.searchParams.set('page', filters.page + 1); previousPageUrl.searchParams.set('page', filters.page - 1); } catch (error) { console.log("Error setting pagination"); nextPageUrl.searchParams.set('page', 1); previousPageUrl.searchParams.set('page', 1); } if (start > 1) { html += start > 2 ? /*html*/ `<a href="${previousPageUrl.href}" class="previous-page"> <svg width="12" height="22" class="page-svg arrow-left-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-left-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-inactive"></use></svg> </a> <a class="btn page-link" data-page="1" href="${firstPage.href}">1</a> <span class="btn page-link ellipsis ellipsis-prev">...</span>` : ``; }; const ithPage = new URL(document.location); let mobileHide = ""; for (let i = start; i <= start + 4; i++) { if(pages > 4 && i > start + 1 && i < start + 4) { mobileHide = " mobile-hide" } else { mobileHide = ""; } if (i > pages) break; ithPage.searchParams.set('page', i); if(i === 1) { html += /*html*/ `<a href="${previousPageUrl.href}" class="previous-page"> <svg width="12" height="22" class="page-svg arrow-left-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-left-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-inactive"></use></svg> </a>`; } html += /*html*/ `<a class="btn${page == i ? " active" : ""} page-link ${mobileHide}" data-page="${i}" href="${ithPage.href}">${i}</a>`; if (i == start + 4 && i < pages) { ithPage.searchParams.set('page', pages); html += /*html*/ `<span class="btn page-link ellipsis">...</span>`; if (i < pages + 1) { ithPage.searchParams.set('page', pages); html += /*html*/`<a class="btn page-link${mobileHide} no-m" data-page="${pages}" href="${ithPage.href}">${pages}</a>`; }; }; }; html += `<a href="${nextPageUrl.href}" class="next-page"> <svg width="12" height="22" class="page-svg arrow-right-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-right-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-right-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-right-inactive"></use></svg> </a></div>`; this.paginationEl.innerHTML = html; this.paginationEl.style.display = "flex"; try { this.paginationEl.classList.remove("first-page"); } catch (error) { } try { this.paginationEl.classList.remove("last-page"); } catch (error) { } try { this.paginationEl.classList.remove("no-pages"); } catch (error) { } if(pages === 0) { this.paginationEl.classList.add(`no-pages`); this.paginationEl.style.display = "none"; } else if(filters.page === pages){ this.paginationEl.classList.add(`last-page`); } else if(filters.page === 1) { this.paginationEl.classList.add(`first-page`); } try { let windowWidth = window.innerWidth; if(windowWidth <= 960) { let top = document.getElementById('results-wrapper').offsetHeight + 55; this.mapEl.style.height = `calc(100vh - ${top}px)`; } } catch (error) { console.log(error); } }; addWindowListeners = () => { window.addEventListener('resize', this.helpers.debounce(() => { return; const w = window.innerWidth; if (w < 1000) { if (this.mapEl.style.top !== '78px') { this.logo.style.width = '85px'; this.logo.style.height = '50px'; this.mapEl.style.top = '78px'; this.mapEl.style.height = 'calc(100vh - 78px)'; }; } else { const h = document.querySelector('header'); this.logo.style.width = '170px'; this.logo.style.height = '99px'; this.mapEl.style.top = `${h.clientHeight}px`; this.mapEl.style.height = `calc(100vh - ${h.clientHeight}px)`; }; }, 100)); window.onpopstate = (event) => { this.searchParams = prepURLSearchParams(); this.init(); }; window.addEventListener('scroll', () => this.map.setOffset()); }; populateTimeFromState = () => { const activeDays = Object.entries(this.state.filters.AvailableDays).filter(x => this.state.specificDays && x[1].active).map(x => x[0]); let labelText = "Availability "; const label = document.querySelector("#AvailabilityLabel"); if(activeDays.length) { if(activeDays.length !== 7) { labelText += ": " + activeDays.map(day => { switch(day) { case "Monday": return "M"; case "Tuesday": return "T"; case "Wednesday": return "W"; case "Thursday": return "Th"; case "Friday": return "F"; case "Saturday": return "S"; case "Sunday": return "Su"; default: return ""; } }).join(", ") + " "; } } if(this.state.filters.AvailableTime !== "any") { let time = this.state.filters.AvailableTime.split(""); time[0] = time[0].toUpperCase(); labelText += ": " + time.join("") + " "; } labelText += `<span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; label.innerHTML = labelText; }; populateDaytimeSelectorFromState = () => { const timeSelector = document.querySelector("#time-selector"); const daySelector = document.querySelector("#day-selector"); const checkboxes = document.querySelector("#availability-days-checkboxes"); const dayCheckboxes = document.querySelectorAll(".day-checkbox"); timeSelector.value = this.state.filters.AvailableTime; if(this.state.specificDays) { daySelector.value = "specific-day"; checkboxes.classList.remove("hidden"); } else { daySelector.value = "any-day"; checkboxes.classList.add("hidden"); } dayCheckboxes.forEach(checkbox => { const state = this.state.filters.AvailableDays[checkbox.innerHTML].active; if(!state) { checkbox.classList.remove("active"); } else { checkbox.classList.add("active"); } }); const activeDays = Object.entries(this.state.filters.AvailableDays).filter(x => this.state.specificDays && x[1].active).map(x => x[0]); let labelText = "Availability "; const label = document.querySelector("#AvailabilityLabel"); if(activeDays.length) { if(activeDays.length !== 7) { labelText += ": " + activeDays.map(day => { switch(day) { case "Monday": return "M"; case "Tuesday": return "T"; case "Wednesday": return "W"; case "Thursday": return "Th"; case "Friday": return "F"; case "Saturday": return "S"; case "Sunday": return "Su"; default: return ""; } }).join(", ") + " "; } } if(this.state.filters.AvailableTime !== "any") { let time = this.state.filters.AvailableTime.split(""); time[0] = time[0].toUpperCase(); labelText += ": " + time.join("") + " "; } labelText += `<span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; label.innerHTML = labelText; }; updateTimeSelectorState = (time) => { document.querySelectorAll("#timesList .active").forEach(e => { e.classList.remove("active"); }); time.classList.add("active"); this.state.filters.AvailableTime = time.id; this.populateTimeFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; updateDaySelectorState = (day) => { let autoChecked = false; if(day.classList.contains("active") && day.id !== "any-day"){ this.state.filters.AvailableDays[day.id].active = false; } if(!day.classList.contains("active")){ day.classList.add("active"); if(day.id !== "any-day") { this.state.filters.AvailableDays[day.id].active = true; } } else if (day.id !== "any-day") { day.classList.remove("active"); /* If this was the last checked box being unchecked, we need to automatically check the "any days" box */ if (!document.querySelectorAll("#daysList .day.active").length) { try { document.querySelector("#any-day").classList.add("active"); this.state.specificDays = false; autoChecked = true; } catch (error) { console.log(error); } } } if (!autoChecked) { if (day.id !== "any-day") { this.state.specificDays = true; try { document.querySelector("#any-day").classList.remove("active"); } catch (error) { console.log(error); } } else { this.state.specificDays = false; document.querySelectorAll("#daysList .day.active").forEach(e => { e.classList.remove("active"); }); this.state.filters.AvailableDays["Monday"].active = false; this.state.filters.AvailableDays["Tuesday"].active = false; this.state.filters.AvailableDays["Wednesday"].active = false; this.state.filters.AvailableDays["Thursday"].active = false; this.state.filters.AvailableDays["Friday"].active = false; } } this.populateTimeFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; updateDaySelectorStateAllDays = (day, check) => { if(day.id == "any-day") { if (check && day.classList.contains("active")) { this.state.specificDays = true; day.classList.remove("active"); } else { if (!check && !day.classList.contains("active")) { this.state.specificDays = false; day.classList.add("active"); } } } else { if(day.classList.contains("active") && !check) { day.classList.remove("active"); this.state.filters.AvailableDays[day.id].active = false; } else if(!day.classList.contains("active") && check) { day.classList.add("active"); this.state.filters.AvailableDays[day.id].active = true; } } this.populateTimeFromState(); }; updateDayTimeSelectorState = () => { const timeSelector = document.querySelector("#time-selector"); this.state.filters.AvailableTime = timeSelector.value; const daySelector = document.querySelector("#day-selector"); this.state.specificDays = (daySelector.value === "specific-day"); const checkboxes = document.querySelector("#availability-days-checkboxes"); this.populateDaytimeSelectorFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; /** <!-- childReplacement provOmniSearchFunctions --> **/ /** * @description Convert a JavaScript object into a serialized string * @param {object} obj - JavaScript object capable of serialization * @param {boolean} usePlusSpace - Use '+' instead of '%20' to represent spaces * @returns {string} - Serialized string * * This converts an object into a URL query or URL encoded form body. * The returned string is not prepended by a question mark. * Example with usePlusSpace is truthy: * { a: '', b: '1 2', c: '1 2' } --> 'a&b=1+2&c=1+2' * Example when usePlusSpace is falsy: * { a: '', b: '1 2', c: '1 2' } --> 'a&b=1%202&c=1%202' */ serializeObject(obj, usePlusSpace) { return Object.keys(obj).map((key) => { let value = encodeURIComponent(obj[key] || ''); if (usePlusSpace) value = value.replace(/%20/g, '+'); return encodeURIComponent(key) + '=' + value; }).join('&'); } xhrProvidenceGeolocationAutocomplete(location) { return new Promise((resolve, reject) => { let geocodeUrl; if (this.isStaging) { geocodeUrl = 'https://providencekyruusstaging.azurewebsites.net/api/geolocation/api/autocomplete/'+location+'?take=5'; }else{ geocodeUrl = 'https://providencekyruus.azurewebsites.net/api/geolocation/api/autocomplete/'+location+'?take=5'; } const xhr = new XMLHttpRequest(); xhr.open('GET', geocodeUrl, true); xhr.withCredentials = true; xhr.setRequestHeader("traceparent","00-7c12f8055a29436f95167ba40787dcae-92e09794d75a4f19-01"); xhr.setRequestHeader("x-requested-with","XMLHttpRequest"); if (this.isStaging) { xhr.setRequestHeader("RequestVerificationToken", "E0-x1LbtEMXUSEIfOvRQXj9EY-u2bR1VJpBe-z7eQIzN8eoy0QgjmInrnmLtlJwyuM7x6CdjAD5Q_GREZr0A_WR1uGWT4rJ1QgGUkznTSZo1:DG_76BAidglwME5Li0a2saKhFlCIpsNYFT3iawY9SwQFpiFD-1gwWlOgHFyFANWTe4re6YgvEMiDOW-Y8gGuxTBBqMs8hiwguwKZ3102tF41"); }else{ xhr.setRequestHeader("RequestVerificationToken", "3brXwene3ShS4TLB4IT1rG9vLrzdUgU88z9FHag9cLeRr1rekAkPtkmUkAUv2miRgl8Qpp0HzxxrETfJ6-l7Z3V8Fd0pyZ6nB1RovFdJb041:6E51pGciBcnM_aG91aokWULU6JNKdIR7o7uULQ7B6xAEYUxY2GavtVGNc9qyRnOGwkASFWxBQ12fgXS6n53NTRiIY27SrDAh6js-bSVaOjg1"); } xhr.responseType = 'json'; xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { /* IE compatibility */ const json = typeof xhr.response === 'string' ? JSON.parse(xhr.response) : xhr.response; if (json && json.success) resolve(json); else reject('Could not guess user location from zipcode'); } else { reject('xhrProvidenceGeolocationAutocomplete did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(); }) .then((data) => { let results = []; results.push("location-value " + location); data.results.forEach((loc) => { results.push(loc.city + ', ' + loc.state + ' ' + loc.zip); }); return results; }) .catch((error) => { console.error('Unable to fetch typeahead results for geolocator\n', error); return []; }); } handleGeoUpdate(event) { event.preventDefault(); let location = event.submitter.value; if(location) { let loc1 = location.split(","); let loc2 = loc1[1].split(" "); let city = loc1[0]; let state = loc2[1]; let zip = loc2[2]; document.querySelector('#geoLocatorLabel span').textContent = location; document.querySelector('#geo-locator-ck').checked = false; document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'none'); this.setGeoProvLocation(zip, city, state); /*Set user Entered zipcode cookie; important to providence // 24 hours */ const setCookie = (id, value, maxage, path, domain, secure) => { let cookieString = id + "=" + value; cookieString += path ? "; path=" + path : "; path=/"; if (maxage !== undefined) cookieString += "; max-age=" + maxage; if (domain) cookieString += "; domain=" + domain; if (secure) cookieString += "; secure"; document.cookie = cookieString; }; setCookie('UserEnteredLocationGeoCoordinates-v3', `{"City":"${city}","Latitude":"undefined","Longitude":"undefined","PostalCode":"${zip}","StateCode":"${state}","Regions":[],"Version":1}`, 86400); /* localstorage user entered zipcode is only used by us */ localStorage.setItem('UserEnteredLocationGeoCoordinates-v3', `{"City":"${city}","Latitude":"undefined","Longitude":"undefined","PostalCode":"${zip}","StateCode":"${state}","Regions":[],"Version":1}`); siteSearch.helpers.getGoogleLocation("geoLocatorSuggestions input:first-of-type"); } /*Set input value to selected value*/ document.querySelector('#geoLocatorInput').value = event.submitter.value; /*Remove options*/ let inputs = document.querySelectorAll('#geoLocatorSuggestions input'); for (let i = 0; i < inputs.length; i++) { inputs[i].remove(); } } /* Set the userlocation on Providence's end */ setGeoProvLocation(postalcodeSaved, citySaved, stateSaved) { return new Promise((resolve, reject) => { const body = { scController: 'DetectUserLocation', scAction: 'SetUserLocation', postalcode: postalcodeSaved, city: citySaved, state: stateSaved }; let geocodeUrl; if (this.isStaging) { geocodeUrl = 'https://preview-providence.provhealth.org/locations'; }else{ geocodeUrl = 'https://www.providence.org/locations'; } const xhr = new XMLHttpRequest(); xhr.open('POST', geocodeUrl); xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest'); xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded; charset=UTF-8'); xhr.setRequestHeader('Authorization', 'Bearer ' + '7i9p5na47e3xgxz2bk472jgyfm989tt5'); xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { checkLoginStatus(); /*console.log("Providence geolocation updated - setGeoProvLocation()");*/ } else { reject('setGeoProvLocation did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(this.serializeObject(body)); }); } guessUserLocation() { return new Promise((resolve, reject) => { const geocodeUrl = 'https://wompservices.wompmobile.com/geoip'; const xhr = new XMLHttpRequest(); xhr.open('GET', geocodeUrl); xhr.responseType = 'json'; xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { /* IE compatibility */ const json = typeof xhr.response === 'string' ? JSON.parse(xhr.response) : xhr.response; if (json && json.success) resolve(json); else reject('Could not guess user location'); } else { reject('guessUserLocation did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(); }); } updateGeoTypeahead(event, currentTarget) { let typeaheadList = document.querySelector('#geoLocator-form .suggestion'); if (!typeaheadList) return console.error('Unable to locate typeahead list for typeahead update'); typeaheadList.querySelectorAll('input').forEach(e => e.remove()); this.xhrProvidenceGeolocationAutocomplete(event.target.value) .then((results) => { /* only add the results if they match the current typeahead */ if(document.getElementById('geoLocatorInput').value === results[0].replace('location-value ','')) { /* Remove first validation value */ results.shift(); results.forEach((loc) => { let btn = document.createElement('input'); btn.value = loc; btn.type = "submit"; btn.style.border = "none"; btn.style.textAlign = "left"; typeaheadList.append(btn); }); } }); } /** <!-- end childReplacement provOmniSearchFunctions --> **/ init = async () => { this.searchParams = prepURLSearchParams(); /** <!-- childReplacement provOmniGeoTypeahead --> **/ function getCookie(name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); if (match) { return match[2]; } } try{ /*Add geo locator to utility nav if cookie is set*/ let userGeoCookie = getCookie("UserEnteredLocationGeoCoordinates-v3"); let geoCookie = getCookie("GeoIpLocationGeoCoordinates-v3"); let geoStorage = localStorage.getItem('UserEnteredLocationGeoCoordinates-v3'); if(userGeoCookie || geoCookie || geoStorage){ const userGeoObj = JSON.parse(userGeoCookie || geoCookie || geoStorage); if(document.querySelector('#topheader nav')){ let location = userGeoObj.City + ", " + userGeoObj.StateCode + " " + userGeoObj.PostalCode; let span = document.querySelector('#geoLocatorLabel span'); if(span){ span.textContent = location; document.querySelector('#geoLocatorInput').value = location; } const customStyles = document.querySelector('style'); let css = ` #topheader label#geoLocatorLabel { color: inherit; pointer-events: all; } label#geoLocatorLabel { display: block; } `; if (customStyles) { customStyles.innerHTML += css; } } }else{ this.guessUserLocation() .then((userLocation) => { if (userLocation.city && userLocation.zip && userLocation.state) { let location = userLocation.city + ", " + userLocation.state + " " + userLocation.zip; let label = document.querySelector('#geoLocatorLabel'); let span = document.querySelector('#geoLocatorLabel span'); span.textContent = location; document.querySelector('#geoLocatorInput').value = location; const customStyles = document.querySelector('style'); let css = ` #topheader label#geoLocatorLabel { color: inherit; pointer-events: all; } label#geoLocatorLabel { display: block; } `; if (customStyles) { customStyles.innerHTML += css; } } }) .catch(error => console.log(error)); } } catch (error) { console.error('Add Geo Locator failed\n', error); } /* Geolocator autocomplete */ const geolocator = document.querySelector('#geoLocatorInput'); if (geolocator) { const typeaheadList = document.querySelector('#geoLocator-form .suggestion'); /* Workaround for event.currentTarget === null when debounced is used See: https://github.com/jashkenas/underscore/issues/1905 */ let debouncedUpdateGeoTypeahead = this.helpers.debounce(this.updateGeoTypeahead, 250).bind(this); geolocator.addEventListener('input', function (event) { debouncedUpdateGeoTypeahead(event, event.currentTarget); document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'block'); }); geolocator.addEventListener('click', function (event) { if(document.querySelector('#geoLocator-form .suggestion').style.display !== "block"){ /*Select input on click so that the user can overwrite saved location*/ this.setSelectionRange(0, this.value.length); document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'block'); } }); let geoLocatorLabel = document.querySelector('#geoLocatorLabel'); geoLocatorLabel.addEventListener('click', function (event) { document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'none'); if(window.innerWidth < 1000) { document.querySelector('main').style.setProperty('z-index','0'); } }); } /* Catch geolocator form submission */ let geoForm = document.querySelector('#geoLocator-form'); if(geoForm){ geoForm.addEventListener('submit', this.handleGeoUpdate.bind(this)); } let geoUseCurrentLocation = document.querySelector('#geoUseCurrentLocation'); if(geoUseCurrentLocation) { geoUseCurrentLocation.addEventListener('click', () => { document.querySelector('#geo-locator-ck').checked = false; }); } /** <!-- end childReplacement provOmniGeoTypeahead --> **/ /* Create cookies for linker params, if they don't already exist */ if (typeof cookieLinkerParams != "undefined") cookieLinkerParams(); this.state = { pageInit: true, pageType: 'Provider Directory', page_subtype: 'Provider Search Results', query: "", /** <!-- childReplacement brandColor --> **/ color: "#00338e", /** <!-- end childReplacement brandColor --> **/ disclaimerSearch: false, disclaimerSearchKeywords: [ 'transgender surgery', 'IVF', 'vasectomy', ], excludeSearch: false, excludeSearchKeywords: [ 'abortion', 'physician assisted suicide', 'physician-assisted suicide', 'euthanasia', 'euthenasia' ], pushHistory: false, specificDays: false, filters: { previousPostalCode: false, distance: this.searchParams.get("distance") || false, page: Number(this.searchParams.get("page")) || 1, coordinates: false, Gender: {}, InsuranceAccepted: {}, PrimarySpecialties: {}, /** <!-- childReplacement providerOrgState --> **/ ProviderOrganization: {}, /** <!-- end childReplacement providerOrgState --> **/ Languages: {}, availability: "all", visitType: false, /** <!-- childReplacement brandState --> **/ brand: this.searchParams.get("brand") || "providence", /** <!-- end childReplacement brandState --> **/ sortby: false, init: true, locationType: false, location: false, LocationsOnly: ((this.searchParams.get("LocationsOnly") ?? "false") === "true"), AvailableDays: { Sunday: { active: false }, Monday: { active: false }, Tuesday: { active: false }, Wednesday: { active: false }, Thursday: { active: false }, Friday: { active: false }, Saturday: { active: false }, }, AvailableTime: "any", tier: this.searchParams.get("Tier") || this.searchParams.get("tier") }, results: false, }; this.map.build(); this.map.setOffset(); /* Create day-time selector */ /* populate day-time selector state from URL if it exists */ if(this.searchParams.get("time")) { const time = this.searchParams.get("time").trim(); switch(time) { case "any": case "morning": case "afternoon": this.state.filters.AvailableTime = time; default: break; } } if(this.searchParams.get("days")) { const days = this.searchParams.get("days").split(",").map(x => +x); this.state.specificDays = true; days.forEach(n => { const day = DAYS[n]; if(day && this.state.filters.AvailableDays[day]) { this.state.filters.AvailableDays[day].active = true; } }) } document.querySelectorAll("#timesList a").forEach(time => { time.addEventListener('click', () => this.updateTimeSelectorState(time)); }); document.querySelectorAll("#daysList a").forEach(day => { day.addEventListener('click', () => this.updateDaySelectorState(day)); }); document.querySelector("#time-selector").addEventListener("change", () => { this.updateDayTimeSelectorState(); }); document.querySelector("#day-selector").addEventListener("change", () => { this.updateDayTimeSelectorState(); }); let dayTimeHTML = ""; DAYS.forEach((day, i) => { if(i === 0 || i === 6) { return; } const isActive = this.state.filters.AvailableDays[day].active; dayTimeHTML += ` <a class="day-checkbox dropdown-item omni-checkbox ${isActive ? " active" : ""}" id="${day}-checkbox">${day}</a> `; }); document.querySelector("#availability-days-checkboxes").innerHTML = dayTimeHTML; document.querySelectorAll(".day-checkbox").forEach(checkbox => { checkbox.addEventListener("click", e => { const state = this.state.filters.AvailableDays[checkbox.innerHTML].active; this.state.filters.AvailableDays[checkbox.innerHTML].active = !state; this.updateDayTimeSelectorState(); }) }); document.querySelector("#day-selector").value = "any-day"; this.populateDaytimeSelectorFromState(); /* end day-time selector */ const { searchParams, state, map, helpers } = this; const { createPixel, trackIt, trackFromEmitted } = helpers; const lsInsurance = localStorage.getItem('omniSearchInsurance') || false; const lsLocation = localStorage.getItem('omniSearchLocation') || false; const loopable = ['gender', 'languages', 'insuranceaccepted', 'primaryspecialties'/** <!-- childReplacement providerOrgLoopable --> **/ , "ProviderOrganization" /** <!-- end childReplacement providerOrgLoopable --> **/]; const isMobile = this.helpers.viewportLessThan(); let tmp; document.addEventListener('timeslot-clicked', event => trackFromEmitted('timeslot', event)); document.addEventListener('phone-clicked', event => trackFromEmitted('phone', event)); /* if (isMobile) { this.logo.style.width = '85px'; this.logo.style.height = '50px'; }; */ let hasInsuranceParam = false; const isTest = searchParams.get('test'); if (isTest && isTest == "true") { this.state.isTest = true; } /* Loop through params that can have more than one value */ for (let param of loopable) { if (searchParams.get(param)) { if (param === 'insuranceaccepted') { hasInsuranceParam = true; } tmp = searchParams.get(param).split(","); tmp.forEach((val) => { switch(param) { case 'gender': param = 'Gender'; break; case 'languages': param = 'Languages'; break; case 'primaryspecialties': param = 'PrimarySpecialties'; break; case 'insuranceaccepted': param = 'InsuranceAccepted'; break; default: } state.filters[param][val] = { active: true }; }); }; } if (lsInsurance && !hasInsuranceParam) { const split = lsInsurance.split(','); for (let ins of split) { if (!this.state.filters.InsuranceAccepted) { this.state.filters.InsuranceAccepted = {}; } this.state.filters.InsuranceAccepted[ins] = { active: true } } } /* Set query if not empty */ if (searchParams.get("query")) { window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = decodeURIComponent(searchParams.get('query')); } else { window.sessionStorage.setItem('previousQuery', ''); }; /* If a PracticeGroup param is present, then set state */ if (searchParams.get('practicegroup')) { this.state.filters.PracticeGroup = searchParams.get('practicegroup'); } /* If a userlocation param is present, then set state */ if (searchParams.get('userlocation')) { this.state.filters.location = searchParams.get('userlocation'); this.state.currentLoc = searchParams.get('userlocation'); } else if (lsLocation) { this.state.filters.location = lsLocation } /* If there is a search param of LocationName and it has a length, set that to state */ if (searchParams.get('locationname')) { this.state.filters.LocationName = searchParams.get('locationname'); } /* If there is a search param of distance and it has a length, set that to state */ if (searchParams.get('distance')) { this.state.filters.distance = searchParams.get('distance'); } /* Check search params for LocationId and PracticeGroupId, and set state accordingly */ if (searchParams.get('locationid')) { this.state.filters.LocationId = searchParams.get('locationid'); } if (searchParams.get('practicegroupid')) { this.state.filters.PracticeGroupId = searchParams.get('practicegroupid'); } await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthSearchFacets?brand=` + this.state.filters.brand) .then(res => res.json()) .then(res => { this.state.facets = res["facets"]; }); /* Check for / apply visit type filters */ let visitTypes = searchParams.get('visittypes'); if (visitTypes) { visitTypes = visitTypes.split(','); document.querySelector('button[data-type="all"]').classList.remove('active'); this.state.filters.visitType = {}; for (let type of visitTypes) { document.querySelector(`button[data-type="${type}"]`).classList.add('active'); this.state.filters.visitType[type] = { active: true }; } } const userState = await fetch('https://wompservices.wompmobile.com/geoip/') .then(res => res.json()) .then(res => res.state ? res.state : "Undefined") .catch(() => { return "Undefined"; }); this.state.filters.userState = userState; let brands = { pacmed: "Pacific Medical", providence: "Providence", swedish: "Swedish" }; document.getElementById("OrganizationLabel").innerHTML = `Organization: ${brands[this.state.filters.brand]} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; /* Handle sort order */ const sortby = searchParams.get("sortby"); if (sortby) { switch (sortby) { case "GeocodedCoordinate": if (searchParams.get("userlocation")) { siteSearch.state.filters.sortby = "GeocodedCoordinate"; const sortByLabel = document.querySelector("#SortByLabel"); sortByLabel.innerHTML = `Sort by: Distance <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; } else { siteSearch.state.filters.sortby = false; }; break; default: siteSearch.state.filters.sortby = sortby; break; }; }; this.handleSubmit(); }; }; const prepURLSearchParams = () => { const url = new URL(window.location); const searchParams = new URLSearchParams(url.search); const qStrings = [ 'gender', 'insuranceaccepted', 'languages', 'locationid', 'locationname', 'practicegroup', 'practicegroupid', 'primaryspecialties', 'providerorganization', 'userlocation', 'visittypes' ]; /* convert certain param labels to lowercase */ searchParams.forEach((value, key) => { const match = qStrings.find((str) => { return str === key.toLowerCase(); }); if (match) { searchParams.delete(key); searchParams.set(match, value); } }); url.search = searchParams.toString(); window.history.pushState('Find a Doctor','',url); return searchParams; }; const siteSearch = new Search( `https://${/staging/gi.test(window.location.hostname) ? 'womphealthdevapi' : 'womphealthapi'}.azurewebsites.net/api/WompHealthSearch?` ); siteSearch.textInputs.forEach((el) => { if (el.id === 'searchRadius') { el.addEventListener("keydown", function (e) { if (e.keyCode === 13) { const searchRadiusVal = document.querySelector('#searchRadius').value; if (searchRadiusVal?.length) { siteSearch.state.filters.distance = searchRadiusVal; } else { siteSearch.state.filters.distance = ''; } siteSearch.handleSubmit(); } }); } if (el.id === 'query') { el.addEventListener("keydown", function (e) { if (e.keyCode === 13) siteSearch.handleSubmit(); }); }; if (el.id === "custom-location") { el.addEventListener("keydown", function(e) { if (e.keyCode === 13) { siteSearch.helpers.getGoogleLocation(); } }); } el.addEventListener("change", function (e) { const { id, value } = e.target; if (id === "query") { return; } else { let val = value?.length ? value : false; siteSearch.state.filters[id] = val; }; }); }); siteSearch.form.addEventListener("click", siteSearch.handleSubmit); siteSearch.form2.addEventListener("click", siteSearch.handleSubmit); siteSearch.createClickListener(); siteSearch.createChangeListeners(); siteSearch.addWindowListeners(); siteSearch.init(); try { let mtoggle = localStorage.getItem("maptoggle"); if(mtoggle !== null) { document.getElementById("showmap").checked = mtoggle; } document.getElementById("maptoggle").addEventListener("click", function(e){ localStorage.setItem("maptoggle", !document.getElementById("showmap").checked); }); } catch (error) { console.log(error); } class Autocomplete { constructor(args) { this.useHistory = args.useHistory || false; this.param = args.param; this.src = args.src; this.id = args.id; this.localStorageKey = args.localStorageKey; this.shouldSubmit = args.shouldSubmit || true; this.minChars = args.minChars || 3; this.isStaging = /staging/gi.test(window.location.hostname); this.apiSub = this.isStaging ? 'womphealthdevapi' : 'womphealthapi'; /** <!-- childReplacement autocompleteBrand --> **/ this.brand = 'providence'; /** <!-- end childReplacement autocompleteBrand --> **/ this.state = { initValue: '' }; /* Automatically call the init script for the autocomplete */ this.init(); this.helpers.trackAutocomplete = this.helpers.trackAutocomplete.bind(this); }; helpers = { debounce(func, delay = 200) { let timeout = null; return function(){ let context = this; let args = arguments; if(document.getElementById('searchBox').classList.length === 0) { document.getElementById("searchBox").classList.add("is-dirty"); if(document.getElementById('mobileSearchWrapper').classList.length === 1) { document.getElementById("mobileSearchWrapper").classList.add("is-dirty"); } } clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args) }, delay); }; }, slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); }, removeSelected() { document.querySelectorAll('div[data-autocomplete].selected').forEach(div => div.classList.remove('selected')); }, fetchSuggestions: async(val) => { const { src, param, helpers, autocompleteEl } = this; const { slugify } = helpers; const results = await fetch(`${src}&${param}=${val}`).then(res => res.json()) .then(res => { /* If the length is 0, there are no results to the search */ if (res.value.length == 0) { autocompleteEl.innerHTML = '<small>No suggestions</small>'; return 'success'; }; /* Map out the returned results */ const tmp = res.value.reduce((html, r) => r?.text ? html + `<div id="${slugify(r.text)}" data-autocomplete>${r.text}</div>` : html, ''); autocompleteEl.innerHTML = tmp; return 'success'; }); return results; }, getCID: () => { let cid = localStorage.getItem('cid'); /* If we DON'T have a CID saved to localStorage... */ if (!cid || !cid.length) { /* If crypto's randomUUID func is available, create a CID with it */ if (typeof crypto?.randomUUID == 'function') { cid = crypto.randomUUID(); } /* Else, go old school... */ else { cid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /* Save the new cid to localStorage */ localStorage.setItem('cid', cid); } /* Send CID to the requestor */ return cid; }, trackAutocomplete: async (str) => { const data = JSON.stringify({ text: this.state.initValue, autocomplete: str }); const { pathname, search } = window.location; await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthAnalytics?brand=${this.brand}&t=event&ec=search&ea=autocomplete&el=${data}&cid=${this.helpers.getCID()}&dp=${encodeURIComponent(pathname + search)}`) .then(res => res.json()) .then(res => console.log(res)); } }; addListener = { input: () => { const { src, param, helpers, autocompleteEl, getPreviousSearches, minChars, state, inputEl } = this; const { debounce, slugify, fetchSuggestions } = helpers; inputEl.addEventListener('input', debounce(async function(e) { const val = inputEl.value; /* This is the text that the user input */ state.initValue = val; /* If more than minChars characters are input, start querying the API */ if (val.length >= minChars) { await fetchSuggestions(val); } /* Otherwise, show previous and suggested searches */ else { getPreviousSearches(); }; })); }, key: () => { const { debounce, removeSelected, trackAutocomplete } = this.helpers; const { inputEl, autocompleteEl, state } = this; inputEl.addEventListener('keydown', debounce(function(e) { const selected = document.querySelector('div[data-autocomplete].selected'); let nextSibling = false; let prevSibling = false; if (selected) { nextSibling = selected.nextElementSibling ? selected.nextElementSibling : false; prevSibling = selected.previousElementSibling ? selected.previousElementSibling : false; if (prevSibling && !prevSibling.hasAttribute('data-autocomplete')) { prevSibling = false; }; }; const { key } = e; switch(key) { case 'ArrowDown': if (nextSibling) { removeSelected(); nextSibling.classList.add('selected'); inputEl.value = nextSibling.innerText; } else if (!selected) { const first = document.querySelector('div[data-autocomplete]'); inputEl.value = first.innerText; first.classList.add('selected'); }; break; case 'ArrowUp': if (prevSibling) { removeSelected(); prevSibling.classList.add('selected'); inputEl.value = prevSibling.innerText; } else { if (selected) { removeSelected(); inputEl.value = state.initValue; }; }; break; case 'Enter': if (selected) { /* trackAutocomplete(selected.innerText); */ } autocompleteEl.style.display = 'none'; inputEl.blur(); window.wmFromAutoComplete = true; break; default: break; } }, 100)); }, clicks: () => { const { autocompleteEl, inputEl, shouldSubmit, id, getPreviousSearches, helpers } = this; const { removeSelected, fetchSuggestions } = helpers; document.body.addEventListener('click', (e) => { const { target } = e; /* If it has data-autocomplete, it's an autocomplete option. Update the autocomplete */ if (target.hasAttribute('data-autocomplete')) { removeSelected(); /* helpers.trackAutocomplete(target.innerText); */ target.classList.add('selected'); inputEl.value = e.target.innerText; autocompleteEl.style.display = "none"; if (shouldSubmit) { inputEl.dispatchEvent(new KeyboardEvent('keydown',{'key':'Enter'})); }; } /* Else, if the id matches the current id, then its the auto complete element. Run default logic. */ else if (target.id == id) { if (target.value.length > 0) { fetchSuggestions(target.value); } else { getPreviousSearches(); }; } /* Otherwise, it's a click outside autocomplete. Hide the autocomplete options. */ else { autocompleteEl.style.display = "none"; }; }); /* If the input is focused on, show the autocomplete box */ inputEl.addEventListener('focus', (e) => { autocompleteEl.style.display = 'block'; }); } }; checkRequiredArgs = () => { const requiredArgs = ["id", "src", "param"]; let missing = false; const missingRequiredArgs = requiredArgs.some(arg => { const tmp = !this[arg]; missing = arg; return tmp; }); if (missingRequiredArgs) { console.error(`Please add key "${missing}" to autocomplete options object`); } else if (this.id == "autocomplete") { console.error('Input id cannot be "autocomplete". Please choose another id.'); return false; }; return !missingRequiredArgs; }; getPreviousSearches = async () => { const { src, param, helpers, autocompleteEl, useHistory, localStorageKey } = this; const { slugify } = helpers; let theHtml = await fetch(`${src}&${param}=`).then(res => res.json()) .then(res => { if (!res.value?.length) { return ''; } const tmp = res.value.reduce((html, r) => r?.text ? html + `<div id="${slugify(r.text)}" data-autocomplete>${r.text}</div>` : html, ''); return `<h5>Suggested Searches</h5>${tmp}`; }); /* If we are using histor AND a localStorageKey is set */ if (useHistory && localStorageKey) { this.previousSearches = JSON.parse(localStorage.getItem(localStorageKey)); const { previousSearches } = this; theHtml += '<div class="availability-divider"></div><h5>Previous Searches</h5>'; /* Make sure the data we have in an array before populated the autocomplete */ if (Array.isArray(previousSearches) && previousSearches.length) { theHtml += previousSearches.reduce((html, search, i) => { const id = this.helpers.slugify(search) + 1; const el = `<div id="${id}" data-autocomplete>${search}</div>`; return html + el; }, ''); } else { theHtml += '<small>No previous searches</small>'; }; autocompleteEl.innerHTML = theHtml; }; }; addAutocomplete = () => { this.inputEl = document.querySelector("#" + this.id); this.inputEl.insertAdjacentHTML('afterend', '<div id="autocomplete" style="display: none;"></div>'); this.autocompleteEl = document.querySelector("#autocomplete"); const { inputEl, id, addListener } = this; for (let listener of Object.values(addListener)) { listener(); }; }; init() { const { checkRequiredArgs, addAutocomplete } = this; if (!checkRequiredArgs()) return; addAutocomplete(); }; }; const autocomplete = new Autocomplete({ id: 'query', src: `https://${/staging/gi.test(window.location.hostname) ? 'womphealthdevapi' : 'womphealthapi'}.azurewebsites.net/api/WompHealthSearch?type=autocomplete&brand=pacmed&top=5&skip=0`, param: 'search', useHistory: true, localStorageKey: 'previousSearches', minChars: 3 }); </script> <script id="invocaDecoration" type="text/javascript"> var internalUserError = ''; const isInternalUser = fetch("https://s3-us-west-1.amazonaws.com/providence-internal/analytics.gif") .then((res) => { return res.status == 200 ? true : false; }) .catch((error) => { console.log(error); internalUserError = "true"; return false; }); function getCookieInvoca(name) { const match = document.cookie.match(new RegExp(`(?=${name}=)([^;]*)`)); if (match) { return match[0].split('=')[1]; } } function hasUrlParam(param, de) { return (param ? `${param}` : `${de}`); } function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } function fireInvoca() { let url = window.location.href; const invocaCookie = getCookieInvoca("invoca_session"); let invocaHasOutbound; let invocaConfigPhone; if( (internalUserError === "true" || !isInternalUser) && invocaCookie !== undefined) { let invocaCookieJSON; if(invocaCookie.charAt(0) === '%'){ invocaCookieJSON = JSON.parse(decodeURIComponent(invocaCookie)); } else { invocaCookieJSON = JSON.parse(invocaCookie); } if(invocaCookieJSON) { let hgcrmAgency = hasUrlParam(invocaCookieJSON.session.hgcrm_agency, "client"), hgcrmCampaignid = hasUrlParam(invocaCookieJSON.session.campaignid, "607"), hgcrmChannel = hasUrlParam(invocaCookieJSON.session.hgcrm_channel, "email"), hgcrmSource = hasUrlParam(invocaCookieJSON.session.hgcrm_source, "mh"), hgcrmTacticid = hasUrlParam(invocaCookieJSON.session.hgcrm_tacticid, "15688"), hgcrmTrackingsetid = hasUrlParam(invocaCookieJSON.session.offersetid, "22561"), landingPage = hasUrlParam(invocaCookieJSON.session.landing_page, ""); landingPage = landingPage.split('?')[0].replace("https://",""); invocaHasOutbound = `landing_page=${landingPage}&hgcrm_channel=${hgcrmChannel}&hgcrm_source=${hgcrmSource}&campaignid=${hgcrmCampaignid}&hgcrm_tacticid=${hgcrmTacticid}&offersetid=${hgcrmTrackingsetid}`; invocaConfigPhone = `calling_page=${encodeURIComponent(url)}&calling_url=${encodeURIComponent(url)}&${invocaHasOutbound}&advertiser_campaign_id_from_network=2&network_id=1758&tealium_vid=AMP_STATE(wheelhouse.tealium_vid)`; waitForElm('#results .provider').then((elm) => { /*Replace Phone Number*/ let phones = document.getElementsByClassName("phoneLink"); for (i = 0; i < phones.length; ++i) { let tel = phones[i]; let telNum = tel.href.replace("tel:+1-","").replace("tel:","").replaceAll("-","").trim(); if(tel) { tel.insertAdjacentHTML( "beforebegin", ` <amp-list data-no-nudge class="callTrackingList" id="tel1" src="https://tealium.wheelhousedmg.com/i.gif?tealium_vid=CLIENT_ID(AMP_ECID_GOOGLE)" layout="fixed-height" height="19px" width="auto" items="." single-item binding="always" template="callTrackTemplate${i}" > <div placeholder> ${tel.outerHTML} </div> </amp-list> <template type="amp-mustache" id="callTrackTemplate${i}"> <amp-call-tracking class="callTracking" config="https://pnapi.invoca.net/amp?invoca_id=AMP_STATE(wheelhouse.tealium_vid)&_pcid=AMP_STATE(wheelhouse._pcid)&${invocaConfigPhone}&invoca_detected_destination=${telNum}"> ${tel.outerHTML} </amp-call-tracking> </template> ` ); tel.remove(); } } var script = document.createElement('script'); script.setAttribute("custom-element", 'amp-call-tracking'); script.src = 'https://cdn.ampproject.org/v0/amp-call-tracking-0.1.js'; script.setAttribute("async", "true"); document.head.appendChild(script); }); /*Decorate Outbound Links*/ let outboundInvocaLinks; let v; if(url.indexOf("pacmed") > -1 || url.indexOf("pacificmedicalcenters") > -1) { outboundInvocaLinks = document.body.querySelectorAll("a[href*='pacificmedicalcenters']"); for (i = 0; i < outboundInvocaLinks.length; ++i) { if(outboundInvocaLinks[i].href.indexOf("?") > -1) { v = outboundInvocaLinks[i].href + "&" + invocaHasOutbound; } else { v = outboundInvocaLinks[i].href + "?" + invocaHasOutbound; } outboundInvocaLinks[i].href = v; } } else if(url.indexOf("swedish") > -1) { outboundInvocaLinks = document.body.querySelectorAll("a[href*='swedish']"); for (i = 0; i < outboundInvocaLinks.length; ++i) { if(outboundInvocaLinks[i].href.indexOf("?") > -1) { v = outboundInvocaLinks[i].href + "&" + invocaHasOutbound; } else { v = outboundInvocaLinks[i].href + "?" + invocaHasOutbound; } outboundInvocaLinks[i].href = v; } } } } } fireInvoca(); </script> <amp-list src="amp-state:wheelhouse" layout="fixed-height" height="1" single-item="" items="." style="z-index: -100"> <template type="amp-mustache"> <div id="wheelhouseJson" style="width: 1px; height: 1px; overflow: hidden;"> { "pcid": "{{ _pcid }}", "traceId": "{{ trace_id }}" } </div> </template> </amp-list> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,500,700&amp;display=swap" rel="stylesheet"> <div id="wm_footer" class="wm_footer section"> <div class="footer-bs-wrap" id="topFooter"> <div class="footer-section-wrapper"> <div class="d-col large-col"> <div class="mission-and-values"> <div class="mission"> <h3>OUR MISSION</h3> <p>As expressions of God's healing love, witnessed through the ministry of Jesus, we are steadfast in serving all, especially those who are poor and vulnerable.</p> </div> <div class="values"> <h3>OUR VALUES</h3> <p>Compassion, Dignity, Justice, Excellence, Integrity. <a class="underline" href="https://www.providence.org/about/our-mission">Learn more about our mission, vision and values.</a></p> </div> </div> <a class="btn white contact-btn" aria-label="Contact us at Providence" href="https://www.providence.org/about/contactus">Contact Us</a> </div> <div class="more-links"> <div class="d-col"> <h3>ACROSS PROVIDENCE</h3> <ul> <li><a href="https://www.providence.org/about">About Us</a></li> <li><a href="https://www.providence.org/about/health-equity" aria-label="Health Equity footer link">Health Equity</a></li> <li><a href="https://www.providence.org/about/initiatives" aria-label="Initiatives at Providence">Initiatives</a></li> <li><a href="https://www.collaboratingforbetterhealthcare.com/c/a/62g1OpEWK4O9ZckbK4vI08">Help Us Improve Health Care</a></li> <li><a href="https://www.providence.org/about/annual-report" aria-label="Annual Report to Our Communities footer link">Annual Reports to Our Community</a></li> <li><a href="https://www.providence.org/about/annual-report/reports/chna-and-chip-reports" aria-label="Community Health Needs Assessments and Improvement Plans footer link">Community Health Needs Assessments and Improvement Plans</a></li> <li><a href="https://www.providence.org/doctors" aria-label="Our Doctors footer link">Our Doctors</a></li> <li><a href="https://www.providence.org/business/nursing-institute" aria-label="Our Nurses footer link">Our Nurses</a></li> </ul> </div> <div class="d-col"> <h3>RESOURCES</h3> <ul> <li><a href="https://www.providence.org/for-employees" aria-label="For Care givers (our employees)" title="For Care givers (our employees)">For Caregivers (our employees)</a></li> <li><a href="https://psjhealth.jobs/" aria-label="Career Opportunities footer link">Career Opportunities</a></li> <li><a href="https://psdconnect.org/partners/providence" aria-label="Physician &amp; Provider Careers footer link">Physician &amp; Provider Careers</a></li> <li><a href="https://www.providence.org/business/supplier-information" aria-label="Supplier Portal footer link">Supplier Portal</a></li> <li><a href="https://psjhcrmwebsites.microsoftcrmportals.com/home" aria-label="Classes and Events footer link">Classes and Events</a></li> <li><a href="https://www.providence.org/obp" aria-label="Online Bill Pay &amp; Financial Assistance footer link">Online Bill Pay &amp; Financial Assistance</a></li> <li><a href="https://www.providence.org/obp/get-a-price-estimate" aria-label="Get a Price Estimate footer link">Get a Price Estimate</a></li> <li><a href="https://www.providence.org/obp/pricing-transparency">Pricing Transparency</a></li> <li><a href="https://blog.providence.org/" aria-label="Read our blog footer link">Read our Blog</a></li> </ul> </div> </div> </div> </div> <div id="footNote" class="wm_footer section"> <div class="footer-bs-wrap"> <div id="fnWrap" class="footer-section-wrapper"> <div class="d-col social"> <h3>Connect with us</h3> <div> <a href="https://www.facebook.com/providence" title="Check us out on Facebook" target="_blank"> <span class="screenreader">Check us out on Facebook</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/facebook.png" alt="Facebook"></amp-img> </a> <a href="https://twitter.com/providence" title="Check us out on Twitter" target="_blank"> <span class="screenreader">Check us out on Twitter</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/twitter.png" alt="Twitter"></amp-img> </a> <a href="https://www.linkedin.com/company/providencest.josephhealth" title="Check us out on LinkedIn" target="_blank"> <span class="screenreader">Check us out on LinkedIn</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/linkedin.png" alt="LinkedIn"></amp-img> </a> <a href="https://www.instagram.com/providencehealthsystem/" title="Check us out on Instagram" target="_blank"> <span class="screenreader">Check us out on Instagram</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/instagram.png" alt="Instgram"></amp-img> </a> <a href="https://www.youtube.com/channel/UCCJ8wEkiHBHkVD8Jb7sbIBg/featured" title="Check us out on YouTube" target="_blank"> <span class="screenreader">Check us out on YouTube</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/youtube.png" alt="YouTube"></amp-img> </a> </div> </div> <div class="d-col legal"> <div class="footer-info"> <a href="https://www.providence.org/utility-pages/disclaimer">Disclaimer</a> <span>|</span> <a href="https://www.providence.org/utility-pages/privacy-policy">Terms of Use &amp; Privacy Policy</a> <span>|</span> <a href="https://www.providence.org/utility-pages/nondiscrimination-statement">Notice of Nondiscrimination &amp; Communication Assistance</a> <span>|</span> <a href="https://www.providence.org/utility-pages/notice-of-privacy-practices">Notice of Privacy Practices</a> </div> <span class="copyright">©2024 Providence. All rights reserved.</span> </div> </div> </div> </div> </div> <svg hidden="" class="hidden" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <symbol id="icon-building-o" viewBox="0 0 22 28"> <title>Building Icon</title> <desc>Building Icon</desc> <path d="M6 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 26h6v-24h-18v24h6v-3.5c0-0.266 0.234-0.5 0.5-0.5h5c0.266 0 0.5 0.234 0.5 0.5v3.5zM22 1v26c0 0.547-0.453 1-1 1h-20c-0.547 0-1-0.453-1-1v-26c0-0.547 0.453-1 1-1h20c0.547 0 1 0.453 1 1z"></path> </symbol> <symbol id="icon-building-hospital" viewBox="0 0 448 512"> <title>Hospital</title> <desc>Hospital</desc> <path fill="currentColor" d="M128 244v-40c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v40c0 6.627-5.373 12-12 12h-40c-6.627 0-12-5.373-12-12zm140 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm-76 84v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm76 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm180 124v36H0v-36c0-6.627 5.373-12 12-12h19.5V85.035C31.5 73.418 42.245 64 55.5 64H144V24c0-13.255 10.745-24 24-24h112c13.255 0 24 10.745 24 24v40h88.5c13.255 0 24 9.418 24 21.035V464H436c6.627 0 12 5.373 12 12zM79.5 463H192v-67c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v67h112.5V112H304v24c0 13.255-10.745 24-24 24H168c-13.255 0-24-10.745-24-24v-24H79.5v351zM266 64h-26V38a6 6 0 0 0-6-6h-20a6 6 0 0 0-6 6v26h-26a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h26v26a6 6 0 0 0 6 6h20a6 6 0 0 0 6-6V96h26a6 6 0 0 0 6-6V70a6 6 0 0 0-6-6z"></path> </symbol> <symbol id="icon-shield" viewBox="0 0 24 24"> <titl |
| 10 |
Error
On line 1, column 347600 0 24 24"> <title>Information shield</title> <desc>Information shield</desc> <path d="M12 20.862c-1.184-0.672-4.42-2.695-6.050-5.549-0.079-0.138-0.153-0.276-0.223-0.417-0.456-0.911-0.727-1.878-0.727-2.896v-6.307l7-2.625 7 2.625v6.307c0 1.018-0.271 1.985-0.726 2.897-0.070 0.14-0.145 0.279-0.223 0.417-1.631 2.854-4.867 4.876-6.050 5.549zM12.447 22.894c0 0 4.989-2.475 7.34-6.589 0.096-0.168 0.188-0.34 0.276-0.515 0.568-1.135 0.937-2.408 0.937-3.79v-7c0-0.426-0.267-0.79-0.649-0.936l-8-3c-0.236-0.089-0.485-0.082-0.702 0l-8 3c-0.399 0.149-0.646 0.527-0.649 0.936v7c0 1.382 0.369 2.655 0.938 3.791 0.087 0.175 0.179 0.346 0.276 0.515 2.351 4.114 7.34 6.589 7.34 6.589 0.292 0.146 0.62 0.136 0.894 0z"></path> </symbol> <symbol id="icon-building-hospital" viewBox="0 0 448 512"> <titl |
| 11 |
Info
On line 1, column 328302 urance_q"> <input type="submit" value="Submit"> </form> </div></header> <main> <section id="map" class="show-map"> <div id="virtual-only" style=" width: 100%; height: 100%; "> <picture style="float: right;width: auto;height: 100%;background: white;"> <source media="(min-width: 1000px)" srcset="https://az690879.vo.msecnd.net/7914/DexCare_VirtualProviders_lg.png"> <img src="https://az690879.vo.msecnd.net/7914/DexCare_VirtualProviders_sm.png" alt="Virtual Visit Options" style="width: auto;max-height: 400px;margin: 0 auto;display: block;"> </picture> </div> <div id="wm_map"></div> </section> <section class="container" id="results-wrapper"> <div class="row"> <div class="col-xs-12"> <div class="topbtns flex"> <label for="fullsearch" class="btn">Filter Results</label> <label for="showmap" class="togglebtn btn" id="maptoggle"><span></span>Map</label> </div> </div> <div class="col-xs-12 col-md-6 py-3"> <div id="results"> <div class="loader"></div> </div> </div> </div> <div class="row"> <div class="col-xs-12 col-md-6" id="pagination"></div> </div> </section> </main> <script> function clearInput(id) { var elem = document.getElementById(id); var event = new Event('input', { bubbles: true, cancelable: true, }); elem.value = ''; elem.dispatchEvent(event); if(id="query"){ document.getElementById("searchBox").classList.remove('is-dirty'); } } </script> <script id="searchScripts" type="text/javascript"> function mapRequirement() { /* google's javascript api requires a callback */ console.log('Google Map Loaded'); window.mapLoaded = true; } sessionStorage.removeItem("online_scheduling_filter"); const DAYS = [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ]; /** <!-- childReplacement provOmniLoginStatus --> **/ let myChartLoggedIn = false; async function checkLoginStatus() { let geolocator = document.querySelector('#geoLocatorInput'); const myAccountWrapper = document.querySelector('#topheader .account-wrapper'); const provUrl = siteSearch.isStaging ? 'https://preview-providence.provhealth.org' : 'https://www.providence.org'; return fetch(`${provUrl}/identitynav/getjson`, { credentials: "include" }) .then(resp => resp.json()) .then(resp => { myChartLoggedIn = true; if(myAccountWrapper) myAccountWrapper.classList.remove('hidden'); if(resp && resp.children.length > 0) { /* User verified Populate & stop hiding user dropdown menu */ const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(loginButton) loginButton.classList.add('hidden'); if(myAccountDiv) myAccountDiv.classList.remove('hidden'); let myChartUrl = ''; /* Set username labels */ if(myAccountDiv) { const userLabel = myAccountDiv.querySelector('#mychart-user'); if(userLabel) userLabel.innerHTML = resp.text; const homeItem = resp.children.filter(l => l.text === "MyChart Home")[0]; if(homeItem) { /* Remove '/home' from mychart url */ myChartUrl = homeItem.href.slice(0, -5); } /* Populate dropdown menu */ const accountMenu = document.querySelector('#account-menu'); accountMenu.innerHTML = ''; resp.children.forEach(link => { const menuItem = document.createElement('li'); const menuItemLink = document.createElement('a'); menuItemLink.classList.add('mychart'); menuItemLink.href = link.href; menuItemLink.innerText = link.text; if(menuItemLink.href.includes('mychart')) { /* From Dustin B: if the user does not have an active session on the MyChart side, going directly to a deep link will present them with the login screen. This is not ideal, since we already know they have an active session with the external identity provider by virtue of being logged in to the Providence website. Unfortunately, MyChart does not have a mechanism to automatically obtain that session and redirect them to the deep-linked page. To get around this, we have a script on our side that overrides the click event on any MyChart links. */ menuItemLink.addEventListener('click', click => { click.preventDefault(); click.stopImmediatePropagation(); /* In a new tab, open MyChart link that automatically obtains the session and returns the user to the MyChart home page */ let other = window.open(`${myChartUrl}/Authentication/SAML/Login?idp=SSO`, "_blank"); if(other !== null && menuItemLink.getAttribute('href').toLowerCase().indexOf('/home') === -1) { /* Listen for a message from MyChart */ window.addEventListener("message", function(e) { /* Verify the sender and message */ if (myChartUrl.toLowerCase().indexOf(e.origin.toLowerCase()) === 0 && e.data === "Hello from MyChart") { /* Redirect the user to the deep link */ other.location = menuItemLink.getAttribute("href"); } }); } }); } menuItem.appendChild(menuItemLink); if(accountMenu) accountMenu.appendChild(menuItem); /* Show account menu now that it's populated */ myAccountDiv.classList.remove('hidden'); }); } } else if(resp.method === "POST") { myChartLoggedIn = false; /* remove account form if geo locator set to WA if(!geolocator || /, wa/gi.test(geolocator.value.toLowerCase())) { if(myAccountWrapper) myAccountWrapper.classList.add('hidden'); return; }*/ /* Update login post action url */ const myAccountLogin = document.querySelector('.my-account.unverified'); myAccountLogin.classList.remove('hidden'); const myAccountLoginAnchor = document.querySelector('#accountLogin'); myAccountLoginAnchor.addEventListener('click', e => { e.preventDefault(); fetch(resp.action, { method: "post" }); }); /*myAccountLogin.innerHTML = ''; const loginForm = document.createElement('form'); loginForm.setAttribute('action', resp.action); loginForm.setAttribute('method', "post"); loginForm.innerHTML = '<button type="submit" class="account-btn"><svg class="account" aria-hidden="true" role="img"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#account-icon"></use></svg>My Account</button>'; myAccountLogin.append(loginForm);*/ } else { const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(myAccountDiv) myAccountDiv.classList.add('hidden'); if(loginButton) { loginButton.classList.remove('hidden'); myAccountWrapper.classList.remove('hidden'); }; } }).catch(e => { console.error("Failed to receive users MyChart identity status", e); const myAccountDiv = document.querySelector('.my-account.verified'); const loginButton = document.querySelector('.my-account.unverified'); if(myAccountDiv) myAccountDiv.classList.add('hidden'); if(loginButton) { loginButton.classList.remove('hidden'); myAccountWrapper.classList.remove('hidden'); }; }); } /** <!-- end childReplacement provOmniLoginStatus --> **/ class Search { constructor(fetchUrl) { this.fetchUrl = fetchUrl; }; form = document.querySelector("#search-submit"); form2 = document.querySelector("#search-submit2"); input = document.querySelector("#query"); autocomplete = document.querySelector('#autocomplete'); textInputs = document.querySelectorAll(".textInput"); resultsEl = document.querySelector("#results"); paginationEl = document.querySelector("#pagination"); mapEl = document.querySelector('#map'); showMapEl = document.querySelector('#showmap'); headerEl = document.querySelector('header'); distanceInput = document.querySelector("#distance"); logo = document.querySelector('#header-logo'); isStaging = /staging/gi.test(window.location.hostname); apiSub = this.isStaging ? 'womphealthdevapi' : 'womphealthapi'; /* if user passes query param hos=true, we removed the IsClinic=true filter param from our call womphealth api. This allows Providence to link into the directory and closely match the sitecore provider listings by displaying ALL providers at a location, even if that provider only has the location listed as type HOS */ hosFlag = false; helpers = { async fetchWithTimeout(resource, options = {}) { const { timeout = 1000 } = options; const controller = new AbortController(); const id = setTimeout(() => controller.abort(), timeout); const response = await fetch(resource, { ...options, signal: controller.signal }); clearTimeout(id); return response; }, getCID: () => { let cid = localStorage.getItem('cid'); /* If we DON'T have a CID saved to localStorage... */ if (!cid || !cid.length) { /* If crypto's randomUUID func is available, create a CID with it */ if (typeof crypto?.randomUUID == 'function') { cid = crypto.randomUUID(); } /* Else, go old school... */ else { cid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /* Save the new cid to localStorage */ localStorage.setItem('cid', cid); } /* Send CID to the requestor */ return cid; }, getAvailability(state) { const availableDays = state?.filters?.AvailableDays === undefined ? [0, 1, 2, 3, 4, 5, 6] : Object.entries(state?.filters?.AvailableDays ?? {}).filter(([day, value]) => !state.specificDays || value.active).map(([day,]) => DAYS.indexOf(day)); return (availableDays.length === 0 ? [0, 1, 2, 3, 4, 5, 6] : availableDays).join(","); }, slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); }, debounce(func, delay = 200){ let timeout = null; return function(){ let context = this; let args = arguments; clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args) }, delay); }; }, getCookie(name) { if (name == "trace_id" && localStorage.getItem('trace_id')) { return localStorage.getItem('trace_id'); } const match = document.cookie.match(new RegExp(`(?=${name}=)([^;]*)`)); if (match) { return match[0].split('=')[1]; } }, reportToWheelhouse: async (data) => { try { /* Grabbing URL params from the window's current location */ let tempParams = new URLSearchParams(window.location.search); /* Save the Google ID for Wheelhouse lookups to session storage */ let ampEcidGoogle = sessionStorage.getItem('ampEcidGoogle'); if (!ampEcidGoogle) { ampEcidGoogle = this.helpers.getCookie('AMP_ECID_GOOGLE'); sessionStorage.setItem('ampEcidGoogle', ampEcidGoogle) } let wheelhouse = sessionStorage.getItem('wheelhouseData'); if (!wheelhouse) { const data = await fetch(`https://tealium.wheelhousedmg.com/i.gif?tealium_vid=${ampEcidGoogle}&__amp_source_origin=${encodeURIComponent(window.location.origin)}`) .then(res => res.json()); sessionStorage.setItem('wheelhouseData', JSON.stringify(data)); wheelhouse = data; } else { wheelhouse = JSON.parse(wheelhouse); }; let gclid = this.helpers.getCookie('u_gclid'); if (!gclid) { for (let p of ['gclid', 'u_gclid']) { /* If the linkers are set in the URL, use that value */ const tempParam = tempParams.get(p); if (tempParam) { gclid = tempParam; /* If we find a param, we don't need to continue looping through possible params. */ continue; } } } let piwik_vid = wheelhouse.piwik_vid; let tealium_vid = wheelhouse.tealium_vid; let dt = new Date(); let timezoneOffset = dt.getTimezoneOffset(); let tData = { dom_domain:window.location.host, dom_pathname:window.location.pathname, dom_title: window.document.title, dom_url: window.location.href, dom_viewport_height: window.innerHeight, dom_viewport_width: window.innerWidth, "dom.domain": window.location.host, "dom.pathname": window.location.pathname, "dom.title": window.document.title, "dom.url": window.location.href, "dom.viewport_height": window.innerHeight, "dom.viewport_width": window.innerWidth, lang: window.navigator.language, page_platform: "amp", piwik_id: piwik_vid, tealium_account: "providence", tealium_profile: "main", tealium_vid: tealium_vid, timestamp: Date.now(), tz: timezoneOffset, "send_to_as": "true", tealium_event:"amp_event" }; if (gclid) { tData.gclid = gclid; } for (const d of data) { tData[d.key] = d.value; }; let tealiumUrl = `https://tealium.wheelhousedmg.com/i.gif`; return fetch(tealiumUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(tData) }) .then(resp => resp.json()) .then(json => { console.log('wheelhouse beacon resp: ', json); let providers = document.querySelectorAll(".provider-link, .schedule-appt, .sched-dis"); let providerSlots = document.querySelectorAll("directory-timeslots"); const linkers = { _gl: { type: "cookie", enabled: true, encode: true, overwrite: false, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ], id: '_ga', linkerParams: ['_gl', '_ga'] }, gclid: { type: "cookie", enabled: true, encode: false, createCookie: true, overwrite: true, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ], id: 'u_gclid', linkerParams: ['gclid', 'u_gclid'] } }; const template = { type: "custom", enabled: true, destinationDomains: [ "provinnovate.com", "psjhmedgroups.org", "provhealth.org", "providence.org", "psjhealth.org", "clockwisemd.com", "swedish.org" ] }; linkers._pcid = {...template, id: "pcid", value: `${json._pcid}`}; for(let i = 0;i < providers.length; i++) { providers[i].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); providers[i].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } function onVisible(element, callback) { new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if(entry.intersectionRatio > 0) { callback(element); observer.disconnect(); } }); }).observe(element); if(!callback) return new Promise(r => callback=r); } /* Wait for shadowroot to load */ for(let i = 0;i < providerSlots.length; i++) { onVisible(providerSlots[i], () => { let slotLinks = providerSlots[i].shadowRoot.querySelectorAll(".schedule-appt, .sched-dis"); for(let j = 0;j < slotLinks.length; j++) { slotLinks[j].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); slotLinks[j].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } }); } /* Get initial shadowroot load, sometimes the Intersection observer isn't catching it */ setTimeout(() => { for(let i = 0;i < providerSlots.length; i++) { let slotLinks = providerSlots[i].shadowRoot.querySelectorAll(".schedule-appt, .sched-dis"); for(let j = 0;j < slotLinks.length; j++) { slotLinks[j].addEventListener("click", function(e) { addLinkers(e, linkers, false); }); slotLinks[j].addEventListener("contextmenu", function(e) { addLinkers(e, linkers, false); }); } } }, 2000); }) .catch(error => { console.log('wheelhouse beacon error: ', error); }); } catch (ex) { console.log('Error: failed to report to wheelhouse; ' + ex); } }, autoNudgeAmpElements: (enable) => { if (!enable) { /* removeEventListener will not complain if scrollCallback is undefined */ window.removeEventListener('scroll', this.helpers.autoNudgeAmpElements.scrollCallback); return; } if (!this.helpers.autoNudgeAmpElements.scrollCallback) { this.helpers.autoNudgeAmpElements.scrollCallback = this.helpers.debounce(function () { /* Always grab fresh set of elements on scroll callback in case new ones were dynamically added to the body */ const ampElements = [].concat( /*Array.from(document.getElementsByTagName('amp-list')),*/ Array.from(document.getElementsByTagName('amp-img')) ); /* Look for AMP elements in the viewport */ ampElements.forEach((ampElm) => { if (this.helpers.isInViewport(ampElm)) { /* Give AMP a chance to handle the lazy-load on its own before we force layout */ setTimeout(() => { this.helpers.nudgeAmpElementLayout(ampElm); }, 50); } }); }, 50).bind(this); } /* Trigger on leading edge, before a scroll event occurs */ this.helpers.autoNudgeAmpElements.scrollCallback(); window.addEventListener('scroll', this.helpers.autoNudgeAmpElements.scrollCallback); }, nudgeAmpElementLayout: (ampElement) => { if (!ampElement) return; if (ampElement.hasAttribute('data-no-nudge')) return; if (ampElement['__AMP__RESOURCE'] && ampElement['__AMP__RESOURCE'].isLayoutPending && ampElement['__AMP__RESOURCE'].isLayoutPending() && ampElement['__AMP__RESOURCE'].startLayout) { try { console.log('Manually called layout for ' + ampElement.tagName.toLowerCase()); ampElement['__AMP__RESOURCE'].layoutScheduled(Date.now()); ampElement['__AMP__RESOURCE'].startLayout(); } catch (ex) { console.error('Unable to start layout on AMP element\n', ampElement, ex); } } }, isInViewport: (element) => { if (!element) return false; const winLeft = 0; const winRight = window.innerWidth; const winTop = 0; const winBottom = window.innerHeight; /* Hopefully this won't ever happen */ if (winRight <= winLeft || winBottom <= winTop) return false; /* Only consider non-hidden elements */ if (!(element.offsetWidth || element.offsetHeight || element.getClientRects().length)) return false; /* Check that some part of the element is in the viewport */ const rect = element.getBoundingClientRect(); return rect.top <= winBottom && rect.bottom >= winTop && rect.left <= winRight && rect.right >= winLeft; }, trackWheelhouseEvents: async () => { try { const { filters, query, facets, previousQuery, useMyLocationClicked, updatedLocation } = this.state; const { reportToWheelhouse } = this.helpers; const { scrollLeft, scrollTop } = document.body; const { Gender, Languages, InsuranceAccepted, PracticeGroup } = facets; const getActiveFilters = (name) => { return facets[name] .reduce((arr, v) => { if (!filters[name]) return []; return filters[name][v]?.active ? [...arr, v] : arr; }, []); }; const createEventBody = (category, action, label, extraVals = []) => { const body = [ {key: "tealium_event", value: "amp_event"}, {key: "scroll_x", value: scrollLeft}, {key: "scroll_y", value: scrollTop}, {key: "event_category", value: category}, {key: "event_action", value: action}, {key: "event_label", value: label}, ...extraVals ]; return body; }; /* Send location set beacon */ if (useMyLocationClicked || updatedLocation) { this.state.updatedLocation = false; await reportToWheelhouse( createEventBody('Provider Search', 'location', 'set') ) } /* Send event for clicked "Use My Location" button */ if (useMyLocationClicked) { const isOn = document.querySelector('#switch')?.checked ? "on" : "off"; await reportToWheelhouse( createEventBody('Provider Search', 'useMyLocation', isOn) ); this.state.useMyLocationClicked = false; } /* Send analytic events for query or query removal */ /*if (query?.length) { await reportToWheelhouse( createEventBody('Provider Search', 'search-term', 'Search term set') ); } else if (previousQuery?.length) { await reportToWheelhouse( createEventBody('Provider Search', 'search-term-removed', 'Search term not set') ); }*/ /* Create the gender event, if a gender is active on search filters */ if (Gender.length) { const genders = getActiveFilters('Gender'); if (genders.length) { await reportToWheelhouse( createEventBody('Provider Search', 'gender', genders.join(',')) ); } } /* Create the languages event, if a language is active on search filters */ if (Languages.length) { const languages = getActiveFilters('Languages'); if (languages.length) { await reportToWheelhouse( createEventBody('Provider Search', 'language', languages.join(',')) ); } } /* Create the insurances event, if an insurance is active on search filters */ if (InsuranceAccepted.length) { const insurances = getActiveFilters('InsuranceAccepted'); if (insurances.length) { await reportToWheelhouse( createEventBody( 'Provider Search', 'Patient Insurance Information', insurances.join(','), [{ key: "cd64", value: insurances.join(',') }] ) ); } } /* Create the practice group event, if a medical group is active on search filters */ if (PracticeGroup.length) { const practiceGroups = getActiveFilters('PracticeGroup'); if (practiceGroups.length) { await reportToWheelhouse( createEventBody( 'Provider Search', 'medical-group', practiceGroups.join(',') ) ); } } } catch (err) { console.error(err); } }, trackWompEvent: async (data) => { const { ec, el, ea } = data; const { pathname, search } = window.location; await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthAnalytics?brand=${this.state.filters.brand}&t=event&ec=${ec}&ea=${ea}&el=${el}&cid=${this.helpers.getCID()}&dp=${encodeURIComponent(pathname + search)}`) .then(res => res.json()) .then(res => console.log(res)); }, createPixel: (params = {}) => { const wheelHouseDataRaw = document.querySelector('#wheelhouseJson'); const wheelhouseJson = wheelHouseDataRaw ? JSON.parse(wheelHouseDataRaw.innerText) : {}; const { pageType, page_subtype } = this.state; const { origin, pathname, href, host } = window.location; const { referrer, title } = document; const timestamp = new Date().toISOString(); const clientId = this.helpers.getCookie('_ga') || 'no client id'; const ECID = this.helpers.getCookie('AMP_ECID_GOOGLE') || 'no client id'; const baseUrl = "https://pc-providence-collect.tealiumiq.com/vdata/i.gif"; const account = "providence"; const profile = "main"; const datasource = "neli68"; let gclid = this.helpers.getCookie('u_gclid') || false; if (!gclid) { let tempParams = new URLSearchParams(window.location.search); for (let p of ['u_gclid', 'gclid']) { /* If the linkers are set in the URL, use that value */ const tempParam = tempParams.get(p); if (tempParam) { gclid = tempParam; /* If we find a param, we don't need to continue looping through possible params. */ continue; } } }; let fromAmpScript = {}; try { fromAmpScript = JSON.parse(document.querySelector('amp-analytics#tealium_collect script').innerHTML).extraUrlParams; } catch (e) { console.error(`Could not parse amp-script JSON. Error: ${e}`); }; const isInternal = fromAmpScript.internalUser || 'False'; let traceId = fromAmpScript.tealium_trace_id || 'Not Set'; if(traceId === 'Not Set') { let tealiumTraceId = getCookieInvoca("tealium_trace_id"); if(tealiumTraceId){ traceId = tealiumTraceId; } } const parameters = { /* Tealium defaults */ tealium_account: account, tealium_profile: profile, datasource: datasource, tealium_vid: ECID, /* Variables sent with every view and event */ hitTimestamp: timestamp, clientID: clientId, /* sessionID: 9999, This should be a cookie? */ user_agent: navigator.userAgent, utag_profile_version: `${profile} | ${datasource}`, "ut.visitor_id": ECID, "dom.title": title, "dom.url": href, "dom.domain": host, "dom.pathname": pathname, dom_title: title, dom_url: href, dom_domain: host, dom_pathname: pathname, event_url: origin + pathname, experience_platform: "Web - AMP", /** <!-- childReplacement brandTrack --> **/ brand: "Providence", /** <!-- end childReplacement brandTrack --> **/ web_auth_state: "Undetermined", pageType, page_subtype, state: this.state.filters.userState, tealium_event: 'womp_directory_screen_view', tealium_event_type: 'view', internalUser: isInternal, "send_to_as": "true", "search_location_filter_within": 'Not Set', /* location_partner: "NEED DATA", */ /* Event specific parameters passed into function */ ...params }; try { let searchParams = new URLSearchParams(window.location.search); if(searchParams.get('campaignid')) { parameters['campaignid'] = searchParams.get('campaignid'); } if(searchParams.get('tacticid')) { parameters['tacticid'] = searchParams.get('tacticid'); } if(searchParams.get('offersetid')) { parameters['offersetid'] = searchParams.get('offersetid'); } if(searchParams.get('channel')) { parameters['channel'] = searchParams.get('channel'); } } catch (error) { console.log(error); } try { let dist = this.state.filters.distance; if(!isNaN(dist) && dist) { parameters.search_location_filter_within = dist; console.log("dist: dist " + dist); } } catch (error) { console.log("Search radius is not set."); } if (traceId) { parameters['tealium_trace_id'] = traceId; } if (wheelhouseJson?.pcid) { parameters.pcid = wheelhouseJson.pcid; } if (gclid) { parameters.gclid = gclid; } const pixelUrl = baseUrl + Object.keys(parameters).reduce((str, key, i) => { const prefix = i === 0 ? '?' : '&'; return str + prefix + key + '=' + encodeURIComponent(parameters[key]); }, ''); return pixelUrl; }, /* Takes a pixel URL and adds it to the DOM for tracking */ trackIt: (data) => { let pixels = Array.isArray(data) ? data.reduce((str, url) => str + `<img src="${url}" />`, '') : `<img src="${data}" />`; /* 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', ` <div id="pixels-wrapper" style="display: none;"> ${pixels} </div> `); }; }, /* 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(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) => { let validZip = true; if (res.results[0]) { let resultIndex; try { resultIndex = res.results.findIndex(address => address.address_components.findIndex(ac => ac.long_name.toLowerCase().includes('united states') ) > -1 ); } catch (ex) { console.log(ex); resultIndex = -1; } if (resultIndex > -1) { AMP.setState({locationState: {validZip: 1}}); const geo = res.results[resultIndex].geometry.location; coords = { latitude: geo.lat, longitude: geo.lng, }; console.log(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 { validZip = false; } } else { siteSearch.helpers.getGoogleLocationFallback(); } if (!validZip) { console.log('invalid zip'); AMP.setState({locationState: {validZip: 0}}); return; } }); }; }, getGoogleLocationFallback: (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?components=postal_code%3A${ value }%7Ccountry%3AUS&key=AIzaSyC9aTi6UWN30wj-e4uJHJ5j0y6szBTQ4BY` ) .then((res) => res.json()) .then((res) => { let validZip = true; if (res.results[0]) { let resultIndex; try { resultIndex = res.results.findIndex(address => address.address_components.findIndex(ac => ac.long_name.toLowerCase().includes('united states') ) > -1 ); } catch (ex) { console.log(ex); resultIndex = -1; } if (resultIndex > -1) { AMP.setState({locationState: {validZip: 1}}); const geo = res.results[resultIndex].geometry.location; coords = { latitude: geo.lat, longitude: geo.lng, }; console.log(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 { validZip = false; } } else { validZip = false; } if (!validZip) { console.log('invalid zip'); 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); }; }, 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} <svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg>`; */ this.state.filters.location = newLoc; /** <!-- childReplacement provOmniGeoLocatorShowPosition --> **/ const zipParts = res.results[0].address_components.filter((x) => x.types .join("") .match( /postal_code/gi ) ); const zip = zipParts[0].long_name; const fullLocation = this.state.filters.location + ' ' + zip; const headerLocationLabel = document.querySelector('#geoLocatorLabel span'); headerLocationLabel.innerHTML = fullLocation; document.querySelector('#geoLocatorInput').value = fullLocation; checkLoginStatus(); /** <!-- end childReplacement provOmniGeoLocatorShowPosition --> **/ }); this.state.filters = { ...this.state.filters, coordinates: { latitude, longitude, }, locationType: "user", }; this.handleSubmit(); }, showError: (error) => { console.log("Error when trying to get users location:"); console.log(error.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}<br />${City}, ${Region} ${PostalCode}`; } else if (type == 'providerCard') { formattedAddress = data; let zip = formattedAddress.split(',').pop(); let cityState = formattedAddress.split(',').slice(0,2).join(',').trim(); formattedAddress = formattedAddress.replace(cityState, ''); formattedAddress = formattedAddress.replace(zip, ''); formattedAddress = formattedAddress.slice(2); formattedAddress = `${formattedAddress} ${cityState},${zip}`; /* Following inserts comma before Suite text if Suite text is present. */ if (formattedAddress.toLowerCase().indexOf("suite") >= 0) { let suiteIndex = formattedAddress.toLowerCase().indexOf("suite"); let potentiallyComma = suiteIndex - 2; if (formattedAddress[potentiallyComma] != ",") { formattedAddress = formattedAddress.slice(0, potentiallyComma + 1) + ', ' + formattedAddress.slice(suiteIndex); } } } return formattedAddress; }, formatPhoneNumber: (num, format = 'default') => { /* Filter only numbers from the input */ const cleaned = ('' + num).replace(/\D/g, ''); /* Check if the input is of correct */ const match = cleaned.match(/^(1|)?(\d{3})(\d{3})(\d{4})$/); if (match) { /* Remove the matched extension code. Change this to format for any country code.*/ const intlCode = (match[1] ? '' : ''); switch(format) { case 'hyphenated': return `${intlCode}${match[2]}-${match[3]}-${match[4]}`; default: return `${intlCode}(${match[2]}) ${match[3]}-${match[4]}`; }; }; return null; }, viewportLessThan: (px = 1000) => { return window.innerWidth < px; }, resolveLocation: (data) => { const hasZip = data.zip && data.zip.length > 1; const hasCity = data.city && data.city.length > 1; const hasState = data.state && data.state.length > 1; const hasNeighborhood = data.neighborhood && data.neighborhood.length > 1; const hasCounty = data.county && data.county.length > 1; if (hasNeighborhood) { return data.neighborhood + (hasState ? `, ${data.state}` : ''); } else if (hasZip) { return data.zip; } else if (hasCity && hasState) { return `${data.city}, ${data.state}`; } else if (hasCounty) { return data.county + (hasState ? `, ${data.state}` : ''); } else { return 'Seattle, WA'; } }, pushHistory: async (queryParams = '') => { const { filters, query, pageType, page_subtype, defaultLoc, currentLoc, pushHistory } = this.state; const { createPixel, trackIt, reportToWheelhouse } = this.helpers; const trackingPixels = []; let search_match = "Not Set"; try { let match_filters = this.state.results.info.facets.PrimarySpecialties; if(match_filters.length > 0) { search_match = match_filters.toString(); } else if(query && query.length) { search_match = "No Match"; } /* Potentially return all Matches */ /* Possibly problematic for searches such as "Joe has Cancer" */ /*let matches = this.state.results.info.matches; if(matches.length > 0) { search_match = matches.toString(); } */ } catch (error) { if(query && query.length) { search_match = "No Match"; } console.log("Search did not match PrimarySpecialties."); } /* Set all the search event params for Tealium */ const searchTrackingParams = { search_type: this.state.pageInit ? 'Passive' : 'Active', search_term: `${search_match}`, medical_group_filter: /** <!-- childReplacement medGroupTealiumParam --> **/ Object.values(filters.ProviderOrganization).some(po => po.active) ? 'Set' : 'Not Set', /** <!-- end childReplacement medGroupTealiumParam --> **/ new_patient_availability_filter: filters.clinicVisits ? 'Set' : 'Not Set', telehealth_availability_filter: filters.videoVisits ? 'Set' : 'Not Set', online_scheduling_filter: filters.onlineScheduling ? 'Set' : 'Not Set', search_location_filter: defaultLoc !== currentLoc ? 'Set' : 'Default', event_category: 'Directory Journeys', event_action: 'Provider Directory', event_label: 'Search / Filter', tealium_event: 'womp_directory_event', tealium_event_type: 'event' }; /* Create a new URLSearchParams object from the provided query string, as well as a URL object from the current page location */ const apiUrl = new URLSearchParams(queryParams); const url = new URL(window.location); const curUrl = new URLSearchParams(window.location.search); /* Set the referrer in state to the current URL. */ this.state.referrer = url; /* If the referrer is set on document, use that. If not, use what we've saved to state. */ const referrer = document.referrer ? document.referrer : this.state.referrer.href; /* WheelhouseDMG tracking */ await reportToWheelhouse([ { key: "tealium_event", value: "amp_screen_view" }, { key: "dom_referrer", value: referrer }, { key: "dom.referrer", value: referrer }, { key: "screen_width", value: `${window.innerWidth}x${window.innerHeight}`} ]); /* Create the pageview pixel and push it to our pixels array */ const pageView = createPixel({ "dom.referrer": referrer, "dom_referrer": referrer }); trackingPixels.push(pageView); /* Loop through the params we want to add and determine if they should be in the URL pushed to history state */ const paramsToAdd = [ "brand", "languages", "LocationsOnly", "gender", "insuranceaccepted", "primaryspecialties", /** <!-- childReplacement providerOrgParamToAdd --> **/ "ProviderOrganization", /** <!-- end childReplacement providerOrgParamToAdd --> **/ "page", "sortby", "visittypes", "query", "locationname", "userlocation", "practicegroup", "LocationsOnly", "time", "days", "Tier", "distance" ]; for (let param of paramsToAdd) { const { pageType, page_subtype, filters, query, currentLoc } = this.state; const { visitType, InsuranceAccepted, PrimarySpecialties, LocationName, PracticeGroup, LocationsOnly, tier, distance } = filters; /** <!-- childReplacement providerOrgGetFilter --> **/ let ProviderOrganization = filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetFilter --> **/ let val, keys; switch (param) { case "Tier": url.searchParams.delete("tier"); if(!tier) { url.searchParams.delete("Tier"); continue; } val = tier; break; case "time": val = this.state.filters.AvailableTime ?? "any"; break; case "days": if(!this.state.specificDays) { url.searchParams.delete("days"); continue; } val = this.helpers.getAvailability(this.state); break; case "LocationsOnly": if(!LocationsOnly) { continue; } val = LocationsOnly; break; case "query": val = query; break; case "visittypes": keys = Object.keys(visitType).filter(key => visitType[key]?.active === true && visitType[key]?.auto != true); if (keys.length) { /* Create query string from keys */ val = keys.reduce((str, key) => str.length ? str + `,${key}` : key, ''); /* Set value for clinic visits in pixel */ if (visitType['clinicVisits']?.active === true || (visitType['clinicVisits']?.active === false && ![null, undefined].includes(visitType.clinicVisits))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['new_patient_availability_filter'] = 'Set'; }else if(curUrl.get("visittypes").indexOf('clinicVisits') > -1) { searchTrackingParams['new_patient_availability_filter'] = 'Default'; }else { searchTrackingParams['new_patient_availability_filter'] = 'Set'; } } else { searchTrackingParams['new_patient_availability_filter'] = 'Not Set'; } /* Set value for virtual visits in pixel */ if (visitType['videoVisits']?.active === true || (visitType['videoVisits']?.active === false && ![null, undefined].includes(visitType.videoVisits))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['telehealth_availability_filter'] = 'Set'; }else if(curUrl.get("visittypes").indexOf('videoVisits') > -1) { searchTrackingParams['telehealth_availability_filter'] = 'Default'; }else { searchTrackingParams['telehealth_availability_filter'] = 'Set'; } } else { searchTrackingParams['telehealth_availability_filter'] = 'Not Set'; } /* Set value for virtual visits in pixel */ if (visitType['onlineScheduling']?.active === true || (visitType['onlineScheduling']?.active === false && ![null, undefined].includes(visitType.onlineScheduling))) { if(curUrl.get("visittypes") === null) { searchTrackingParams['online_scheduling_filter'] = 'Set'; sessionStorage.setItem("online_scheduling_filter", "Set"); }else if(curUrl.get("visittypes").indexOf('clinicVisits%2ConlineScheduling') > -1) { if(sessionStorage.getItem("online_scheduling_filter") === null) { searchTrackingParams['online_scheduling_filter'] = 'Default'; } else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } }else if(curUrl.get("visittypes").indexOf('onlineScheduling') > -1) { if(sessionStorage.getItem("online_scheduling_filter") === null) { searchTrackingParams['online_scheduling_filter'] = 'Default'; } else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } }else { searchTrackingParams['online_scheduling_filter'] = 'Set'; } } else { searchTrackingParams['online_scheduling_filter'] = 'Not Set'; } } else { /* Remove the visitTypes param from the query string */ url.searchParams.delete(param); /* Set the values for virtual and clinic visits on the tracking pixel */ searchTrackingParams['new_patient_availability_filter'] = 'Not Set'; searchTrackingParams['telehealth_availability_filter'] = 'Not Set'; searchTrackingParams['online_scheduling_filter'] = 'Not Set'; /* Break out of the loops current iteration */ continue; }; break; case "brand": case "page": val = this.state.filters[param]; break; case "locationname": if (LocationName) { val = LocationName; break; } else { url.searchParams.delete(param); continue; } case "distance": if (distance) { val = distance; break; } else { url.searchParams.delete(param); continue; } case "userlocation": val = currentLoc; break; case "gender": case "insuranceaccepted": case "languages": let lookup = { languages: 'provider_language_filter', gender: 'provider_gender_filter', insuranceaccepted: 'insurance_filter' }; const tracker = lookup[param]; const filter = this.state.filters[ param === 'gender' ? 'Gender' : param === 'languages' ? 'Languages' : 'InsuranceAccepted' ]; /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(filter).filter(f => filter[f]?.active === true && filter[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); searchTrackingParams[tracker] = "Set"; } else { searchTrackingParams[tracker] = "Not Set"; url.searchParams.delete(param); continue; } break; case "primaryspecialties": /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(PrimarySpecialties).filter(f => PrimarySpecialties[f]?.active === true && PrimarySpecialties[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); } else { url.searchParams.delete(param); continue; } break; /** <!-- childReplacement providerOrgParsing --> **/ case "ProviderOrganization": /* Create an array of keys from the filters that are active and that were not autoset by the API */ keys = Object.keys(ProviderOrganization).filter(f => ProviderOrganization[f]?.active === true && ProviderOrganization[f]?.auto != true); if (keys.length > 0) { val = keys.reduce((str, key, i) => str.length ? str + `,${key}` : key, ''); } else { url.searchParams.delete(param); continue; } param = 'providerorganization'; break; /** <!-- end childReplacement providerOrgParsing --> **/ case "sortby": if (apiUrl.get(param)) { val = apiUrl.get(param); } else { url.searchParams.delete(param); /* Break out of the loops current iteration */ continue; }; break; case "practicegroup": if (apiUrl.get(param)) { val = apiUrl.get(param); }else if(apiUrl.get("PracticeGroup")) { val = apiUrl.get("PracticeGroup"); } else { url.searchParams.delete(param); continue; } break; default: break; }; url.searchParams.set(param, val); }; if (window.history.pushState) { window.history.pushState('Find a Doctor','',url); } else { window.history.replaceState({}, 'Find a Doctor', url); this.state.pushHistory = true; } /* Push the current url to the window history, if it's not the initial load if (pushHistory) { window.history.pushState({}, "Womp Health Demo", url); } else { window.history.replaceState({}, "Womp Health Demo", url); this.state.pushHistory = true; }*/ /* Create pagination links based on url state */ this.paginate(); /* Create our tracking pixel for the search event and add it to the pixels array */ const searchEvent = createPixel(searchTrackingParams); trackingPixels.push(searchEvent); /* If the page has timeslots, create an event for Timeslot Enabled Results */ if (document.querySelectorAll('timeslots-widget, directory-timeslots').length > 0) { const timeslotEnabled = createPixel({ event_category: 'Directory Journeys', event_action: 'Provider Directory', event_label: 'Timeslot Enabled Results', tealium_event: 'womp_directory_event', tealium_event_type: 'event' }); trackingPixels.push(timeslotEnabled); }; /* Add the created pixels to the DOM to track the events */ trackIt(trackingPixels); /* Set this to false. Mainly being used for handling the search box update in state. */ this.state.pageInit = false; /* Track autocomplete if search was initated from the autocomplete el and if the window variable is true */ if (window.wmFromAutoComplete === true) { autocomplete.helpers.trackAutocomplete(this.state.query); window.wmFromAutoComplete = false; } await this.helpers.trackWheelhouseEvents(); }, getSlotsType: () => { const { visitType } = this.state.filters; if (!(visitType.clinicVisits?.active && visitType.videoVisits?.active)) { let tmp = Object.keys(visitType).filter(key => visitType[key]?.active); if (tmp.length) { return tmp[0].replace('Visits', ''); }; } return 'all'; }, /* Determine what, if any, timeslots to show for a provider */ checkForTimeslots: (NewClinicVisit, NewVideoVisit, DaysUntilNextAppt) => { let hasTimeslots = false; if (DaysUntilNextAppt <= 30 && (NewClinicVisit == 1 || NewVideoVisit == 1)) { hasTimeslots = true; } return hasTimeslots; }, createProviderLink: (omni) => { let providerLink = `/doctors/profile/${omni.ProfileUrl}`; if (/swedish/gi.test(location.origin) && omni.ProviderUniqueUrlSwedish) { providerLink = omni.ProviderUniqueUrlSwedish.replace(/https:\/\/[a-z\.]*\.org/gi, ''); } if (/pacmed|pacificmedicalcenters/gi.test(location.origin) && omni.ProviderUniqueUrlPacmed) { providerLink = omni.ProviderUniqueUrlPacmed.replace(/https:\/\/[a-z\.]*\.org/gi, ''); } if (/providence/gi.test(location.origin) && omni.ProviderUniqueUrlOnesite) { providerLink = omni.ProviderUniqueUrlOnesite; } return providerLink; } }; state = {}; map = { lookup: {}, markers: {}, locations: {}, infoWindow: false, promises: [], selectedNpi: false, selectedLocation: false, bounds: false, icons: {}, toggleVirtual: (isVirtual = false) => { if (isVirtual) { document.querySelector('main').classList.add('no-locations'); document.querySelector('#map').classList.remove('show-map'); } else { document.querySelector('main').classList.remove('no-locations'); document.querySelector('#map').classList.add('show-map'); }; }, selectProvider: npi => { const w = window.innerWidth; const npiCard = document.querySelector(`#npi${npi}`); /* Saving some time by not modifying the DOM if the NPI hasn't changed since last click */ if (npi !== this.map.selectedNpi || !npiCard.classList.contains('selected')) { /* Remove the selected classs from the currently selected Npi */ if (this.map.selectedNpi) { document.querySelector(`#npi${this.map.selectedNpi}`).classList.remove('selected'); }; /* Highight card for selected Npi */ this.map.selectedNpi = npi; npiCard.classList.add('selected'); }; /* We'll scroll to the card regardless, just in case the user changed scroll position since last click */ if (w < 1000) { this.resultsEl.scrollBy({ top: 0, /* 32px is the amount of spacing between the viewports left edge and the selected provider card */ /* only exception is the very first card (has 16px), but this still works for that edge case */ left: npiCard.getBoundingClientRect().left - 32, behavior: 'smooth' }); } else { this.helpers.scrollToTarget(npiCard, 30); }; }, createMarkers: () => { this.map.bounds = new google.maps.LatLngBounds(); try { /* Sets Map bounds to the searched for geoIP */ var ePoint1 = new google.maps.LatLng(this.state.results.geoip.coordinates.lat + 0.01, this.state.results.geoip.coordinates.lng + 0.01); var ePoint2 = new google.maps.LatLng(this.state.results.geoip.coordinates.lat - 0.01, this.state.results.geoip.coordinates.lng - 0.01); this.map.bounds.extend(ePoint1); this.map.bounds.extend(ePoint2); } catch (error) { console.log("Error: Could not get geoIP coordinates"); } const { locations, el, setMapOnAll, lookup } = this.map; const { createPixel, trackIt } = this.helpers; const ids = Object.keys(locations); const byUUID = {}; const selectFields = [ "id", "Region", "City", "PostalCode", "Address", "Name", "GeocodedCoordinate", "AddressId" ]; const encodedFields = encodeURIComponent(selectFields.join(',')); const encodedIds = encodeURIComponent(ids?.join(',')); /* ids?.length && uses short-circuit evalutaion to skip the fetch call if ids is null or not an array for any reason */ /*Using womphealthapi temp because weird data in staging ${this.apiSub} : 'womphealthdevapi' : 'womphealthapi'; */ ids?.length && fetch(`https://womphealthapi.azurewebsites.net/api/WompHealthData?brand=${this.state.filters.brand}&data=locations&ids=${encodedIds}&select=${encodedFields}`) .then(res => res.json()) .then(res => { res.forEach((loc, i) => { if (!loc) { console.debug(`Location information not available for id=${ids[i]}`); return; } const { pageType, page_subtype } = this.state; const { GeocodedCoordinate, City, Region, PostalCode, Address, Name } = loc; const uuid = loc.AddressId; lookup[loc.id] = uuid; if (!byUUID[uuid]) { byUUID[uuid] = loc; } const coordinates = { lat: loc.GeocodedCoordinate.coordinates[1], lng: loc.GeocodedCoordinate.coordinates[0] }; const coords = new google.maps.LatLng(coordinates.lat, coordinates.lng); const marker = new google.maps.Marker({ position: coords, map: el, icon: this.map.icons.default }); marker.icon.url = 'https://pacmed.azureedge.net/7914/[email protected]'; marker.addListener('mouseover', () => { try { marker.setIcon(this.map.icons.selected); } catch (error) { console.log(error); } }); marker.addListener('mouseout', () => { try { if(this.map.selectedMarker !== marker) { marker.setIcon(this.map.icons.default); } } catch (error) { console.log(error); } }); marker.addListener('click', () => { try { const isMobile = this.helpers.viewportLessThan(); this.map.resetMarkerIcon(); if (this.map.infoWindow) this.map.infoWindow.close(); this.map.selectedMarker = marker; this.map.selectedLocation = uuid; marker.setIcon(this.map.icons.selected); if (!isMobile) { /* Track pin clicks that open a location's info window */ trackIt(createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Click', event_label: Name, tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); /* Remove the selected class from the currently selected Npi if it's for another location */ if (this.map.selectedNpi && !this.map.locations[loc.id].some(curr => curr.Npi === this.map.selectedNpi)) { document.querySelector(`#npi${this.map.selectedNpi}`).classList.remove('selected'); }; const FormattedAddress = this.helpers.formatAddress('infoWindow', { City: City, Region: Region, PostalCode: PostalCode, Address: Address }); let providers = []; for (const [key, value] of Object.entries(this.map.lookup)) { if (this.map.locations[key] === undefined) { continue; } if (value && value == uuid) { providers = [...providers, ...this.map.locations[key]] }; }; const contentString = /*html*/`<div class="poi-info-window" data-location="${uuid}"> <div> ${FormattedAddress} <h3 style="margin: 8px 0 8px 0; font-size: 16px;">Providers</h3> ${providers.reduce((all, curr) => { const { Npi, virtual, ImageUrl, Name, ProviderTitle } = curr; const providerHtml = `<div class="provider" onclick="siteSearch.map.selectProvider(${Npi})"> <div class="provider-thumbnail-wrapper ${virtual ? 'hasVirtual' : ''}"> <div class="provider-thumbnail" style="background-image: url('${ImageUrl}')"></div> ${virtual ? `<div class="virtualVisits infoWindow"> <svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit"></use></svg> </div>` : ''} </div> <div class="provider-info"> <h4>${Name}</h4> <div class="provider-specialty">${ProviderTitle ? ProviderTitle : '<br />'}</div> </div> </div>`; return all + providerHtml; }, '')} ${!providers.length ? '<p>No providers found as this location</p>' : ''} </div> </div>`; /* Set and open info window */ const infoWindow = new google.maps.InfoWindow({ content: contentString }); this.map.infoWindow = infoWindow; this.map.infoWindow.open(el, marker); } else { this.map.el.setCenterWithOffset(marker.getPosition(), 0, 150); }; } catch (err) { console.log(err); throw err; } }); this.map.markers[uuid] = marker; try { /* Keep the map within the first 172.5 miles from the center */ if(i == 0 || ((this.map.bounds.Lh.hi - 2.5 < coordinates.lat && this.map.bounds.Lh.hi + 2.5 > coordinates.lat) && (this.map.bounds.ci.hi - 2.5 < coordinates.lng && this.map.bounds.ci.hi + 2.5 > coordinates.lng) )) { /* extend the bounds to include each marker's position */ this.map.bounds.extend(coords); const { bounds } = this.map; if (bounds.getNorthEast().equals(bounds.getSouthWest())) { var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01); var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01); this.map.bounds.extend(extendPoint1); this.map.bounds.extend(extendPoint2); }; } } catch (error) { console.log(error); try { if(i == 0 || ((this.map.bounds.La.hi - 2.5 < coordinates.lat && this.map.bounds.La.hi + 2.5 > coordinates.lat) && (this.map.bounds.eb.hi - 2.5 < coordinates.lng && this.map.bounds.eb.hi + 2.5 > coordinates.lng) )) { /* extend the bounds to include each marker's position */ this.map.bounds.extend(coords); const { bounds } = this.map; if (bounds.getNorthEast().equals(bounds.getSouthWest())) { var extendPoint1 = new google.maps.LatLng(bounds.getNorthEast().lat() + 0.01, bounds.getNorthEast().lng() + 0.01); var extendPoint2 = new google.maps.LatLng(bounds.getNorthEast().lat() - 0.01, bounds.getNorthEast().lng() - 0.01); this.map.bounds.extend(extendPoint1); this.map.bounds.extend(extendPoint2); }; } } catch (error) { console.log(error); } } this.map.el.fitBounds(this.map.bounds); this.map.setMapOnAll(this.map.el); }); }) .catch(err => console.log(err)); }, deleteMarkers: () => { if (this.map.markers) { this.map.setMapOnAll(null); this.map.infoWindows = {}; this.map.markers = {}; }; }, resetMarkerIcon: () => { const { markers, icons, selectedMarker } = this.map; if (selectedMarker) { selectedMarker.setIcon(icons.default); }; }, setOffset: () => { try { let windowWidth = window.innerWidth; if(windowWidth <= 960) { let top = this.helpers.pxInViewport(this.headerEl); let topOffset = document.getElementById('results-wrapper').offsetHeight + 60; this.mapEl.style.top = `${top}px`; this.mapEl.style.height = `calc(100vh - ${topOffset}px)`; } } catch (error) { console.log(error); let top = this.helpers.pxInViewport(this.headerEl); this.mapEl.style.top = `${top}px`; this.mapEl.style.height = `calc(100vh - ${top}px)`; } }, setMapOnAll: map => { const { markers, bounds } = this.map; let vals = Object.values(markers); for (let val of vals) { val.setMap(map); } }, build: () => { this.helpers.waitForGlobal('mapLoaded', () => { console.log('found google'); /* Add a prototype function to allow px offsets for map center */ google.maps.Map.prototype.setCenterWithOffset = function(latlng, offsetX = 0, offsetY = 0) { var map = this; var ov = new google.maps.OverlayView(); ov.onAdd = function() { var proj = this.getProjection(); var aPoint = proj.fromLatLngToContainerPixel(latlng); aPoint.x = aPoint.x+offsetX; aPoint.y = aPoint.y+offsetY; map.panTo(proj.fromContainerPixelToLatLng(aPoint)); }; ov.draw = function() {}; ov.setMap(this); }; /* Create the position object for centering the map and populate it with the coordinates we passed in. */ const position = { lat: 39.50, lng: -98.35 }; /* Set icons object */ this.map.icons = { default: { url: "https://pacmed.azureedge.net/7914/[email protected]", scaledSize: new google.maps.Size(24, 35), origin: new google.maps.Point(0,0), anchor: new google.maps.Point(12, 35) }, selected: { url: "https://pacmed.azureedge.net/7914/[email protected]", scaledSize: new google.maps.Size(24, 35), origin: new google.maps.Point(0,0), anchor: new google.maps.Point(12, 35) } }; /* Initialize the map on the page */ this.map.el = new google.maps.Map(document.getElementById("wm_map"), { mapTypeId: google.maps.MapTypeId.ROADMAP, gestureHandling: "greedy", disableDefaultUI: true, }); console.log('map initialized'); const {el: map} = this.map; /* POI marker visibility settings */ const styles = { default: [], hide: [ { featureType: "poi.business", stylers: [{ visibility: "off" }], }, { featureType: "poi.medical", stylers: [{ visibility: "off" }], }, { featureType: "transit", elementType: "labels.icon", stylers: [{ visibility: "off" }], } ], }; /* Update the map to show location data */ map.setOptions({ styles: styles["hide"] }); /* Set map offset from top of screen */ const w = window.innerWidth; /* Track Scroll */ /*google.maps.event.addListener(map, 'dragend', () => { let firstMapEvent = sessionStorage.getItem("fme"); if(!firstMapEvent || firstMapEvent === 'zoom load') { siteSearch.helpers.trackIt(siteSearch.helpers.createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Interaction', event_label: 'Scroll', tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); sessionStorage.setItem("fme", "occured"); } });*/ /* Track Zoom */ /*google.maps.event.addListener(map, 'zoom_changed', () => { let firstMapEvent = sessionStorage.getItem("fme"); if(!firstMapEvent) { sessionStorage.setItem("fme", "zoom load");} else if(firstMapEvent === 'zoom load') { siteSearch.helpers.trackIt(siteSearch.helpers.createPixel({ event_category: 'Key Engagements', event_action: 'Maps & Directions Interaction', event_label: 'Zoom', tealium_event: 'womp_directory_event', tealium_event_type: 'event' })); sessionStorage.setItem("fme", "occured"); } });*/ google.maps.event.addListener(map, 'click', () => { const selectedNpi = document.querySelector(`#npi${this.map.selectedNpi}`); if (selectedNpi) { selectedNpi.classList.remove('selected'); } if (this.map.infoWindow) { this.map.infoWindow.close(); this.map.resetMarkerIcon(); } }); }); } }; MapOldQueryParams = function () { /* Post migration to ProvOmni, providence will still have old links to providence search, using old query params. https://app.asana.com/0/1204100916165836/1205550979625263/f Handle query term replacement to map old providence query params to new ProvOmni params */ let queryString = document.location.search; if (queryString.includes('search-term')) { queryString = queryString.replace('search-term', 'query'); } if (queryString.includes('provider-organization')) { queryString = queryString.replace('provider-organization', 'providerorganization'); } if (queryString.includes('language=')) { queryString = queryString.replace('language=', 'languages='); } if (queryString.includes('insurance=')) { queryString = queryString.replace('insurance=', 'insuranceaccepted='); } if (queryString.includes('&location=')) { queryString = queryString.replace('&location=', '&userlocation='); } let acceptsNew = false; let offersVideo = false; if (queryString.includes('acceptingNewPatients=on')) { acceptsNew = true; queryString = queryString.replace('acceptingNewPatients=on', ''); } if (queryString.includes('virtualCare=on')) { offersVideo = true; queryString = queryString.replace('virtualCare=on', '') } if (acceptsNew && offersVideo) { queryString += '&visittypes=clinicVisits%252CvideoVisits'; } else if (acceptsNew) { queryString += '&visittypes=clinicVisits'; } else if (offersVideo) { queryString += '&visittypes=videoVisits'; } /* if the query string changed, refresh the page */ if (document.location.search != queryString) { document.location.search = queryString } }; containsDisclaimerSearch = (query) => { this.state.disclaimerSearch = false; /* reset */ const regex = new RegExp(this.state.disclaimerSearchKeywords.join( "|" ), "i"); /* true if query or input value includes a disclaimer keyword */ this.state.disclaimerSearch = regex.test(query) || regex.test(this.input.value); }; containsExcludeKeywords = (query) => { this.state.excludeSearch = false; /* reset */ const regex = new RegExp(this.state.excludeSearchKeywords.join( "|" ), "i"); /* true if query or input value includes an exclude keyword */ const exclude = regex.test(query) || regex.test(this.input.value); return exclude; }; handleSubmit = async (e, { ignoreElements = [] } = {}) => { this.MapOldQueryParams(); this.resultsEl.innerHTML = "<div class='loader'></div>"; this.paginationEl.style.display = "none"; this.map.locations = {}; this.map.selectedNpi = false; this.map.selectedLocation = false; this.map.selectedMarker = false; this.map.deleteMarkers(); try { let allDirtyE = document.querySelectorAll(".is-dirty"); allDirtyE.forEach((element) => { element.classList.remove('is-dirty'); }); } catch (error) { console.log(""); } try { if(this.state.query !== '') { document.getElementById("searchBox").classList.add("is-dirty"); } } catch (error) { console.log(error); } document .querySelectorAll(".dropdown, #searchBox") .forEach((drop) => { if(!ignoreElements.includes(drop.id)) { drop.classList.remove("active"); } }); document.querySelector('#fullsearch').checked = false; if (!this.state.pageInit) { this.state.previousQuery = this.state.query; this.state.query = this.input.value; }; /* Exclusions and disclaimers */ if (this.containsExcludeKeywords(this.state.query || '')) { this.state.excludeSearch = true; this.state.query = ''; this.input.value = ''; } this.containsDisclaimerSearch(this.state.query || ''); /* Destructing state */ const { query, geolocation, filters, isTest, autoApplied } = this.state; const { formatPhoneNumber, formatAddress, showError, showPosition, slugify, trackIt, createPixel } = this.helpers; /* Update previousSearch state */ if (query.length) { let fromLocal = localStorage.getItem('previousSearches'); let fromSession = sessionStorage.getItem('previousSearches'); /* If localStorage already existed, parse the string ot JSON. If not, create an empty array */ fromLocal !== null ? fromLocal = JSON.parse(fromLocal) : fromLocal = []; /* If sessionStorage already existed, parse the string ot JSON. If not, create an empty array */ fromSession !== null ? fromSession = JSON.parse(fromSession) : fromSession = []; /* Append the newest query to the array of previous searches */ let prevSearchesLocal = [...new Set([query, ...fromLocal])]; let prevSearchesSession = [...new Set([query, ...fromSession])]; /* Write the combined array back to localStorage */ localStorage.setItem('previousSearches', JSON.stringify(prevSearchesLocal.slice(0,5))); /* Write the combined array back to sessionStorage for analytics */ sessionStorage.setItem('previousSearches', JSON.stringify(prevSearchesSession)); }; /* Remove autofilters from what is being sent to the API */ if (autoApplied && autoApplied.length) { for (const filter of autoApplied) { const inState = filters[filter.name]; /* If we actually have this value in state, then set active to false */ if (inState && inState[filter.value]) { inState[filter.value].active = false; } } } /* Destructing filters */ const { previousPostalCode, distance, brand, Languages, Gender, InsuranceAccepted, PrimarySpecialties, visitType, coordinates, LocationId, PracticeGroupId, LocationsOnly, } = filters; let LocationName = filters.LocationName; let PracticeGroup = filters.PracticeGroup; /** <!-- childReplacement providerOrgGetFilter --> **/ let ProviderOrganization = filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetFilter --> **/ /* If certain terms are in search, set clinicVisits to true */ if (/(family medicine|family doctor|internal medicine|family care)/gi.test(query)) { const shouldSkipAutoFilter = query != this.state.previousQuery ? false : /(family medicine|family doctor|internal medicine|family care)/gi.test(this.state.previousQuery); if (!shouldSkipAutoFilter) { if (!filters.visitType) { filters.visitType = { clinicVisits: { active: true } } } else { filters.visitType = {...filters.visitType, clinicVisits: { active: true }} }; } }; /* If certain terms are in search, set videoVisits to true */ if (/(video|virtual)/gi.test(query)) { if (!filters.visitType) { filters.visitType = { videoVisits: { active: true } }; } else { filters.visitType = { ...filters.visitType, videoVisits: { active: true } }; }; }; /* If certain requirements are met and search terms exists, turn on 'near me' location toggle */ if (navigator.geolocation && !this.state.nearme && /near me/gi.test(query)) { this.state.nearme = true; navigator.geolocation.getCurrentPosition(showPosition, showError); return; }; /* Return to page one when new filters are applied */ if(!this.state.filters.init) { this.state.filters.page = 1; } /* Some variables for our API request */ const post = { search: query, top: 20, skip: (this.state.filters.page - 1) * 20, highlightPreTag: "", highlightPostTag: "", }; /* Starting point for building out the request params + filters */ let queryParams = `&top=${post.top}&skip=${post.skip}`; /* Check for a default search (provided in before-render when a slug is present in URL) */ const defaultSearch = document.querySelector('#defaultSearch'); const slugText = document.querySelector('#slugText'); if(this.state.filters.init && defaultSearch) { const slugSearchTerm = defaultSearch.querySelector('#slugSearchTerm'); const slugLocation = defaultSearch.querySelector('#slugLocation'); const searchString = slugSearchTerm.innerText; const locationString = slugLocation.innerText; this.state.filters.location = locationString; this.state.query = searchString; post.search = searchString; this.input.value = searchString; window.sessionStorage.setItem('previousQuery', searchString); } else if(!this.state.filters.init && slugText) { /* If additional searches are performed, remove the slug & slug description text */ slugText.remove(); } /* Add visit type, if applicable */ /* const types = Object.keys(visitType).filter(type => visitType[type] === true); if (types.length > 0) { queryParams += types.reduce((str, type) => str + `&${type}=1`, ""); }; */ if (visitType.clinicVisits?.active == true) { queryParams += '&AcceptingNewPatients=1'; } if (visitType.videoVisits?.active == true) { queryParams += '&VirtualCare=1'; } /* Grab languages, if applicable */ const languages = Object.keys(Languages).filter(lang => lang && Languages[lang]?.active === true); if (languages.length > 0) { queryParams += languages.reduce((str, lang, i) => i === 0 ? str + lang : `${str},${lang}`, "&Languages="); }; /* Grab genders, if applicable */ let genders = Object.keys(Gender).filter(gender => Gender[gender]?.active === true); let genderVals = ["Female", "Male", "Non-binary"]; for (const [key, value] of Object.entries(genders)) { if(!genderVals.includes(value)) { delete Gender[value]; let gendersCk = value.charAt(0).toUpperCase() + value.toLowerCase().slice(1); if(!genderVals.includes(gendersCk)) { genders.splice(key, 1); } } } if (genders.length > 0) { queryParams += genders.reduce((str, gender, i) => i === 0 ? str + gender.charAt(0).toUpperCase() + gender.toLowerCase().slice(1) : `${str},${gender.charAt(0).toUpperCase() + gender.toLowerCase().slice(1)}`, "&Gender="); for (const [key, value] of Object.entries(this.state.filters.Gender)) { if(!genderVals.includes(key)) { delete this.state.filters.Gender[key]; } } }; try { /* Check for preselected filters */ await this.helpers.fetchWithTimeout(`https://www.providence.org/integrationapi/getinsuranceselectionjson`, { timeout: 2000 }) .then(res => res.json()) .then(res => { console.log("getinsuranceselectionjson " + res.insurance); if(res.insurance !== null) { queryParams += `&InsuranceAccepted=${res.insurance}`; localStorage.setItem('omniSearchInsurance', res.insurance); } }); } catch (err) { console.error('Insurance selection API not responding:\n' + err); } /* Grab insurances accepted, if applicable */ const insurances = Object.keys(InsuranceAccepted).filter(ins => InsuranceAccepted[ins]?.active === true); if (insurances.length > 0) { let reduced = insurances.reduce((str, ins, i) => i === 0 ? str + ins : `${str},${ins}`, ""); if(reduced.indexOf(',') !== -1){ let currentInsurance = localStorage.getItem('omniSearchInsurance'); if(reduced.split(",")[0] === currentInsurance) { reduced = reduced.split(",").pop(); } else { reduced = reduced.substring(0, reduced.indexOf(',')); } } queryParams += `&InsuranceAccepted=${reduced}`; localStorage.setItem('omniSearchInsurance', reduced); try { await this.helpers.fetchWithTimeout("https://www.providence.org/integrationapi/SetInsuranceSelection?plan=" + reduced, { timeout: 2000 }); } catch (err) { console.error('Insurance selection API not responding.'); } }; /** <!-- childReplacement providerOrgQueryParams --> **/ /* Get medical organizations for ProvidenceOmni */ let organizations = Object.keys(ProviderOrganization).filter(spec => ProviderOrganization[spec]?.active === true); try { for (var i = 0; i < organizations.length; i++) { if(organizations[i] === 'Providence Affiliated Physicians') { let org = []; for (var j = 0; j < organizations.length; j++) { /* Check if valid medical organization */ if(this.state.facets.ProviderOrganization.includes(organizations[j])) { org.push(organizations[j]); } } let po = this.searchParams.get("ProviderOrganization").replaceAll(", ", "!"); po = po.split(','); for (var j = 0; j < po.length; j++) { po[j] = po[j].replace("!", ", "); /* Check if valid medical organization */ if(!this.state.facets.ProviderOrganization.includes(po[j])) { po.splice(j, 1); } } organizations = Array.from([...new Set([...org, ...po])]); break; } } } catch (error) { console.log(error); } /* Check if valid medical organization */ let orgs = []; for (var j = 0; j < organizations.length; j++) { /* Check if valid medical organization */ if(this.state.facets.ProviderOrganization.includes(organizations[j])) { orgs.push(organizations[j]); } } organizations = orgs; if (organizations.length > 0) { const encodedOrganizations = encodeURIComponent(organizations.reduce((str, spec, i) => i === 0 ? str + spec : `${str},${spec}`, "")); queryParams += `&ProviderOrganization=${encodedOrganizations}`; } /** <!-- end childReplacement providerOrgQueryParams --> **/ /* Grab primary specialties, if applicable */ const specialties = Object.keys(PrimarySpecialties).filter(spec => PrimarySpecialties[spec]?.active === true); if (specialties.length > 0) { queryParams += specialties.reduce((str, spec, i) => i === 0 ? str + spec : `${str},${spec}`, "&PrimarySpecialties="); }; /* Set filter for LocationName */ if (LocationName && LocationName.length) { LocationName = encodeURIComponent(LocationName); queryParams += `&LocationName=${LocationName}`; } /* Set filter for distance */ if (distance && distance.length) { queryParams += `&distance=${distance}`; } /* Set filter for PracticeGroup */ if (PracticeGroup && PracticeGroup.length) { PracticeGroup = encodeURIComponent(PracticeGroup); queryParams += `&PracticeGroup=${PracticeGroup}`; } /* Set filter for LocationId */ if (LocationId && LocationId.length) { queryParams += `&LocationId=${this.state.filters.LocationId}`; } /* Set filter for PracticeGroupId */ if (PracticeGroupId && PracticeGroupId.length) { queryParams += `&PracticeGroupId=${this.state.filters.PracticeGroupId}`; } /* Not 100% necessary but it double-checks that we do indeed have coordinates before adding them to the request. */ if (this.state.filters.location) { queryParams += `&location=${this.state.filters.location}`; } else if (this.state.currentLoc) { queryParams += `&location=${this.state.currentLoc}`; } /* If sortby is not false, set param */ if (this.state.filters.sortby) { queryParams += `&sortby=${this.state.filters.sortby}`; }; /* Create the user id if not present */ if (!localStorage.getItem('cid')) { localStorage.setItem('cid', crypto.randomUUID()); }; queryParams += `&cid=${localStorage.getItem('cid')}`; /* Set locations=true if LocationsOnly is set */ if(this.state.filters.LocationsOnly) { queryParams += `&locations=${this.state.filters.LocationsOnly}`; } if(this.state.specificDays && this.state.filters.AvailableDays) { queryParams += `&days=${Object.entries(this.state.filters.AvailableDays).filter(x => x[1].active).map(x => DAYS.indexOf(x[0])).join(",")}`; } if(this.state.filters.AvailableTime) { queryParams += `&time=${this.state.filters.AvailableTime}`; } if(this.state.filters.tier) { queryParams += `&Tier=${this.state.filters.tier}`; } /* Provider cards in the directory should not list their hospital locations as the "primary" address. This occurs because we index a provider record for each location for a provider. We can now filter out those records with IsClinic=true. */ /** <!-- childReplacement isClinicOmniQueryParam --> **/ queryParams += '&IsClinic=true'; /** <!-- end childReplacement isClinicOmniQueryParam --> **/ let userQueryParams = document.location.search; if (!this.hosFlag) { if (userQueryParams.includes('hos=true')) { /* The hos=true flag has not been raised and the param exists, so we must not filter out HOS locations. */ siteSearch.hosFlag = true; queryParams = queryParams.replace('&IsClinic=true', ''); } } else if (userQueryParams.includes('hos=true')) { /* If the HOS flag has been raised, that means a hos=true search has already been performed once, so we want to remove it from the url query params since it is no longer in use. It is only used for the initial search, then discarded for future searches */ userQueryParams = userQueryParams.replace('hos=true', ''); if (history.pushState) { window.history.pushState({}, "Find a Doctor | Providence", location.origin + location.pathname + userQueryParams); } } let degree = this.searchParams.get('degree'); if(degree !== null) { queryParams += `&Degrees=${degree.toUpperCase()}`; } /* Add a \ (%5C) to allow for , in queryParams, example - ProviderOrganization */ queryParams = queryParams.replaceAll('%2C%20','%5C%2C%20'); /*Change queryParams lowercase to uppercase for certain values to get results*/ const qStrings = [ 'Gender', 'InsuranceAccepted', 'Languages', 'LocationId', 'LocationName', 'PracticeGroup', 'PracticeGroupId', 'PrimarySpecialties', 'ProviderOrganization', 'userLocation', 'visitTypes' ]; const searchParams = new URLSearchParams(window.location.search); let qStringsLength = qStrings.length; for (let i = 0; i < qStringsLength; i++) { /*replace with correct queryParams*/ queryParams = queryParams.replace(qStrings[i].toLowerCase(), qStrings[i]); /*if not in queryParams check if in url*/ if(searchParams.get(qStrings[i]) && !(queryParams.includes(qStrings[i]) || queryParams.includes(qStrings[i].toLowerCase())) ) { queryParams += `&${qStrings[i]}=${searchParams.get(qStrings[i]).replaceAll(', ','%5C%2C%20').replaceAll('&','%26')}`; }else if(searchParams.get(qStrings[i].toLowerCase()) && !(queryParams.includes(qStrings[i]) || queryParams.includes(qStrings[i].toLowerCase())) ) { queryParams += `&${qStrings[i]}=${searchParams.get(qStrings[i].toLowerCase()).replaceAll(', ','%5C%2C%20').replaceAll('&','%26')}`; } } /* Sending the actual request */ await fetch( `${this.fetchUrl}&type=search&brand=${brand}&search=${post.search}${queryParams}${isTest ? '&test=true' : ''}` ) .then((res) => res.json()) .then((res) => { this.state.results = res; }) .then(() => { const { results, query, filters } = this.state; /* If coordinates are returned from the API, save them to state */ if (this.state.results.geoip) { const { geoip } = this.state.results; const { lat, lng } = geoip.coordinates; this.state.currentLoc = this.helpers.resolveLocation(geoip); localStorage.setItem('omniSearchLocation', this.state.currentLoc); queryParams += `&userLocation=${this.state.currentLoc}`; if (!this.state.defaultLoc) { this.state.defaultLoc = this.state.currentLoc; }; this.state.filters.coordinates = { latitude: lat, longitude: lng }; }; /* Update local storage with previous searches */ const terms = query.split(/\s+/); let previousTerms = window.sessionStorage.getItem('previousQuery'); previousTerms = previousTerms && previousTerms.length ? previousTerms.split(/\s+/) : []; let inserted = []; let deleted = []; for (let term of terms) { if (!previousTerms.includes(term)) { inserted.push(term); }; }; for (let term of previousTerms) { if (!terms.includes(term)) { deleted.push(term); }; }; window.sessionStorage.setItem('previousQuery', this.state.query); /* If we get an error from the API, write the error to the page and stop the rest of the function from running. */ if (results.error) { this.resultsEl.innerHTML = `<p>${this.state.results.error.message}</p>`; 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("filter-buttons"); filtersElem.innerHTML = ""; const filtersWrapper = 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")) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log("Couldn't remove filter"); } try { const searchRadiusVal = siteSearch.state.filters.distance; if (searchRadiusVal) { document.querySelector('#searchRadius').value = searchRadiusVal; } if (searchRadiusVal?.length) { let filterText = `Within ${searchRadiusVal} miles`; if (searchRadiusVal == 1) { filterText = `Within ${searchRadiusVal} mile`; } let distanceFilterEl = { facets: [{ name: "distance", value: filterText }], value: filterText }; siteSearch.state.results.info.filters.push(distanceFilterEl); } for (let filter of siteSearch.state.results.info.filters) { try { if (filter.facets[0].name === "IsClinic" || filter.facets[0].name === "Degrees") { continue; } } catch {} let filterButton = document.createElement("span"); let filterText = filter.value; /* LocationId becomes LocationName */ if ( 'info' in this.state.results && 'locations' in this.state.results.info && this.state.results.info.locations.length && this.state.results.info.locations.some((loc) => loc.id == filterText) ) { filterText = this.state.results.info.locations.find((loc) => loc.id == filterText)?.name; } filterButton.classList.add("filter-button"); filterButton.innerHTML = `${filterText} <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; 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 (e) { throw(e); } } /* 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 }]; } } try { if(dataFacets.length === 1) { filterButton.classList.add(dataFacets[0].name); } } catch (error) { console.log(error); } /* Set the data-facets attribute */ filterButton.setAttribute('data-facets', JSON.stringify(dataFacets)); /* Create an event listener to handle clicks, removing filters */ filterButton.addEventListener('click', e => { const { target } = e; const { dataset } = target; if (dataset.text) { let theRegEx = new RegExp(dataset.text, "gi"); window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } else if(target.textContent) { let theRegEx = new RegExp(target.textContent, "gi"); window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } const facets = JSON.parse(dataset.facets); let clearedInsurance = false; let clearInsurancePromise; for (let facet of facets) { const { name, value } = facet; const state = this.state.filters[name]; if (state) { if (typeof state === 'string') { this.state.filters[name] = false; } else if (typeof state === 'object') { state[value].active = false; /* Catch weird values that have ',' in them */ try { let csv = value.split(","); for (let v of csv) { state[v].active = false; } } catch (error) { console.log("Error removing cvs filter."); } } } if (name == "InsuranceAccepted") { let lsInsurance = localStorage.getItem('omniSearchInsurance') || false; if (lsInsurance) { lsInsurance = lsInsurance.split(',').filter(ins => { ins != value }); lsInsurance.length ? localStorage.setItem('omniSearchInsurance', lsInsurance.join(',')) : localStorage.removeItem('omniSearchInsurance'); clearedInsurance = true; clearInsurancePromise = fetch("https://www.providence.org/integrationapi/clearinsuranceselection"); } } else if (name == "distance") { let searchRadiusEl = document.querySelector('#searchRadius'); searchRadiusEl.value = ''; } } 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(); if (history.pushState) { window.history.replaceState({}, "Find a Doctor", url); } } catch (error) { console.log("Error updating url"); } if (clearedInsurance) { Promise.allSettled([clearInsurancePromise]).then(()=>this.handleSubmit()); } else { this.handleSubmit(); } if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } }); /* Add the filter button to the DOM */ filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } /* Add badges for Virtual and New Patient visit type selections */ if (this.state.filters.visitType) { const { clinicVisits, onlineScheduling, videoVisits } = this.state.filters.visitType; const hasClinicVisits = clinicVisits && clinicVisits.active; const hasOnlineScheduling = onlineScheduling && onlineScheduling.active; const hasVideoVisits = videoVisits && videoVisits.active; const handleVisitFiltersClick = e => { const { value } = e.target.dataset; const { filters } = this.state; const state = filters.visitType; if (state) { state[value].active = false; } if (value == 'onlineScheduling') { if (filters.visitType['clinicVisits'].active != document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.contains('active')) { document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.toggle("active"); } document.querySelectorAll("#daysList a").forEach(day => { siteSearch.updateDaySelectorStateAllDays(day, false) }); if (state) { state['clinicVisits'].active = false; } } try { if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log(error); } this.handleSubmit(); }; handleVisitFiltersClick.bind(this); if (hasClinicVisits) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Accepting New Patients <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "clinicVisits"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } if (hasOnlineScheduling) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Online Scheduling <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "onlineScheduling"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } if (hasVideoVisits) { let filterButton = document.createElement("span"); filterButton.classList.add("filter-button"); filterButton.innerHTML = `Video Visits <span class="pill-svg"><svg class="pill-close" height="20px" width="14px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pill-close"></use></svg></span>`; /* Set the data-facets attribute */ filterButton.setAttribute('data-value', "videoVisits"); filterButton.addEventListener('click', handleVisitFiltersClick); filtersElem.appendChild(filterButton); if(!filtersWrapper.classList.contains("hasFilter")){ filtersWrapper.classList.add("hasFilter"); } } /* Create an event listener to handle clicks, removing filters */ document.querySelectorAll('.visit-filter').forEach(handleVisitFiltersClick); } } catch (e) { console.log(`Facets failed to load`); } 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 = `<link rel="canonical" href="https://${baseUrl}${window.location.pathname}?page=${this.state.filters.page}">`; 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 = ""; let secondaryLocationIds = []; if (this.state.description) { resultsHtml += `<p style="margin-bottom: 1em; margin-top: 0;"><small>${this.state.description}</small></p>`; 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 += `<div class="search-type" style="display: flex; flex-wrap: wrap; margin: 10px 0;"> <h4 style="margin: 0 6px 0 0;">Search type(s):</h4> <p style="margin: 0;">${results.info.type.join(', ')}</p> </div>`; } /* Pretty self explanatory: if the results count is zero, tell the user there are no results */ if (count == 0) { this.resultsEl.innerHTML = "No results"; /* Set map to center on Bellevue */ this.map.el.setCenterWithOffset(new google.maps.LatLng(47.659159, -122.183832), 0, 0); return; } /* Otherwise, let's display the total number of results */ else { try { if(results.geoip.zip !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.zip}</span></p>`; } else if(results.geoip.city !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.city}</span></p>`; } else if(results.geoip.county !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.county}</span></p>`; } else if(results.geoip.state !== undefined){ resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results From <span class="c-b">${results.geoip.state}</span></p>`; }else { resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results</p>`; } } catch (error) { resultsHtml += `<p id="results-num">Showing <span class="c-b">${count.toLocaleString()}</span> Results</p>`; console.log(error); } } this.state.disclaimerSearch ? resultsHtml += '<h5>This page is intended to provide the most comprehensive information available regarding services offered in your area. Some of the services listed might not be offered at Providence facilities or in Providence affiliated practices. Providence is not responsible for services offered by providers on an independent basis.</h5>' : ''; /* 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 { ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, PrimarySpecialties, ProviderTitle, Addresses, PracticeGroup, LocationNames, LocationName, Rating, virtual, distance, distances, Npi, id, clinicVisits, NewClinicVisit, NewVideoVisit, GeocodedCoordinate, LocationId, LocationIds, RatingCount, ReviewCount, Degrees, Phones, ProfileUrl, AcceptingNewPatients, VirtualCare, SjhScheduleProviderNum, AllowsOpenSchedule, AllowsDirectSchedule, LocationsIsClinic, acceptingNewPatients, AppointmentRequestUrl, DaysUntilNextAppt, DaysUntilFollowUpAppt, Tier, Networks } = result; let ProviderOrganization = result.ProviderOrganization; const searchFeatures = result['@search.features']; const width = (150 / ImageHeight) * ImageWidth; const badges = result.badges ? Object.values(result.badges) : []; const supportsOdhp = (acceptingNewPatients == 1 && AllowsOpenSchedule == 1) || AllowsDirectSchedule == 1; const onlineBooking = supportsOdhp || SjhScheduleProviderNum ? true : false; let AcceptingClinicVisits = AcceptingNewPatients == 1 ? `<div class="new-patients mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg></span> <span class="anp">Accepting New Patients</span> </div>` : ""; const hasTimeslots = this.helpers.checkForTimeslots(NewClinicVisit, NewVideoVisit, DaysUntilNextAppt); /* Determining the description to show. If we have a Provider Title, use that. When no provider title, check for a primary specialty. If none, show no text. */ let descr = ""; if (PrimarySpecialties?.length) { descr = PrimarySpecialties.join(', '); } else if (ProviderTitle?.length) { descr = ProviderTitle; } else { descr = ""; }; this.map.deleteMarkers(); for (const location of LocationIds) { let isClinic; try { let iOfLoc = LocationIds.indexOf(location); isClinic = LocationsIsClinic[iOfLoc]; } catch (ex) { console.log(`Failed to determine if location is CLI for provider: Npi=${Npi}, LocationId=${location}; ` + ex); isClinic = true; } if (isClinic) { if (!this.map.locations[location]) { this.map.locations[location] = []; } this.map.locations[location].push({ Npi, ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, ProviderTitle: descr, Rating, PracticeGroup, virtual }); } } /* If we have badges for matched, create element HTML for it */ let badgesEls = ""; if (badges.length) { badges.forEach((badge) => {if (!badge.includes('PracticeGroupId')) {badgesEls += `<span>${badge}</span>`}}); } /* If a location name is present in the results, choose the proper text and create element HTML for it */ let locationName = ""; if (LocationName) { locationName = /*html*/`<h4 class="location-n"><span class="dd-svg"><svg class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span>${LocationName}</h4>`; } else if (LocationNames.length) { if (result["@search.highlights"] && result["@search.highlights"].LocationNames) { locationName = /*html*/`<h4 class="location-n"><span class="dd-svg"><svg class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span>${result["@search.highlights"].LocationNames[0]}</h4>`; }; }; let locationNames = false; if (LocationNames.length > 1) { locationNames = LocationNames.filter(loc => loc !== locationName); }; /* Loop through the LocationNames and Addresses arrays. If these are present, then build out the locs array for listing addresses on the provider detail cards */ let locs = []; let primaryLocation = false; let secondaryLocations = false; /* Formats address */ if (LocationNames.length && Addresses.length) { for (let i = 0; i < LocationNames.length; i++) { if (Addresses[i]) { locs.push({ name: LocationNames[i], address: formatAddress('providerCard', Addresses[i]), phone: Phones[i] }); } } /* Code for identifying clinics vs hospitals */ for (let i = 0; i < locs.length; i++) { locs[i].isClinic = result.LocationsIsClinic[i]; locs[i].distance = result.distances[i]; } let hasClinic = false; let tempLocArr = []; if (locs.length > 1) { locs.forEach(item => { if (item.isClinic == true) { hasClinic = true; } }); if (hasClinic) { for (let i = 0; i < locs.length; i++) { if (locs[i].isClinic) { tempLocArr.push(locs[i]); } } locs = tempLocArr; } } /* Locations should be sorted by distance Some providers have multiple locations within the same physical location (https://www.providence.org/doctors/general-surgery/mt/missoula/deron-ludwig-1245310200) In those cases, we want to display the first location in the list */ if (hasClinic && locs.length > 1) { locs = locs.sort((loc1, loc2) => loc1.distance - loc2.distance); } else { locs = locs.sort((a) => a.name === LocationName ? -1 : 1); } primaryLocation = locs[0]; secondaryLocations = locs.length > 1 ? locs.slice(1) : false; }; /* If a practice group is present in the result, choose the proper text and create element HTML for it */ let practiceGroup = ""; if (PracticeGroup.length) { if (result["@search.highlights"] && result["@search.highlights"].PracticeGroup) { practiceGroup = /*html*/`<small class="practice-group">${result["@search.highlights"].PracticeGroup[0]}</small>`; }; }; /* We only want to display review/rating data for providers that have "PHS Reviews Eligible Providers" listed as a network */ let rating = ""; if (Networks?.includes('PHS Reviews Eligible Providers')) { /* Create star rating for provider, if rating is present */ if (Rating) { let ratingFull = ""; let lastStar = parseInt(Rating.toString().slice(2,3)) * 1.6; if(Rating > 4) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3"></span> <span class="full-s fs-4"></span> <span class="full-s fs-5" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 3) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3"></span> <span class="full-s fs-4" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 2) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2"></span> <span class="full-s fs-3" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } else if(Rating > 1) { ratingFull = ` <span class="full-s fs-1"></span> <span class="full-s fs-2" style="width: ${lastStar}px; margin-left: 2px;"></span> `; } if(ReviewCount > 0) { rating = /*html*/`<div class="reviews hide-zero-average_rating${Rating}"> <span class="stars s${Rating}"> <span class="empty"> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> </span> <span class="full"> ${ratingFull} </span> </span> <span class="stats"> ${Math.round(Rating * 100) / 100} </span> <a class="survey providence" href="https://www.providence.org/about/our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> <a class="survey swedish" href="https://www.swedish.org/about/about-our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> </div> <div class="rating_comment_count"><span>${RatingCount} Ratings</span> | <span>${ReviewCount} Reviews</span></div>`; } else { rating = /*html*/`<div class="reviews hide-zero-average_rating${Rating}"> <span class="stars s${Rating}"> <span class="empty"> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> <span class="empty-s"></span> </span> <span class="full"> ${ratingFull} </span> </span> <span class="stats"> ${Math.round(Rating * 100) / 100} </span> <a class="survey" href="https://www.providence.org/about/our-survey"> <svg class="rating-info"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#info"></use></svg> </a> </div> <div class="rating_comment_count"><span>${RatingCount} Ratings</span></div>`; } } } /* If provider offers virtual visits, create virtual visit html */ let offersVirtual = ""; let virtualBadge = ""; if(VirtualCare && AcceptingNewPatients == 1) { AcceptingClinicVisits = ` <div class="vv-and-patients desktop-only"> <div class="virtualVisits"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div> <div class="new-patients mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg></span> <span class="anp">Accepting New Patients</span> </div> </div> <div class="virtualVisits mobile-only mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div> <div class="new-patients mobile-only mobile-map-hide"> <svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#accepting-patients"></use></svg> <span class="anp">Accepting New Patients</span> </div> `; } else if (VirtualCare) { offersVirtual = /*html*/ `<div class="virtualVisits"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span><span class="ovv">Offers Video Visits</span></div>`; virtualBadge = /*html*/`<div class="virtualVisits infoWindow mobile-map-hide"> <span class="vv-svg"><svg><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#video-visit-2"></use></svg></span> </div>` }; /* If a distance from ZIP exists, include that in the provider card. */ let distanceEl = ""; if (distance) { distanceEl = /*html*/`<div class="distance">${parseFloat(Number(distance).toFixed(1))} miles away</div>`; }; let name = Name; if (Degrees.length) { name = `${Name}, ${Degrees.join(', ')}`; }; let providerLink = this.helpers.createProviderLink(result); let wheelhouseData = sessionStorage.getItem('wheelhouseData'); if (!wheelhouseData) { try { const wheelHouseDataRaw = document.querySelector('#wheelhouseJson'); wheelhouseData = wheelHouseDataRaw ? JSON.parse(wheelHouseDataRaw.innerText) : {}; } catch (error) { console.log("Couldn't get wheelhouse data"); } } else{ wheelhouseData = JSON.parse(wheelhouseData); } const locName = primaryLocation?.name ? primaryLocation.name : LocationName ? LocationName : ''; const providerPhone = primaryLocation?.phone ?? Phones[0]; if (/swedish/gi.test(location.origin) && result.ProviderUniqueUrlSwedish) { /* use kyruus profile URL as source of truth for link to to profile */ providerLink = result.ProviderUniqueUrlSwedish; /* some providers still have old profile URL per request from Anita, overwrite with correct www. URL */ if (providerLink.includes('schedule.swedish.org')) { providerLink = providerLink.replace('schedule.swedish.org', 'www.swedish.org'); } } if (!/virtual/gi.test(locName)) { withLocations += 1; }; /* Determine whether or not to display the 90 days fallback message when timeslots are not available */ const shouldShowAlert = () => { return false; /* - We don't care about followup appointments anymore, only new patient availability if (DaysUntilFollowUpAppt && DaysUntilFollowUpAppt >=0 && DaysUntilFollowUpAppt <= 90) { return true; } else */ if (DaysUntilNextAppt && DaysUntilNextAppt >=0 && DaysUntilNextAppt <= 90) { return true; } else { return false; } }; const wompTrackData = { ec: '', el: `${name}, ${i+1}`, ea: JSON.stringify(result['@search.features']) }; let medicalGroupsAndAffiliations = ''; /** <!-- childReplacement provOmniMedicalAffiliations --> **/ try { ProviderOrganization = ProviderOrganization.filter(org => org.toLowerCase() != 'providence affiliated provider'); if (ProviderOrganization.length === 1) { if (ProviderOrganization[0].toLowerCase() === 'providence') { medicalGroupsAndAffiliations = ''; } else { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[0]} </div> </div>`; } } else if (ProviderOrganization.length > 1) { if (ProviderOrganization[0].toLowerCase() === 'providence') { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[1]} </div> </div>`; } else { medicalGroupsAndAffiliations = ` <div class="providerAffiliations"> <h4 class="location-name"> <span><svg class="prov-svg" height="16px" width="24px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#med-groups"></use></svg></span> <span class="medgroups">Medical Groups & Affiliations</span> </h4> <div class="med-group-affiliations"> ${ProviderOrganization[0]} </div> </div>`; } } } catch (ex) { console.log('Failed to display provider org: ' + ex); } /** <!-- end childReplacement provOmniMedicalAffiliations --> **/ let distanceSec = ""; try { if (distances) { distanceSec = /*html*/`<div class="distance">${parseFloat(Number(distances[0]).toFixed(1))} miles away</div>`; }; } catch (error) { console.log("Couldn't get distances"); } let brandedLocNames = ["Providence", "Jude Medical Center", "Mission Heritage", "Jude Heritage", "Joseph Heritage", "Mary High Desert", "Facey", "Joseph Hospital", "Santa Rosa Memorial", "Petaluma Valley Hospital", "Sacred Heart Medical", "Saint Johns Health Cancer", "Doctors of Saint", "Pavilion for Women", "Swedish", "Pacific Medical Centers", "Covenant", "Grace Clinics", "Grace Clinic", "Kadlec"]; let addPO = true; let dpo = ""; if(ProviderOrganization?.length > 0) { /** See provOmniMedicalAffiliations, interesting case where we use the second PO if there are 2 and the first is providence **/ if (ProviderOrganization.length > 1 && ProviderOrganization[0].toLowerCase() === 'providence') { dpo = `dpo-${ProviderOrganization[1].replace(/\s+/g, '')}`; } else { dpo = `dpo-${ProviderOrganization[0].replace(/\s+/g, '')}`; } for (var i = 0; i < brandedLocNames.length; i++) { if(locName.indexOf(brandedLocNames[i]) > -1){ addPO = false; break; } } } else { addPO = false; } let matchedLocName = locName; let matchedLocAdd = primaryLocation.address; try { if (secondaryLocations.length > 0){ for (let i = 0; i < secondaryLocations.length; i++) { /* check if secondary location matches what we are searching for */ if (this.searchParams.get('locationname').includes(secondaryLocations[i].name)) { let secondName = locName; let secondAdd = primaryLocation.address; matchedLocName = secondaryLocations[i].name; matchedLocAdd = secondaryLocations[i].address; secondaryLocations[i].name = secondName; secondaryLocations[i].address = secondAdd; } } } } catch (error) { console.log("couldn't find secondary location: " + error); } resultsHtml += /*html*/ `<div id="npi${Npi}" class="card card-body provider${addPO ? ` ${dpo}` : ''}" data-location="${LocationId}" data-npi="${Npi}" data-position="${i+1}" data-location-name="${locName}"> <div class="provider-image"> <a href="${providerLink}" class="provider-link"> <amp-img src="${ImageSourceUrl || ImageUrl}" alt="Image of ${name}" width="${ImageWidth ? width : 150}" height="150" layout="${ImageWidth ? "intrinsic" : "fill"}" ></amp-img> ${virtualBadge} </a> </div> <div class="provider-info"> <a href="${providerLink}" class="provider-link"> <h3 class="color-headings">${name}</h3> </a> <p class="resultDescription">${descr}</p> ${rating} ${offersVirtual} ${AcceptingClinicVisits} ${isTest && Tier ? `<div class="tier"><span>Tier: ${Tier}</span></div>` : ''} </div> <div class="locations-and-timeslots mobile-map-hide"> ${isTest && ProviderOrganization?.length ? '<div id="test__organizations"><h4>Organizations</h4>' + ProviderOrganization.reduce((html, curr) => html + `<p>${curr}</p>`, '') + '</div>' : '' } ${isTest && searchFeatures && Object.keys(searchFeatures).length ? '<div id="test__organizations"><h4>Search Features</h4>' + Object.keys(searchFeatures).reduce((html, key) => html + `<p>${key}: ${searchFeatures[key]?.similarityScore.toFixed(3)}</p>`, '') + '</div>' : '' } <div class="read-more-wrap"> <div class="location-name ${matchedLocName}"> <span class="hide-content"> <svg width="24" height="24" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#providence-icon"></use></svg> <svg width="24" height="24" class="swed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#swedish-icon"></use></svg> <svg width="16" height="24" class="pacmed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pacmed-icon"></use></svg> <svg width="24" height="24" class="cov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#covenant-icon"></use></svg> <svg width="24" height="24" class="grace-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grace-icon"></use></svg> <svg width="20" height="24" class="kadlec-svg" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#kadlec-icon"></use></svg> </span> <h4 class="location-n ${matchedLocName}"> ${matchedLocName} </h4> </div> <div class="address"> <div class="hov-ul">${primaryLocation?.address ? matchedLocAdd : ''}</div> ${distanceEl} </div> ${providerPhone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(providerPhone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(providerPhone, 'hyphenated')}">${formatPhoneNumber(providerPhone)}</a>` : '' } ${medicalGroupsAndAffiliations} <div class="badges">${badgesEls}</div> ${hasTimeslots ? /** <!-- childReplacement searchPageTimeslotsWidget --> **/ /*html*/` <directory-timeslots provider="${Npi}" provider-link="${providerLink}" address="${primaryLocation.address.split(',')[0] || ''}" staging="${this.isStaging}" days="${this.helpers.getAvailability(this.state)}" time="${this.state.filters.AvailableTime}" ></directory-timeslots>` /** <!-- end childReplacement searchPageTimeslotsWidget --> **/ : '' } </div> </div> <div class="remaining-data mobile-map-hide"> ${!onlineBooking && AppointmentRequestUrl?.length ? `<div> <a class="btn-primary r-consult" style="padding: 5px 15px 7px; display: inline-block; margin-top: 0.5rem;" href="${AppointmentRequestUrl}" target="_blank"> <small>Request a Consultation</small></a> </div>` : '' } ${!hasTimeslots && shouldShowAlert() ? /*html*/` <div> <span class="fp-return-patients mychart-wa-mt"> Return Patients <a href="https://mychartwa.providence.org/mychart/Authentication/Login" target="_top">schedule with MyChart</a> </span> </div>` : '' } <input type="checkbox" class="read-more-state" id="provider${Npi}" /> ${secondaryLocations.length ? /*html*/` <div class="read-more-target"> ${secondaryLocations.reduce((html, curr) => { return html + /*html*/ `<div class="additional-location" data-location-name="curr.name" data-location=""> <div class="location-name ${curr.name}"> <span class="hide-content"> <svg width="24" height="24" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#providence-icon"></use></svg> <svg width="24" height="24" class="swed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#swedish-icon"></use></svg> <svg width="16" height="24" class="pacmed-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#pacmed-icon"></use></svg> <svg width="24" height="24" class="cov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#covenant-icon"></use></svg> <svg width="24" height="24" class="grace-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#grace-icon"></use></svg> <svg width="20" height="24" class="kadlec-svg" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#kadlec-icon"></use></svg> </span> <h4 class="location-n ${curr.name}"> ${curr.name} </h4> </div> <div class="address"> <div class="hov-ul">${curr.address}</div> </div> ${distanceSec} <span id="${Npi}-${curr.name}"> <div class="phone"> <small>${curr.phone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(curr.phone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(curr.phone, 'hyphenated')}">${formatPhoneNumber(curr.phone)}</a>` : ''}</small> </div> </span> </div>` }, '')} </div> ` : ''} ${secondaryLocations.length ? `<div> <label for="provider${Npi}" class="read-more-trigger"> <span class="loc-wrap"> <svg class="expand" width="14" height="14" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chev-down"></use></svg> <svg class="compress" width="14" height="2" ><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#chev-up"></use></svg> </span> ${secondaryLocations.length} <span class="more-location">More</span> <span class="less-location">Less</span> Location${secondaryLocations.length === 1 ? '' : 's'} </label></div>` : ''} </div> </div>`; try { for (let i = 0; i < secondaryLocations.length; i++) { secondaryLocationIds.push(LocationIds[LocationNames.indexOf(secondaryLocations[i].name)]); }; } catch (error) { console.log(error); } }); } if (results.info.locations) { /* Check if clinic locations are stored as object or string */ let clinicNamesAlpha; if (results.info.locations.length && results.info.locations[0].name) { let greenListedLocs = ['providence', 'mission heritage', 'st. jude heritage', 'st. joseph heritage', 'st. mary high desert', 'facey', 'st. joseph hospital', 'st. jude medical center', 'covenant', 'swedish', 'pacific medical centers', 'kadlec', 'grace clinics', 'st. jude', 'grace clinic', 'oregon clinic']; document.CurrentClinics = results.info.locations.filter(loc => greenListedLocs.some(gLoc => loc.name.toLowerCase().includes(gLoc)) ); let alphaSortedClinics = document.CurrentClinics.sort((locA, locB) => { return locA.name.toLowerCase().localeCompare(locB.name.toLowerCase()); }); clinicNamesAlpha = alphaSortedClinics.map(loc => loc.name); } else { clinicNamesAlpha = results.info.locations; } document.querySelector('#location-selection').innerHTML = clinicNamesAlpha.reduce((html, name) => { return html + `<option value="${name}" ${name === this.state.filters.LocationName ? 'selected' : ''}>${name}</option>`; }, `<option class="clinic-value" value="">Select a Clinic</option>`); document.querySelector('#mobile-location-selection').innerHTML = clinicNamesAlpha.reduce((html, name) => { return html + `<li><a tabindex="0" class="dropdown-item dc facet-value loc-sel">${name}</a></li>`; }, `<li><a tabindex="0" class="dropdown-item dc facet-value loc-sel">Select a Clinic</a></li>`); } else { document.querySelector('#custom-location + small.dc')?.remove(); document.querySelector('#location-selection')?.remove(); document.querySelector('#mobile-location-selection')?.remove(); } /* We want to make sure the corrected text from search response is always in the search box. If there's been a spelling correction, update the input value */ let decodedQ = query; try { decodedQ = decodeURIComponent(query); } catch { console.log(`Search query "${query}" is not a valid URI.`); } if (decodedQ !== results.info.search) { resultsHtml = /*html*/ `<div> <h5 class="results-text">Showing results for <strong>${results.info.search}</strong></h5> </div> ${resultsHtml}`; this.state.query = results.info.search; this.input.value = results.info.search; }; if (!decodedQ?.length) { this.input.value = ""; } if (results.info.any) { resultsHtml = /*html*/ `<div> <h5 class="results-text" data-no-search-results>There are no exact matches for ${ query.length ? `"${decodeURIComponent(this.state.query)}"` : "your search" }. Here are some similar results:</h5> </div> ${resultsHtml}`; }; /* If an exclude search keyword was used */ if (this.state.excludeSearch) { resultsHtml = /*html*/ `<div> <h5 class="results-text" data-no-search-results>There are no results for this search. Here are some similar results:</h5> </div> ${resultsHtml}`; } /* Update the HTML for our results container to show new results */ this.resultsEl.innerHTML = resultsHtml; try { let addLocs = document.getElementsByClassName("additional-location"); for (let i = 0; i < addLocs.length; i++) { addLocs[i].setAttribute("data-location", secondaryLocationIds[i]); } } catch (error) { console.log("Couldn't add data-location to additional location: " + error); } if (LocationsOnly || withLocations > 0 ) { this.map.toggleVirtual(false); } else { this.map.toggleVirtual(true); } }) .then(() => { if (this.state.filters.visitType) { queryParams += `&visittypes=${Object.keys(this.state.filters.visitType).join(',')}`; }; /* Set show the proper filters as having been selected */ this.displayFilters(); this.map.createMarkers(); this.helpers.pushHistory(queryParams); }) .then(async () => { const res = this.state.results; /* Query secondary locations for timeslot availability */ /* Populate array with KyruusId-LocationIds */ const parsedIds = []; /* Check each provider for multiple locations & create necessary timeslots */ for(let i = 0; i < res.results.length; i++) { let promises = []; const { LocationIds, LocationId, KyruusId } = res.results[i]; if(LocationIds.length > 1) { /* If more than 2 locations, loop through each location and check for availability */ for(let j = 0; j < LocationIds.length; j++) { /* Skip primary location because its timeslot data was already returned from omni */ if(LocationId === LocationIds[j]) { continue; } else { parsedIds.push(`${KyruusId}-${LocationIds[j]}`); } } } } const encodedIds = encodeURIComponent(parsedIds?.join(',')); const whDataUrl = `https://${this.apiSub}.azurewebsites.net/api/WompHealthData?`; const selectFields = [ "NewClinicVisit", "NewVideoVisit", "DaysUntilNextAppt", "Npi", "LocationName", "ProfileUrl", "ProviderUniqueUrlSwedish", "ProviderUniqueUrlPacmed", "ProviderUniqueUrlOnesite", "Phones" ]; const encodedFields = encodeURIComponent(selectFields.join(',')); /* parsedIds?.length && uses short-circuit evalutaion to skip the fetch call if ids is null or not an array for any reason */ parsedIds?.length && fetch(`${whDataUrl}&brand=${brand}&data=providers&ids=${encodedIds}&select=${encodedFields}`).then((res) => res.json()).then(res => { res.forEach((prov, i) => { if (!prov) { console.debug(`Location information not available for id=${parsedIds[i]}`); return; } const hasTimeslots = this.helpers.checkForTimeslots(prov.NewClinicVisit, prov.NewVideoVisit, prov.DaysUntilNextAppt); if (hasTimeslots) { const providerLink = this.helpers.createProviderLink(prov); const locTimeslotWrapper = document.getElementById(prov.Npi + "-" + prov.LocationName); if (locTimeslotWrapper) { locTimeslotWrapper.innerHTML = /*html*/` <timeslots-widget phone="${this.helpers.formatPhoneNumber(prov.Phones[0])}" provider="${prov.Npi}" type="${this.state.slotsType}" color="#3572A9" days-ahead="30" more-appointments-url="${providerLink}#aptType" ></timeslots-widget> `; } } }); }).catch(err => { console.error(err); }); fireInvoca(); }); }; renderLocations = (locations) => { if(!Array.isArray(locations)) { throw new Error(`Expected array, got ${typeof locations}`); } let resultHTML = ""; locations.forEach(location => { if(location.type !== "location") { throw new Error(`Expected location, got ${location.type ?? "<no type>"}`) } const { ImageUrl, ImageSourceUrl, ImageWidth, ImageHeight, Name, PrimarySpecialties, ProviderTitle, Addresses, Address, PracticeGroup, LocationNames, LocationName, Rating, virtual, distance, Npi, id, clinicVisits, NewClinicVisit, NewVideoVisit, GeocodedCoordinate, RatingCount, Degrees, Phones, ProfileUrl, AcceptingNewPatients, VirtualCare, Phone } = location; const { formatPhoneNumber } = this.helpers; /* If we have badges for matched, create element HTML for it */ const badges = location.badges ? Object.values(location.badges) : []; let badgesEls = badges.reduce((acc, badge) => acc + `<span>${badge}</span>`, ""); /* TODO map the locations better */ if (!this.map.locations[id]) { this.map.locations[id] = []; } /* Determine the image to show for the location */ let locationImageSrc = 'https://az690879.vo.msecnd.net/7772/clinic-placeholder.jpg'; if (ImageSourceUrl?.length) { locationImageSrc = ImageSourceUrl; } else if (ImageUrl?.length) { locationImageSrc = ImageUrl; } /* Build the card for the location */ resultHTML += ` <div id=${id} class="card card-body" data-location="${id}"> <div class="provider-image"> <a href="#"> <amp-img src="${locationImageSrc}" width="120" height="120"></amp-img> </a> </div> <div class="provider-info"> <a href="#"> <h3 class="color-headings">${Name}</h3> </a> <div class="read-more-wrap"> <div class="address"> <div class="hov-ul">${Address}</div> </div> ${Phone ? `<a class="phoneLink" href="tel:${formatPhoneNumber(Phone, 'hyphenated')}" data-vars-phone-link="${formatPhoneNumber(Phone, 'hyphenated')}">${formatPhoneNumber(Phone)}</a>` : ''} </div> <div class="badges">${badgesEls}</div> </div> </div> `; }); return resultHTML; }; getLocation = (e) => { /** <!-- childReplacement provOmniGeoLocatorLoadingLocation --> **/ const headerLocationLabel = document.querySelector('#geoLocatorLabel span'); headerLocationLabel.innerHTML = 'Detecting...'; /** <!-- end childReplacement provOmniGeoLocatorLoadingLocation --> **/ const LocationLabel = document.querySelector("#LocationLabel"); LocationLabel.innerHTML = `Location: Detecting... <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; if (navigator.geolocation) { navigator.geolocation.getCurrentPosition(siteSearch.helpers.showPosition, siteSearch.helpers.showError, { timeout: 10000 }); }; }; createClickListener = async () => { const { trackWompEvent } = this.helpers; document.body.addEventListener("click", async (e) => { const clearActiveDropdowns = () => document .querySelectorAll(".dropdown, #searchBox") .forEach((drop) => drop.classList.remove("active")); const el = e.target; const parent = el.parentNode; const { filters, query } = this.state; /* If this is an autocomplete... */ if (parent.id == "autocomplete") { this.input.value = el.innerText; this.state.query = el.innerText; this.handleSubmit(); return; }; if (el.id == "LocationLabel") { setTimeout(() => { if (document.querySelector('.dropdown.location.active')) { const el = document.querySelector('#custom-location'); el.select(); } }, 100); } /** <!-- childReplacement provOmniGeoLocatorClickEvent --> **/ /* Providence specific geolocation toggle in the top nav */ if (el.id == 'geoLocatorInput') { document.querySelector('#geoLocatorSuggestions').style.display = 'block'; el.select(); } if (el.id == 'geoLocatorClear') { document.querySelector('#geoLocatorSuggestions').style.display = 'none'; if(window.innerWidth < 1000) { document.querySelector('main').style.setProperty('z-index','auto'); } } /** <!-- end childReplacement provOmniGeoLocatorClickEvent --> **/ /* If user is clicking the 'update location' button then... */ if (el.id == "updateLocation") { const locNameVal = document.querySelector('#location-selection').value; if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } const searchRadiusVal = document.querySelector('#searchRadius').value; /* ∞ */ if (searchRadiusVal?.length) { this.state.filters.distance = searchRadiusVal; } else { this.state.filters.distance = ''; } this.helpers.getGoogleLocation(); }; if(el.classList.contains("search-clear-label")) { document.querySelector("#search-input").value = ""; document.querySelector("#search-link").href= "https://www.providence.org/search-results"; } if (el.classList.contains("visitType")) { const type = el.dataset.type; const btnEls = document.querySelectorAll(".visitType"); let submitViaOnlineScheduling = false; switch (type) { case "all": /* If something other than 'all' is selected... */ if (filters.visitType) { this.state.filters.visitType = false; btnEls.forEach((el) => el.classList.remove("active")); el.classList.add("active"); break; /* else, just exit the function */ } else { return; } default: /* Remove the active stage on 'All' */ btnEls[0].classList.remove('active'); /* If we don't have an active filter, create the object */ if (!filters.visitType) { filters.visitType = {}; } if (type == "onlineScheduling") { /* Set the value for this visit type to the opposite of previous value */ filters.visitType["clinicVisits"] = { active: !filters.visitType[type]?.active }; filters.visitType[type] = { active: !filters.visitType[type]?.active }; /* Toggle the active state */ el.classList.toggle("active"); if (filters.visitType[type].active != document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.contains('active')) { document.querySelector(`.visitType[data-type="clinicVisits"]`).classList.toggle("active"); } document.querySelectorAll("#daysList a").forEach(day => { siteSearch.updateDaySelectorStateAllDays(day, filters.visitType[type].active) }); submitViaOnlineScheduling = true; } else { /* Set the value for this visit type to the opposite of previous value */ filters.visitType[type] = { active: !filters.visitType[type]?.active }; /* Toggle the active state */ el.classList.toggle("active"); } /* Highlight 'All' if both video and clinic visits are unselected */ if (Object.keys(filters.visitType).every(key => filters.visitType[key]?.active == false)) { btnEls[0].classList.add('active'); } break; }; try { if(document.querySelectorAll('.filter-button').length === 1) { document.getElementById("filterWrapper").classList.remove("hasFilter"); } } catch (error) { console.log(error); } if (submitViaOnlineScheduling) { this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); } else { this.handleSubmit(); } }; /* Don't close the dropdown when clicking in a dropdown menu or input */ if(el.classList.contains("dropdown-menu") || el.id === "insuranceInput" || el.id === "specialtyInput") { return; } /* If we're clicking the body of the document and a dropdown is active, remove active state */ if ( document.querySelector(".dropdown.active") && !( el.closest('.dropdown') || parent.classList.contains("dropdown") || el.classList.contains("dropdown-item") || el.classList.contains("dc") ) ) { clearActiveDropdowns(); }; /* If we're clicking the body of the document and search is active, remove active state */ if(document.querySelector("#searchBox.active")&& !(el.closest('#searchBox') || parent.parentNode.id === 'searchBox')) { document.querySelector("#searchBox.active").classList.remove("active"); } /* If clicking in a dropdown... */ if (parent.classList.contains("dropdown")) { if (!parent.classList.contains("active")) { clearActiveDropdowns(); el.parentNode.classList.add("active"); /* Auto focus on the input element if one exists */ const toggleInput = el.parentNode.querySelector("input"); if (toggleInput) { toggleInput.focus(); } } else { el.parentNode.classList.remove("active"); }; return; /* This prevents further logic from running unnecessarily */ }; /* If clicking in an element inside a dropdown... */ if (parent.classList.contains("dropdown-toggle")) { if (!parent.parentNode.classList.contains("active")) { clearActiveDropdowns(); parent.parentNode.classList.add("active"); } else { parent.parentNode.classList.remove("active"); }; return; /* This prevents further logic from running unnecessarily */ }; /* If clicking the search bar */ if (parent.classList.contains("input-hover")) { if (!parent.parentNode.classList.contains("active")) { clearActiveDropdowns(); parent.parentNode.classList.add("active"); } else { parent.parentNode.classList.remove("active"); } } if(el.id === 'location-selection') { let locNameVal = document.querySelector('#location-selection').value; if(locNameVal !== this.state.filters.LocationName && locNameVal !== "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } else if(el.id === 'locationSelection') { let locNameVal = document.querySelector('#locationSelection').value; if(locNameVal !== this.state.filters.LocationName && locNameVal === "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } /* Mobile location selection for the clinic selection */ if(el.classList.contains("loc-sel")) { let locNameVal = el.text; if(locNameVal !== this.state.filters.LocationName && locNameVal !== "") { if (locNameVal?.length) { this.state.filters.LocationName = locNameVal; } else { this.state.filters.LocationName = ''; } this.helpers.getGoogleLocation(); } } /* Handle dropdown logic */ if (el.classList.contains("facet-value")) { if(el.classList.contains("loc-sel")) { if(el.text !== "Select a Clinic") { document.getElementById("location-selection").value = el.text; }else { document.getElementById("location-selection").value = ""; } document.getElementById("locationSelectionText").textContent = el.text; document.getElementById("locationSelectionInput").checked = false; return; } const toggleEl = el.closest('.dropdown.facet, .dropdown.sort'); const filterID = toggleEl.id; const isActive = el.classList.contains("active"); if (filterID === "sortby") { if (el.innerText == "Relevance") { this.state.filters.sortby = false; } else { this.state.filters.sortby = el.dataset.sort; }; toggleEl.querySelector( "button" ).innerHTML = `Sort by: ${el.innerText} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; toggleEl.classList.remove("active"); trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); this.handleSubmit(); return; }; if (filterID === "organization") { this.state.brand = el.dataset.organization; this.state.filters.brand = this.state.brand; toggleEl.querySelector( "button" ).innerHTML = `Organization: ${el.innerText} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; toggleEl.classList.remove("active"); trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); this.handleSubmit(); return; }; if (isActive) { /* If this was autoapplied, we save the text string that matched the filter and remove it from the query when clicked */ if (this.state.filters[filterID][el.innerText].text) { let theRegEx = new RegExp(this.state.filters[filterID][el.innerText].text, "gi"); /* Update our session storage to keep track of our previous query */ window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = this.state.query.replaceAll(/ +/gi, " ").replaceAll(theRegEx, "").replaceAll(/ +/gi, " ").trim(); document.getElementById("query").value = this.state.query; } this.state.filters[filterID][el.innerText] = { active: false }; } else { this.state.filters[filterID][el.innerText] = { active: true }; trackWompEvent({ ec: 'Dropdown Used', ea: filterID, el: el.innerText }); el.classList.add("active"); }; this.handleSubmit(); }; /* Clear filters click handling */ if (el.id == "clearFilters") { let clearInsurancePromise = fetch("https://www.providence.org/integrationapi/clearinsuranceselection"); let searchRadiusEl = document.querySelector('#searchRadius'); searchRadiusEl.value = ''; const filtersWrapper = document.getElementById("filterWrapper"); /* Remove user entered queries from state and the input el */ this.state.query = ""; this.input.value = ""; this.state.specificDays = false; /* Clear filters */ this.state.filters = { ...this.state.filters, Gender: {}, InsuranceAccepted: {}, Languages: {}, PrimarySpecialties: {}, /** <!-- childReplacement providerOrgClearFilter --> **/ ProviderOrganization: {}, /** <!-- end childReplacement providerOrgClearFilter --> **/ page: 1, LocationName: false, PracticeGroup: '', LocationId: '', distance: '', location: false, AvailableDays: { Sunday: { active: false }, Monday: { active: false }, Tuesday: { active: false }, Wednesday: { active: false }, Thursday: { active: false }, Friday: { active: false }, Saturday: { active: false }, }, AvailableTime: "any", visitType: false }; this.state.currentLoc = false; /* Remove Query Params*/ let url = new URL(window.location); url.search = ""; if (history.pushState) { window.history.replaceState({}, "Find a Doctor", url); this.state.pushHistory = true; } /* Clear localStorage of retained insurance and location filters */ localStorage.removeItem('omniSearchInsurance'); localStorage.removeItem('omniSearchLocation'); document.querySelector("#custom-location").value = ''; this.populateDaytimeSelectorFromState(); Promise.allSettled([clearInsurancePromise]).then(()=>this.handleSubmit()); filtersWrapper.classList.remove("hasFilter"); }; /* Pagination clicks */ if (e.target.classList.contains("page-link")) { this.state.filters.page = e.target.dataset.page; }; /* Geolcation clicks */ if (e.target.classList.contains("useLocation")) { const isOn = document.querySelector('#switch')?.checked; if (!isOn) { this.getLocation(e); } else { this.helpers.getGoogleLocation(); } this.state.useMyLocationClicked = true; return; }; /* toggle between sorting clinics in location dropdown by alpha or distance */ if (e.target.classList.contains("distanceSort")) { const isOn = document.querySelector('#switchDistanceSort')?.checked; let clinicNames; if (!isOn) { if (document.CurrentClinics.length && document.CurrentClinics[0].distance) { clinicNames = document.CurrentClinics.sort((locA, locB) => locA.distance - locB.distance).map(loc => loc.name) } else { clinicNames = document.CurrentClinics; } } else { if (document.CurrentClinics.length && document.CurrentClinics[0].name) { let sortedClinics = document.CurrentClinics.sort((locA, locB) => { return locA.name.toLowerCase().localeCompare(locB.name.toLowerCase()); }); clinicNames = sortedClinics.map(loc => loc.name); } else { clinicNames = document.CurrentClinics; } } document.querySelector('#location-selection').innerHTML = clinicNames.reduce((html, name) => { return html + `<option value="${name}">${name}</option>`; }, `<option value="">--Select a clinic</option>`); return; } /* Map clicks */ if (e.target.classList.contains('gm-ui-hover-effect') && e.target.tagName == "BUTTON") { const selectedNpi = document.querySelector(`#npi${this.map.selectedNpi}`); if (selectedNpi) { selectedNpi.classList.remove('selected'); }; if (this.map.selectedMarker) { this.map.resetMarkerIcon(); }; }; /* Provider card clicks */ var setInfoWindow = el => { var { location, npi } = el.dataset; let additionalLoc = ""; /* Provider card click - Additional Location */ if (e.target.classList.contains("additional-location")) { additionalLoc = e.target.getAttribute("data-location"); }else if(e.target.parentElement.classList.contains("additional-location")) { additionalLoc = e.target.parentElement.getAttribute("data-location"); }else if(e.target.parentElement.parentElement.classList.contains("additional-location")) { additionalLoc = e.target.parentElement.parentElement.getAttribute("data-location"); } if(additionalLoc) {location = additionalLoc;} var id = this.map.lookup[location]; google.maps.event.trigger(this.map.markers[id], 'click'); this.map.selectProvider(npi); }; const closestEl = e.target.closest('.card.provider'); if (closestEl) { if (e.target.tagName !== "TIMESLOTS-WIDGET" && e.target.tagName !== "DIRECTORY-TIMESLOTS" && !e.target.closest('a')) { setInfoWindow(closestEl); } const linkEl = e.target.closest('a'); if (linkEl) { const index = Number(closestEl.dataset.position); const provider = this.state.results.providers[index - 1]; const hasTimeslots = closestEl.querySelector('timeslots-widget') ? 1 : 0; const defaultVals = { ea: `${provider.Name}, ${index}, timeslots: ${hasTimeslots}`, el: JSON.stringify(provider['@search.features']) }; if (linkEl.classList.contains('provider-link')) { /* Prevent routing until analytics tracking finishes */ e.preventDefault(); /* Send beacon */ await this.helpers.trackWompEvent({ ec: 'Provider Click', ...defaultVals }) .catch(err => console.error(err)); /* Route to link */ window.location = linkEl.href; } if (linkEl.classList.contains('phoneLink')) { this.helpers.trackWompEvent({ ec: "Direct Contact", ea: "Phone Click-to-Call", el: linkEl.dataset.varsPhoneLink }); this.helpers.trackWompEvent({ ec: 'Provider Click-To-Call', ...defaultVals }) } } } }); }; createChangeListeners = () => { this.showMapEl.addEventListener('change', e => { if (e.target.checked) { this.map.el.fitBounds(this.map.bounds); } else { /* allows amp-imgs to load when map view is toggled off after a search was performed and elements are scrolled into view vertically after being outside of the viewport horizontally */ this.helpers.autoNudgeAmpElements(true); } }); }; displayFilters = () => { const { Gender, Languages, InsuranceAccepted, PrimarySpecialties, visitType, brand, location, sortby, locationType, distance } = this.state.filters; /** <!-- childReplacement providerOrgGetDisplayFilter --> **/ let ProviderOrganization = this.state.filters.ProviderOrganization; /** <!-- end childReplacement providerOrgGetDisplayFilter --> **/ const { geoip, info } = this.state.results; /* Grab the object for the filterable facets */ let facets = this.state.facets; const facetSet = { Gender, Languages, InsuranceAccepted, distance, /** <!-- childReplacement providerOrgFacetSet --> **/ ProviderOrganization, /** <!-- end childReplacement providerOrgFacetSet --> **/ PrimarySpecialties }; /* Listen for input changes inside of dropdowns and filter list items based on the input value */ function createFilterInputListener(facet, keys, input, list) { const filterInput = document.getElementById(input); const filterList = document.getElementById(list); filterInput.addEventListener('input', e => { const regex = new RegExp(e.target.value, 'i'); let active = 0; let filteredHtml = ''; let total = 0; for(let key of keys) { let isActive = false; if (facet[key]?.active === true) { active++; isActive = true; }; if (!regex.test(key)) continue; total++; filteredHtml += /*html*/ `<li> <a class="dropdown-item dc facet-value${isActive ? " active" : ""}" ><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } /* Hide the list when no list items match input */ filterList.style.display = total > 0 ? "block" : "none"; filterList.innerHTML = filteredHtml; }); fireInvoca(); } /* We'll use this to remove auto-applied filters at search time */ const autoApplied = []; /* Iterate over the facets and set filter displays */ try{ for (let facet in facetSet) { /* We want to make sure that the facet exists in the facets obj before proceeding */ if (facets[facet]) { facets[facet].forEach((val) => { const filter = info.facets[facet]; let isSelected = false; if (filter && filter.includes(val)) { isSelected = true; }; this.state.filters[facet][val] = { ...this.state.filters[facet][val], active: isSelected }; }); } }; } catch { console.log(`Facets failed to load`); } for (const f of info.filters) { const { facets: fs } = f; if (f.text?.length && fs && fs.length) { console.log(f); fs.forEach(filter => { autoApplied.push({ name: filter.name, value: filter.value }); }); } } /* Update autoapplied filters in state */ this.state.autoApplied = autoApplied; /* For each of the filter dropdowns we've added, update the UL to show the available filters */ document.querySelectorAll(".dropdown.facet").forEach((dropdown) => { const facet = this.state.filters[dropdown.id]; if (facet) { /* Use blocklists to explicitly remove specific filter values For example, providence requested we remove multiple provider orgs from the "Medical Group" dropdown */ let blocklists = { 'ProviderOrganization': [ "Providence Affiliated Physicians", " Los Angeles County", " Mission", " St. Joseph", " St. Jude", " St. Mary", /** <!-- childReplacement provomniProviderOrgBlocklist --> **/ 'Providence', 'Providence Affiliated Physicians, Los Angeles', 'Axminster Medical Group & Providence Care Network Providers', 'Covenant Health Partners Affiliated', 'Covenant Rural Health Clinics', 'Facey Affiliated Provider', 'PacMed AdvantAge Health Center', 'Providence Affiliated Provider', 'Providence Medical Group', 'Providence Medical Institute', 'Saint John\'s Physician Partners Affiliated Providers', 'St. Joseph Health Medical Group', 'Providence Care Network', 'Providence Medical Associates' /** <!-- end childReplacement provomniProviderOrgBlocklist --> **/ ] }; let isFiltered = false; let html = "<ul>"; let keys = Object.keys(facet); keys = keys.sort(); let active = 0; /* Add a text input and a list id to lists which can be filtered by text */ if(dropdown.id === "InsuranceAccepted") { html = /*html*/ `<div class="search-wrap"> <button id="insuranceInputClear" class="hidden" onclick="clearInput('insuranceInput')"></button> <label for="insuranceInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='insuranceInput' placeholder='Search insurance' /> <label for="insuranceInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='insuranceList'> ` } else if(dropdown.id === "PrimarySpecialties") { html = /*html*/ `<div class="search-wrap"> <button id="specialtyInputClear" class="hidden" onclick="clearInput('specialtyInput')"></button> <label for="specialtyInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='specialtyInput' placeholder='Search specialty' /> <label for="specialtyInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='specialtiesList'>` } else if(dropdown.id === "ProviderOrganization") { html = /*html*/ `<div class="search-wrap"> <button id="organizationInputClear" class="hidden" onclick="clearInput('organizationInput')"></button> <label for="organizationInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='organizationInput' placeholder='Search medical group' /> <label for="organizationInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='organizationList'>` } else if(dropdown.id === "Languages") { html = /*html*/ `<div class="search-wrap"> <button id="languageInputClear" class="hidden" onclick="clearInput('languageInput')"></button> <label for="languageInput"><svg width="16" height="16"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#search-new"></use></svg></label> <input id='languageInput' placeholder='Search languages' /> <label for="languageInputClear"><svg width="12" height="12"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#x"></use></svg></label> </div> <ul class='filtered-list' id='languagesList'>` } /* If we have a blocklist for our filter, then remove blocked values before generating UI */ if (dropdown.id in blocklists) { let allowedKeys = keys.filter(function(key) { return !blocklists[dropdown.id].includes(key); }); keys = allowedKeys; } for (let key of keys) { let isActive = false; if (facet[key]?.active === true) { active++; isActive = true; }; if(dropdown.id ==="Gender"){ const genderVals = ["Female", "Male", "Non-binary"]; if(genderVals.includes(key)) { html += /*html*/ `<li> <a tabindex="0" class="dropdown-item dc facet-value${isActive ? " active" : ""}"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } } else { html += /*html*/ `<li> <a tabindex="0" class="dropdown-item dc facet-value${isActive ? " active" : ""}"><svg class="filter-unchecked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#unchecked"></use></svg><svg class="filter-checked" height="17px" width="16px"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#checked"></use></svg>${key}</a> </li>`; } }; html += '</ul>'; dropdown.lastElementChild.innerHTML = html; if (dropdown.id === "InsuranceAccepted") { createFilterInputListener(facet, keys, "insuranceInput", "insuranceList"); } else if(dropdown.id === "PrimarySpecialties") { createFilterInputListener(facet, keys, "specialtyInput", "specialtiesList"); } else if(dropdown.id === "ProviderOrganization") { createFilterInputListener(facet, keys, "organizationInput", "organizationList"); } else if(dropdown.id === "Languages") { createFilterInputListener(facet, keys, "languageInput", "languagesList"); } if (active > 0) { dropdown.querySelector("button").classList.add("filtered"); } else { dropdown.querySelector("button").classList.remove("filtered"); }; } }); /* Set query in search box */ if (this.state.query.length) { document.querySelector("#query").value = decodeURIComponent( this.state.query ); }; /* Set location information in location dropdown */ if (this.state.currentLoc?.length) { try { document.querySelector( "#LocationLabel" ).innerHTML = `Location: ${this.state.currentLoc} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; document.querySelector( 'input[name="location"]' ).value = this.state.currentLoc; /** <!-- childReplacement provOmniGeoLocatorInputUpdate --> **/ try { this.textInputs.forEach(input => { if (['custom-location'].includes(input.id)) { if(document.querySelector('#geoLocatorLabel span').textContent.indexOf(this.state.currentLoc) > -1) { input.value = document.querySelector('#geoLocatorLabel span').textContent; }else { input.value = this.state.currentLoc; } } if (['geoLocatorInput'].includes(input.id)) { checkLoginStatus(); } }); } catch (ex) { console.log(ex); } /** <!-- end childReplacement provOmniGeoLocatorInputUpdate --> **/ } catch (err) { console.error(err); } } /* Set the visit type toggle */ document.querySelectorAll('#visitTypeFilters button').forEach(button => { const type = button.dataset.type; const { visitType } = this.state.filters; if (!visitType) { type === 'all' ? button.classList.add('active') : button.classList.remove('active'); } else { type === 'all' ? button.classList.remove('active') : visitType[type]?.active ? button.classList.add('active') : button.classList.remove('active'); } }); }; paginate = () => { const { filters } = this.state; const { pages, page } = filters; let start; let html = ``; if(pages === 1 && page === pages) { html = /*html*/`<button id="queryClear" class="hidden" onclick="clearInput('query')"></button> <div class="btn-group single-page" role="group" aria-label="Results pages">`; } else { html = /*html*/`<button id="queryClear" class="hidden" onclick="clearInput('query')"></button> <div class="btn-group" role="group" aria-label="Results pages">`; } /* Let's make sure we display 5 results any time there are 5 or more pages */ if (page <= 4) { start = 1; } else if (pages - page < 4) { start = page - (4 - (pages - page)); } else { start = page - 2; }; const firstPage = new URL(document.location); firstPage.searchParams.set('page', 1); var nextPageUrl = new URL(document.location); var previousPageUrl = new URL(document.location); try { nextPageUrl.searchParams.set('page', filters.page + 1); previousPageUrl.searchParams.set('page', filters.page - 1); } catch (error) { console.log("Error setting pagination"); nextPageUrl.searchParams.set('page', 1); previousPageUrl.searchParams.set('page', 1); } if (start > 1) { html += start > 2 ? /*html*/ `<a href="${previousPageUrl.href}" class="previous-page"> <svg width="12" height="22" class="page-svg arrow-left-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-left-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-inactive"></use></svg> </a> <a class="btn page-link" data-page="1" href="${firstPage.href}">1</a> <span class="btn page-link ellipsis ellipsis-prev">...</span>` : ``; }; const ithPage = new URL(document.location); let mobileHide = ""; for (let i = start; i <= start + 4; i++) { if(pages > 4 && i > start + 1 && i < start + 4) { mobileHide = " mobile-hide" } else { mobileHide = ""; } if (i > pages) break; ithPage.searchParams.set('page', i); if(i === 1) { html += /*html*/ `<a href="${previousPageUrl.href}" class="previous-page"> <svg width="12" height="22" class="page-svg arrow-left-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-left-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-left-inactive"></use></svg> </a>`; } html += /*html*/ `<a class="btn${page == i ? " active" : ""} page-link ${mobileHide}" data-page="${i}" href="${ithPage.href}">${i}</a>`; if (i == start + 4 && i < pages) { ithPage.searchParams.set('page', pages); html += /*html*/ `<span class="btn page-link ellipsis">...</span>`; if (i < pages + 1) { ithPage.searchParams.set('page', pages); html += /*html*/`<a class="btn page-link${mobileHide} no-m" data-page="${pages}" href="${ithPage.href}">${pages}</a>`; }; }; }; html += `<a href="${nextPageUrl.href}" class="next-page"> <svg width="12" height="22" class="page-svg arrow-right-active"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-right-active"></use></svg> <svg width="12" height="22" class="page-svg arrow-right-inactive"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#arrow-right-inactive"></use></svg> </a></div>`; this.paginationEl.innerHTML = html; this.paginationEl.style.display = "flex"; try { this.paginationEl.classList.remove("first-page"); } catch (error) { } try { this.paginationEl.classList.remove("last-page"); } catch (error) { } try { this.paginationEl.classList.remove("no-pages"); } catch (error) { } if(pages === 0) { this.paginationEl.classList.add(`no-pages`); this.paginationEl.style.display = "none"; } else if(filters.page === pages){ this.paginationEl.classList.add(`last-page`); } else if(filters.page === 1) { this.paginationEl.classList.add(`first-page`); } try { let windowWidth = window.innerWidth; if(windowWidth <= 960) { let top = document.getElementById('results-wrapper').offsetHeight + 55; this.mapEl.style.height = `calc(100vh - ${top}px)`; } } catch (error) { console.log(error); } }; addWindowListeners = () => { window.addEventListener('resize', this.helpers.debounce(() => { return; const w = window.innerWidth; if (w < 1000) { if (this.mapEl.style.top !== '78px') { this.logo.style.width = '85px'; this.logo.style.height = '50px'; this.mapEl.style.top = '78px'; this.mapEl.style.height = 'calc(100vh - 78px)'; }; } else { const h = document.querySelector('header'); this.logo.style.width = '170px'; this.logo.style.height = '99px'; this.mapEl.style.top = `${h.clientHeight}px`; this.mapEl.style.height = `calc(100vh - ${h.clientHeight}px)`; }; }, 100)); window.onpopstate = (event) => { this.searchParams = prepURLSearchParams(); this.init(); }; window.addEventListener('scroll', () => this.map.setOffset()); }; populateTimeFromState = () => { const activeDays = Object.entries(this.state.filters.AvailableDays).filter(x => this.state.specificDays && x[1].active).map(x => x[0]); let labelText = "Availability "; const label = document.querySelector("#AvailabilityLabel"); if(activeDays.length) { if(activeDays.length !== 7) { labelText += ": " + activeDays.map(day => { switch(day) { case "Monday": return "M"; case "Tuesday": return "T"; case "Wednesday": return "W"; case "Thursday": return "Th"; case "Friday": return "F"; case "Saturday": return "S"; case "Sunday": return "Su"; default: return ""; } }).join(", ") + " "; } } if(this.state.filters.AvailableTime !== "any") { let time = this.state.filters.AvailableTime.split(""); time[0] = time[0].toUpperCase(); labelText += ": " + time.join("") + " "; } labelText += `<span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; label.innerHTML = labelText; }; populateDaytimeSelectorFromState = () => { const timeSelector = document.querySelector("#time-selector"); const daySelector = document.querySelector("#day-selector"); const checkboxes = document.querySelector("#availability-days-checkboxes"); const dayCheckboxes = document.querySelectorAll(".day-checkbox"); timeSelector.value = this.state.filters.AvailableTime; if(this.state.specificDays) { daySelector.value = "specific-day"; checkboxes.classList.remove("hidden"); } else { daySelector.value = "any-day"; checkboxes.classList.add("hidden"); } dayCheckboxes.forEach(checkbox => { const state = this.state.filters.AvailableDays[checkbox.innerHTML].active; if(!state) { checkbox.classList.remove("active"); } else { checkbox.classList.add("active"); } }); const activeDays = Object.entries(this.state.filters.AvailableDays).filter(x => this.state.specificDays && x[1].active).map(x => x[0]); let labelText = "Availability "; const label = document.querySelector("#AvailabilityLabel"); if(activeDays.length) { if(activeDays.length !== 7) { labelText += ": " + activeDays.map(day => { switch(day) { case "Monday": return "M"; case "Tuesday": return "T"; case "Wednesday": return "W"; case "Thursday": return "Th"; case "Friday": return "F"; case "Saturday": return "S"; case "Sunday": return "Su"; default: return ""; } }).join(", ") + " "; } } if(this.state.filters.AvailableTime !== "any") { let time = this.state.filters.AvailableTime.split(""); time[0] = time[0].toUpperCase(); labelText += ": " + time.join("") + " "; } labelText += `<span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; label.innerHTML = labelText; }; updateTimeSelectorState = (time) => { document.querySelectorAll("#timesList .active").forEach(e => { e.classList.remove("active"); }); time.classList.add("active"); this.state.filters.AvailableTime = time.id; this.populateTimeFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; updateDaySelectorState = (day) => { let autoChecked = false; if(day.classList.contains("active") && day.id !== "any-day"){ this.state.filters.AvailableDays[day.id].active = false; } if(!day.classList.contains("active")){ day.classList.add("active"); if(day.id !== "any-day") { this.state.filters.AvailableDays[day.id].active = true; } } else if (day.id !== "any-day") { day.classList.remove("active"); /* If this was the last checked box being unchecked, we need to automatically check the "any days" box */ if (!document.querySelectorAll("#daysList .day.active").length) { try { document.querySelector("#any-day").classList.add("active"); this.state.specificDays = false; autoChecked = true; } catch (error) { console.log(error); } } } if (!autoChecked) { if (day.id !== "any-day") { this.state.specificDays = true; try { document.querySelector("#any-day").classList.remove("active"); } catch (error) { console.log(error); } } else { this.state.specificDays = false; document.querySelectorAll("#daysList .day.active").forEach(e => { e.classList.remove("active"); }); this.state.filters.AvailableDays["Monday"].active = false; this.state.filters.AvailableDays["Tuesday"].active = false; this.state.filters.AvailableDays["Wednesday"].active = false; this.state.filters.AvailableDays["Thursday"].active = false; this.state.filters.AvailableDays["Friday"].active = false; } } this.populateTimeFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; updateDaySelectorStateAllDays = (day, check) => { if(day.id == "any-day") { if (check && day.classList.contains("active")) { this.state.specificDays = true; day.classList.remove("active"); } else { if (!check && !day.classList.contains("active")) { this.state.specificDays = false; day.classList.add("active"); } } } else { if(day.classList.contains("active") && !check) { day.classList.remove("active"); this.state.filters.AvailableDays[day.id].active = false; } else if(!day.classList.contains("active") && check) { day.classList.add("active"); this.state.filters.AvailableDays[day.id].active = true; } } this.populateTimeFromState(); }; updateDayTimeSelectorState = () => { const timeSelector = document.querySelector("#time-selector"); this.state.filters.AvailableTime = timeSelector.value; const daySelector = document.querySelector("#day-selector"); this.state.specificDays = (daySelector.value === "specific-day"); const checkboxes = document.querySelector("#availability-days-checkboxes"); this.populateDaytimeSelectorFromState(); this.handleSubmit(undefined, { ignoreElements: ["Availability"]}); }; /** <!-- childReplacement provOmniSearchFunctions --> **/ /** * @description Convert a JavaScript object into a serialized string * @param {object} obj - JavaScript object capable of serialization * @param {boolean} usePlusSpace - Use '+' instead of '%20' to represent spaces * @returns {string} - Serialized string * * This converts an object into a URL query or URL encoded form body. * The returned string is not prepended by a question mark. * Example with usePlusSpace is truthy: * { a: '', b: '1 2', c: '1 2' } --> 'a&b=1+2&c=1+2' * Example when usePlusSpace is falsy: * { a: '', b: '1 2', c: '1 2' } --> 'a&b=1%202&c=1%202' */ serializeObject(obj, usePlusSpace) { return Object.keys(obj).map((key) => { let value = encodeURIComponent(obj[key] || ''); if (usePlusSpace) value = value.replace(/%20/g, '+'); return encodeURIComponent(key) + '=' + value; }).join('&'); } xhrProvidenceGeolocationAutocomplete(location) { return new Promise((resolve, reject) => { let geocodeUrl; if (this.isStaging) { geocodeUrl = 'https://providencekyruusstaging.azurewebsites.net/api/geolocation/api/autocomplete/'+location+'?take=5'; }else{ geocodeUrl = 'https://providencekyruus.azurewebsites.net/api/geolocation/api/autocomplete/'+location+'?take=5'; } const xhr = new XMLHttpRequest(); xhr.open('GET', geocodeUrl, true); xhr.withCredentials = true; xhr.setRequestHeader("traceparent","00-7c12f8055a29436f95167ba40787dcae-92e09794d75a4f19-01"); xhr.setRequestHeader("x-requested-with","XMLHttpRequest"); if (this.isStaging) { xhr.setRequestHeader("RequestVerificationToken", "E0-x1LbtEMXUSEIfOvRQXj9EY-u2bR1VJpBe-z7eQIzN8eoy0QgjmInrnmLtlJwyuM7x6CdjAD5Q_GREZr0A_WR1uGWT4rJ1QgGUkznTSZo1:DG_76BAidglwME5Li0a2saKhFlCIpsNYFT3iawY9SwQFpiFD-1gwWlOgHFyFANWTe4re6YgvEMiDOW-Y8gGuxTBBqMs8hiwguwKZ3102tF41"); }else{ xhr.setRequestHeader("RequestVerificationToken", "3brXwene3ShS4TLB4IT1rG9vLrzdUgU88z9FHag9cLeRr1rekAkPtkmUkAUv2miRgl8Qpp0HzxxrETfJ6-l7Z3V8Fd0pyZ6nB1RovFdJb041:6E51pGciBcnM_aG91aokWULU6JNKdIR7o7uULQ7B6xAEYUxY2GavtVGNc9qyRnOGwkASFWxBQ12fgXS6n53NTRiIY27SrDAh6js-bSVaOjg1"); } xhr.responseType = 'json'; xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { /* IE compatibility */ const json = typeof xhr.response === 'string' ? JSON.parse(xhr.response) : xhr.response; if (json && json.success) resolve(json); else reject('Could not guess user location from zipcode'); } else { reject('xhrProvidenceGeolocationAutocomplete did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(); }) .then((data) => { let results = []; results.push("location-value " + location); data.results.forEach((loc) => { results.push(loc.city + ', ' + loc.state + ' ' + loc.zip); }); return results; }) .catch((error) => { console.error('Unable to fetch typeahead results for geolocator\n', error); return []; }); } handleGeoUpdate(event) { event.preventDefault(); let location = event.submitter.value; if(location) { let loc1 = location.split(","); let loc2 = loc1[1].split(" "); let city = loc1[0]; let state = loc2[1]; let zip = loc2[2]; document.querySelector('#geoLocatorLabel span').textContent = location; document.querySelector('#geo-locator-ck').checked = false; document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'none'); this.setGeoProvLocation(zip, city, state); /*Set user Entered zipcode cookie; important to providence // 24 hours */ const setCookie = (id, value, maxage, path, domain, secure) => { let cookieString = id + "=" + value; cookieString += path ? "; path=" + path : "; path=/"; if (maxage !== undefined) cookieString += "; max-age=" + maxage; if (domain) cookieString += "; domain=" + domain; if (secure) cookieString += "; secure"; document.cookie = cookieString; }; setCookie('UserEnteredLocationGeoCoordinates-v3', `{"City":"${city}","Latitude":"undefined","Longitude":"undefined","PostalCode":"${zip}","StateCode":"${state}","Regions":[],"Version":1}`, 86400); /* localstorage user entered zipcode is only used by us */ localStorage.setItem('UserEnteredLocationGeoCoordinates-v3', `{"City":"${city}","Latitude":"undefined","Longitude":"undefined","PostalCode":"${zip}","StateCode":"${state}","Regions":[],"Version":1}`); siteSearch.helpers.getGoogleLocation("geoLocatorSuggestions input:first-of-type"); } /*Set input value to selected value*/ document.querySelector('#geoLocatorInput').value = event.submitter.value; /*Remove options*/ let inputs = document.querySelectorAll('#geoLocatorSuggestions input'); for (let i = 0; i < inputs.length; i++) { inputs[i].remove(); } } /* Set the userlocation on Providence's end */ setGeoProvLocation(postalcodeSaved, citySaved, stateSaved) { return new Promise((resolve, reject) => { const body = { scController: 'DetectUserLocation', scAction: 'SetUserLocation', postalcode: postalcodeSaved, city: citySaved, state: stateSaved }; let geocodeUrl; if (this.isStaging) { geocodeUrl = 'https://preview-providence.provhealth.org/locations'; }else{ geocodeUrl = 'https://www.providence.org/locations'; } const xhr = new XMLHttpRequest(); xhr.open('POST', geocodeUrl); xhr.setRequestHeader('x-requested-with', 'XMLHttpRequest'); xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded; charset=UTF-8'); xhr.setRequestHeader('Authorization', 'Bearer ' + '7i9p5na47e3xgxz2bk472jgyfm989tt5'); xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { checkLoginStatus(); /*console.log("Providence geolocation updated - setGeoProvLocation()");*/ } else { reject('setGeoProvLocation did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(this.serializeObject(body)); }); } guessUserLocation() { return new Promise((resolve, reject) => { const geocodeUrl = 'https://wompservices.wompmobile.com/geoip'; const xhr = new XMLHttpRequest(); xhr.open('GET', geocodeUrl); xhr.responseType = 'json'; xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { /* IE compatibility */ const json = typeof xhr.response === 'string' ? JSON.parse(xhr.response) : xhr.response; if (json && json.success) resolve(json); else reject('Could not guess user location'); } else { reject('guessUserLocation did not succeed: xhr.status=' + xhr.status); } } }; xhr.send(); }); } updateGeoTypeahead(event, currentTarget) { let typeaheadList = document.querySelector('#geoLocator-form .suggestion'); if (!typeaheadList) return console.error('Unable to locate typeahead list for typeahead update'); typeaheadList.querySelectorAll('input').forEach(e => e.remove()); this.xhrProvidenceGeolocationAutocomplete(event.target.value) .then((results) => { /* only add the results if they match the current typeahead */ if(document.getElementById('geoLocatorInput').value === results[0].replace('location-value ','')) { /* Remove first validation value */ results.shift(); results.forEach((loc) => { let btn = document.createElement('input'); btn.value = loc; btn.type = "submit"; btn.style.border = "none"; btn.style.textAlign = "left"; typeaheadList.append(btn); }); } }); } /** <!-- end childReplacement provOmniSearchFunctions --> **/ init = async () => { this.searchParams = prepURLSearchParams(); /** <!-- childReplacement provOmniGeoTypeahead --> **/ function getCookie(name) { const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)')); if (match) { return match[2]; } } try{ /*Add geo locator to utility nav if cookie is set*/ let userGeoCookie = getCookie("UserEnteredLocationGeoCoordinates-v3"); let geoCookie = getCookie("GeoIpLocationGeoCoordinates-v3"); let geoStorage = localStorage.getItem('UserEnteredLocationGeoCoordinates-v3'); if(userGeoCookie || geoCookie || geoStorage){ const userGeoObj = JSON.parse(userGeoCookie || geoCookie || geoStorage); if(document.querySelector('#topheader nav')){ let location = userGeoObj.City + ", " + userGeoObj.StateCode + " " + userGeoObj.PostalCode; let span = document.querySelector('#geoLocatorLabel span'); if(span){ span.textContent = location; document.querySelector('#geoLocatorInput').value = location; } const customStyles = document.querySelector('style'); let css = ` #topheader label#geoLocatorLabel { color: inherit; pointer-events: all; } label#geoLocatorLabel { display: block; } `; if (customStyles) { customStyles.innerHTML += css; } } }else{ this.guessUserLocation() .then((userLocation) => { if (userLocation.city && userLocation.zip && userLocation.state) { let location = userLocation.city + ", " + userLocation.state + " " + userLocation.zip; let label = document.querySelector('#geoLocatorLabel'); let span = document.querySelector('#geoLocatorLabel span'); span.textContent = location; document.querySelector('#geoLocatorInput').value = location; const customStyles = document.querySelector('style'); let css = ` #topheader label#geoLocatorLabel { color: inherit; pointer-events: all; } label#geoLocatorLabel { display: block; } `; if (customStyles) { customStyles.innerHTML += css; } } }) .catch(error => console.log(error)); } } catch (error) { console.error('Add Geo Locator failed\n', error); } /* Geolocator autocomplete */ const geolocator = document.querySelector('#geoLocatorInput'); if (geolocator) { const typeaheadList = document.querySelector('#geoLocator-form .suggestion'); /* Workaround for event.currentTarget === null when debounced is used See: https://github.com/jashkenas/underscore/issues/1905 */ let debouncedUpdateGeoTypeahead = this.helpers.debounce(this.updateGeoTypeahead, 250).bind(this); geolocator.addEventListener('input', function (event) { debouncedUpdateGeoTypeahead(event, event.currentTarget); document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'block'); }); geolocator.addEventListener('click', function (event) { if(document.querySelector('#geoLocator-form .suggestion').style.display !== "block"){ /*Select input on click so that the user can overwrite saved location*/ this.setSelectionRange(0, this.value.length); document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'block'); } }); let geoLocatorLabel = document.querySelector('#geoLocatorLabel'); geoLocatorLabel.addEventListener('click', function (event) { document.querySelector('#geoLocator-form .suggestion').style.setProperty('display', 'none'); if(window.innerWidth < 1000) { document.querySelector('main').style.setProperty('z-index','0'); } }); } /* Catch geolocator form submission */ let geoForm = document.querySelector('#geoLocator-form'); if(geoForm){ geoForm.addEventListener('submit', this.handleGeoUpdate.bind(this)); } let geoUseCurrentLocation = document.querySelector('#geoUseCurrentLocation'); if(geoUseCurrentLocation) { geoUseCurrentLocation.addEventListener('click', () => { document.querySelector('#geo-locator-ck').checked = false; }); } /** <!-- end childReplacement provOmniGeoTypeahead --> **/ /* Create cookies for linker params, if they don't already exist */ if (typeof cookieLinkerParams != "undefined") cookieLinkerParams(); this.state = { pageInit: true, pageType: 'Provider Directory', page_subtype: 'Provider Search Results', query: "", /** <!-- childReplacement brandColor --> **/ color: "#00338e", /** <!-- end childReplacement brandColor --> **/ disclaimerSearch: false, disclaimerSearchKeywords: [ 'transgender surgery', 'IVF', 'vasectomy', ], excludeSearch: false, excludeSearchKeywords: [ 'abortion', 'physician assisted suicide', 'physician-assisted suicide', 'euthanasia', 'euthenasia' ], pushHistory: false, specificDays: false, filters: { previousPostalCode: false, distance: this.searchParams.get("distance") || false, page: Number(this.searchParams.get("page")) || 1, coordinates: false, Gender: {}, InsuranceAccepted: {}, PrimarySpecialties: {}, /** <!-- childReplacement providerOrgState --> **/ ProviderOrganization: {}, /** <!-- end childReplacement providerOrgState --> **/ Languages: {}, availability: "all", visitType: false, /** <!-- childReplacement brandState --> **/ brand: this.searchParams.get("brand") || "providence", /** <!-- end childReplacement brandState --> **/ sortby: false, init: true, locationType: false, location: false, LocationsOnly: ((this.searchParams.get("LocationsOnly") ?? "false") === "true"), AvailableDays: { Sunday: { active: false }, Monday: { active: false }, Tuesday: { active: false }, Wednesday: { active: false }, Thursday: { active: false }, Friday: { active: false }, Saturday: { active: false }, }, AvailableTime: "any", tier: this.searchParams.get("Tier") || this.searchParams.get("tier") }, results: false, }; this.map.build(); this.map.setOffset(); /* Create day-time selector */ /* populate day-time selector state from URL if it exists */ if(this.searchParams.get("time")) { const time = this.searchParams.get("time").trim(); switch(time) { case "any": case "morning": case "afternoon": this.state.filters.AvailableTime = time; default: break; } } if(this.searchParams.get("days")) { const days = this.searchParams.get("days").split(",").map(x => +x); this.state.specificDays = true; days.forEach(n => { const day = DAYS[n]; if(day && this.state.filters.AvailableDays[day]) { this.state.filters.AvailableDays[day].active = true; } }) } document.querySelectorAll("#timesList a").forEach(time => { time.addEventListener('click', () => this.updateTimeSelectorState(time)); }); document.querySelectorAll("#daysList a").forEach(day => { day.addEventListener('click', () => this.updateDaySelectorState(day)); }); document.querySelector("#time-selector").addEventListener("change", () => { this.updateDayTimeSelectorState(); }); document.querySelector("#day-selector").addEventListener("change", () => { this.updateDayTimeSelectorState(); }); let dayTimeHTML = ""; DAYS.forEach((day, i) => { if(i === 0 || i === 6) { return; } const isActive = this.state.filters.AvailableDays[day].active; dayTimeHTML += ` <a class="day-checkbox dropdown-item omni-checkbox ${isActive ? " active" : ""}" id="${day}-checkbox">${day}</a> `; }); document.querySelector("#availability-days-checkboxes").innerHTML = dayTimeHTML; document.querySelectorAll(".day-checkbox").forEach(checkbox => { checkbox.addEventListener("click", e => { const state = this.state.filters.AvailableDays[checkbox.innerHTML].active; this.state.filters.AvailableDays[checkbox.innerHTML].active = !state; this.updateDayTimeSelectorState(); }) }); document.querySelector("#day-selector").value = "any-day"; this.populateDaytimeSelectorFromState(); /* end day-time selector */ const { searchParams, state, map, helpers } = this; const { createPixel, trackIt, trackFromEmitted } = helpers; const lsInsurance = localStorage.getItem('omniSearchInsurance') || false; const lsLocation = localStorage.getItem('omniSearchLocation') || false; const loopable = ['gender', 'languages', 'insuranceaccepted', 'primaryspecialties'/** <!-- childReplacement providerOrgLoopable --> **/ , "ProviderOrganization" /** <!-- end childReplacement providerOrgLoopable --> **/]; const isMobile = this.helpers.viewportLessThan(); let tmp; document.addEventListener('timeslot-clicked', event => trackFromEmitted('timeslot', event)); document.addEventListener('phone-clicked', event => trackFromEmitted('phone', event)); /* if (isMobile) { this.logo.style.width = '85px'; this.logo.style.height = '50px'; }; */ let hasInsuranceParam = false; const isTest = searchParams.get('test'); if (isTest && isTest == "true") { this.state.isTest = true; } /* Loop through params that can have more than one value */ for (let param of loopable) { if (searchParams.get(param)) { if (param === 'insuranceaccepted') { hasInsuranceParam = true; } tmp = searchParams.get(param).split(","); tmp.forEach((val) => { switch(param) { case 'gender': param = 'Gender'; break; case 'languages': param = 'Languages'; break; case 'primaryspecialties': param = 'PrimarySpecialties'; break; case 'insuranceaccepted': param = 'InsuranceAccepted'; break; default: } state.filters[param][val] = { active: true }; }); }; } if (lsInsurance && !hasInsuranceParam) { const split = lsInsurance.split(','); for (let ins of split) { if (!this.state.filters.InsuranceAccepted) { this.state.filters.InsuranceAccepted = {}; } this.state.filters.InsuranceAccepted[ins] = { active: true } } } /* Set query if not empty */ if (searchParams.get("query")) { window.sessionStorage.setItem('previousQuery', this.state.query); this.state.query = decodeURIComponent(searchParams.get('query')); } else { window.sessionStorage.setItem('previousQuery', ''); }; /* If a PracticeGroup param is present, then set state */ if (searchParams.get('practicegroup')) { this.state.filters.PracticeGroup = searchParams.get('practicegroup'); } /* If a userlocation param is present, then set state */ if (searchParams.get('userlocation')) { this.state.filters.location = searchParams.get('userlocation'); this.state.currentLoc = searchParams.get('userlocation'); } else if (lsLocation) { this.state.filters.location = lsLocation } /* If there is a search param of LocationName and it has a length, set that to state */ if (searchParams.get('locationname')) { this.state.filters.LocationName = searchParams.get('locationname'); } /* If there is a search param of distance and it has a length, set that to state */ if (searchParams.get('distance')) { this.state.filters.distance = searchParams.get('distance'); } /* Check search params for LocationId and PracticeGroupId, and set state accordingly */ if (searchParams.get('locationid')) { this.state.filters.LocationId = searchParams.get('locationid'); } if (searchParams.get('practicegroupid')) { this.state.filters.PracticeGroupId = searchParams.get('practicegroupid'); } await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthSearchFacets?brand=` + this.state.filters.brand) .then(res => res.json()) .then(res => { this.state.facets = res["facets"]; }); /* Check for / apply visit type filters */ let visitTypes = searchParams.get('visittypes'); if (visitTypes) { visitTypes = visitTypes.split(','); document.querySelector('button[data-type="all"]').classList.remove('active'); this.state.filters.visitType = {}; for (let type of visitTypes) { document.querySelector(`button[data-type="${type}"]`).classList.add('active'); this.state.filters.visitType[type] = { active: true }; } } const userState = await fetch('https://wompservices.wompmobile.com/geoip/') .then(res => res.json()) .then(res => res.state ? res.state : "Undefined") .catch(() => { return "Undefined"; }); this.state.filters.userState = userState; let brands = { pacmed: "Pacific Medical", providence: "Providence", swedish: "Swedish" }; document.getElementById("OrganizationLabel").innerHTML = `Organization: ${brands[this.state.filters.brand]} <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; /* Handle sort order */ const sortby = searchParams.get("sortby"); if (sortby) { switch (sortby) { case "GeocodedCoordinate": if (searchParams.get("userlocation")) { siteSearch.state.filters.sortby = "GeocodedCoordinate"; const sortByLabel = document.querySelector("#SortByLabel"); sortByLabel.innerHTML = `Sort by: Distance <span class="dd-svg"><svg width="12" height="7" class="prov-svg"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown"></use></svg><svg width="12" height="8" class="prov-svg-selected"><use xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="#dropdown-selected"></use></svg></span>`; } else { siteSearch.state.filters.sortby = false; }; break; default: siteSearch.state.filters.sortby = sortby; break; }; }; this.handleSubmit(); }; }; const prepURLSearchParams = () => { const url = new URL(window.location); const searchParams = new URLSearchParams(url.search); const qStrings = [ 'gender', 'insuranceaccepted', 'languages', 'locationid', 'locationname', 'practicegroup', 'practicegroupid', 'primaryspecialties', 'providerorganization', 'userlocation', 'visittypes' ]; /* convert certain param labels to lowercase */ searchParams.forEach((value, key) => { const match = qStrings.find((str) => { return str === key.toLowerCase(); }); if (match) { searchParams.delete(key); searchParams.set(match, value); } }); url.search = searchParams.toString(); window.history.pushState('Find a Doctor','',url); return searchParams; }; const siteSearch = new Search( `https://${/staging/gi.test(window.location.hostname) ? 'womphealthdevapi' : 'womphealthapi'}.azurewebsites.net/api/WompHealthSearch?` ); siteSearch.textInputs.forEach((el) => { if (el.id === 'searchRadius') { el.addEventListener("keydown", function (e) { if (e.keyCode === 13) { const searchRadiusVal = document.querySelector('#searchRadius').value; if (searchRadiusVal?.length) { siteSearch.state.filters.distance = searchRadiusVal; } else { siteSearch.state.filters.distance = ''; } siteSearch.handleSubmit(); } }); } if (el.id === 'query') { el.addEventListener("keydown", function (e) { if (e.keyCode === 13) siteSearch.handleSubmit(); }); }; if (el.id === "custom-location") { el.addEventListener("keydown", function(e) { if (e.keyCode === 13) { siteSearch.helpers.getGoogleLocation(); } }); } el.addEventListener("change", function (e) { const { id, value } = e.target; if (id === "query") { return; } else { let val = value?.length ? value : false; siteSearch.state.filters[id] = val; }; }); }); siteSearch.form.addEventListener("click", siteSearch.handleSubmit); siteSearch.form2.addEventListener("click", siteSearch.handleSubmit); siteSearch.createClickListener(); siteSearch.createChangeListeners(); siteSearch.addWindowListeners(); siteSearch.init(); try { let mtoggle = localStorage.getItem("maptoggle"); if(mtoggle !== null) { document.getElementById("showmap").checked = mtoggle; } document.getElementById("maptoggle").addEventListener("click", function(e){ localStorage.setItem("maptoggle", !document.getElementById("showmap").checked); }); } catch (error) { console.log(error); } class Autocomplete { constructor(args) { this.useHistory = args.useHistory || false; this.param = args.param; this.src = args.src; this.id = args.id; this.localStorageKey = args.localStorageKey; this.shouldSubmit = args.shouldSubmit || true; this.minChars = args.minChars || 3; this.isStaging = /staging/gi.test(window.location.hostname); this.apiSub = this.isStaging ? 'womphealthdevapi' : 'womphealthapi'; /** <!-- childReplacement autocompleteBrand --> **/ this.brand = 'providence'; /** <!-- end childReplacement autocompleteBrand --> **/ this.state = { initValue: '' }; /* Automatically call the init script for the autocomplete */ this.init(); this.helpers.trackAutocomplete = this.helpers.trackAutocomplete.bind(this); }; helpers = { debounce(func, delay = 200) { let timeout = null; return function(){ let context = this; let args = arguments; if(document.getElementById('searchBox').classList.length === 0) { document.getElementById("searchBox").classList.add("is-dirty"); if(document.getElementById('mobileSearchWrapper').classList.length === 1) { document.getElementById("mobileSearchWrapper").classList.add("is-dirty"); } } clearTimeout(timeout); timeout = setTimeout(function(){ func.apply(context, args) }, delay); }; }, slugify(str) { return str .toLowerCase() .trim() .replace(/[^\w\s-]/g, '') .replace(/[\s_-]+/g, '-') .replace(/^-+|-+$/g, ''); }, removeSelected() { document.querySelectorAll('div[data-autocomplete].selected').forEach(div => div.classList.remove('selected')); }, fetchSuggestions: async(val) => { const { src, param, helpers, autocompleteEl } = this; const { slugify } = helpers; const results = await fetch(`${src}&${param}=${val}`).then(res => res.json()) .then(res => { /* If the length is 0, there are no results to the search */ if (res.value.length == 0) { autocompleteEl.innerHTML = '<small>No suggestions</small>'; return 'success'; }; /* Map out the returned results */ const tmp = res.value.reduce((html, r) => r?.text ? html + `<div id="${slugify(r.text)}" data-autocomplete>${r.text}</div>` : html, ''); autocompleteEl.innerHTML = tmp; return 'success'; }); return results; }, getCID: () => { let cid = localStorage.getItem('cid'); /* If we DON'T have a CID saved to localStorage... */ if (!cid || !cid.length) { /* If crypto's randomUUID func is available, create a CID with it */ if (typeof crypto?.randomUUID == 'function') { cid = crypto.randomUUID(); } /* Else, go old school... */ else { cid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /* Save the new cid to localStorage */ localStorage.setItem('cid', cid); } /* Send CID to the requestor */ return cid; }, trackAutocomplete: async (str) => { const data = JSON.stringify({ text: this.state.initValue, autocomplete: str }); const { pathname, search } = window.location; await fetch(`https://${this.apiSub}.azurewebsites.net/api/WompHealthAnalytics?brand=${this.brand}&t=event&ec=search&ea=autocomplete&el=${data}&cid=${this.helpers.getCID()}&dp=${encodeURIComponent(pathname + search)}`) .then(res => res.json()) .then(res => console.log(res)); } }; addListener = { input: () => { const { src, param, helpers, autocompleteEl, getPreviousSearches, minChars, state, inputEl } = this; const { debounce, slugify, fetchSuggestions } = helpers; inputEl.addEventListener('input', debounce(async function(e) { const val = inputEl.value; /* This is the text that the user input */ state.initValue = val; /* If more than minChars characters are input, start querying the API */ if (val.length >= minChars) { await fetchSuggestions(val); } /* Otherwise, show previous and suggested searches */ else { getPreviousSearches(); }; })); }, key: () => { const { debounce, removeSelected, trackAutocomplete } = this.helpers; const { inputEl, autocompleteEl, state } = this; inputEl.addEventListener('keydown', debounce(function(e) { const selected = document.querySelector('div[data-autocomplete].selected'); let nextSibling = false; let prevSibling = false; if (selected) { nextSibling = selected.nextElementSibling ? selected.nextElementSibling : false; prevSibling = selected.previousElementSibling ? selected.previousElementSibling : false; if (prevSibling && !prevSibling.hasAttribute('data-autocomplete')) { prevSibling = false; }; }; const { key } = e; switch(key) { case 'ArrowDown': if (nextSibling) { removeSelected(); nextSibling.classList.add('selected'); inputEl.value = nextSibling.innerText; } else if (!selected) { const first = document.querySelector('div[data-autocomplete]'); inputEl.value = first.innerText; first.classList.add('selected'); }; break; case 'ArrowUp': if (prevSibling) { removeSelected(); prevSibling.classList.add('selected'); inputEl.value = prevSibling.innerText; } else { if (selected) { removeSelected(); inputEl.value = state.initValue; }; }; break; case 'Enter': if (selected) { /* trackAutocomplete(selected.innerText); */ } autocompleteEl.style.display = 'none'; inputEl.blur(); window.wmFromAutoComplete = true; break; default: break; } }, 100)); }, clicks: () => { const { autocompleteEl, inputEl, shouldSubmit, id, getPreviousSearches, helpers } = this; const { removeSelected, fetchSuggestions } = helpers; document.body.addEventListener('click', (e) => { const { target } = e; /* If it has data-autocomplete, it's an autocomplete option. Update the autocomplete */ if (target.hasAttribute('data-autocomplete')) { removeSelected(); /* helpers.trackAutocomplete(target.innerText); */ target.classList.add('selected'); inputEl.value = e.target.innerText; autocompleteEl.style.display = "none"; if (shouldSubmit) { inputEl.dispatchEvent(new KeyboardEvent('keydown',{'key':'Enter'})); }; } /* Else, if the id matches the current id, then its the auto complete element. Run default logic. */ else if (target.id == id) { if (target.value.length > 0) { fetchSuggestions(target.value); } else { getPreviousSearches(); }; } /* Otherwise, it's a click outside autocomplete. Hide the autocomplete options. */ else { autocompleteEl.style.display = "none"; }; }); /* If the input is focused on, show the autocomplete box */ inputEl.addEventListener('focus', (e) => { autocompleteEl.style.display = 'block'; }); } }; checkRequiredArgs = () => { const requiredArgs = ["id", "src", "param"]; let missing = false; const missingRequiredArgs = requiredArgs.some(arg => { const tmp = !this[arg]; missing = arg; return tmp; }); if (missingRequiredArgs) { console.error(`Please add key "${missing}" to autocomplete options object`); } else if (this.id == "autocomplete") { console.error('Input id cannot be "autocomplete". Please choose another id.'); return false; }; return !missingRequiredArgs; }; getPreviousSearches = async () => { const { src, param, helpers, autocompleteEl, useHistory, localStorageKey } = this; const { slugify } = helpers; let theHtml = await fetch(`${src}&${param}=`).then(res => res.json()) .then(res => { if (!res.value?.length) { return ''; } const tmp = res.value.reduce((html, r) => r?.text ? html + `<div id="${slugify(r.text)}" data-autocomplete>${r.text}</div>` : html, ''); return `<h5>Suggested Searches</h5>${tmp}`; }); /* If we are using histor AND a localStorageKey is set */ if (useHistory && localStorageKey) { this.previousSearches = JSON.parse(localStorage.getItem(localStorageKey)); const { previousSearches } = this; theHtml += '<div class="availability-divider"></div><h5>Previous Searches</h5>'; /* Make sure the data we have in an array before populated the autocomplete */ if (Array.isArray(previousSearches) && previousSearches.length) { theHtml += previousSearches.reduce((html, search, i) => { const id = this.helpers.slugify(search) + 1; const el = `<div id="${id}" data-autocomplete>${search}</div>`; return html + el; }, ''); } else { theHtml += '<small>No previous searches</small>'; }; autocompleteEl.innerHTML = theHtml; }; }; addAutocomplete = () => { this.inputEl = document.querySelector("#" + this.id); this.inputEl.insertAdjacentHTML('afterend', '<div id="autocomplete" style="display: none;"></div>'); this.autocompleteEl = document.querySelector("#autocomplete"); const { inputEl, id, addListener } = this; for (let listener of Object.values(addListener)) { listener(); }; }; init() { const { checkRequiredArgs, addAutocomplete } = this; if (!checkRequiredArgs()) return; addAutocomplete(); }; }; const autocomplete = new Autocomplete({ id: 'query', src: `https://${/staging/gi.test(window.location.hostname) ? 'womphealthdevapi' : 'womphealthapi'}.azurewebsites.net/api/WompHealthSearch?type=autocomplete&brand=pacmed&top=5&skip=0`, param: 'search', useHistory: true, localStorageKey: 'previousSearches', minChars: 3 }); </script> <script id="invocaDecoration" type="text/javascript"> var internalUserError = ''; const isInternalUser = fetch("https://s3-us-west-1.amazonaws.com/providence-internal/analytics.gif") .then((res) => { return res.status == 200 ? true : false; }) .catch((error) => { console.log(error); internalUserError = "true"; return false; }); function getCookieInvoca(name) { const match = document.cookie.match(new RegExp(`(?=${name}=)([^;]*)`)); if (match) { return match[0].split('=')[1]; } } function hasUrlParam(param, de) { return (param ? `${param}` : `${de}`); } function waitForElm(selector) { return new Promise(resolve => { if (document.querySelector(selector)) { return resolve(document.querySelector(selector)); } const observer = new MutationObserver(mutations => { if (document.querySelector(selector)) { resolve(document.querySelector(selector)); observer.disconnect(); } }); observer.observe(document.body, { childList: true, subtree: true }); }); } function fireInvoca() { let url = window.location.href; const invocaCookie = getCookieInvoca("invoca_session"); let invocaHasOutbound; let invocaConfigPhone; if( (internalUserError === "true" || !isInternalUser) && invocaCookie !== undefined) { let invocaCookieJSON; if(invocaCookie.charAt(0) === '%'){ invocaCookieJSON = JSON.parse(decodeURIComponent(invocaCookie)); } else { invocaCookieJSON = JSON.parse(invocaCookie); } if(invocaCookieJSON) { let hgcrmAgency = hasUrlParam(invocaCookieJSON.session.hgcrm_agency, "client"), hgcrmCampaignid = hasUrlParam(invocaCookieJSON.session.campaignid, "607"), hgcrmChannel = hasUrlParam(invocaCookieJSON.session.hgcrm_channel, "email"), hgcrmSource = hasUrlParam(invocaCookieJSON.session.hgcrm_source, "mh"), hgcrmTacticid = hasUrlParam(invocaCookieJSON.session.hgcrm_tacticid, "15688"), hgcrmTrackingsetid = hasUrlParam(invocaCookieJSON.session.offersetid, "22561"), landingPage = hasUrlParam(invocaCookieJSON.session.landing_page, ""); landingPage = landingPage.split('?')[0].replace("https://",""); invocaHasOutbound = `landing_page=${landingPage}&hgcrm_channel=${hgcrmChannel}&hgcrm_source=${hgcrmSource}&campaignid=${hgcrmCampaignid}&hgcrm_tacticid=${hgcrmTacticid}&offersetid=${hgcrmTrackingsetid}`; invocaConfigPhone = `calling_page=${encodeURIComponent(url)}&calling_url=${encodeURIComponent(url)}&${invocaHasOutbound}&advertiser_campaign_id_from_network=2&network_id=1758&tealium_vid=AMP_STATE(wheelhouse.tealium_vid)`; waitForElm('#results .provider').then((elm) => { /*Replace Phone Number*/ let phones = document.getElementsByClassName("phoneLink"); for (i = 0; i < phones.length; ++i) { let tel = phones[i]; let telNum = tel.href.replace("tel:+1-","").replace("tel:","").replaceAll("-","").trim(); if(tel) { tel.insertAdjacentHTML( "beforebegin", ` <amp-list data-no-nudge class="callTrackingList" id="tel1" src="https://tealium.wheelhousedmg.com/i.gif?tealium_vid=CLIENT_ID(AMP_ECID_GOOGLE)" layout="fixed-height" height="19px" width="auto" items="." single-item binding="always" template="callTrackTemplate${i}" > <div placeholder> ${tel.outerHTML} </div> </amp-list> <template type="amp-mustache" id="callTrackTemplate${i}"> <amp-call-tracking class="callTracking" config="https://pnapi.invoca.net/amp?invoca_id=AMP_STATE(wheelhouse.tealium_vid)&_pcid=AMP_STATE(wheelhouse._pcid)&${invocaConfigPhone}&invoca_detected_destination=${telNum}"> ${tel.outerHTML} </amp-call-tracking> </template> ` ); tel.remove(); } } var script = document.createElement('script'); script.setAttribute("custom-element", 'amp-call-tracking'); script.src = 'https://cdn.ampproject.org/v0/amp-call-tracking-0.1.js'; script.setAttribute("async", "true"); document.head.appendChild(script); }); /*Decorate Outbound Links*/ let outboundInvocaLinks; let v; if(url.indexOf("pacmed") > -1 || url.indexOf("pacificmedicalcenters") > -1) { outboundInvocaLinks = document.body.querySelectorAll("a[href*='pacificmedicalcenters']"); for (i = 0; i < outboundInvocaLinks.length; ++i) { if(outboundInvocaLinks[i].href.indexOf("?") > -1) { v = outboundInvocaLinks[i].href + "&" + invocaHasOutbound; } else { v = outboundInvocaLinks[i].href + "?" + invocaHasOutbound; } outboundInvocaLinks[i].href = v; } } else if(url.indexOf("swedish") > -1) { outboundInvocaLinks = document.body.querySelectorAll("a[href*='swedish']"); for (i = 0; i < outboundInvocaLinks.length; ++i) { if(outboundInvocaLinks[i].href.indexOf("?") > -1) { v = outboundInvocaLinks[i].href + "&" + invocaHasOutbound; } else { v = outboundInvocaLinks[i].href + "?" + invocaHasOutbound; } outboundInvocaLinks[i].href = v; } } } } } fireInvoca(); </script> <amp-list src="amp-state:wheelhouse" layout="fixed-height" height="1" single-item="" items="." style="z-index: -100"> <template type="amp-mustache"> <div id="wheelhouseJson" style="width: 1px; height: 1px; overflow: hidden;"> { "pcid": "{{ _pcid }}", "traceId": "{{ trace_id }}" } </div> </template> </amp-list> <link href="https://fonts.googleapis.com/css?family=Montserrat:400,500,700&amp;display=swap" rel="stylesheet"> <div id="wm_footer" class="wm_footer section"> <div class="footer-bs-wrap" id="topFooter"> <div class="footer-section-wrapper"> <div class="d-col large-col"> <div class="mission-and-values"> <div class="mission"> <h3>OUR MISSION</h3> <p>As expressions of God's healing love, witnessed through the ministry of Jesus, we are steadfast in serving all, especially those who are poor and vulnerable.</p> </div> <div class="values"> <h3>OUR VALUES</h3> <p>Compassion, Dignity, Justice, Excellence, Integrity. <a class="underline" href="https://www.providence.org/about/our-mission">Learn more about our mission, vision and values.</a></p> </div> </div> <a class="btn white contact-btn" aria-label="Contact us at Providence" href="https://www.providence.org/about/contactus">Contact Us</a> </div> <div class="more-links"> <div class="d-col"> <h3>ACROSS PROVIDENCE</h3> <ul> <li><a href="https://www.providence.org/about">About Us</a></li> <li><a href="https://www.providence.org/about/health-equity" aria-label="Health Equity footer link">Health Equity</a></li> <li><a href="https://www.providence.org/about/initiatives" aria-label="Initiatives at Providence">Initiatives</a></li> <li><a href="https://www.collaboratingforbetterhealthcare.com/c/a/62g1OpEWK4O9ZckbK4vI08">Help Us Improve Health Care</a></li> <li><a href="https://www.providence.org/about/annual-report" aria-label="Annual Report to Our Communities footer link">Annual Reports to Our Community</a></li> <li><a href="https://www.providence.org/about/annual-report/reports/chna-and-chip-reports" aria-label="Community Health Needs Assessments and Improvement Plans footer link">Community Health Needs Assessments and Improvement Plans</a></li> <li><a href="https://www.providence.org/doctors" aria-label="Our Doctors footer link">Our Doctors</a></li> <li><a href="https://www.providence.org/business/nursing-institute" aria-label="Our Nurses footer link">Our Nurses</a></li> </ul> </div> <div class="d-col"> <h3>RESOURCES</h3> <ul> <li><a href="https://www.providence.org/for-employees" aria-label="For Care givers (our employees)" title="For Care givers (our employees)">For Caregivers (our employees)</a></li> <li><a href="https://psjhealth.jobs/" aria-label="Career Opportunities footer link">Career Opportunities</a></li> <li><a href="https://psdconnect.org/partners/providence" aria-label="Physician &amp; Provider Careers footer link">Physician &amp; Provider Careers</a></li> <li><a href="https://www.providence.org/business/supplier-information" aria-label="Supplier Portal footer link">Supplier Portal</a></li> <li><a href="https://psjhcrmwebsites.microsoftcrmportals.com/home" aria-label="Classes and Events footer link">Classes and Events</a></li> <li><a href="https://www.providence.org/obp" aria-label="Online Bill Pay &amp; Financial Assistance footer link">Online Bill Pay &amp; Financial Assistance</a></li> <li><a href="https://www.providence.org/obp/get-a-price-estimate" aria-label="Get a Price Estimate footer link">Get a Price Estimate</a></li> <li><a href="https://www.providence.org/obp/pricing-transparency">Pricing Transparency</a></li> <li><a href="https://blog.providence.org/" aria-label="Read our blog footer link">Read our Blog</a></li> </ul> </div> </div> </div> </div> <div id="footNote" class="wm_footer section"> <div class="footer-bs-wrap"> <div id="fnWrap" class="footer-section-wrapper"> <div class="d-col social"> <h3>Connect with us</h3> <div> <a href="https://www.facebook.com/providence" title="Check us out on Facebook" target="_blank"> <span class="screenreader">Check us out on Facebook</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/facebook.png" alt="Facebook"></amp-img> </a> <a href="https://twitter.com/providence" title="Check us out on Twitter" target="_blank"> <span class="screenreader">Check us out on Twitter</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/twitter.png" alt="Twitter"></amp-img> </a> <a href="https://www.linkedin.com/company/providencest.josephhealth" title="Check us out on LinkedIn" target="_blank"> <span class="screenreader">Check us out on LinkedIn</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/linkedin.png" alt="LinkedIn"></amp-img> </a> <a href="https://www.instagram.com/providencehealthsystem/" title="Check us out on Instagram" target="_blank"> <span class="screenreader">Check us out on Instagram</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/instagram.png" alt="Instgram"></amp-img> </a> <a href="https://www.youtube.com/channel/UCCJ8wEkiHBHkVD8Jb7sbIBg/featured" title="Check us out on YouTube" target="_blank"> <span class="screenreader">Check us out on YouTube</span> <amp-img layout="fixed" width="24" height="24" src="https://amp-providers.providence.org/7772/youtube.png" alt="YouTube"></amp-img> </a> </div> </div> <div class="d-col legal"> <div class="footer-info"> <a href="https://www.providence.org/utility-pages/disclaimer">Disclaimer</a> <span>|</span> <a href="https://www.providence.org/utility-pages/privacy-policy">Terms of Use &amp; Privacy Policy</a> <span>|</span> <a href="https://www.providence.org/utility-pages/nondiscrimination-statement">Notice of Nondiscrimination &amp; Communication Assistance</a> <span>|</span> <a href="https://www.providence.org/utility-pages/notice-of-privacy-practices">Notice of Privacy Practices</a> </div> <span class="copyright">©2024 Providence. All rights reserved.</span> </div> </div> </div> </div> </div> <svg hidden="" class="hidden" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <symbol id="icon-building-o" viewBox="0 0 22 28"> <title>Building Icon</title> <desc>Building Icon</desc> <path d="M6 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 20.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 16.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM6 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 12.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM10 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 8.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM18 4.5v1c0 0.266-0.234 0.5-0.5 0.5h-1c-0.266 0-0.5-0.234-0.5-0.5v-1c0-0.266 0.234-0.5 0.5-0.5h1c0.266 0 0.5 0.234 0.5 0.5zM14 26h6v-24h-18v24h6v-3.5c0-0.266 0.234-0.5 0.5-0.5h5c0.266 0 0.5 0.234 0.5 0.5v3.5zM22 1v26c0 0.547-0.453 1-1 1h-20c-0.547 0-1-0.453-1-1v-26c0-0.547 0.453-1 1-1h20c0.547 0 1 0.453 1 1z"></path> </symbol> <symbol id="icon-building-hospital" viewBox="0 0 448 512"> <titl |
| 12 |
Error
On line 1, column 348720 448 512"> <title>Hospital</title> <desc>Hospital</desc> <path fill="currentColor" d="M128 244v-40c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v40c0 6.627-5.373 12-12 12h-40c-6.627 0-12-5.373-12-12zm140 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm-76 84v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm76 12h40c6.627 0 12-5.373 12-12v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12zm180 124v36H0v-36c0-6.627 5.373-12 12-12h19.5V85.035C31.5 73.418 42.245 64 55.5 64H144V24c0-13.255 10.745-24 24-24h112c13.255 0 24 10.745 24 24v40h88.5c13.255 0 24 9.418 24 21.035V464H436c6.627 0 12 5.373 12 12zM79.5 463H192v-67c0-6.627 5.373-12 12-12h40c6.627 0 12 5.373 12 12v67h112.5V112H304v24c0 13.255-10.745 24-24 24H168c-13.255 0-24-10.745-24-24v-24H79.5v351zM266 64h-26V38a6 6 0 0 0-6-6h-20a6 6 0 0 0-6 6v26h-26a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h26v26a6 6 0 0 0 6 6h20a6 6 0 0 0 6-6V96h26a6 6 0 0 0 6-6V70a6 6 0 0 0-6-6z"></path> </symbol> <symbol id="phone" viewBox="0 0 24 24"> <titl |
| 13 |
Info
On line 1, column 335939 0 24 24"> <title>Information shield</title> <desc>Information shield</desc> <path d="M12 20.862c-1.184-0.672-4.42-2.695-6.050-5.549-0.079-0.138-0.153-0.276-0.223-0.417-0.456-0.911-0.727-1.878-0.727-2.896v-6.307l7-2.625 7 2.625v6.307c0 1.018-0.271 1.985-0.726 2.897-0.070 0.14-0.145 0.279-0.223 0.417-1.631 2.854-4.867 4.876-6.050 5.549zM12.447 22.894c0 0 4.989-2.475 7.34-6.589 0.096-0.168 0.188-0.34 0.276-0.515 0.568-1.135 0.937-2.408 0.937-3.79v-7c0-0.426-0.267-0.79-0.649-0.936l-8-3c-0.236-0.089-0.485-0.082-0.702 0l-8 3c-0.399 0.149-0.646 0.527-0.649 0.936v7c0 1.382 0.369 2.655 0.938 3.791 0.087 0.175 0.179 0.346 0.276 0.515 2.351 4.114 7.34 6.589 7.34 6.589 0.292 0.146 0.62 0.136 0.894 0z"></path> </symbol> <symbol id="icon-info-circle" viewBox="0 0 24 28"> <title>Information Circle</title> <desc>Information Circle Icon</desc> <path d="M16 21.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-1.5v-8c0-0.281-0.219-0.5-0.5-0.5h-5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h1.5v5h-1.5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h7c0.281 0 0.5-0.219 0.5-0.5zM14 7.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-3c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h3c0.281 0 0.5-0.219 0.5-0.5zM24 14c0 6.625-5.375 12-12 12s-12-5.375-12-12 5.375-12 12-12 12 5.375 12 12z"></path> </symbol> <symbol id="icon-check" viewBox="0 0 28 28"> <path d="M26.109 8.844c0 0.391-0.156 0.781-0.438 1.062l-13.438 13.438c-0.281 0.281-0.672 0.438-1.062 0.438s-0.781-0.156-1.062-0.438l-7.781-7.781c-0.281-0.281-0.438-0.672-0.438-1.062s0.156-0.781 0.438-1.062l2.125-2.125c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l4.594 4.609 10.25-10.266c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l2.125 2.125c0.281 0.281 0.438 0.672 0.438 1.062z"></path> </symbol> <symbol id="icon-chevron-down" viewBox="0 0 28 28"> <path d="M26.297 12.625l-11.594 11.578c-0.391 0.391-1.016 0.391-1.406 0l-11.594-11.578c-0.391-0.391-0.391-1.031 0-1.422l2.594-2.578c0.391-0.391 1.016-0.391 1.406 0l8.297 8.297 8.297-8.297c0.391-0.391 1.016-0.391 1.406 0l2.594 2.578c0.391 0.391 0.391 1.031 0 1.422z"></path> </symbol> <symbol id="search" viewBox="0 0 19 21"> <title>Search</title> <desc>Search Icon</desc> <path d="M17.693 20.533a1 1 0 0 1-.75-.34l-3.86-4.37a8.53 8.53 0 0 1-12.92-5.59 8.57 8.57 0 1 1 16.43 1.35 1 1 0 1 1-1.87-.71 6.57 6.57 0 0 0-6-8.88 6.46 6.46 0 0 0-5.16 2.32 6.57 6.57 0 0 0 8.5 9.8 2 2 0 0 1 2.53.42l3.86 4.37a1 1 0 0 1-.75 1.66l-.01-.03z"></path> </symbol> <symbol id="close" viewBox="0 0 320 512"> <title>Close</title> <desc>Close icon</desc> <path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path> </symbol> <symbol id="check" viewBox="0 0 21 21"> <title>Check</title> <desc>Checkmark</desc> <path d="M11 21c-4.525.02-8.527-3.06-10-7.5C.173 9.096 2.166 4.456 6 1.833 10.165.038 15.153.82 18.5 4.333c2.914 3.144 3.391 8.17.833 11.667-.137.748-.65.863-.833.833-.59-.46-.706-.973-.833-1.666C20.303 11.874 19.48 7.146 16 4.333 12.79 1.9 8.002 2.23 5.167 5.167 2.093 8.346 1.928 13.142 4.333 16c3.07 3.65 7.823 4.308 11.667 1.667-.08.088.236.066.833 0-.063.326.114.589 0 .833.155.348.017.633 0 .833C14.961 20.406 13.025 21 11 21zm-.772-6.667h-.034a.824.824 0 0 1-.586-.24L7.955 12.43a.834.834 0 0 1 .345-1.439.823.823 0 0 1 .82.268l1 1.005 2.727-3.432a.824.824 0 0 1 1.433.205.835.835 0 0 1-.145.834l-3.304 4.155a.825.825 0 0 1-.603.307z"></path> </symbol> <symbol id="heart-empty" viewBox="0 0 15 13"> <title>Empty Heart</title> <desc>Empty Heart Icon</desc> <path d="M4.115 1.733c-.63 0-1.225.26-1.674.734-.957 1.006-.957 2.643 0 3.65l4.166 4.056c.001.003.004.004.006.006l.75.74 4.936-4.825c.935-.984.935-2.621-.022-3.627a2.329 2.329 0 0 0-3.348 0c-.408.429-.658 1-.707 1.609a.867.867 0 0 1-1.728-.003 2.63 2.63 0 0 0-.705-1.606 2.297 2.297 0 0 0-1.674-.734M7.359 13a.862.862 0 0 1-.609-.25L1.207 7.333c-1.606-1.687-1.606-4.396-.022-6.06A4.015 4.015 0 0 1 4.115 0c1.11 0 2.15.452 2.93 1.272.112.118.217.242.314.371.097-.129.203-.253.314-.37A4.015 4.015 0 0 1 10.603 0c1.11 0 2.15.452 2.93 1.272 1.583 1.665 1.583 4.374 0 6.04l-5.568 5.441a.864.864 0 0 1-.606.247"></path> </symbol> <symbol id="heart-full" viewBox="0 0 15 13"> <title>Full Heart</title> <desc>Full Heart Icon</desc> <path d="M5.936 11.457L7.5 13l6.4-6.254c1.467-1.543 1.467-4.045 0-5.589a3.627 3.627 0 0 0-5.313 0 4.03 4.03 0 0 0-1.088 2.466 4.02 4.02 0 0 0-1.086-2.466 3.628 3.628 0 0 0-5.313 0c-1.467 1.544-1.467 4.046 0 5.59l3.275 3.199 1.561 1.511z"></path> </symbol> <symbol id="people" viewBox="0 0 20 18"> <title>People</title> <desc>People Icon</desc> <path d="M6.477 17.002H2.429A2.429 2.429 0 0 1 0 14.573v-2.59A2.494 2.494 0 0 1 1.514 9.78a12.476 12.476 0 0 1 6.55-.712.82.82 0 0 1-.26 1.619c-1.91-.3-3.867-.085-5.667.623a.899.899 0 0 0-.485.697v2.566c0 .447.362.81.81.81h4.047a.81.81 0 0 1 0 1.619h-.032zm3.238-13.31a3.643 3.643 0 1 0-3.643 3.595 3.619 3.619 0 0 0 3.643-3.595zm-1.619 0a2.024 2.024 0 1 1-2.024-1.976 2 2 0 0 1 2.024 1.976zm11.335 11.424v-2.72a2.21 2.21 0 0 0-1.344-1.96 10.42 10.42 0 0 0-7.683 0 2.21 2.21 0 0 0-1.344 1.96v3.796a.81.81 0 1 0 1.619 0v-3.789a.615.615 0 0 1 .316-.445 8.817 8.817 0 0 1 6.477 0 .615.615 0 0 1 .315.445v2.72a.275.275 0 0 1-.283.268h-4.55a.81.81 0 1 0 0 1.62h4.574a1.895 1.895 0 0 0 1.903-1.895zM17 5.295a2.834 2.834 0 1 0-2.833 2.793 2.817 2.817 0 0 0 2.834-2.785v-.008zm-1.618 0a1.214 1.214 0 1 1-1.215-1.174 1.198 1.198 0 0 1 1.215 1.182v-.008z"></path> </symbol> <symbol id="chat" viewBox="0 0 19 19"> <title>Chat</title> <desc>Chat Icon</desc> <path d="M4.524 19a.905.905 0 0 1-.643-1.547l3.086-3.085a2.714 2.714 0 0 1 1.918-.797H16c.5 0 .905-.405.905-.904V2.714c0-.5-.405-.904-.905-.904H2.714c-.5 0-.904.405-.904.904v9.953c0 .5.405.904.904.904h.905a.905.905 0 1 1 0 1.81h-.905A2.714 2.714 0 0 1 0 12.667V2.714C0 1.215 1.215 0 2.714 0H16c1.5 0 2.714 1.215 2.714 2.714v9.953c0 1.499-1.215 2.714-2.714 2.714H8.894a.905.905 0 0 0-.643.262L5.166 18.73c-.17.172-.4.27-.642.271zm8.762-13.662c0-.5-.405-.905-.905-.905H5.429a.905.905 0 1 0 0 1.81h6.952c.5 0 .905-.405.905-.905zm-3.62 3.62c0-.5-.404-.906-.904-.906H5.429a.905.905 0 0 0 0 1.81h3.333c.5 0 .905-.405.905-.905z"></path> </symbol> <symbol id="phone" viewBox="0 0 24 24"> <titl |
| 14 |
Error
On line 1, column 349870 0 24 24"> <title>Phone</title> <desc>Phone Icon</desc> <path d="M17.87 17.113l1.544 1.544c.374.374.58.87.58 1.397 0 .53-.206 1.024-.58 1.398l-2.25 2.25a.98.98 0 0 1-.657.287A15.963 15.963 0 0 1 4.66 19.341 15.958 15.958 0 0 1 .011 7.493a.983.983 0 0 1 .286-.656l2.251-2.251a1.98 1.98 0 0 1 2.795 0L6.887 6.13a3.982 3.982 0 0 1 1 3.965l-.958 3.188a.977.977 0 0 1-1.222.658.971.971 0 0 1-.581-.474.974.974 0 0 1-.076-.747l.958-3.188A2.023 2.023 0 0 0 5.5 7.517L3.955 5.973 1.962 7.947a13.814 13.814 0 0 0 4.084 10.007c2.68 2.679 6.207 4.152 10.008 4.083l1.973-1.972a.014.014 0 0 0 0-.02L16.482 18.5a2.024 2.024 0 0 0-2.014-.507l-3.188.957a.985.985 0 0 1-1.22-.657.973.973 0 0 1 .074-.747c.124-.23.331-.4.583-.475l3.188-.958a3.984 3.984 0 0 1 3.965 1zM14.024 12A2.027 2.027 0 0 0 12 9.975a.981.981 0 0 1 0-1.96A3.99 3.99 0 0 1 15.986 12a.982.982 0 0 1-1.962 0zM12 4.007c4.407 0 7.993 3.585 7.993 7.993a.981.981 0 0 1-1.962 0A6.038 6.038 0 0 0 12 5.968a.981.981 0 0 1 0-1.961zM12 0c6.617 0 12 5.383 12 12 0 .54-.44.981-.98.981a.982.982 0 0 1-.981-.981c0-5.535-4.504-10.039-10.04-10.039A.981.981 0 0 1 12 0z"></path> </symbol> <symbol id="fax" viewBox="0 0 28 28"> <titl |
| 15 |
Info
On line 1, column 337089 0 24 24"> <title>Phone</title> <desc>Phone Icon</desc> <path d="M17.87 17.113l1.544 1.544c.374.374.58.87.58 1.397 0 .53-.206 1.024-.58 1.398l-2.25 2.25a.98.98 0 0 1-.657.287A15.963 15.963 0 0 1 4.66 19.341 15.958 15.958 0 0 1 .011 7.493a.983.983 0 0 1 .286-.656l2.251-2.251a1.98 1.98 0 0 1 2.795 0L6.887 6.13a3.982 3.982 0 0 1 1 3.965l-.958 3.188a.977.977 0 0 1-1.222.658.971.971 0 0 1-.581-.474.974.974 0 0 1-.076-.747l.958-3.188A2.023 2.023 0 0 0 5.5 7.517L3.955 5.973 1.962 7.947a13.814 13.814 0 0 0 4.084 10.007c2.68 2.679 6.207 4.152 10.008 4.083l1.973-1.972a.014.014 0 0 0 0-.02L16.482 18.5a2.024 2.024 0 0 0-2.014-.507l-3.188.957a.985.985 0 0 1-1.22-.657.973.973 0 0 1 .074-.747c.124-.23.331-.4.583-.475l3.188-.958a3.984 3.984 0 0 1 3.965 1zM14.024 12A2.027 2.027 0 0 0 12 9.975a.981.981 0 0 1 0-1.96A3.99 3.99 0 0 1 15.986 12a.982.982 0 0 1-1.962 0zM12 4.007c4.407 0 7.993 3.585 7.993 7.993a.981.981 0 0 1-1.962 0A6.038 6.038 0 0 0 12 5.968a.981.981 0 0 1 0-1.961zM12 0c6.617 0 12 5.383 12 12 0 .54-.44.981-.98.981a.982.982 0 0 1-.981-.981c0-5.535-4.504-10.039-10.04-10.039A.981.981 0 0 1 12 0z"></path> </symbol> <symbol id="fax" viewBox="0 0 28 28"> <titl |
| 16 |
Error
On line 1, column 351490 0 28 28"> <title>Fax</title> <desc>Fax Icon</desc> <path d="M4.5 6c1.375 0 2.5 1.125 2.5 2.5v17c0 1.375-1.125 2.5-2.5 2.5h-2c-1.375 0-2.5-1.125-2.5-2.5v-17c0-1.375 1.125-2.5 2.5-2.5h2zM26 8.547c1.188 0.688 2 1.984 2 3.453v12c0 2.203-1.797 4-4 4h-13.5c-1.375 0-2.5-1.125-2.5-2.5v-24c0-0.828 0.672-1.5 1.5-1.5h10.5c0.828 0 1.984 0.484 2.562 1.062l2.375 2.375c0.578 0.578 1.062 1.734 1.062 2.562v2.547zM14.5 24v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM14.5 20v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM14.5 16v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM18.5 24v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM18.5 20v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM18.5 16v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM22.5 24v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM22.5 20v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM22.5 16v-2c0-0.281-0.219-0.5-0.5-0.5h-2c-0.281 0-0.5 0.219-0.5 0.5v2c0 0.281 0.219 0.5 0.5 0.5h2c0.281 0 0.5-0.219 0.5-0.5zM24 10v-4h-2.5c-0.828 0-1.5-0.672-1.5-1.5v-2.5h-10v8h14z"></path> </symbol> <symbol id="chat" viewBox="0 0 19 19"> <titl |
| 17 |
Info
On line 1, column 335216 0 24 24"> <title>Information shield</title> <desc>Information shield</desc> <path d="M12 20.862c-1.184-0.672-4.42-2.695-6.050-5.549-0.079-0.138-0.153-0.276-0.223-0.417-0.456-0.911-0.727-1.878-0.727-2.896v-6.307l7-2.625 7 2.625v6.307c0 1.018-0.271 1.985-0.726 2.897-0.070 0.14-0.145 0.279-0.223 0.417-1.631 2.854-4.867 4.876-6.050 5.549zM12.447 22.894c0 0 4.989-2.475 7.34-6.589 0.096-0.168 0.188-0.34 0.276-0.515 0.568-1.135 0.937-2.408 0.937-3.79v-7c0-0.426-0.267-0.79-0.649-0.936l-8-3c-0.236-0.089-0.485-0.082-0.702 0l-8 3c-0.399 0.149-0.646 0.527-0.649 0.936v7c0 1.382 0.369 2.655 0.938 3.791 0.087 0.175 0.179 0.346 0.276 0.515 2.351 4.114 7.34 6.589 7.34 6.589 0.292 0.146 0.62 0.136 0.894 0z"></path> </symbol> <symbol id="icon-info-circle" viewBox="0 0 24 28"> <title>Information Circle</title> <desc>Information Circle Icon</desc> <path d="M16 21.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-1.5v-8c0-0.281-0.219-0.5-0.5-0.5h-5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h1.5v5h-1.5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h7c0.281 0 0.5-0.219 0.5-0.5zM14 7.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-3c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h3c0.281 0 0.5-0.219 0.5-0.5zM24 14c0 6.625-5.375 12-12 12s-12-5.375-12-12 5.375-12 12-12 12 5.375 12 12z"></path> </symbol> <symbol id="icon-check" viewBox="0 0 28 28"> <path d="M26.109 8.844c0 0.391-0.156 0.781-0.438 1.062l-13.438 13.438c-0.281 0.281-0.672 0.438-1.062 0.438s-0.781-0.156-1.062-0.438l-7.781-7.781c-0.281-0.281-0.438-0.672-0.438-1.062s0.156-0.781 0.438-1.062l2.125-2.125c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l4.594 4.609 10.25-10.266c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l2.125 2.125c0.281 0.281 0.438 0.672 0.438 1.062z"></path> </symbol> <symbol id="icon-chevron-down" viewBox="0 0 28 28"> <path d="M26.297 12.625l-11.594 11.578c-0.391 0.391-1.016 0.391-1.406 0l-11.594-11.578c-0.391-0.391-0.391-1.031 0-1.422l2.594-2.578c0.391-0.391 1.016-0.391 1.406 0l8.297 8.297 8.297-8.297c0.391-0.391 1.016-0.391 1.406 0l2.594 2.578c0.391 0.391 0.391 1.031 0 1.422z"></path> </symbol> <symbol id="search" viewBox="0 0 19 21"> <title>Search</title> <desc>Search Icon</desc> <path d="M17.693 20.533a1 1 0 0 1-.75-.34l-3.86-4.37a8.53 8.53 0 0 1-12.92-5.59 8.57 8.57 0 1 1 16.43 1.35 1 1 0 1 1-1.87-.71 6.57 6.57 0 0 0-6-8.88 6.46 6.46 0 0 0-5.16 2.32 6.57 6.57 0 0 0 8.5 9.8 2 2 0 0 1 2.53.42l3.86 4.37a1 1 0 0 1-.75 1.66l-.01-.03z"></path> </symbol> <symbol id="close" viewBox="0 0 320 512"> <title>Close</title> <desc>Close icon</desc> <path d="M207.6 256l107.72-107.72c6.23-6.23 6.23-16.34 0-22.58l-25.03-25.03c-6.23-6.23-16.34-6.23-22.58 0L160 208.4 52.28 100.68c-6.23-6.23-16.34-6.23-22.58 0L4.68 125.7c-6.23 6.23-6.23 16.34 0 22.58L112.4 256 4.68 363.72c-6.23 6.23-6.23 16.34 0 22.58l25.03 25.03c6.23 6.23 16.34 6.23 22.58 0L160 303.6l107.72 107.72c6.23 6.23 16.34 6.23 22.58 0l25.03-25.03c6.23-6.23 6.23-16.34 0-22.58L207.6 256z"></path> </symbol> <symbol id="check" viewBox="0 0 21 21"> <title>Check</title> <desc>Checkmark</desc> <path d="M11 21c-4.525.02-8.527-3.06-10-7.5C.173 9.096 2.166 4.456 6 1.833 10.165.038 15.153.82 18.5 4.333c2.914 3.144 3.391 8.17.833 11.667-.137.748-.65.863-.833.833-.59-.46-.706-.973-.833-1.666C20.303 11.874 19.48 7.146 16 4.333 12.79 1.9 8.002 2.23 5.167 5.167 2.093 8.346 1.928 13.142 4.333 16c3.07 3.65 7.823 4.308 11.667 1.667-.08.088.236.066.833 0-.063.326.114.589 0 .833.155.348.017.633 0 .833C14.961 20.406 13.025 21 11 21zm-.772-6.667h-.034a.824.824 0 0 1-.586-.24L7.955 12.43a.834.834 0 0 1 .345-1.439.823.823 0 0 1 .82.268l1 1.005 2.727-3.432a.824.824 0 0 1 1.433.205.835.835 0 0 1-.145.834l-3.304 4.155a.825.825 0 0 1-.603.307z"></path> </symbol> <symbol id="heart-empty" viewBox="0 0 15 13"> <title>Empty Heart</title> <desc>Empty Heart Icon</desc> <path d="M4.115 1.733c-.63 0-1.225.26-1.674.734-.957 1.006-.957 2.643 0 3.65l4.166 4.056c.001.003.004.004.006.006l.75.74 4.936-4.825c.935-.984.935-2.621-.022-3.627a2.329 2.329 0 0 0-3.348 0c-.408.429-.658 1-.707 1.609a.867.867 0 0 1-1.728-.003 2.63 2.63 0 0 0-.705-1.606 2.297 2.297 0 0 0-1.674-.734M7.359 13a.862.862 0 0 1-.609-.25L1.207 7.333c-1.606-1.687-1.606-4.396-.022-6.06A4.015 4.015 0 0 1 4.115 0c1.11 0 2.15.452 2.93 1.272.112.118.217.242.314.371.097-.129.203-.253.314-.37A4.015 4.015 0 0 1 10.603 0c1.11 0 2.15.452 2.93 1.272 1.583 1.665 1.583 4.374 0 6.04l-5.568 5.441a.864.864 0 0 1-.606.247"></path> </symbol> <symbol id="heart-full" viewBox="0 0 15 13"> <title>Full Heart</title> <desc>Full Heart Icon</desc> <path d="M5.936 11.457L7.5 13l6.4-6.254c1.467-1.543 1.467-4.045 0-5.589a3.627 3.627 0 0 0-5.313 0 4.03 4.03 0 0 0-1.088 2.466 4.02 4.02 0 0 0-1.086-2.466 3.628 3.628 0 0 0-5.313 0c-1.467 1.544-1.467 4.046 0 5.59l3.275 3.199 1.561 1.511z"></path> </symbol> <symbol id="people" viewBox="0 0 20 18"> <title>People</title> <desc>People Icon</desc> <path d="M6.477 17.002H2.429A2.429 2.429 0 0 1 0 14.573v-2.59A2.494 2.494 0 0 1 1.514 9.78a12.476 12.476 0 0 1 6.55-.712.82.82 0 0 1-.26 1.619c-1.91-.3-3.867-.085-5.667.623a.899.899 0 0 0-.485.697v2.566c0 .447.362.81.81.81h4.047a.81.81 0 0 1 0 1.619h-.032zm3.238-13.31a3.643 3.643 0 1 0-3.643 3.595 3.619 3.619 0 0 0 3.643-3.595zm-1.619 0a2.024 2.024 0 1 1-2.024-1.976 2 2 0 0 1 2.024 1.976zm11.335 11.424v-2.72a2.21 2.21 0 0 0-1.344-1.96 10.42 10.42 0 0 0-7.683 0 2.21 2.21 0 0 0-1.344 1.96v3.796a.81.81 0 1 0 1.619 0v-3.789a.615.615 0 0 1 .316-.445 8.817 8.817 0 0 1 6.477 0 .615.615 0 0 1 .315.445v2.72a.275.275 0 0 1-.283.268h-4.55a.81.81 0 1 0 0 1.62h4.574a1.895 1.895 0 0 0 1.903-1.895zM17 5.295a2.834 2.834 0 1 0-2.833 2.793 2.817 2.817 0 0 0 2.834-2.785v-.008zm-1.618 0a1.214 1.214 0 1 1-1.215-1.174 1.198 1.198 0 0 1 1.215 1.182v-.008z"></path> </symbol> <symbol id="chat" viewBox="0 0 19 19"> <titl |
| 18 |
Error
On line 1, column 352214 0 19 19"> <title>Chat</title> <desc>Chat Icon</desc> <path d="M4.524 19a.905.905 0 0 1-.643-1.547l3.086-3.085a2.714 2.714 0 0 1 1.918-.797H16c.5 0 .905-.405.905-.904V2.714c0-.5-.405-.904-.905-.904H2.714c-.5 0-.904.405-.904.904v9.953c0 .5.405.904.904.904h.905a.905.905 0 1 1 0 1.81h-.905A2.714 2.714 0 0 1 0 12.667V2.714C0 1.215 1.215 0 2.714 0H16c1.5 0 2.714 1.215 2.714 2.714v9.953c0 1.499-1.215 2.714-2.714 2.714H8.894a.905.905 0 0 0-.643.262L5.166 18.73c-.17.172-.4.27-.642.271zm8.762-13.662c0-.5-.405-.905-.905-.905H5.429a.905.905 0 1 0 0 1.81h6.952c.5 0 .905-.405.905-.905zm-3.62 3.62c0-.5-.404-.906-.904-.906H5.429a.905.905 0 0 0 0 1.81h3.333c.5 0 .905-.405.905-.905z"></path> </symbol> <symbol id="search" viewBox="0 0 19 21"> <titl |
| 19 |
Info
On line 1, column 331572 0 24 24"> <title>Information shield</title> <desc>Information shield</desc> <path d="M12 20.862c-1.184-0.672-4.42-2.695-6.050-5.549-0.079-0.138-0.153-0.276-0.223-0.417-0.456-0.911-0.727-1.878-0.727-2.896v-6.307l7-2.625 7 2.625v6.307c0 1.018-0.271 1.985-0.726 2.897-0.070 0.14-0.145 0.279-0.223 0.417-1.631 2.854-4.867 4.876-6.050 5.549zM12.447 22.894c0 0 4.989-2.475 7.34-6.589 0.096-0.168 0.188-0.34 0.276-0.515 0.568-1.135 0.937-2.408 0.937-3.79v-7c0-0.426-0.267-0.79-0.649-0.936l-8-3c-0.236-0.089-0.485-0.082-0.702 0l-8 3c-0.399 0.149-0.646 0.527-0.649 0.936v7c0 1.382 0.369 2.655 0.938 3.791 0.087 0.175 0.179 0.346 0.276 0.515 2.351 4.114 7.34 6.589 7.34 6.589 0.292 0.146 0.62 0.136 0.894 0z"></path> </symbol> <symbol id="icon-info-circle" viewBox="0 0 24 28"> <title>Information Circle</title> <desc>Information Circle Icon</desc> <path d="M16 21.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-1.5v-8c0-0.281-0.219-0.5-0.5-0.5h-5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h1.5v5h-1.5c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h7c0.281 0 0.5-0.219 0.5-0.5zM14 7.5v-2.5c0-0.281-0.219-0.5-0.5-0.5h-3c-0.281 0-0.5 0.219-0.5 0.5v2.5c0 0.281 0.219 0.5 0.5 0.5h3c0.281 0 0.5-0.219 0.5-0.5zM24 14c0 6.625-5.375 12-12 12s-12-5.375-12-12 5.375-12 12-12 12 5.375 12 12z"></path> </symbol> <symbol id="icon-check" viewBox="0 0 28 28"> <path d="M26.109 8.844c0 0.391-0.156 0.781-0.438 1.062l-13.438 13.438c-0.281 0.281-0.672 0.438-1.062 0.438s-0.781-0.156-1.062-0.438l-7.781-7.781c-0.281-0.281-0.438-0.672-0.438-1.062s0.156-0.781 0.438-1.062l2.125-2.125c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l4.594 4.609 10.25-10.266c0.281-0.281 0.672-0.438 1.062-0.438s0.781 0.156 1.062 0.438l2.125 2.125c0.281 0.281 0.438 0.672 0.438 1.062z"></path> </symbol> <symbol id="icon-chevron-down" viewBox="0 0 28 28"> <path d="M26.297 12.625l-11.594 11.578c-0.391 0.391-1.016 0.391-1.406 0l-11.594-11.578c-0.391-0.391-0.391-1.031 0-1.422l2.594-2.578c0.391-0.391 1.016-0.391 1.406 0l8.297 8.297 8.297-8.297c0.391-0.391 1.016-0.391 1.406 0l2.594 2.578c0.391 0.391 0.391 1.031 0 1.422z"></path> </symbol> <symbol id="search" viewBox="0 0 19 21"> <titl |
| 20 |
Error
On line 1, column 354233 0 19 21"> <title>Search</title> <desc>Search Icon</desc> <path d="M17.693 20.533a1 1 0 0 1-.75-.34l-3.86-4.37a8.53 8.53 0 0 1-12.92-5.59 8.57 8.57 0 1 1 16.43 1.35 1 1 0 1 1-1.87-.71 6.57 6.57 0 0 0-6-8.88 6.46 6.46 0 0 0-5.16 2.32 6.57 6.57 0 0 0 8.5 9.8 2 2 0 0 1 2.53.42l3.86 4.37a1 1 0 0 1-.75 1.66l-.01-.03z"></path> </symbol> <symbol id="instagram" viewBox="0 0 448 512"> <path d="M224.1 141c-63.6 0-114.9 51.3-114.9 114.9s51.3 114.9 114.9 114.9S339 319.5 339 255.9 287.7 141 224.1 141zm0 189.6c-41.1 0-74.7-33.5-74.7-74.7s33.5-74.7 74.7-74.7 74.7 33.5 74.7 74.7-33.6 74.7-74.7 74.7zm146.4-194.3c0 14.9-12 26.8-26.8 26.8-14.9 0-26.8-12-26.8-26.8s12-26.8 26.8-26.8 26.8 12 26.8 26.8zm76.1 27.2c-1.7-35.9-9.9-67.7-36.2-93.9-26.2-26.2-58-34.4-93.9-36.2-37-2.1-147.9-2.1-184.9 0-35.8 1.7-67.6 9.9-93.9 36.1s-34.4 58-36.2 93.9c-2.1 37-2.1 147.9 0 184.9 1.7 35.9 9.9 67.7 36.2 93.9s58 34.4 93.9 36.2c37 2.1 147.9 2.1 184.9 0 35.9-1.7 67.7-9.9 93.9-36.2 26.2-26.2 34.4-58 36.2-93.9 2.1-37 2.1-147.8 0-184.8zM398.8 388c-7.8 19.6-22.9 34.7-42.6 42.6-29.5 11.7-99.5 9-132.1 9s-102.7 2.6-132.1-9c-19.6-7.8-34.7-22.9-42.6-42.6-11.7-29.5-9-99.5-9-132.1s-2.6-102.7 9-132.1c7.8-19.6 22.9-34.7 42.6-42.6 29.5-11.7 99.5-9 132.1-9s102.7-2.6 132.1 9c19.6 7.8 34.7 22.9 42.6 42.6 11.7 29.5 9 99.5 9 132.1s2.7 102.7-9 132.1z"></path> </symbol> <symbol id="icon_videocam" viewBox="0 0 24 24" fill="currentColor"> <path d="M0 0h24v24H0V0z" fill="none"></path> <path d="M15 8v8H5V8h10m1-2H4c-.55 0-1 .45-1 1v10c0 .55.45 1 1 1h12c.55 0 1-.45 1-1v-3.5l4 4v-11l-4 4V7c0-.55-.45-1-1-1z"></path> </symbol> <symbol id="menu-icon" viewBox="0 0 448 512"> <title>Menu</title> <desc>Main Menu Toggle</desc> <path d="M0 96C0 78.3 14.3 64 32 64H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32C14.3 128 0 113.7 0 96zM0 256c0-17.7 14.3-32 32-32H416c17.7 0 32 14.3 32 32s-14.3 32-32 32H32c-17.7 0-32-14.3-32-32zM448 416c0 17.7-14.3 32-32 32H32c-17.7 0-32-14.3-32-32s14.3-32 32-32H416c17.7 0 32 14.3 32 32z"></path> </symbol> <symbol id="close" viewBox="0 0 320 512"> <titl |
| 21 |
Info
On line 1, column 331936 0 19 21"> <title>Search</title> <desc>Search Icon</desc> <path d="M17.693 20.533a1 1 0 0 1-.75-.34l-3.86-4.37a8.53 8.53 0 0 1-12.92-5.59 8.57 8.57 0 1 1 16.43 1.35 1 1 0 1 1-1.87-.71 6.57 6.57 0 0 0-6-8.88 6.46 6.46 0 0 0-5.16 2.32 6.57 6.57 0 0 0 8.5 9.8 2 2 0 0 1 2.53.42l3.86 4.37a1 1 0 0 1-.75 1.66l-.01-.03z"></path> </symbol> <symbol id="close" viewBox="0 0 320 512"> <titl |
| 22 |
Error
On line 1, column 25 type html&gt;&lt;html amp&gt;&lt;head&gt; |
| 23 |
Error
On line 1, column 52 amp><head><style style="background-color: #ffff80;">amp-custom="">body { |
| 24 |
Error
On line 1, column 5482 ar-color: #515151; } .f |
| 25 |
Error
On line 1, column 6150 ar-color: #515151; } .d |
| 26 |
Error
On line 1, column 25559 lots::part(time) { margin: 0; |
| 27 |
Error
On line 1, column 25657 slots::part(day) { min-width: |
| 28 |
Error
On line 1, column 25788 erflow-y: clip; } #s |
| 29 |
Error
On line 1, column 26498 te-space: revert; padd |
| 30 |
Error
On line 1, column 30629 me-color-alt##; } .wm_footer h |
| 31 |
Error
On line 1, column 30631 -color-alt##; } .wm_footer h3 |
| 32 |
Error
On line 1, column 57113 ##link-color##; } .wmHeader .l |
| 33 |
Error
On line 1, column 57115 link-color##; } .wmHeader .log |
| 34 |
Error
On line 1, column 59969 -theme-color##; } .site-search |
| 35 |
Error
On line 1, column 59971 heme-color##; } .site-search h |
| 36 |
Error
On line 1, column 61246 t-color2-alt##; color: ##link- |
| 37 |
Error
On line 1, column 61269 ##link-color##; } .ak .reg.ak, |
| 38 |
Error
On line 1, column 61271 link-color##; } .ak .reg.ak, . |
| 39 |
Error
On line 1, column 62933 -theme-color##; } .site-search |
| 40 |
Error
On line 1, column 62935 heme-color##; } .site-search l |
| 41 |
Error
On line 1, column 66878 calc(40px+5.5em); } .wmHeader |
| 42 |
Error
On line 1, column 75269 ar-color: #515151; max- |
| 43 |
Error
On line 1, column 79809 lesheet"> <style style="background-color: #ffff80;">amp-boilerplate="">body{- |
| 44 |
Error
On line 1, column 80451 <noscript><style style="background-color: #ffff80;">amp-boilerplate="">body{- |
| 45 |
Error
On line 1, column 80832 doctors"> <script async="" custom-element="amp-bind" src="https://cdn.ampproject.org/v0/amp-bind-0.1.js"></scri |
| 46 |
Error
On line 1, column 80937 </script> <script async="" custom-element="amp-list" src="https://cdn.ampproject.org/v0/amp-list-0.1.js"></scri |
| 47 |
Error
On line 1, column 81051 </script> <script async="" custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></scri |
| 48 |
Error
On line 1, column 81156 </script> <script async="" custom-element="amp-form" src="https://cdn.ampproject.org/v0/amp-form-0.1.js"></scri |
| 49 |
Info
On line 1, column 81262 </script> <script src="~~assetOrigin~~/7914/2022-10-05_1330/js/linker.js" type="text/javascript" async=""></scri |
| 50 |
Error
On line 1, column 81954 </script> <script async="" custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></scri |
| 51 |
Error
On line 1, column 82063 </script> <script async="" custom-element="amp-script" src="https://cdn.ampproject.org/v0/amp-script-0.1.js"></scri |
| 52 |
Error
On line 1, column 82177 </script> <script async="" custom-template="amp-mustache" src="https://cdn.ampproject.org/v0/amp-mustache-0.2.js"></scri |
| 53 |
Error
On line 1, column 82588 nudge=""> <template type="amp-mustache"> <amp- |
| 54 |
Error
On line 1, column 97815 p-script> <script id="amp-list-source-script" type="text/plain" target="amp-script"> const |
| 55 |
Error
On line 1, column 100126 ocation"> <svg aria-hidden="true" focusable="false" class="input-icon" role="presentation" width="17" height="22" viewBox="0 0 17 22" fill="#00338E" xmlns="http://www.w3.org/2000/svg"> <path |
| 56 |
Error
On line 1, column 101097 __query"> <input type="search" id="geoLocatorInput" title="globalGeoInputLabel" aria-label="Set Your Location" placeholder="Zip code or city" autocomplete="off" max="100" class="textInput"> <div |
| 57 |
Error
On line 1, column 111796 </label> <<span style="background-color: #ffff80;">divspan> class="site-search"> <<span style="background-color: #ffff80;">divspan> |
| 58 |
Error
On line 1, column 112055 rchTerm&quot;&gt; &lt; |
| 59 |
Error
On line 1, column 123857 mp-state> <input type="text" name="location" class="dc textInput" [class]="(locationState.validZip != 0 ? 'dc textInput' : 'dc textInput invalid')" id="custom-location"> <div |
| 60 |
Error
On line 1, column 123942 ocation"> <div class="hidden" [class]="(locationState.validZip != 0 ? 'hidden' : 'tool_tip')">Invali |
| 61 |
Error
On line 1, column 126190 mp-state> <form id="setInsuranceSection" class="hidden" method="GET" action="https://www.providence.org/integrationapi/SetInsuranceSelection" action-xhr="https://www.providence.org/integrationapi/SetInsuranceSelection" target="_top"> <inpu |
| 62 |
Error
On line 1, column 126253 t="_top"> <input type="text" name="plan" value="" [value]="insurance_q"> <inpu |
| 63 |
Info
On line 1, column 126357 r> <main> <section id="map" class="show-map"> <div |
| 64 |
Info
On line 1, column 126886 /section> <section class="container" id="results-wrapper"> <div |
| 65 |
Info
On line 1, column 127652 </script> <script id="searchScripts" type="text/javascript"> funct |
| 66 |
Info
On line 1, column 314989 </script> <script id="invocaDecoration" type="text/javascript"> var i |
| 67 |
Error
On line 1, column 319676 x: -100"> <template type="amp-mustache"> <div |
| 68 |
Error
On line 1, column 325696 v> </div> <svg hidden="" class="hidden" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs |
| 69 |
Error
On line 1, column 113451 .</label> <label id="qClear" for="queryClear"><svg w |
| 70 |
Error
On line 1, column 120970 </button> <div class="dropdown-menu" aria-labelledby="GenderLabel"></div> |
| 71 |
Info
On line 1, column 25 type html><html amp><head> |
β