Locator
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#map-container {
width: 100%;
height: 550px;
position: relative;
/*font-family: "Roboto", sans-serif;*/
box-sizing: border-box;
}
#map-container a {
text-decoration: none;
color: #1967d2;
}
#map-container button {
background: none;
color: inherit;
border: none;
padding: 0;
font: inherit;
font-size: inherit;
cursor: pointer;
}
#gmp-map {
position: absolute;
left: 22em;
top: 0;
right: 0;
bottom: 0;
}
#locations-panel {
position: absolute;
left: 0;
width: 22em;
top: 0;
bottom: 0;
overflow-y: auto;
background: white;
padding: 0.5em;
box-sizing: border-box;
}
@media only screen and (max-width: 876px) {
#gmp-map {
left: 0;
bottom: 50%;
}
#locations-panel {
top: 50%;
right: 0;
width: unset;
}
}
#locations-panel-list > header {
padding: 1.4em 1.4em 0 1.4em;
}
#locations-panel-list h1.search-title {
font-size: 1em;
font-weight: 500;
margin: 0;
}
#locations-panel-list h1.search-title > img {
vertical-align: bottom;
margin-top: -1em;
}
#locations-panel-list .search-input {
width: 100%;
margin-top: 0.8em;
position: relative;
}
#locations-panel-list .search-input input {
width: 100%;
border: 1px solid rgba(0, 0, 0, 0.2);
border-radius: 0.3em;
height: 2.2em;
box-sizing: border-box;
padding: 0 2.5em 0 1em;
font-size: 1em;
}
#locations-panel-list .search-input-overlay {
position: absolute;
}
#locations-panel-list .search-input-overlay.search {
right: 2px;
top: 2px;
bottom: 2px;
width: 2.4em;
}
#locations-panel-list .search-input-overlay.search button {
width: 100%;
height: 100%;
border-radius: 0.2em;
color: black;
background: transparent;
}
#locations-panel-list .search-input-overlay.search .icon {
margin-top: 0.05em;
vertical-align: top;
}
#locations-panel-list .section-name {
font-weight: 500;
font-size: 0.9em;
margin: 1.8em 0 1em 1.5em;
}
#locations-panel-list .location-result {
position: relative;
padding: 0.8em 3.5em 0.8em 1.4em;
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
cursor: pointer;
}
#locations-panel-list .location-result:first-of-type {
border-top: 1px solid rgba(0, 0, 0, 0.12);
}
#locations-panel-list .location-result:last-of-type {
border-bottom: none;
}
#locations-panel-list .location-result.selected {
outline: 2px solid #4285f4;
}
#locations-panel-list button.select-location {
margin-bottom: 0.6em;
text-align: left;
}
#locations-panel-list .location-result h2.name {
font-size: 1em;
font-weight: 500;
margin: 0;
}
#locations-panel-list .location-result .address {
font-size: 0.9em;
margin-bottom: 0.5em;
}
#locations-panel-list .directions-button {
position: absolute;
right: 1.2em;
top: 2.3em;
}
#locations-panel-list .directions-button-background:hover {
fill: rgba(116,120,127,0.1);
}
#locations-panel-list .directions-button-background {
fill: rgba(255,255,255,0.01);
}
#locations-panel-list .location-result .distance {
position: absolute;
top: 0.9em;
right: 0;
text-align: center;
font-size: 0.9em;
width: 5em;
}
#locations-panel-list .option-container {
display: inline-block;
margin: 0.2em 0;
position: relative;
}
#locations-panel-list .option-container button:hover,
#locations-panel-list .option-container a:hover {
background-color: #f1f3f4;
}
#locations-panel-list .option {
border: 1px solid #bdc1c6;
border-radius: 0.9em;
color: #1967d2;
font-size: 0.9em;
font-weight: 500;
padding: 0.3em 0;
}
#locations-panel-list .option > span {
margin: 0 0.9em;
}
#location-results-list {
list-style-type: none;
margin: 0;
padding: 0;
}
/* ------------- DETAILS PANEL ------------------------------- */
#locations-panel-details {
padding: 1.4em;
box-sizing: border-box;
display: none;
}
#locations-panel-details .back-button {
font-size: 1em;
font-weight: 500;
color: #1967d2;
display: block;
text-decoration: none;
background: none;
border: none;
cursor: pointer;
padding: 0;
font-family: inherit;
}
#locations-panel-details .back-button .icon {
width: 20px;
height: 20px;
vertical-align: bottom;
/* Match link color #1967d2 */
filter: invert(30%) sepia(67%) saturate(7379%) hue-rotate(209deg) brightness(95%) contrast(80%);
}
#locations-panel-details > header {
text-align: center;
}
#locations-panel-details .banner {
margin-top: 1em;
}
#locations-panel-details h2 {
font-size: 1.1em;
font-weight: 500;
margin-bottom: 0.3em;
}
#locations-panel-details .distance {
font-size: 0.9em;
text-align: center;
}
#locations-panel-details .address {
text-align: center;
font-size: 0.9em;
margin-top: 1.3em;
}
#locations-panel-details .atmosphere {
text-align: center;
font-size: 0.9em;
margin: 0.8em 0;
}
#locations-panel-details .star-rating-numeric {
color: #555;
}
#locations-panel-details .star-icon {
width: 1.2em;
height: 1.2em;
margin-right: -0.3em;
margin-top: -0.08em;
vertical-align: top;
filter: invert(88%) sepia(60%) saturate(2073%) hue-rotate(318deg) brightness(93%) contrast(104%);
}
#locations-panel-details .star-icon:last-of-type {
margin-right: 0.2em;
}
#locations-panel-details .price-dollars {
color: #555;
}
#locations-panel-details hr {
height: 1px;
color: rgba(0, 0, 0, 0.12);
background-color: rgba(0, 0, 0, 0.12);
border: none;
margin-bottom: 1em;
}
#locations-panel-details .contact {
font-size: 0.9em;
margin: 0.8em 0;
display: flex;
align-items: center;
}
#locations-panel-details .contact .icon {
flex: 0 0 auto;
width: 1.5em;
height: 1.5em;
}
#locations-panel-details .contact .right {
padding: 0.1em 0 0 1em;
}
#locations-panel-details .hours .weekday {
display: inline-block;
width: 5em;
}
#locations-panel-details .website a {
white-space: nowrap;
display: inline-block;
overflow: hidden;
max-width: 16em;
text-overflow: ellipsis;
}
#locations-panel-details p.attribution {
color: #777;
margin: 0;
font-size: 0.8em;
font-style: italic;
}
'use strict';
/** Hide a DOM element. */
function hideElement(el) {
el.style.display = 'none';
}
/** Show a DOM element that has been hidden. */
function showElement(el) {
el.style.display = 'block';
}
/** Helper function to generate a Google Maps directions URL */
function generateDirectionsURL(origin, destination) {
const googleMapsUrlBase = 'https://www.google.com/maps/dir/?';
const searchParams = new URLSearchParams('api=1');
searchParams.append('origin', origin);
const destinationParam = [];
// Add title to destinationParam except in cases where Quick Builder set
// the title to the first line of the address
if (destination.title !== destination.address1) {
destinationParam.push(destination.title);
}
destinationParam.push(destination.address1, destination.address2);
searchParams.append('destination', destinationParam.join(','));
return googleMapsUrlBase + searchParams.toString();
}
/**
* Defines an instance of the Locator+ solution, to be instantiated
* when the Maps library is loaded.
*/
function LocatorPlus(configuration) {
const locator = this;
locator.locations = configuration.locations || [];
locator.capabilities = configuration.capabilities || {};
const mapEl = document.getElementById('gmp-map');
const panelEl = document.getElementById('locations-panel');
locator.panelListEl = document.getElementById('locations-panel-list');
const sectionNameEl =
document.getElementById('location-results-section-name');
const resultsContainerEl = document.getElementById('location-results-list');
const itemsTemplate = Handlebars.compile(
document.getElementById('locator-result-items-tmpl').innerHTML);
locator.searchLocation = null;
locator.searchLocationMarker = null;
locator.selectedLocationIdx = null;
locator.userCountry = null;
// Initialize the map -------------------------------------------------------
locator.map = new google.maps.Map(mapEl, configuration.mapOptions);
// Store selection.
const selectResultItem = function(locationIdx, panToMarker, scrollToResult) {
locator.selectedLocationIdx = locationIdx;
for (let locationElem of resultsContainerEl.children) {
locationElem.classList.remove('selected');
if (getResultIndex(locationElem) === locator.selectedLocationIdx) {
locationElem.classList.add('selected');
if (scrollToResult) {
panelEl.scrollTop = locationElem.offsetTop;
}
}
}
if (panToMarker && (locationIdx != null)) {
locator.map.panTo(locator.locations[locationIdx].coords);
}
};
// Create a marker for each location.
const markers = locator.locations.map(function(location, index) {
const marker = new google.maps.Marker({
position: location.coords,
map: locator.map,
title: location.title,
});
marker.addListener('click', function() {
selectResultItem(index, false, true);
});
return marker;
});
// Fit map to marker bounds.
locator.updateBounds = function() {
const bounds = new google.maps.LatLngBounds();
if (locator.searchLocationMarker) {
bounds.extend(locator.searchLocationMarker.getPosition());
}
for (let i = 0; i < markers.length; i++) {
bounds.extend(markers[i].getPosition());
}
locator.map.fitBounds(bounds);
};
if (locator.locations.length) {
locator.updateBounds();
}
// Get the distance of a store location to the user's location,
// used in sorting the list.
const getLocationDistance = function(location) {
if (!locator.searchLocation) return null;
// Use travel distance if available (from Distance Matrix).
if (location.travelDistanceValue != null) {
return location.travelDistanceValue;
}
// Fall back to straight-line distance.
return google.maps.geometry.spherical.computeDistanceBetween(
new google.maps.LatLng(location.coords),
locator.searchLocation.location);
};
// Render the results list --------------------------------------------------
// When the results list re-renders, always update directions on the
// first selection event.
let updateDirectionsOnSelect;
const getResultIndex = function(elem) {
return parseInt(elem.getAttribute('data-location-index'));
};
locator.renderResultsList = function() {
let locations = locator.locations.slice();
for (let i = 0; i 0) {
const result = results[0];
geocodeCache.set(query, result);
handleResult(result);
}
}
});
};
// Set up geocoding on the search input.
searchButtonEl.addEventListener('click', function() {
geocodeSearch(searchInputEl.value.trim());
});
// Initialize Autocomplete.
initializeSearchInputAutocomplete(
locator, searchInputEl, geocodeSearch, updateSearchLocation);
}
/** Add Autocomplete to the search input. */
function initializeSearchInputAutocomplete(
locator, searchInputEl, fallbackSearch, searchLocationUpdater) {
// Set up Autocomplete on the search input. Bias results to map viewport.
const autocomplete = new google.maps.places.Autocomplete(searchInputEl, {
types: ['geocode'],
fields: ['place_id', 'formatted_address', 'geometry.location']
});
autocomplete.bindTo('bounds', locator.map);
autocomplete.addListener('place_changed', function() {
const placeResult = autocomplete.getPlace();
if (!placeResult.geometry) {
// Hitting 'Enter' without selecting a suggestion will result in a
// placeResult with only the text input value as the 'name' field.
fallbackSearch(placeResult.name);
return;
}
searchLocationUpdater(
placeResult.formatted_address, placeResult.geometry.location);
});
}
/** Initialize Distance Matrix for the locator. */
function initializeDistanceMatrix(locator) {
const distanceMatrixService = new google.maps.DistanceMatrixService();
// Annotate travel times to the selected location using Distance Matrix.
locator.updateTravelTimes = function() {
if (!locator.searchLocation) return;
const units = (locator.userCountry === 'USA') ?
google.maps.UnitSystem.IMPERIAL :
google.maps.UnitSystem.METRIC;
const request = {
origins: [locator.searchLocation.location],
destinations: locator.locations.map(function(x) {
return x.coords;
}),
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: units,
};
const callback = function(response, status) {
if (status === 'OK') {
const distances = response.rows[0].elements;
for (let i = 0; i < distances.length; i++) {
const distResult = distances[i];
let travelDistanceText, travelDistanceValue;
if (distResult.status === 'OK') {
travelDistanceText = distResult.distance.text;
travelDistanceValue = distResult.distance.value;
}
const location = locator.locations[i];
location.travelDistanceText = travelDistanceText;
location.travelDistanceValue = travelDistanceValue;
}
// Re-render the results list, in case the ordering has changed.
locator.renderResultsList();
}
};
distanceMatrixService.getDistanceMatrix(request, callback);
};
}
/** Initialize Directions service for the locator. */
function initializeDirections(locator) {
const directionsCache = new Map();
const directionsService = new google.maps.DirectionsService();
const directionsRenderer = new google.maps.DirectionsRenderer({
suppressMarkers: true,
});
// Update directions displayed from the search location to
// the selected location on the map.
locator.updateDirections = function() {
if (!locator.searchLocation || (locator.selectedLocationIdx == null)) {
return;
}
const cacheKey = JSON.stringify(
[locator.searchLocation.location, locator.selectedLocationIdx]);
if (directionsCache.has(cacheKey)) {
const directions = directionsCache.get(cacheKey);
directionsRenderer.setMap(locator.map);
directionsRenderer.setDirections(directions);
return;
}
const request = {
origin: locator.searchLocation.location,
destination: locator.locations[locator.selectedLocationIdx].coords,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status === 'OK') {
directionsRenderer.setMap(locator.map);
directionsRenderer.setDirections(response);
directionsCache.set(cacheKey, response);
}
});
};
locator.clearDirections = function() {
directionsRenderer.setMap(null);
};
}
/** Initialize Place Details service and UI for the locator. */
function initializeDetails(locator) {
const panelDetailsEl = document.getElementById('locations-panel-details');
const detailsService = new google.maps.places.PlacesService(locator.map);
const detailsTemplate = Handlebars.compile(
document.getElementById('locator-details-tmpl').innerHTML);
const renderDetails = function(context) {
panelDetailsEl.innerHTML = detailsTemplate(context);
panelDetailsEl.querySelector('.back-button')
.addEventListener('click', hideDetails);
};
const hideDetails = function() {
showElement(locator.panelListEl);
hideElement(panelDetailsEl);
};
locator.showDetails = function(locationIndex) {
const location = locator.locations[locationIndex];
const context = {location};
// Helper function to create a fixed-size array.
const initArray = function(arraySize) {
const array = [];
while (array.length e.split(/\:\s+/))
.map(e => ({'days': e[0].substr(0, 3), 'hours': e[1]}));
for (let i = 1; i < daysHours.length; i++) {
if (daysHours[i - 1].hours === daysHours[i].hours) {
if (daysHours[i - 1].days.indexOf('-') !== -1) {
daysHours[i - 1].days =
daysHours[i - 1].days.replace(/\w+$/, daysHours[i].days);
} else {
daysHours[i - 1].days += ' - ' + daysHours[i].days;
}
daysHours.splice(i--, 1);
}
}
place.openingHoursSummary = daysHours;
}
if (place.rating) {
const starsOutOfTen = Math.round(2 * place.rating);
const fullStars = Math.floor(starsOutOfTen / 2);
const halfStars = fullStars !== starsOutOfTen / 2 ? 1 : 0;
const emptyStars = 5 - fullStars - halfStars;
// Express stars as arrays to make iterating in Handlebars easy.
place.fullStarIcons = initArray(fullStars);
place.halfStarIcons = initArray(halfStars);
place.emptyStarIcons = initArray(emptyStars);
}
if (place.price_level) {
place.dollarSigns = initArray(place.price_level);
}
if (place.website) {
const url = new URL(place.website);
place.websiteDomain = url.hostname;
}
context.place = place;
renderDetails(context);
}
});
}
renderDetails(context);
hideElement(locator.panelListEl);
showElement(panelDetailsEl);
};
}
const CONFIGURATION = {
"locations": [
{"title":"Houri Hearing Dubai","address1":"1079 Al Wasl Road","address2":"Dubai, United Arab Emirates","coords":{"lat":25.141332,"lng":55.2055273},"placeId":"ChIJi-WHbi9rXz4R8q8yr5OQbKc"},
{"title":"Houri Hearing Abu Dhabi","address1":"108 Khalifa Bin Zayed the First Street","address2":"Abu Dhabi, United Arab Emirates","coords":{"lat":24.486016,"lng":54.353355},"placeId":"ChIJX4AO1TxnXj4Rf5JYVj6ajhc"}
],
"mapOptions": {"center":{"lat":38.0,"lng":-100.0},"fullscreenControl":true,"mapTypeControl":false,"streetViewControl":false,"zoom":4,"zoomControl":true,"maxZoom":17,"mapId":""},
"mapsApiKey": "AIzaSyBHEKF9g1xfeLqnzSHhYUoQMNjv1CEPAGU",
"capabilities": {"input":true,"autocomplete":true,"directions":true,"distanceMatrix":true,"details":true,"actions":false}
};
function initMap() {
new LocatorPlus(CONFIGURATION);
}
{{#each locations}}
{{address1}}
{{address2}}
{{#if travelDistanceText}}
{{travelDistanceText}}
{{/if}}
{{/each}}
{{#if location.travelDistanceText}}
{{location.travelDistanceText}} away
{{/if}}
{{location.address1}}
{{location.address2}}
{{#if place.rating}}
{{place.rating}}
{{#each place.fullStarIcons}}
{{/each}}
{{#each place.halfStarIcons}}
{{/each}}
{{#each place.emptyStarIcons}}
{{/each}}
{{/if}}
{{#if place.user_ratings_total}}
{{place.user_ratings_total}} reviews
{{else}}
See on Google Maps
{{/if}}
{{#if place.price_level}}
•
{{#each place.dollarSigns}}${{/each}}
{{/if}}
{{#if place.opening_hours}}
{{/if}}
{{#if place.website}}
{{/if}}
{{#if place.formatted_phone_number}}
{{/if}}
{{#if place.html_attributions}}
{{#each place.html_attributions}}
{{{this}}}
{{/each}}
{{/if}}
Find a location near you
All locations