1/* eslint-disable */ 2 3var WPViews = WPViews || {}; 4 5WPViews.ViewAddonMaps = function( $ ) { 6 const API_GOOGLE = 'google'; 7 const API_AZURE = 'azure'; 8 9 const self = this; 10 11 /** @var {Object} views_addon_maps_i10n */ 12 13 if ( API_GOOGLE === views_addon_maps_i10n.api_used ) { 14 if ( typeof google === 'undefined' || typeof google.maps === 'undefined' ) { 15 self.api = null; 16 } else { 17 /** @var {Object} google */ 18 self.api = google.maps; 19 } 20 } else { 21 /** @var {Object} atlas */ 22 self.api = null; 23 } 24 25 self.maps_data = []; 26 self.maps = {}; 27 self.markers = {}; 28 self.infowindows = {}; 29 self.bounds = {}; 30 self.view_map_blocks_relation = {}; 31 32 self.default_cluster_options = { 33 imagePath: views_addon_maps_i10n.cluster_default_imagePath, 34 gridSize: 60, 35 maxZoom: null, 36 zoomOnClick: true, 37 minimumClusterSize: 2 38 }; 39 40 self.cluster_options = {}; 41 self.has_cluster_options = {}; 42 43 self.resize_queue = []; 44 45 /** 46 * collect_maps_data 47 * 48 * Before init_maps 49 * 50 * @since 1.0 51 */ 52 53 self.collect_maps_data = function() { 54 $( '.js-wpv-addon-maps-render' ).each( function() { 55 self.collect_map_data( $( this ) ); 56 }); 57 58 $( document ).trigger('js_event_wpv_addon_maps_map_data_collected'); 59 }; 60 61 /** 62 * Initializes street view mode on given map. 63 * @param Object map 64 * @param latlon 65 * @since 1.5 66 */ 67 self.initStreetView = function( map, latlon ) { 68 var streetViewService = new google.maps.StreetViewService(); 69 var panorama = self.maps[ map.map_id ].getStreetView(); 70 71 streetViewService.getPanoramaByLocation( latlon, 100, function (streetViewPanoramaData, status) { 72 73 if ( status === google.maps.StreetViewStatus.OK ) { 74 var streetLatlon = streetViewPanoramaData.location.latLng; 75 var heading = ( map.map_options.heading !== '' ) 76 ? map.map_options.heading 77 : google.maps.geometry.spherical.computeHeading( streetLatlon, latlon); 78 var pitch = ( map.map_options.pitch !== '' ) ? map.map_options.pitch : 0; 79 80 panorama.setPosition( streetLatlon ); 81 panorama.setPov({ 82 heading: heading, 83 pitch: pitch 84 }); 85 panorama.setVisible( true ); 86 } 87 }); 88 }; 89 90 /** 91 * Sets a trigger to init street view after map is ready. Namespaces trigger so we do this only once per map. 92 * @param String mapId 93 * @param jQuery $marker 94 * @listens js_event_wpv_addon_maps_init_map_completed 95 * @since 1.5 96 */ 97 self.waitForMapInitThenInitStreetView = function( mapId, $marker ) { 98 $( document ).on( 'js_event_wpv_addon_maps_init_map_completed', function( event, map ) { 99 if ( mapId === map.map_id ){ 100 var latlon = new google.maps.LatLng( $marker.data('markerlat'), $marker.data('markerlon') ); 101 self.initStreetView( map, latlon ); 102 } 103 } ); 104 }; 105 106 /** 107 * collect_map_data 108 * 109 * @since 1.0 110 * @since 1.5 handle Street Views 111 */ 112 self.collect_map_data = function( thiz_map ) { 113 var thiz_map_id = thiz_map.data( 'map' ), 114 thiz_map_points = [], 115 thiz_map_options = {}; 116 var streetViewMarkerFound = false; 117 118 $( '.js-wpv-addon-maps-markerfor-' + thiz_map_id ).each( function() { 119 var thiz_marker = $( this ); 120 // Handle special case when we don't have coordinates, but instead need to ask browser for current users 121 // position (only for Google API) 122 if ( 123 thiz_marker.data( 'markerlat' ) === 'geo' 124 && API_GOOGLE === views_addon_maps_i10n.api_used 125 ) { 126 // In case map render wait is requested by a marker, add that data to map 127 if (thiz_marker.data( 'markerlon' ) === 'wait') { 128 thiz_map_options['render'] = 'wait'; 129 self.add_current_visitor_location_after_geolocation(thiz_map_id, thiz_marker); 130 } else { 131 self.add_current_visitor_location_after_init(thiz_map_id, thiz_marker); 132 } 133 return true; 134 } 135 136 // Handle Street View marker as special case (only when using Google API) 137 if ( 138 thiz_map.data('streetview') === 'on' 139 && API_GOOGLE === views_addon_maps_i10n.api_used 140 ) { 141 if ( thiz_map.data('markerid') === thiz_marker.data('marker') ) { 142 self.waitForMapInitThenInitStreetView( thiz_map_id, thiz_marker ); 143 streetViewMarkerFound = true; 144 return true; 145 } else if ( thiz_map.data('location') === 'first' && !streetViewMarkerFound ) {
146 self.waitForMapInitThenInitStreetView( thiz_map_id, thiz_marker ); 147 streetViewMarkerFound = true; 148 return true; 149 } 150 } 151 152 thiz_map_points.push( 153 { 154 'marker': thiz_marker.data('marker'), 155 'title': thiz_marker.data('markertitle'), 156 'markerlat': thiz_marker.data('markerlat'), 157 'markerlon': thiz_marker.data('markerlon'), 158 'markerinfowindow': thiz_marker.html(), 159 'markericon': thiz_marker.data('markericon'), 160 'markericonhover': thiz_marker.data('markericonhover') 161 } 162 ); 163 164 }); 165 166 // Some error catching when street view from marker requested and marker not found 167 if ( 168 ( 169 thiz_map.data('location') === 'first' 170 || thiz_map.data('markerid') 171 ) 172 && !streetViewMarkerFound 173 ) { 174 console.warn( views_addon_maps_i10n.marker_not_found_warning, thiz_map_id ); 175 } 176 177 thiz_map_options['general_zoom'] = thiz_map.data( 'generalzoom' ); 178 thiz_map_options['general_center_lat'] = thiz_map.data( 'generalcenterlat' ); 179 thiz_map_options['general_center_lon'] = thiz_map.data( 'generalcenterlon' ); 180 thiz_map_options['fitbounds'] = thiz_map.data( 'fitbounds' ); 181 thiz_map_options['single_zoom'] = thiz_map.data( 'singlezoom' ); 182 thiz_map_options['single_center'] = thiz_map.data( 'singlecenter' ); 183 thiz_map_options['map_type'] = thiz_map.data( 'maptype' ); 184 thiz_map_options['show_layer_interests'] = thiz_map.data( 'showlayerinterests' ); 185 thiz_map_options['marker_icon'] = thiz_map.data( 'markericon' ); 186 thiz_map_options['marker_icon_hover'] = thiz_map.data( 'markericonhover' ); 187 thiz_map_options['draggable'] = thiz_map.data( 'draggable' ); 188 thiz_map_options['scrollwheel'] = thiz_map.data( 'scrollwheel' ); 189 thiz_map_options['double_click_zoom'] = thiz_map.data( 'doubleclickzoom' ); 190 thiz_map_options['map_type_control'] = thiz_map.data( 'maptypecontrol' ); 191 thiz_map_options['full_screen_control'] = thiz_map.data( 'fullscreencontrol' ); 192 thiz_map_options['zoom_control'] = thiz_map.data( 'zoomcontrol' ); 193 thiz_map_options['street_view_control'] = thiz_map.data( 'streetviewcontrol' ); 194 thiz_map_options['background_color'] = thiz_map.data( 'backgroundcolor' ); 195 thiz_map_options['cluster'] = thiz_map.data( 'cluster' ); 196 thiz_map_options['style_json'] = thiz_map.data( 'stylejson' ); 197 thiz_map_options['spiderfy'] = thiz_map.data( 'spiderfy' ); 198 thiz_map_options['lat'] = thiz_map.data( 'lat' ); 199 thiz_map_options['long'] = thiz_map.data( 'long' ); 200 thiz_map_options['heading'] = thiz_map.data( 'heading' ); 201 thiz_map_options['pitch'] = thiz_map.data( 'pitch' ); 202 203 var thiz_cluster_options = { 204 'cluster': thiz_map.data( 'cluster' ), 205 'gridSize': parseInt( thiz_map.data( 'clustergridsize' ) ), 206 'maxZoom': ( parseInt( thiz_map.data( 'clustermaxzoom' ) ) > 0 ) ? parseInt( thiz_map.data( 'clustermaxzoom' ) ) : null, 207 'zoomOnClick': ( thiz_map.data( 'clusterclickzoom' ) == 'off' ) ? false : true, 208 'minimumClusterSize': parseInt( thiz_map.data( 'clusterminsize' ) ) 209 }; 210 211 // As we might have cleared those options if we are on a reload event, we need to set them again with the data we saved in self.has_cluster_options 212 if ( _.has( self.has_cluster_options, thiz_map_id ) ) { 213 if ( _.has( self.has_cluster_options[ thiz_map_id ] , "styles" ) ) { 214 thiz_cluster_options['styles'] = self.has_cluster_options[ thiz_map_id ]['styles']; 215 } 216 if ( _.has( self.has_cluster_options[ thiz_map_id ] , "calculator" ) ) { 217 thiz_cluster_options['calculator'] = self.has_cluster_options[ thiz_map_id ]['calculator']; 218 } 219 } 220 221 var thiz_map_collected = { 222 'map': thiz_map_id, 223 'markers': thiz_map_points, 224 'options': thiz_map_options, 225 'cluster_options': thiz_cluster_options 226 }; 227 228 self.maps_data.push( thiz_map_collected ); 229 self.cluster_options[ thiz_map_id ] = thiz_cluster_options; 230 231 return thiz_map_collected; 232 233 }; 234 235 /** 236 * Gets current visitor location from browser, then adds the marker to given map. 237 * 238 * If location fetching failed, render the map without it... 239 * 240 * @since 1.4 241 * @param {String} thiz_map_id 242 * @param {jQuery} thiz_marker 243 */ 244 self.add_current_visitor_location = function(thiz_map_id, thiz_marker) { 245 var map_key = self.get_map_key_by_id(thiz_map_id); 246 if (navigator.geolocation) { 247 navigator.geolocation.getCurrentPosition( 248 function (position) { 249 // Add data to collection and ask map to redraw 250 self.maps_data[map_key].markers.push({ 251 'marker': thiz_marker.data('marker'), 252 'title': thiz_marker.data('markertitle'), 253 'markerlat': position.coords.latitude, 254 'markerlon': position.coords.longitude, 255 'markerinfowindow': thiz_marker.html(), 256 'markericon': thiz_marker.data('markericon'), 257 'markericonhover': thiz_marker.data('markericonhover') 258 }); 259 260 self.init_map_after_loading_styles(self.maps_data[map_key]); 261 }, 262 function (position_error) { 263 self.init_map_after_loading_styles(self.maps_data[map_key]); 264 } 265 ); 266 } else { 267 self.init_map_after_loading_styles(self.maps_data[map_key]); 268 } 269 }; 270 271 /** 272 * Wraps waiting for map to render first time before adding browser location marker 273 * @since 1.4 274 * @param {String} thiz_map_id 275 * @param {jQuery} thiz_marker 276 */ 277 self.add_current_visitor_location_after_init = function(thiz_map_id, thiz_marker) { 278 $( document ).on( 'js_event_wpv_addon_maps_init_map_completed.'+thiz_map_id, function( event, data ) { 279 if (thiz_map_id === data.map_id) { 280 // Stop listening to this event for this map (because the event can fire multiple times 281 // and we really need to add marker only once) 282 $( document ).off( 'js_event_wpv_addon_maps_init_map_completed.'+thiz_map_id ); 283 284 self.add_current_visitor_location(thiz_map_id, thiz_marker); 285 } 286 }); 287 }; 288 289 /** 290 * Wraps waiting for geolocation before rendering map. 291 * 292 * Waits for all map data to be ready before trying to render it. (Problems could otherwise happen when geolocation 293 * is already approved and map data is not yet collected.) 294 * 295 * @since 1.4 296 * @param {String} thiz_map_id 297 * @param {jQuery} thiz_marker 298 */ 299 self.add_current_visitor_location_after_geolocation = function(thiz_map_id, thiz_marker) { 300 $( document ).one('js_event_wpv_addon_maps_map_data_collected.'+thiz_map_id, function() { 301 self.add_current_visitor_location(thiz_map_id, thiz_marker); 302 }); 303 }; 304 305 /** 306 * Given map id string returns array key for a map in maps_data 307 * @since 1.4 308 * @param {String} map_id 309 * @returns {Number} 310 */
311 self.get_map_key_by_id = function(map_id) { 312 return _.findLastIndex(self.maps_data, { map: map_id }); 313 }; 314 315 /** 316 * Checks if json style needed, loads JSON file with map styles and then inits the map 317 * @since 1.4 318 * @param {Object} map 319 */ 320 self.init_map_after_loading_styles = function( map ) { 321 if ( map.options.style_json ) { 322 $.getJSON( map.options.style_json ) 323 .done( function( styles ) { 324 map.options.styles = styles; 325 } ) 326 .always( function () { 327 // Even if styles loading failed, map can be rendered with standard style, so do it always. 328 self.initMapOrWaitIfInsideHiddenBootstrapAccordionOrTab( map ); 329 } ); 330 } else { 331 self.initMapOrWaitIfInsideHiddenBootstrapAccordionOrTab( map ); 332 } 333 }; 334 335 /** 336 * Checks if this map is inside a hidden (collapsed) Bootstrap accordion. 337 * 338 * @param {String} map_selector 339 * 340 * @return {String} Returns accordion id if found, empty string otherwise 341 * 342 * @since 1.4.2 343 */ 344 self.check_being_inside_collapsed_bootstrap_accordion = function( map_selector ) { 345 var accordion_id = ''; 346 var toggle_href = ''; 347 348 // Find all accordion toggles which are currently collapsed, then find which of the accordions contains our map 349 // and return accordion id. If none, we'll return empty string. 350 $('a[data-toggle="collapse"].collapsed').each( function( index, toggle ) { 351 toggle_href = $( toggle ).attr('href'); 352 353 if ( $( toggle_href ).has( map_selector ).length ) { 354 accordion_id = toggle_href; 355 return false; 356 } 357 } ); 358 359 return accordion_id; 360 }; 361 362 /** 363 * Checks if this map is inside a hidden Bootstrap tab. 364 * 365 * @param {String} map_selector 366 * 367 * @return {string} 368 * 369 * @since 1.4.2 370 */ 371 self.check_being_inside_hidden_bootstrap_tab = function( map_selector ) { 372 var tab_toggle = ''; 373 var toggle_href = ''; 374 var $tab_body; 375 376 $('a[data-toggle="tab"]').each( function( index, toggle ) { 377 toggle_href = $( toggle ).attr('href'); 378 $tab_body = $( toggle_href ); 379 380 if ( !$tab_body.hasClass('active') && $tab_body.has( map_selector ).length ) { 381 tab_toggle = toggle; 382 return false; 383 } 384 } ); 385 386 return tab_toggle; 387 }; 388 389 /** 390 * If not inside hidden Bootstrap accordion or tab, init map, if inside, hook to shown event to init then. 391 * 392 * @param {Object} map 393 * 394 * @since 1.4.2 395 */ 396 self.initMapOrWaitIfInsideHiddenBootstrapAccordionOrTab = function(map ) { 397 var map_selector = '#js-wpv-addon-maps-render-' + map.map; 398 var accordion_id = self.check_being_inside_collapsed_bootstrap_accordion( map_selector ); 399 var tab_toggle = self.check_being_inside_hidden_bootstrap_tab( map_selector ); 400 401 if ( accordion_id ) { 402 $( accordion_id ).one('shown.bs.collapse', function() { 403 self.init_map( map ); 404 } ); 405 } else if( tab_toggle ) { 406 $( tab_toggle ).one('shown.bs.tab', function() { 407 self.init_map( map ); 408 } ); 409 } else { 410 self.init_map( map ); 411 } 412 }; 413 414 /** 415 * init_maps 416 * 417 * @since 1.0 418 */ 419 420 self.init_maps = function() { 421 self.maps_data.map( function( map ) { 422 // If there is a marker on the map with option for map rendering to wait until it's ready, do nothing. 423 // (Marker itself will trigger map rendering when ready.) 424 if ( 425 map.options.render 426 && map.options.render === 'wait' 427 ) { 428 return true; 429 } 430 431 // Handle other maps 432 self.init_map_after_loading_styles( map ); 433 }); 434 }; 435 436 /** 437 * init_map 438 * 439 * @since 1.0 440 */ 441 442 self.init_map = function( map ) { 443 var map_icon = '', 444 map_icon_hover = '', 445 map_settings = { 446 zoom: map.options['general_zoom'] 447 }, 448 event_settings = { 449 map_id: map.map, 450 map_options: map.options 451 }; 452 var spiderfy = false; 453 var clickEvent = 'click'; 454 455 $( document ).trigger( 'js_event_wpv_addon_maps_init_map_started', [ event_settings ] ); 456 457 if ( 458 map.options['general_center_lat'] != '' 459 && map.options['general_center_lon'] != '' 460 ) { 461 map_settings['center'] = { 462 lat: map.options['general_center_lat'], 463 lng: map.options['general_center_lon'] 464 }; 465 } else { 466 map_settings['center'] = { 467 lat: 0, 468 lng: 0 469 }; 470 } 471 472 if ( map.options['draggable'] == 'off' ) { 473 map_settings['draggable'] = false;
474 } 475 476 if ( map.options['scrollwheel'] == 'off' ) { 477 map_settings['scrollwheel'] = false; 478 } 479 480 if ( map.options['double_click_zoom'] == 'off' ) { 481 map_settings['disableDoubleClickZoom'] = true; 482 } 483 484 if ( map.options['map_type_control'] == 'off' ) { 485 map_settings['mapTypeControl'] = false; 486 } 487 488 if ( map.options['full_screen_control'] == 'off' ) { 489 map_settings['fullscreenControl'] = false; 490 } 491 492 if ( map.options['zoom_control'] == 'off' ) { 493 map_settings['zoomControl'] = false; 494 } 495 496 if ( map.options['street_view_control'] == 'off' ) { 497 map_settings['streetViewControl'] = false; 498 } 499 500 if ( map.options['background_color'] != '' ) { 501 map_settings['backgroundColor'] = map.options['background_color']; 502 } 503 504 if ( map.options['styles'] ) { 505 map_settings['styles'] = map.options['styles']; 506 } 507 508 if ( map.options['map_type'] ) { 509 map_settings['mapTypeId'] = map.options['map_type']; 510 } 511 512 const mapElement = document.getElementById( 'js-wpv-addon-maps-render-' + map.map ); 513 514 // In Gutenberg editor, it might happen that the map gets refreshed a couple of times and the div is missing at 515 // this moment. Just give up right now, it will get rendered later on. 516 if ( ! mapElement ) return; 517 518 self.maps[ map.map ] = new self.api.Map( mapElement, map_settings ); 519 520 $( document ).trigger( 'js_event_wpv_addon_maps_init_map_inited', [ event_settings ] ); 521 522 self.bounds[ map.map ] = new self.api.LatLngBounds(); 523 524 if ( map.options['marker_icon'] != '' ) { 525 map_icon = map.options['marker_icon']; 526 } 527 528 if ( map.options['marker_icon_hover'] != '' ) { 529 map_icon_hover = map.options['marker_icon_hover']; 530 } 531 532 if ( 'on' === map.options['spiderfy'] ) { 533 var oms = new OverlappingMarkerSpiderfier( self.maps[ map.map ], { 534 markersWontMove: true, 535 markersWontHide: true, 536 basicFormatEvents: true 537 }); 538 spiderfy = true; 539 } 540 541 map.markers.map( function( marker ) { 542 var marker_lat_long = new self.api.LatLng( marker.markerlat, marker.markerlon ), 543 marker_map_icon = ( marker.markericon == '' ) ? map_icon : marker.markericon, 544 marker_map_icon_hover = ( marker.markericonhover == '' ) ? map_icon_hover : marker.markericonhover, 545 marker_settings = { 546 position: marker_lat_long, 547 optimized: false 548 }; 549 550 // 'default' is just used so that default marker icon doesn't get replaced with global marker icon. Now set 551 // it empty, so the rest of code knows how to handle it. 552 if ( marker_map_icon === 'default' ) { 553 marker_map_icon = ''; 554 } 555 556 if ( spiderfy ) { 557 clickEvent = 'spider_click'; 558 } else { 559 marker_settings.map = self.maps[ map.map ] 560 clickEvent = 'click'; 561 } 562 563 // Helps SVG marker icons to have a reasonable size and render properly in Internet Explorer 564 if ( marker_map_icon.slice( -4 ).toLowerCase() === '.svg' ) { 565 var scaledSize = new google.maps.Size(32, 32); 566 } else { 567 var scaledSize = null; 568 } 569 570 if ( marker_map_icon != '' ) { 571 marker_settings['icon'] = { 572 url: marker_map_icon, 573 scaledSize: scaledSize, 574 } 575 } 576 if ( marker.title != '' ) { 577 marker_settings['title'] = marker.title; 578 } 579 580 self.markers[ map.map ] = self.markers[ map.map ] || {}; 581 582 self.markers[ map.map ][ marker.marker ] = new self.api.Marker(marker_settings); 583 584 self.bounds[ map.map ].extend( self.markers[ map.map ][ marker.marker ].position ); 585 586 if ( 587 marker_map_icon != '' 588 || marker_map_icon_hover != '' 589 ) { 590 marker_map_icon = ( marker_map_icon == '' ) ? views_addon_maps_i10n.marker_default_url : marker_map_icon; 591 marker_map_icon_hover = ( marker_map_icon_hover == '' ) ? marker_map_icon : marker_map_icon_hover; 592 593 if ( marker_map_icon != marker_map_icon_hover ) { 594 var marker_icon_scaled = { 595 url: marker_map_icon, 596 scaledSize: scaledSize, 597 }; 598 var marker_hover_icon_scaled = { 599 url: marker_map_icon_hover, 600 scaledSize: scaledSize, 601 }; 602 603 self.api.event.addListener( self.markers[ map.map ][ marker.marker ], 'mouseover', function() { 604 self.markers[ map.map ][ marker.marker ].setIcon( marker_hover_icon_scaled ); 605 }); 606 self.api.event.addListener( self.markers[ map.map ][ marker.marker ], 'mouseout', function() { 607 self.markers[ map.map ][ marker.marker ].setIcon( marker_icon_scaled ); 608 }); 609 // Add custom classnames to reproduce this hover effect from an HTML element 610 $( document ).on( 'mouseover', '.js-toolset-maps-hover-map-' + map.map + '-marker-' + marker.marker, function() { 611 self.markers[ map.map ][ marker.marker ].setIcon( marker_hover_icon_scaled ); 612 }); 613 $( document ).on( 'mouseout', '.js-toolset-maps-hover-map-' + map.map + '-marker-' + marker.marker, function() { 614 self.markers[ map.map ][ marker.marker ].setIcon( marker_icon_scaled ); 615 }); 616 } 617 } 618 619 if ( marker.markerinfowindow !== '' ) { 620 // Create a single self.api.InfoWindow object for each map, if needed, and populate its content based on 621 // the marker 622 self.infowindows[ map.map ] = self.infowindows[ map.map ] || new self.api.InfoWindow({ content: '' }); 623 self.api.event.addListener( self.markers[ map.map ][ marker.marker ], clickEvent, function() { 624 self.infowindows[ map.map ].setContent( marker.markerinfowindow ); 625 self.infowindows[ map.map ].open( self.maps[ map.map ], self.markers[ map.map ][ marker.marker ] ); 626 }); 627 $( document ).on( 628 'click', 629 '.js-toolset-maps-open-infowindow-map-' + map.map + '-marker-' + marker.marker, 630 function() { 631 self.infowindows[ map.map ].setContent( marker.markerinfowindow ); 632 self.openInfowindowWhenMarkerVisible( map.map, marker.marker ); 633 } 634 ); 635 } 636 637 if ( spiderfy ) { 638 oms.addMarker( self.markers[ map.map ][ marker.marker ] ) 639 } 640 }); 641 642 if ( _.size( map.markers ) == 1 ) { 643 if ( map.options['single_zoom'] != '' ) { 644 self.maps[ map.map ].setZoom( map.options['single_zoom'] ); 645 if ( map.options['fitbounds'] == 'on' ) { 646 self.api.event.addListenerOnce( self.maps[ map.map ], 'bounds_changed', function( event ) { 647 self.maps[ map.map ].setZoom( map.options['single_zoom'] ); 648 }); 649 } 650 } 651 if ( map.options['single_center'] == 'on' ) { 652 for ( var mark in self.markers[ map.map ] ) { 653 self.maps[ map.map ].setCenter( self.markers[ map.map ][ mark ].getPosition() ); 654 break; 655 } 656 } 657 } else if ( _.size( map.markers ) > 1 ) { 658 if ( map.options['fitbounds'] == 'on' ) { 659 self.maps[ map.map ].fitBounds( self.bounds[ map.map ] ); 660 } 661 } 662 663 if ( _.contains( self.resize_queue, map.map ) ) { 664 self.keep_map_center_and_resize( self.maps[ map.map ] ); 665 _.reject( self.resize_queue, function( item ) { 666 return item == map.map; 667 }); 668 } 669 670 $( document ).trigger( 'js_event_wpv_addon_maps_init_map_completed', [ event_settings ] ); 671 672 // Init Street View overlay if lat and long are provided (coming from address) 673 if ( map.options.lat && map.options.long ) { 674 self.initStreetView( event_settings, new google.maps.LatLng( map.options.lat, map.options.long ) ); 675 } 676 }; 677 /** 678 * Make sure that marker is visible (not in cluster) before opening info
678window. If it isn't, wait until it is. 679 * 680 * Because, if marker isn't visible, info window will get wrong location to open (usually another, visible marker). 681 * 682 * @param {String} mapId 683 * @param {String} markerId 684 * @since 1.5 685 */ 686 self.openInfowindowWhenMarkerVisible = function( mapId, markerId ) { 687 if ( self.markers[mapId][markerId].map ) { 688 self.infowindows[mapId].open( self.maps[mapId], self.markers[mapId][markerId] ); 689 } else { 690 _.delay(function () { 691 self.openInfowindowWhenMarkerVisible( mapId, markerId ); 692 }, 150); 693 } 694 }; 695 /** 696 * clean_map_data 697 * 698 * @param map_id 699 * 700 * @since 1.0 701 */ 702 703 self.clean_map_data = function( map_id ) { 704 self.maps_data = _.filter( self.maps_data, function( map_data_unique ) { 705 return map_data_unique.map != map_id; 706 }); 707 708 self.maps = _.omit( self.maps, map_id ); 709 self.markers = _.omit( self.markers, map_id ); 710 self.infowindows = _.omit( self.infowindows, map_id ); 711 self.bounds = _.omit( self.bounds, map_id ); 712 self.cluster_options = _.omit( self.cluster_options, map_id ); 713 714 var settings = { 715 map_id: map_id 716 }; 717 718 $( document ).trigger( 'js_event_wpv_addon_maps_clean_map_completed', [ settings ] ); 719 720 }; 721 722 /** 723 * keep_map_center_and_resize 724 * 725 * @param map 726 * 727 * @since 1.1 728 */ 729 730 self.keep_map_center_and_resize = function( map ) { 731 var map_iter_center = map.getCenter(); 732 self.api.event.trigger( map, "resize" ); 733 map.setCenter( map_iter_center ); 734 }; 735 736 /** 737 * Init all maps - Azure API version 738 * @since 1.5 739 */ 740 self.initMapsAzure = function() { 741 let maps = _.map( self.maps_data, self.resolveGeolocatedMarkerThenInitMapAzure ); 742 let mapIds = _.pluck( self.maps_data, 'map' ); 743 744 self.maps = _.object( mapIds, maps ); 745 }; 746 747 /** 748 * Resolve geolocated marker (if any) 749 * 750 * Among markers for a map, there may be a special one which should trigger current visitor geolocation. (It makes 751 * no sense to have more than one, because the visitor is not a quark). This method checks if there is one such 752 * marker, if geolocation is available, and if the map rendering should wait for geolocation or if that marker 753 * should be added after map and other markers render. 754 * 755 * @since 1.5 756 * 757 * @param {Object} map 758 */ 759 self.resolveGeolocatedMarkerThenInitMapAzure = function( map ) { 760 let geoMarker = _.findWhere( map.markers, {markerlat: 'geo'} ); 761 let renderedMap = null; 762 763 if ( geoMarker ) { 764 let geoMarkerIndex = _.indexOf( map.markers, geoMarker ); 765 766 if ( "immediate" === geoMarker.markerlon ) { 767 // Render map immediately, marker will be added when and if available, center and bounds adjusted if 768 // needed. 769 renderedMap = self.initMapAzure( map ); 770 771 if (navigator.geolocation) { 772 navigator.geolocation.getCurrentPosition(function (position) { 773 map.markers[geoMarkerIndex].markerlat = position.coords.latitude; 774 map.markers[geoMarkerIndex].markerlon = position.coords.longitude; 775 776 if ( 777 map.markers.length === 1 778 && map.options.single_center === "on" 779 ) { 780 let mapCenter = [position.coords.longitude, position.coords.latitude]; 781 renderedMap.setCamera( {center: mapCenter} ); 782 } 783 784 self.maybeFitboundsAzure( map, renderedMap ); 785 786 let pin = new atlas.data.Feature( 787 new atlas.data.Point( [position.coords.longitude, position.coords.latitude] ), 788 {title: geoMarker.title, popup: geoMarker.markerinfowindow}, 789 geoMarker.marker 790 ); 791 renderedMap.addEventListener('load', function() { 792 renderedMap.addPins( [pin], { 793 fontColor: "#000", 794 fontSize: 14, 795 icon: "pin-red", 796 iconSize: 1, 797 name: "default-pin-layer", 798 cluster: ( map.options.cluster === 'on' ), 799 textOffset: [0, 20], 800 }); 801 } ); 802 }); 803 } 804 } else { 805 if (navigator.geolocation) { 806 navigator.geolocation.getCurrentPosition(function (position) { 807 map.markers[geoMarkerIndex].markerlat = position.coords.latitude; 808 map.markers[geoMarkerIndex].markerlon = position.coords.longitude; 809 810 // We now have position for geo marker, render as usual. 811 renderedMap = self.initMapAzure( map ); 812 }); 813 } else { 814 // No geolocation? Render what we have, without this marker. 815 renderedMap = self.initMapAzure( map ); 816 } 817 } 818 } else { 819 // No geomarker, just render as usual. 820 renderedMap = self.initMapAzure( map ); 821 } 822 823 return renderedMap; 824 }; 825 826 /** 827 * If there is more than 1 marker and fitbounds is requested for this map, find bound points and set camera bounds. 828 * 829 * @since 1.5 830 * 831 * @param {Object} map 832 * @param {atlas.Map} renderedMap 833 */ 834 self.maybeFitboundsAzure = function( map, renderedMap ) { 835 if ( map.markers.length <= 1 || map.options.fitbounds !== "on" ) return; 836 837 let markerLons = _.pluck( map.markers, 'markerlon' );
838 let southernmost = _.min( markerLons ); 839 let northernmost = _.max( markerLons ); 840 let markerLats = _.pluck( map.markers, 'markerlat' ); 841 let westmost = _.min( markerLats ); 842 let eastmost = _.max( markerLats ); 843 844 renderedMap.setCameraBounds( { 845 bounds: [southernmost, westmost, northernmost, eastmost], 846 padding: 50 847 } ); 848 }; 849 850 /** 851 * Map type names are sometimes, but not always the same between Google and Azure. 852 * 853 * @since 1.5.3 854 * @param {string} mapType 855 * @return {string} 856 */ 857 self.translateMapTypesToAzure = function( mapType ) { 858 const translationTable = { 859 'roadmap': 'road', 860 'hybrid': 'satellite_road_labels', 861 'terrain': 'road' // 'terrain' type is not supported by Azure, so render the closest - 'road' 862 }; 863 864 if ( mapType in translationTable ) return translationTable[mapType]; 865 // 'satellite' type has the same name, so just falls through. 866 else return mapType; 867 }; 868 869 /** 870 * Returns value for marker icon set from marker, map, or default 871 * 872 * @since 1.5.3 873 * @param {Object} marker 874 * @param {Object} mapOptions 875 * @return {string} 876 */ 877 self.getMarkerIconValue = function( marker, mapOptions ) { 878 if ( marker.markericon ) { 879 return marker.markericon; 880 } else if ( mapOptions.marker_icon ) { 881 return mapOptions.marker_icon; 882 } else { 883 return 'pin-red'; 884 } 885 }; 886 887 /** 888 * Init a map with Azure API 889 * 890 * @since 1.5 891 * 892 * @param {Object} map 893 * 894 * @return {atlas.Map} 895 */ 896 self.initMapAzure = function( map ) { 897 let event_settings = { 898 map_id: map.map, 899 map_options: map.options 900 }; 901 $( document ).trigger( 'js_event_wpv_addon_maps_init_map_started', [ event_settings ] ); 902 903 // Init defaults from map options 904 let mapCenter = [map.options.general_center_lon, map.options.general_center_lat]; 905 let mapZoom = map.options.general_zoom; 906 let cluster = ( map.options.cluster === 'on' ); 907 908 // 1 marker logic 909 if ( map.markers.length === 1 ) { 910 if ( 911 // marker geolocated and rendered after map, handled in self.resolveGeolocatedMarkerThenInitMapAzure 912 _.isNumber( map.markers[0].markerlon ) 913 && map.options.single_center === "on" 914 ) { 915 mapCenter = [map.markers[0].markerlon, map.markers[0].markerlat]; 916 } 917 918 mapZoom = map.options.single_zoom; 919 } 920 921 // Remove possible map loading content. Google Maps does it by itself, Azure doesn't 922 $( '#js-wpv-addon-maps-render-' + map.map ).empty(); 923 924 // Render map 925 let renderedMap = new atlas.Map( "js-wpv-addon-maps-render-" + map.map, { 926 "subscription-key": views_addon_maps_i10n.azure_api_key, 927 center: mapCenter, 928 zoom: mapZoom, 929 scrollZoomInteraction: ( map.options.scrollwheel === 'on' ), 930 dblClickZoomInteraction: ( map.options.double_click_zoom === 'on' ), 931 dragPanInteraction: ( map.options.draggable === 'on' ), 932 style: self.translateMapTypesToAzure( map.options.map_type ) // Ignored with API 1.1, but works with 1.2 933 } ); 934 935 $( document ).trigger( 'js_event_wpv_addon_maps_init_map_inited', [ event_settings ] ); 936 937 // Bounds can only be set on an already rendered map with Azure API 938 self.maybeFitboundsAzure( map, renderedMap ); 939 940 // Create pins (markers) 941 let pins = _.map( map.markers, function( marker ) { 942 return new atlas.data.Feature( 943 new atlas.data.Point( [marker.markerlon, marker.markerlat] ), 944 { 945 title: marker.title, 946 popup: marker.markerinfowindow, 947 icon: self.getMarkerIconValue( marker, map.options ), 948 iconhover: marker.markericonhover ? marker.markericonhover : map.options.marker_icon_hover, 949 id: marker.marker 950 }, 951 marker.marker 952 ); 953 } ); 954 955 // Create custom icons for pins (if any) 956 let icons = _.filter( 957 _.union( 958 _.pluck( map.markers, 'markericon' ), 959 _.pluck( map.markers, 'markericonhover' ), 960 [map.options.marker_icon, map.options.marker_icon_hover] 961 ) 962 ); 963 964 // Add markers and their events to map (waiting for load event first, otherwise the library throws a style 965 // rendering warning) 966 renderedMap.addEventListener('load', function() { 967 _.each( icons, function( icon ) { 968 var iconImg = document.getElementById( icon ); 969 970 if ( iconImg ) { 971 renderedMap.addIcon( icon, iconImg ); 972 } 973 } ); 974 975 self.addPins( renderedMap, pins, cluster ); 976 977 // FUTURE: this needs Azure Maps API 1.2, which is buggy at the moment and kills some other things. Revisit. 978 // Add controls 979 /*if ( map.options.zoom_control === 'on' ) { 980 renderedMap.addControl(new atlas.control.ZoomControl, {}); 981 } 982 if ( map.options.map_type_control === 'on' ) { 983 renderedMap.addControl(new atlas.control.StyleControl, {}); 984 }*/ 985 986 // Add an event listener for click (popup) 987 renderedMap.addEventListener( 'click', "default-pin-layer", function( event ) { 988 // Does pin have some popup text? 989 if ( ! event.features[0].properties.popup ) return; 990 991 // Create content for popup 992 let popupContentElement = document.createElement("div"); 993 popupContentElement.style.padding = "8px"; 994 let popupNameElement = document.createElement("div"); 995 popupNameElement.innerHTML = event.features[0].properties.popup;
996 popupContentElement.appendChild(popupNameElement); 997 998 // Create a popup 999 let popup = new atlas.Popup( { 1000 content: popupContentElement, 1001 position: event.features[0].geometry.coordinates, 1002 pixelOffset: [0, 0] 1003 } ); 1004 1005 popup.open( renderedMap ); 1006 } ); 1007 1008 // Add event listeners for hover (possible marker icon change) 1009 let originalIcon = ''; 1010 let hoveredPinIndex = 0; 1011 renderedMap.addEventListener( 'mouseover', "default-pin-layer", function( event ) { 1012 if ( ! event.features[0].properties.iconhover ) return; 1013 1014 let id = event.features[0].properties.id; 1015 let pin = _.findWhere( pins, {id: id} ); 1016 1017 originalIcon = event.features[0].properties.icon; 1018 hoveredPinIndex = _.indexOf( pins, pin ); 1019 1020 pin.properties.icon = pin.properties.iconhover; 1021 pins[hoveredPinIndex] = pin; 1022 1023 self.addPins( renderedMap, pins, cluster ); 1024 } ); 1025 renderedMap.addEventListener( 'mouseout', "default-pin-layer", function( event ) { 1026 if ( ! originalIcon ) return; 1027 1028 pins[hoveredPinIndex].properties.icon = originalIcon; 1029 1030 self.addPins( renderedMap, pins, cluster ); 1031 1032 originalIcon = ''; 1033 } ); 1034 } ); 1035 1036 $( document ).trigger( 'js_event_wpv_addon_maps_init_map_completed', [ event_settings ] ); 1037 1038 return renderedMap; 1039 }; 1040 1041 /** 1042 * Adds (or changes) all the pins for the given map on default pin layer. 1043 * 1044 * (Because you cannot simply change one pin with Azure, you have to change the layer). 1045 * 1046 * @since 1.5.3 1047 * @param atlas.Map map 1048 * @param {array} pins 1049 * @param {bool} cluster 1050 */ 1051 self.addPins = function( map, pins, cluster ) { 1052 map.addPins( pins, { 1053 fontColor: "#000", 1054 fontSize: 14, 1055 name: "default-pin-layer", 1056 cluster: cluster, 1057 textOffset: [0, 20], 1058 overwrite: true 1059 } ); 1060 }; 1061 1062 // ------------------------------------ 1063 // API 1064 // ------------------------------------ 1065 1066 /** 1067 * WPViews.view_addon_maps.reload_map 1068 * 1069 * @param map_id 1070 * 1071 * @since 1.0 1072 */ 1073 1074 self.reload_map = function( map_id ) { 1075 var settings = { 1076 map_id: map_id 1077 }; 1078 $( document ).trigger( 'js_event_wpv_addon_maps_reload_map_started', [ settings ] ); 1079 $( document ).trigger( 'js_event_wpv_addon_maps_reload_map_triggered', [ settings ] ); 1080 $( document ).trigger( 'js_event_wpv_addon_maps_reload_map_completed', [ settings ] ); 1081 }; 1082 1083 /** 1084 * document.js_event_wpv_addon_maps_reload_map_triggered 1085 * 1086 * @param event 1087 * @param data 1088 * data.map_id 1089 * 1090 * @since 1.0 1091 * @since 1.5 supports Azure API 1092 */ 1093 $( document ).on( 'js_event_wpv_addon_maps_reload_map_triggered', function( event, data ) { 1094 var defaults = { 1095 map_id: false 1096 }, 1097 settings = $.extend( {}, defaults, data ); 1098 if ( 1099 settings.map_id == false 1100 || $( '#js-wpv-addon-maps-render-' + settings.map_id ).length != 1 1101 ) { 1102 return; 1103 } 1104 self.clean_map_data( settings.map_id ); 1105 var mpdata = self.collect_map_data( $( '#js-wpv-addon-maps-render-' + settings.map_id ) ); 1106 1107 if ( API_GOOGLE === views_addon_maps_i10n.api_used ) { 1108 self.init_map_after_loading_styles( mpdata ); 1109 } else { 1110 $( '#js-wpv-addon-maps-render-' + settings.map_id ).empty(); 1111 self.maps[settings.map_id] = self.resolveGeolocatedMarkerThenInitMapAzure( mpdata ); 1112 } 1113 }); 1114 1115 /** 1116 * WPViews.view_addon_maps.get_map 1117 * 1118 * @param map_id 1119 * 1120 * @return google.maps.Map object | false 1121 * 1122 * @since 1.0 1123 */ 1124 1125 self.get_map = function( map_id ) { 1126 if ( map_id in self.maps ) { 1127 return self.maps[ map_id ]; 1128 } else { 1129 return false; 1130 } 1131 }; 1132 1133 /** 1134 * WPViews.view_addon_maps.get_map_marker 1135 * 1136 * @param marker_id 1137 * @param map_id 1138 * 1139 * @return google.maps.Marker object | false 1140 * 1141 * @since 1.0 1142 */ 1143 1144 self.get_map_marker = function( marker_id, map_id ) { 1145 if ( 1146 map_id in self.markers 1147 && marker_id in self.markers[ map_id ] 1148 ) { 1149 return self.markers[ map_id ][ marker_id ]; 1150 } else { 1151 return false; 1152 } 1153 }; 1154 1155 // ------------------------------------ 1156 // Interaction 1157 // ------------------------------------ 1158 1159 /** 1160 * Reload on js-wpv-addon-maps-reload-map.click 1161 * 1162 * @since 1.0 1163 */ 1164 1165 $( document ).on( 'click', '.js-wpv-addon-maps-reload-map', function( e ) { 1166 e.preventDefault(); 1167 var thiz = $( this ); 1168 if ( thiz.attr( 'data-map' ) ) { 1169 self.reload_map( thiz.data( 'map' ) ); 1170 } 1171 }); 1172 1173 /** 1174 * Center on a marker on js-wpv-addon-maps-center-map.click 1175 * 1176 * @since 1.0 1177 * @since 1.5 Azure API supported 1178 */ 1179 $( document ).on( 'click', '.js-wpv-addon-maps-focus-map', function( e ) { 1180 e.preventDefault(); 1181 var thiz = $( this ), 1182 thiz_map, 1183 thiz_marker, 1184 thiz_zoom; 1185 if ( 1186 thiz.attr( 'data-map' ) 1187 && thiz.attr( 'data-marker' ) 1188 ) { 1189 thiz_map = thiz.data( 'map' ); 1190 thiz_marker = thiz.data( 'marker' ); 1191 1192 if ( API_GOOGLE === views_addon_maps_i10n.api_used ) { 1193 if ( 1194 thiz_map in self.maps 1195 && thiz_map in self.markers 1196 && thiz_marker in self.markers[thiz_map] 1197 ) { 1198 thiz_zoom = ($('#js-wpv-addon-maps-render-' + thiz_map).data('singlezoom') != '') 1199 ? $('#js-wpv-addon-maps-render-' + thiz_map).data('singlezoom') 1200 : 14; 1201 self.maps[thiz_map].setCenter(self.markers[thiz_map][thiz_marker].getPosition()); 1202 self.maps[thiz_map].setZoom(thiz_zoom); 1203 } 1204 } else { 1205 if ( 1206 thiz_map in self.maps 1207 ) { 1208 thiz_zoom = ($('#js-wpv-addon-maps-render-' + thiz_map).data('singlezoom') != '') 1209 ? $('#js-wpv-addon-maps-render-' + thiz_map).data('singlezoom') 1210 : 14; 1211 var $thisMarker = $('.js-wpv-addon-maps-marker-' + thiz_marker); 1212 1213 self.maps[thiz_map].setCamera({ 1214 center: [$thisMarker.eq(0).data('markerlon'), $thisMarker.eq(0).data('markerlat')], 1215 zoom: thiz_zoom 1216 }); 1217 } 1218 } 1219 } 1220 }); 1221 1222 /** 1223 * Center map on fitbounds on js-wpv-addon-maps-center-map-fitbounds.click 1224 * 1225 * @todo make the restore work when having several points and no fitbounds, or a single point and no single_zoom or single_center 1226 * 1227 * @since 1.0 1228 */ 1229 1230 $( document ).on( 'click', '.js-wpv-addon-maps-restore-map', function( e ) { 1231 e.preventDefault(); 1232 var thiz = $( this ), 1233 thiz_map, 1234 current_map_data_array, 1235 current_map_data; 1236 if ( thiz.attr( 'data-map' ) ) { 1237 thiz_map = thiz.data( 'map' ); 1238 if ( 1239 thiz_map in self.maps 1240 && thiz_map in self.bounds 1241 ) { 1242 self.maps[ thiz_map ].fitBounds( self.bounds[ thiz_map ] ); 1243 } 1244 1245 if ( _.size( self.markers[ thiz_map ] ) == 1 ) { 1246 current_map_data_array = _.filter( self.maps_data, function( map_data_unique ) { 1247 return map_data_unique.map == thiz_map; 1248 }); 1249 current_map_data = _.first( current_map_data_array ); 1250 if ( current_map_data.options['single_zoom'] != '' ) { 1251 self.maps[ thiz_map ].setZoom( current_map_data.options['single_zoom'] ); 1252 if ( current_map_data.options['fitbounds'] == 'on' ) { 1253 self.api.event.addListenerOnce( self.maps[ thiz_map ], 'bounds_changed', function( event ) { 1254 self.maps[ thiz_map ].setZoom( current_map_data.options['single_zoom'] ); 1255 }); 1256 } 1257 } 1258 if ( current_map_data.options['single_center'] == 'on' ) { 1259 for ( var mark in self.markers[ thiz_map ] ) { 1260 self.maps[ thiz_map ].setCenter( self.markers[ thiz_map ][ mark ].getPosition() ); 1261 break; 1262 } 1263 } 1264 } 1265 1266 if ( API_AZURE === views_addon_maps_i10n.api_used ) { 1267 current_map_data_array = _.filter( self.maps_data, function( map_data_unique ) { 1268 return map_data_unique.map == thiz_map; 1269 }); 1270 current_map_data = _.first( current_map_data_array ); 1271 1272 if ( _.size( current_map_data.markers ) === 1 ) { 1273 var $thisMarker = $('.js-wpv-addon-maps-markerfor-' + thiz_map); 1274 self.maps[thiz_map].setCamera({ 1275 center: [$thisMarker.eq(0).data('markerlon'), $thisMarker.eq(0).data('markerlat')], 1276 zoom: current_map_data.options.single_zoom 1277 }); 1278 } else { 1279 self.maybeFitboundsAzure(current_map_data, self.maps[thiz_map]); 1280 } 1281 } 1282 } 1283 }); 1284 1285 // ------------------------------------ 1286 // Views compatibility 1287 // ------------------------------------ 1288 1289 /** 1290 * Reload the maps contained on the parametric search results loaded via AJAX 1291 * 1292 * @since 1.0 1293 */ 1294 $( document ).on( 'js_event_wpv_parametric_search_results_updated', function( event, data ) { 1295 self.cleanupMarkersFromMapBlock( data.view_unique_id ); 1296 _.each( self.get_affected_maps( data.layout ), self.reload_map ); 1297 } ); 1298 1299 /** 1300 * Reload the maps contained on the pagination results loaded via AJAX 1301 * 1302 * @since 1.0 1303 */ 1304 $( document ).on( 'js_event_wpv_pagination_completed', function( event, data ) { 1305 self.cleanupMarkersFromMapBlock( data.view_unique_id ); 1306 _.each( self.get_affected_maps( data.layout ), self.reload_map ); 1307 } ); 1308 1309 /** 1310 * Check if a Map block contains markers coming from a View, and it it does, remove those, because new ones are 1311 * provided by View. 1312 * 1313 * @since 2.0 1314 * @param {int|string} viewUniqueId 1315 */ 1316 self.cleanupMarkersFromMapBlock = function( viewUniqueId ) { 1317 const viewId = _.isNumber( viewUniqueId ) ? viewUniqueId : viewUniqueId.split( '-' )[ 0 ]; 1318 1319 $( '.js-wpv-addon-maps-marker' ).each( function( key, marker ) { 1320 // If this is a marker coming from given View... 1321 if ( Number( $( marker ).data( 'fromview' ) ) === Number( viewId ) ) { 1322 // Record View - Map blocks relation, as it's useful in some situations 1323 self.view_map_blocks_relation[ viewUniqueId ] = $( marker ).data( 'markerfor' ); 1324 1325 // Remove markers next to Map div (instead of in View div) 1326 $( marker ).prev( '.js-wpv-addon-maps-render' ).next().remove(); 1327 } 1328 } ); 1329 }; 1330 1331 /** 1332 * get_affected_maps 1333 * 1334 * Get all the maps that have related data used in the given containr, no matter if a map render or a marker 1335 * 1336 * @param container object 1337 * 1338 * @return array 1339 * 1340 * @since 1.1 1341 */ 1342 1343 self.get_affected_maps = function( container ) { 1344 var affected_maps = []; 1345 container.find( '.js-wpv-addon-maps-render' ).each( function() { 1346 affected_maps.push( $( this ).data( 'map' ) ); 1347 }); 1348 container.find( '.js-wpv-addon-maps-marker' ).each( function() { 1349 affected_maps.push( $( this ).data( 'markerfor' ) ); 1350 }); 1351 if ( self.view_map_blocks_relation[ container.data( 'viewnumber' ) ] ) { 1352 affected_maps.push( self.view_map_blocks_relation[ container.data( 'viewnumber' ) ] ); 1353 } 1354 affected_maps = _.uniq( affected_maps ); 1355 return affected_maps; 1356 }; 1357 1358 // ------------------------------------ 1359 // Clusters definitions and interactions 1360 // ------------------------------------ 1361 1362 /** 1363 * WPViews.view_addon_maps.set_cluster_options 1364 *
1365 * Sets options for clusters, either global or for a specific map 1366 * 1367 * @param options object 1368 * @param options.styles = [ 1369 * { 1370 * url: string URL of the cluster image for this style, 1371 * height: integer Width of the cluster image for this style, 1372 * width: integer Height of the cluster image for this style, 1373 * textColor: string (optional) Color of the counter text in this cluster, as a color name or hex value (with #), 1374 * textSize integer (optional) Text size for the counter in this cluster, in px (without unit) 1375 * }, 1376 * { ... } 1377 * ] 1378 * @param options.calculator = function( markers, numStyles ) { 1379 * @param markers array Markers in this cluster 1380 * @param numStyles integer Number of styles defined 1381 * @return { 1382 * text: string Text to be displayed inside the marker, 1383 * index: integer Index of the options.styles array that will be applied to this cluster - please make it less than numStyles 1384 * }; 1385 * } 1386 * @param map_id (optional) string The map ID this options will be binded to, global otherwise 1387 * 1388 * @note Most of the cluster options for a map are set in the map shortcode 1389 * @note Maps without specific styling options will get the current global options 1390 * @note We stoe the options in self.has_cluster_options for later usage, like on reload events 1391 * 1392 * @since 1.0 1393 */ 1394 1395 self.set_cluster_options = function( options, map_id ) { 1396 if ( typeof map_id === 'undefined' ) { 1397 // If map_id is undefined, set global options 1398 if ( _.has( options , "styles" ) ) { 1399 self.default_cluster_options['styles'] = options['styles']; 1400 } 1401 if ( _.has( options , "calculator" ) ) { 1402 self.default_cluster_options['calculator'] = options['calculator']; 1403 } 1404 } else { 1405 // Otherwise, bind to a specific map ID 1406 // Note that defaults are also used 1407 self.cluster_options[ map_id ] = self.get_cluster_options( map_id ); 1408 if ( _.has( options , "styles" ) ) { 1409 self.cluster_options[ map_id ]['styles'] = options['styles']; 1410 } 1411 if ( _.has( options , "calculator" ) ) { 1412 self.cluster_options[ map_id ]['calculator'] = options['calculator']; 1413 } 1414 self.has_cluster_options[ map_id ] = options; 1415 } 1416 }; 1417 1418 /** 1419 * WPViews.view_addon_maps.get_cluster_options 1420 * 1421 * Gets options for clusters, either global of for a specific map 1422 * 1423 * @param map_id (optional) string The map ID to get options from 1424 * 1425 * @return options object Set of options, either global or dedicated if the passed map_id has specific options 1426 * 1427 * @since 1.0 1428 */ 1429 1430 self.get_cluster_options = function( map_id ) { 1431 var options = self.default_cluster_options; 1432 if ( 1433 typeof map_id !== 'undefined' 1434 && _.has( self.cluster_options, map_id ) 1435 ) { 1436 options = self.cluster_options[ map_id ]; 1437 } 1438 return options; 1439 }; 1440 1441 // ------------------------------------ 1442 // Init 1443 // ------------------------------------ 1444 1445 /** 1446 * Inits Google Maps API specific code paths 1447 * @since 1.5 1448 */ 1449 self.initGoogle = function() { 1450 if ( self.api === null ) { 1451 if ( self.maps_data.length ) { 1452 self.maybeLazyLoadGoogle(); 1453 } 1454 return; 1455 } else if ( self.api === false ) { // Lazy loading failed, don't get into an infinite loop. 1456 return; 1457 } 1458 1459 self.api.event.addDomListener( window, "resize", function() { 1460 _.each( self.maps, function( map_iter, map_id ) { 1461 self.keep_map_center_and_resize( map_iter ); 1462 }); 1463 }); 1464 1465 $( document ).on( 'js_event_wpv_layout_responsive_resize_completed', function( event ) { 1466 $( '.js-wpv-layout-responsive .js-wpv-addon-maps-render' ).each( function() { 1467 var thiz = $( this ), 1468 thiz_map = thiz.data( 'map' ); 1469 if ( thiz_map in self.maps ) { 1470 self.keep_map_center_and_resize( self.maps[ thiz_map ] ); 1471 } else { 1472 self.resize_queue.push( thiz_map ); 1473 _.uniq( self.resize_queue ); 1474 } 1475 }); 1476 }); 1477 1478 self.maybeInitElementorEditorPreviewFix( self.init_maps ); 1479 1480 self.init_maps(); 1481 }; 1482 1483 /** 1484 * Inits Microsoft Azure Maps specific code paths 1485 * @since 1.5 1486 */ 1487 self.initAzure = function() { 1488 self.initMapsAzure(); 1489 self.maybeInitElementorEditorPreviewFix( self.initMapsAzure ); 1490 }; 1491 1492 /** 1493 * Initialize maps on Elementor widget ready so it works when previewing an Elementor design. 1494 * 1495 * @since 1.5.3 1496 * 1497 * @param {Function} mapInitCallback 1498 */
1499 self.maybeInitElementorEditorPreviewFix = function( mapInitCallback ) { 1500 if (typeof elementor === 'object' && typeof elementorFrontend === 'object') { 1501 elementorFrontend.hooks.addAction('frontend/element_ready/widget', function ($scope) { 1502 var mapInScope = $scope.find('div.js-wpv-addon-maps-render'); 1503 if (mapInScope.length) { 1504 self.collect_maps_data(); 1505 mapInitCallback(); 1506 } 1507 }); 1508 } 1509 }; 1510 1511 /** 1512 * Initializes a single map by given id, and also cleans up if map with same id was already initialized. 1513 * 1514 * API agnostic. 1515 * 1516 * @since 1.7 1517 * @param {string} id 1518 */ 1519 self.initMapById = function( id ) { 1520 self.clean_map_data( id ); 1521 1522 const mapData = self.collect_map_data( $( 'div#js-wpv-addon-maps-render-' + id ) ); 1523 1524 // It might happen that this method was called, but the actual map wasn't rendered yet. In that case, it will 1525 // delay itself until the map HTML is ready. 1526 if ( ! mapData.map ) { 1527 _.delay( self.initMapById, 200, id ); 1528 return; 1529 } 1530 1531 if ( API_GOOGLE === views_addon_maps_i10n.api_used ) { 1532 self.maybeLazyLoadGoogle( self.init_map_after_loading_styles, mapData ); 1533 } else { 1534 self.maps[id] = self.resolveGeolocatedMarkerThenInitMapAzure( mapData ); 1535 } 1536 }; 1537 1538 /** 1539 * Lazy load Google libraries and other libraries depending on it, if needed. 1540 * @param {Function} initMap 1541 * @param {object} mapData Callback parameter. 1542 */ 1543 self.maybeLazyLoadGoogle = function( initMap = null, mapData = null ) { 1544 if ( self.api === null ) { 1545 $.getScript( 1546 `https://maps.googleapis.com/maps/api/js?libraries=places&v=3&key=${ views_addon_maps_i10n.google_api_key }&callback=Function.prototype` 1547 ) 1548 .done( function() { 1549 self.api = window.google.maps; 1550 self.initGoogle(); // Do full init, because only now we have the API ready. 1551 } ) 1552 .fail( function() { 1553 self.api = false; 1554 console.warn( 'Toolset Maps: failed to lazy load Google Maps library.' ); 1555 } ); 1556 } else { 1557 initMap( mapData ); 1558 // This is emitted only by collect_maps_data, not by the single map data collector that's used here, and is 1559 // needed by add_current_visitor_location_after_geolocation in order to run at the right time. (Google API 1560 // only). 1561 $( document ).trigger('js_event_wpv_addon_maps_map_data_collected'); 1562 } 1563 } 1564 1565 self.init = function() { 1566 self.collect_maps_data(); 1567 1568 if ( API_GOOGLE === views_addon_maps_i10n.api_used ) { 1569 self.initGoogle(); 1570 } else { 1571 self.initAzure(); 1572 } 1573 }; 1574 1575 self.init(); 1576}; 1577 1578jQuery( function( $ ) { 1579 WPViews.view_addon_maps = new WPViews.ViewAddonMaps( $ ); 1580});
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.