1 2Vue.config.devtools = true; 3 4Vue.filter("distanceFormat",function(value) { 5 return String.format(window.mapOptions.map.distanceFormat,((value || 0) / 1000).toFixed(1)); 6}); 7 8var app = new Vue({ 9 el: '#retailer-map', 10 data: function() { 11 return { 12 expanded: false, 13 showFeatured: true, 14 currentRetailer: null, 15 currentInfoWindow: null, 16 userPosition: false, 17 userMarker: null, 18 minMaxBounds: null, 19 options: { 20 lat: window.mapOptions.map.lat, 21 lng: window.mapOptions.map.lng, 22 zoom: window.mapOptions.map.zoom, 23 endPoint: window.mapOptions.map.endPoint 24 }, 25 map: null, 26 markers: [], 27 featured: [] 28 }; 29 }, 30 mounted: function() { 31 var that = this; 32 $(window).on("gmapsInit",function() { 33 that.$nextTick(function() { 34 that.loadMap(); 35 }) 36 }); 37 }, 38 methods: { 39 tracking: function(category,action,retailer) { 40 track(category,action,{'location-type': retailer.locationType,'location-subtype': retailer.locationSubType,'location-name': retailer.title,'location-url': retailer.slug}); 41 }, 42 mapsTracking: function(retailer) { 43 track('contact','click-map',{'location-type': retailer.locationType,'location-subtype': retailer.locationSubType,'location-name': retailer.title}); 44 }, 45 loadMap: function() { 46 var that = this; 47 var mobile = $(window).outerWidth() <= 768; // 'ontouchstart' in window || window.DocumentTouch && document instanceof window.DocumentTouch || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0; 48 this.map = new google.maps.Map(this.$refs.map,{ 49 center: {lat: that.options.lat,lng: that.options.lng}, 50 zoom: that.options.zoom, 51 disableDefaultUI: mobile, 52 zoomControl: !mobile, 53 mapTypeId: "roadmap", //hybrid roadmap satellite terrain 54 mapTypeControl: false, 55 scrollwheel: false, 56 draggable: true, 57 streetViewControl: false, 58 styles: googleMapsThemes.grey.json, 59 }); 60 this.fetchRetailers(that.options.endPoint); 61 this.hookupAutocomplete(); 62 this.setSearchOrigin(that.options.lat,that.options.lng); 63 google.maps.event.addListener(this.map,"tilesloaded",function() { 64 [].slice.apply(document.querySelectorAll('#map a, #map [tabindex="0"], #map button')).forEach(function(item) { 65 item.setAttribute('tabindex','-1'); 66 }) 67 }); 68 //this.checkGeoLocation(); 69 }, 70 mapKey($event) { 71 // if ($event.key == "Escape") { 72 // document.querySelectorAll('.find-retailer-map')[0].focus(); 73 // } 74 }, 75 checkRetailerFeaturedKeys(marker,index,url,$event) { 76 if ($event.key == "Enter" || $event.key == " ") { 77 window.location = url; 78 } 79 else { 80 switch ($event.key) { 81 case "ArrowUp": 82 case "ArrowLeft": 83 if (index > 0) { 84 this.$refs.featured[index - 1].focus(); 85 } 86 break; 87 case "ArrowDown": 88 case "ArrowRight": 89 if (index < this.featured.length - 1) { 90 this.$refs.featured[index + 1].focus(); 91 } 92 else if (this.markers.length > 0) { 93 this.$refs.markers[0].focus(); 94 } 95 break; 96 case "Escape": 97 if (this.$refs.featured.length > 0) 98 this.$refs.featured[0].focus(); 99 break; 100 } 101 } 102 }, 103 checkRetailerListKeys(marker,index,$event) { 104 if ($event.key == "Enter" || $event.key == " ") { 105 this.selectRetailer(marker,index,true,$event) 106 } 107 else { 108 switch ($event.key) { 109 case "ArrowUp": 110 case "ArrowLeft": 111 if (index > 0) { 112 this.$refs.markers[index - 1].focus(); 113 } 114 else { 115 if (this.$refs.featured.length > 0) { 116 this.$refs.featured[this.$refs.featured.length - 1].focus(); 117 } 118 } 119 break; 120 case "ArrowDown": 121 case "ArrowRight": 122 if (index < this.markers.length - 1) { 123 this.$refs.markers[index + 1].focus(); 124 } 125 break; 126 case "Escape": 127 if (this.$refs.featured.length > 0) 128 this.$refs.featured[0].focus(); 129 else if (this.$refs.markers.length > 0) 130 this.$refs.markers[0].focus(); 131 break; 132 } 133 } 134 }, 135 selectRetailer: function(marker,index,skipSort) { 136 var that = this; 137 that.currentRetailer = marker.retailer; 138 if (marker && marker.position) { 139 that.setMapCenter(marker.position,skipSort); 140 //track('search-store', 'click-map', {'location-type': marker.retailer.locationType, 'location-subtype' : marker.retailer.locationSubType,'title' : marker.retailer.title, 'city' : marker.retailer.city}); 141 142 setTimeout(function() {
143 // TODO: find a "vue" way of doing this 144 // scroll to the selected retailer in the list 145 var $listElement = $(that.$refs.markerList) 146 var $liElement = $listElement.find("li[data-key=" + marker.retailer.key + "]").first(); 147 var elementY = $liElement[0].offsetTop; 148 var scrollY = $listElement.parent().scrollTop(); 149 var listHeight = $listElement.parent().outerHeight(); 150 151 if (elementY < scrollY || elementY > (scrollY + listHeight)) { 152 $listElement.parent().animate( 153 { 154 scrollTop: elementY 155 },300); 156 } 157 },400); 158 } 159 }, 160 fetchRetailers: function(url) { 161 var that = this; 162 $.get(url) 163 .done(function(data) { 164 that.featured = data.featured || []; 165 if (data.retailers && data.retailers.length) 166 that.addMarkers(data.retailers); 167 that.expanded = that.expanded || (that.showFeatured && that.featured.length > 0); 168 }); 169 }, 170 setMapCenter: function(position,skipSort) { 171 this.map.setCenter(position); 172 if (!skipSort) 173 this.sortMarkersByDistance(); 174 }, 175 setSearchOrigin: function(latitude,longitude) { 176 var point = new google.maps.LatLng({lat: latitude,lng: longitude}); 177 if (!this.userMarker) { 178 this.userMarker = new google.maps.Marker({ 179 map: this.map, 180 position: point, 181 draggable: false, 182 clickable: true, 183 zIndex: 999999, 184 infoWindow: new google.maps.InfoWindow({ 185 content: window.mapOptions.map.startPositionTitle 186 }) 187 }); 188 189 this.userMarker.setIcon({ 190 url: "/assets/js/site/location-search/icons/relevant-position.png", 191 size: new google.maps.Size(32,32), 192 anchor: new google.maps.Point(16,16) 193 }); 194 195 var that = this; 196 that.userMarker.addListener('click',function() { 197 if (that.currentInfoWindow) { 198 that.currentInfoWindow.close(); 199 } 200 that.userMarker.infoWindow.open(that.map,that.userMarker); 201 that.currentInfoWindow = that.userMarker.infoWindow; 202 }); 203 } 204 this.userMarker.setPosition(point); 205 this.sortMarkersByDistance(); 206 }, 207 addMarkers: function(retailers) { 208 var that = this; 209 if (retailers) { 210 retailers.forEach(function(retailer) { 211 var marker = new google.maps.Marker({ 212 map: that.map, 213 position: {lat: retailer.lat,lng: retailer.lng}, 214 retailer: retailer 215 }); 216 217 marker.infoWindow = new google.maps.InfoWindow({ 218 content: String.format("<a href='{5}{0}'>{1}<br />{2}<br />{3},{4}</a>", 219 retailer.slug,retailer.title,retailer.street,retailer.zipAndCity,retailer.country,window.url) 220 }); 221 222 marker.setIcon({ 223 url: marker.retailer.isDuxiana ? "/assets/js/site/location-search/icons/map-marker.svg" : "/assets/js/site/location-search/icons/map-marker-black.svg", 224 anchor: new google.maps.Point(18,45), 225 scaledSize: new google.maps.Size(37,45) 226 }); 227 228 marker.addListener('click',function() { 229 if (that.currentInfoWindow) { 230 that.currentInfoWindow.close(); 231 } 232 233 marker.infoWindow.open(that.map,marker); 234 that.currentInfoWindow = marker.infoWindow; 235 marker.setZIndex(99999); 236 that.selectRetailer(marker,true); 237 }); 238 239 that.markers.push(marker); 240 }); 241 } 242 that.minMaxBounds = Object.assign({},new google.maps.LatLngBounds(),{ 243 lat: Math.min.apply(Math,that.markers.map(function(marker) {return marker.retailer.lat;})), 244 lng: Math.min.apply(Math,that.markers.map(function(marker) {return marker.retailer.lng;})) 245 }); 246 that.sortMarkersByDistance(); 247 }, 248 sortMarkersByDistance: function(origin) { 249 var that = this; 250 251 if (!that.map || !that.markers || !that.markers.length) { 252 return; 253 } 254 origin = origin || (that.userMarker || {}).position || that.map.getCenter(); 255 that.markers.forEach(function(marker) { 256 // Set distance 257 marker.retailer.distanceFromUserPosition = google.maps.geometry.spherical.computeDistanceBetween(marker.getPosition(),origin); 258 that.featured.filter(function(item) { 259 if (item.key == marker.retailer.key) 260 item.distanceFromUserPosition = marker.retailer.distanceFromUserPosition; 261 return true; 262 }); 263 // Set bearing 264 //marker.retailer.headingFromUserPosition = google.maps.geometry.spherical.computeHeading(marker.getPosition(), origin); 265 }); 266 267 that.markers.sort(function(a,b) { 268 return a.retailer.distanceFromUserPosition - b.retailer.distanceFromUserPosition; 269 }); 270 }, 271 272 // PLACES 273 hookupAutocomplete: function() { 274 var that = this; 275 that.autocomplete = new google.maps.places.Autocomplete(that.$refs.placesSearch); 276 277 // Bind the map's bounds (viewport) property to the autocomplete object, 278 // so that the autocomplete requests use the current map bounds for the 279 // bounds option in the request. 280 that.autocomplete.bindTo('bounds',that.map); 281 282 that.autocomplete.addListener('place_changed',function() { 283 var place = that.autocomplete.getPlace(); 284 285 if (!place.geometry) { 286 that.fallbackToFirstPlaceResult(place,that.handleResolvedPlace) 287 } else { 288 that.handleResolvedPlace(place); 289 } 290 that.showFeatured = false;
291 }); 292 }, 293 fallbackToFirstPlaceResult: function(place,callback) { 294 // User entered the name of a Place that was not suggested and pressed the Enter key, or the Place Details request failed. 295 // We try to get the same result again and populate with the first result 296 var that = this; 297 var autocompleteService = new google.maps.places.AutocompleteService(); 298 299 if (!that.minMaxBounds.length) { 300 // Calculate a LatLngBounds object that includes all the markers 301 that.minMaxBounds = new google.maps.LatLngBounds(); 302 that.minMaxBounds.extend({ 303 lat: Math.min.apply(Math,that.markers.map(function(marker) {return marker.retailer.lat;})), 304 lng: Math.min.apply(Math,that.markers.map(function(marker) {return marker.retailer.lng;})) 305 }); 306 } 307 autocompleteService.getPlacePredictions({ 308 input: place.name, 309 offset: place.name.length, 310 bounds: that.minMaxBounds 311 },function(list,status) { 312 if (list && list.length) { 313 var placesService = new google.maps.places.PlacesService(that.$refs.placesSearch); 314 315 placesService.getDetails({'reference': list[0].reference},function detailsresult(detailsResult,placesServiceStatus) { 316 callback(detailsResult); 317 }); 318 } 319 }); 320 }, 321 handleResolvedPlace: function(place) { 322 var maxAutoZoom = 0; 323 var that = this; 324 // Unable to find a place 325 if (!place.geometry) { 326 return; 327 } 328 329 // If the place has a geometry, then present it on a map. 330 if (place.geometry.viewport) { 331 that.map.fitBounds(place.geometry.viewport); 332 if (maxAutoZoom > 0) { 333 // Handle the zoom-level if set 334 google.maps.event.addListenerOnce(that.map,"idle",function() { 335 if (that.map.getZoom() > maxAutoZoom) { 336 that.map.setZoom(maxAutoZoom) 337 }; 338 }); 339 } 340 } else { 341 if (maxAutoZoom > 0) { 342 that.map.setZoom(maxAutoZoom); 343 } 344 } 345 that.setMapCenter(place.geometry.location); 346 that.userPosition = false; 347 that.setSearchOrigin(place.geometry.location.lat(),place.geometry.location.lng()); 348 349 track('search-store','search',{'search-text': that.$refs.placesSearch.value}); 350 $(that.$refs.markerList).parent().animate( 351 { 352 scrollTop: 0 353 },300); 354 that.$refs.placesSearch.blur(); 355 }, 356 // GEO LOCATION 357 checkGeoLocation: function() { 358 var that = this; 359 if (navigator.geolocation) { 360 navigator.geolocation.getCurrentPosition(function(position) { 361 track('search-store','location-enabled',{'enabled': 'true'}); 362 that.setMapCenter({lat: position.coords.latitude,lng: position.coords.longitude}); 363 // Set user's known location 364 that.setSearchOrigin(position.coords.latitude,position.coords.longitude); 365 that.userPosition = true; 366 },function() { 367 track('search-store','location-enabled',{'enabled': 'false'}); 368 that.setMapCenter({lat: that.options.lat,lng: that.options.lng}); 369 }); 370 } 371 else { 372 that.setMapCenter({lat: that.options.lat,lng: that.options.lng}); 373 track('search-store','location-enabled',{'enabled': 'false'}); 374 } 375 that.$refs.placesSearch.value = ''; 376 that.showFeatured = false; 377 378 }, 379 markerHref: function(phone) { 380 return 'tel:' + phone; 381 }, 382 markerMailTo(email) { 383 return 'mailto:' + email; 384 } 385 } 386});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.