geofield_map-8.x-2.67/js/geofield_map_gmap_formatter.js

js/geofield_map_gmap_formatter.js
/**
 * @file
 * Javascript for the Geofield Google Map formatter.
 */

(function ($, Drupal) {

  'use strict';

  /**
   * The Geofield Google Map formatter behavior.
   *
   * @type {Drupal~behavior}
   *
   * @prop {Drupal~behaviorAttach} attach
   *   Attaches the Geofield Google Map formatter behavior.
   */
  Drupal.behaviors.geofieldGoogleMap = {
    attach: function (context, drupalSettings) {

      function loadMap(mapId) {
        let map_settings = drupalSettings['geofield_google_map'][mapId]['map_settings'];
        let data = drupalSettings['geofield_google_map'][mapId]['data'];

        // Set the map_data[mapid] settings.
        Drupal.geoFieldMapFormatter.map_data[mapId] = map_settings;

        // Load before the GMap Library, if needed.
        Drupal.geoFieldMapFormatter.loadGoogle(mapId, map_settings.gmap_api_key, map_settings.map_additional_libraries, function () {
          if (!document.getElementById(mapId)) {
            return;
          }
          Drupal.geoFieldMapFormatter.map_initialize(mapId, map_settings, data, context);
        });
      }

      if (drupalSettings['geofield_google_map']) {
        // If the IntersectionObserver API is available, create an observer to load the map when it enters the viewport
        // It will be used to handle map loading instead of displaying the map on page load.
        let mapObserver = null;
        if ('IntersectionObserver' in window){
          mapObserver = new IntersectionObserver(function (entries, observer) {
            for(var i = 0; i < entries.length; i++) {
              if(entries[i].isIntersecting) {
                once('geofield-map-loaded', entries[i].target).forEach(function (el) {
                  loadMap(el.id);
                });
              }
            }
          });
        }

        once('geofield-processed', 'html .geofield-google-map').forEach(function (element) {
          const mapId = $(element).attr('id');
          if (drupalSettings['geofield_google_map'][mapId]) {
            const map_settings = drupalSettings['geofield_google_map'][mapId]['map_settings'];
            if (mapObserver && map_settings['map_lazy_load']['lazy_load']) {
              mapObserver.observe(element)
            } else {
              loadMap(mapId);
            }
          }
        });
      }
    }
  };

  Drupal.geoFieldMapFormatter = {

    map_start: {
      center: {lat: 41.85, lng: -87.65},
      zoom: 18
    },

    map_data: {},

    // Google Maps are loaded lazily. In some situations load_google() is
    // called twice, which results in "You have included the Google Maps API
    // multiple times on this page. This may cause unexpected errors." errors.
    // This flag will prevent repeat $.getScript() calls.
    maps_api_loading: false,

    /**
     * Returns the re-coded Google Maps api language parameter, from html lang attribute.
     *
     * @param {string} html_language - The language id string
     *
     * @return {string} - The transformed language id string
     */
    googleMapsLanguage: function (html_language) {
      switch (html_language) {
        case 'zh-hans':
          html_language = 'zh-CN';
          break;

        case 'zh-hant':
          html_language = 'zh-TW';
          break;
      }
      return html_language;
    },

    /**
     * Provides the callback that is called when maps loads.
     */
    googleCallback: function () {
      let self = this;
      // Wait until the window load event to try to use the Maps library.
      $(document).ready(function (e) {
        self.googleCallbacks.forEach(function (callback) {
          callback.callback();
        });
        self.googleCallbacks = [];
      });
    },

    /**
     * Adds a callback that will be called once the maps library is loaded.
     *
     * @param {string} callback - The callback
     */
    addCallback: function (callback) {
      let self = this;
      // Ensure callbacks array.
      self.googleCallbacks = self.googleCallbacks || [];
      self.googleCallbacks.push({callback: callback});
    },

    /**
     * Load Google Maps library.
     *
     * @param {string} mapid - The map id
     * @param {string} gmap_api_key - The gmap api key
     * @param {array} additional_libraries - Additional Libraries list
     * @param {string} callback - the Callback function  name
     */
    loadGoogle: function (mapid, gmap_api_key, additional_libraries, callback) {
      let self = this;
      let html_language = $('html').attr('lang') || 'en';

      // Add the callback.
      self.addCallback(callback);

      // Check for Google Maps.
      if (typeof google === 'undefined' || typeof google.maps === 'undefined') {
        if (self.maps_api_loading === true) {
          return;
        }

        self.maps_api_loading = true;
        // Google Maps isn't loaded so lazy load Google Maps.
        // Default script path.
        let scriptPath = self.map_data[mapid]['gmap_api_localization'] + '?v=weekly&sensor=false&language=' + self.googleMapsLanguage(html_language) + '&callback=Drupal.geoFieldMapFormatter.googleCallback'  + '&loading=async';

        // If a Google API key is set, use it.
        if (gmap_api_key) {
          scriptPath += '&key=' + gmap_api_key;
        }

        if (additional_libraries) {
          let libraries = [];
          for (let library in additional_libraries) {
            if (additional_libraries.hasOwnProperty(library)) {
              libraries.push(library);
            }
          }
          scriptPath += '&libraries=' + libraries.join();
        }

        $.getScript(scriptPath)
          .done(function () {
            self.maps_api_loading = false;
          });

      }
      else {
        // Google Maps loaded. Run callback.
        self.googleCallback();
      }
    },

    checkImage: function (imageSrc, setIcon, logError) {
      let img = new Image();
      img.src = imageSrc;
      img.onload = setIcon;
      img.onerror = logError;
    },

    place_feature: function (feature, mapid) {
      let self = this;
      let icon_image = null;

      // Override and set icon image with geojsonProperties.icon, if set as not
      // null/empty.
      if (feature.geojsonProperties.icon && feature.geojsonProperties.icon.length > 0) {
        icon_image = feature.geojsonProperties.icon;
      }

      // Set the personalized Icon Image, if set.
      if (feature.setIcon && icon_image && icon_image.length > 0) {
        self.checkImage(icon_image,
          // Success loading image.
          function () {
            feature.setIcon(icon_image);
          });
      }

      // Add a default Tooltip on the title geojsonProperty, if existing.
      if (feature.setTitle && feature.geojsonProperties.tooltip) {
        feature.setTitle(feature.geojsonProperties.tooltip);
      }

      const map = self.map_data[mapid].map;

      // If the feature is a Point, make it a Marker and extend the Map bounds.
      if (feature.getPosition) {
        // Define the OverlappingMarkerSpiderfier flag.
        const oms = self.map_data[mapid].oms ? self.map_data[mapid].oms : null;
        if (oms) {
          self.map_data[mapid].oms.addMarker(feature);
        }
        else {
          feature.setMap(map);
        }

        // Generate the markers object index based on entity id (and geofield
        // cardinality), and add the marker to the markers object.
        const entity_id = feature['geojsonProperties']['entity_id'];
        if (self.map_data[mapid].geofield_cardinality && self.map_data[mapid].geofield_cardinality !== 1) {
          let i = 0;
          while (self.map_data[mapid].markers[entity_id + '-' + i]) {
            i++;
          }
          self.map_data[mapid].markers[entity_id + '-' + i] = feature;
        }
        else {
          self.map_data[mapid].markers[entity_id] = feature;
        }

        self.map_data[mapid].map_bounds.extend(feature.getPosition());

        // Check for eventual simple or OverlappingMarkerSpiderfier click
        // Listener.
        const clickListener = oms ? 'spider_click' : 'click';
        google.maps.event.addListener(feature, clickListener, function () {
          self.infowindow_open(mapid, feature);
        });
      }

      // If the feature is a Polyline or a Polygon, add to the Map and extend
      // the Map bounds.
      if (feature.getPath) {
        let feature_options = feature.geojsonProperties.path_options ? JSON.parse(feature.geojsonProperties.path_options) : {};
        feature.setOptions(feature_options);
        feature.setMap(map);
        let path = feature.getPath();
        path.forEach(function (element) {
          self.map_data[mapid].map_bounds.extend(element);
        });
        google.maps.event.addListener(feature, 'click', function (event) {
          self.infowindow_open(mapid, feature, event.latLng);
        });
      }

    },

    // Closes and open the Map Infowindow at the input feature.
    infowindow_open: function (mapid, feature, anchor) {
      let self = this;
      let map = self.map_data[mapid].map;
      let properties = feature.get('geojsonProperties');
      if (feature.setTitle && properties && properties.title) {
        feature.setTitle(properties.title);
      }
      map.infowindow.close();
      if (properties.description) {
        map.infowindow.setContent(properties.description);

        // Note: if the feature is a Marker (and not a Polyline/Polygon) its
        // extensions will override the infowindow anchor position, in the map.
        // infowindow.open method.
        map.infowindow.setPosition(anchor);
        setTimeout(function () {
          map.infowindow.open(map, feature);
        }, 200);
      }
    },

    map_refresh: function (mapid) {
      let self = this;
      setTimeout(function () {
        google.maps.event.trigger(self.map_data[mapid].map, 'resize');
      }, 10);
    },

    // Init Geofield Google Map and its functions.
    map_initialize: function (mapid, map_settings, data, context) {
      let self = this;

      // If google and google.maps have been defined.
      if (google && google.maps) {
        let styledMapType;

        let mapOptions = {
          center: map_settings.map_center ? new google.maps.LatLng(map_settings.map_center.lat, map_settings.map_center.lon) : new google.maps.LatLng(42, 12.5),
          zoom: map_settings.map_zoom_and_pan.zoom.initial ? parseInt(map_settings.map_zoom_and_pan.zoom.initial) : 8,
          minZoom: map_settings.map_zoom_and_pan.zoom.min ? parseInt(map_settings.map_zoom_and_pan.zoom.min) : 1,
          maxZoom: map_settings.map_zoom_and_pan.zoom.max ? parseInt(map_settings.map_zoom_and_pan.zoom.max) : 20,
          gestureHandling: map_settings.map_zoom_and_pan.gestureHandling || 'auto',
          mapTypeId: map_settings.map_controls.map_type_id || 'roadmap'
        };

        // Manage the old scrollwheel & draggable settings (deprecated by Google Maps api).
        if (!map_settings.map_zoom_and_pan.scrollwheel && !map_settings.map_zoom_and_pan.gestureHandling) {
          mapOptions.scrollwheel = false;
        }
        if (!map_settings.map_zoom_and_pan.draggable && !map_settings.map_zoom_and_pan.gestureHandling) {
          mapOptions.draggable = false;
        }

        if (map_settings.map_controls.disable_default_ui) {
          mapOptions.disableDefaultUI = map_settings.map_controls.disable_default_ui;
        }
        else {
          // Implement Custom Style Map, if Set.
          if (map_settings.custom_style_map && map_settings.custom_style_map.custom_style_control && map_settings.custom_style_map.custom_style_name.length > 0 && map_settings.custom_style_map.custom_style_options.length > 0) {
            let customMapStyleName = map_settings.custom_style_map.custom_style_name;
            let customMapStyle = JSON.parse(map_settings.custom_style_map.custom_style_options);
            styledMapType = new google.maps.StyledMapType(customMapStyle, {name: customMapStyleName});
            map_settings.map_controls.map_type_control_options_type_ids.push('custom_styled_map');
          }

          mapOptions.zoomControl = !!map_settings.map_controls.zoom_control;
          mapOptions.mapTypeControl = !!map_settings.map_controls.map_type_control;
          mapOptions.mapTypeControlOptions = {
            mapTypeIds: map_settings.map_controls.map_type_control_options_type_ids ? map_settings.map_controls.map_type_control_options_type_ids : ['roadmap', 'satellite', 'hybrid'],
            position: google.maps.ControlPosition.TOP_LEFT
          };
          mapOptions.scaleControl = !!map_settings.map_controls.scale_control;
          mapOptions.streetViewControl = !!map_settings.map_controls.street_view_control;
          mapOptions.fullscreenControl = !!map_settings.map_controls.fullscreen_control;
        }

        // Add map_additional_options if any.
        if (map_settings.map_additional_options.length > 0) {
          let additionalOptions = JSON.parse(map_settings.map_additional_options);
          // Transforms additionalOptions "true", "false" values into true &
          // false.
          for (let prop in additionalOptions) {
            if (additionalOptions.hasOwnProperty(prop)) {
              if (additionalOptions[prop] === 'true') {
                additionalOptions[prop] = true;
              }
              if (additionalOptions[prop] === 'false') {
                additionalOptions[prop] = false;
              }
            }
          }
          // Merge mapOptions with additionalOptions.
          $.extend(mapOptions, additionalOptions);
        }

        // Define the Geofield Google Map.
        let map = new google.maps.Map(document.getElementById(mapid), mapOptions);

        // Add the Map Reset Control, if set.
        if (map_settings.map_zoom_and_pan.map_reset) {
          let mapResetControlPosition = map_settings.map_zoom_and_pan.map_reset_position || 'TOP_RIGHT';

          // Create the DIV to hold the control and call the mapResetControl()
          // constructor passing in this DIV.
          let mapResetControlDiv = document.createElement('div');
          mapResetControlDiv.style.zIndex = "10";
          mapResetControlDiv.index = 1;
          new self.map_reset_control(mapResetControlDiv, mapid);
          map.controls[google.maps.ControlPosition[mapResetControlPosition]].push(mapResetControlDiv);
        }

        if (Drupal.geoFieldMapGeocoder && map_settings.map_geocoder.control) {
          let mapGeocoderControlPosition = map_settings.map_geocoder.settings.position || 'TOP_RIGHT';
          let mapGeocoderControlDiv = document.createElement('div');
          Drupal.geoFieldMapFormatter.map_data[mapid].geocoder_control = new Drupal.geoFieldMapGeocoder.map_control(mapGeocoderControlDiv, mapid);
          mapGeocoderControlDiv.index = 1;
          map.controls[google.maps.ControlPosition[mapGeocoderControlPosition]].push(Drupal.geoFieldMapFormatter.map_data[mapid].geocoder_control);
          Drupal.geoFieldMapGeocoder.map_control_autocomplete(mapid, map_settings.map_geocoder.settings, $(Drupal.geoFieldMapFormatter.map_data[mapid].geocoder_control), 'formatter', 'gmap');
        }

        // If defined a Custom Map Style, associate the styled map with
        // custom_styled_map MapTypeId and set it to display.
        if (styledMapType) {
          map.mapTypes.set('custom_styled_map', styledMapType);
          // Set Custom Map Style to Default, if requested.
          if (map_settings.custom_style_map && map_settings.custom_style_map.custom_style_default) {
            map.setMapTypeId('custom_styled_map');
          }
        }

        // Define a mapid self property, so other code can interact with it.
        self.map_data[mapid].map = map;
        self.map_data[mapid].map_options = mapOptions;
        self.map_data[mapid].features = data.features;
        self.map_data[mapid].markers = {};

        // Define the MapBounds property.
        self.map_data[mapid].map_bounds = new google.maps.LatLngBounds();

        // Set the zoom force and center property for the map.
        self.map_data[mapid].zoom_force = !!map_settings.map_zoom_and_pan.zoom.force;
        self.map_data[mapid].center_force = !!map_settings.map_center.center_force;

        // Parse the Geojson data into Google Maps Locations.
        let features = [];
        for (let i in data.features) {
          features[i] = Drupal.googleGeoJson(data.features[i]);
        }

        if (features.length > 0) {

          /**
           * Implement  OverlappingMarkerSpiderfier if its control set true.
           */
          if (map_settings.map_oms && map_settings.map_oms.map_oms_control && OverlappingMarkerSpiderfier) {
            let omsOptions = map_settings.map_oms.map_oms_options.length > 0 ? JSON.parse(map_settings.map_oms.map_oms_options) : {
              markersWontMove: true,
              markersWontHide: true,
              basicFormatEvents: true,
              keepSpiderfied: true
            };
            self.map_data[mapid].oms = new OverlappingMarkerSpiderfier(map, omsOptions);
          }

          map.infowindow = new google.maps.InfoWindow({
            content: ''
          });

          // If the map.infowindow is defined, add an event listener for the
          // Ajax Infowindow Popup.
          google.maps.event.addListener(map.infowindow, 'domready', function () {
            let element = document.createElement('div');
            element.innerHTML = map.infowindow.getContent().trim();
            let content = $('[data-geofield-google-map-ajax-popup]', element);
            if (content.length) {
              let url = content.data('geofield-google-map-ajax-popup');
              Drupal.ajax({url: url}).execute().done(function () {
                Drupal.attachBehaviors(element, drupalSettings);
              })
            }
          });

          // Iterate on each features and place it to the map.
          if (features.setMap) {
            self.place_feature(features, mapid);
          }
          else {
            for (let i in features) {
              if (features[i].setMap) {
                self.place_feature(features[i], mapid);
              }
              else {
                for (let j in features[i]) {
                  if (features[i][j].setMap) {
                    self.place_feature(features[i][j], mapid);
                  }
                }
              }
            }
          }

          // Implement Markeclustering, if more than 1 marker on the map,
          // and the markercluster option is set to true.
          if (typeof MarkerClusterer !== 'undefined' && map_settings.map_markercluster.markercluster_control) {

            let markeclusterOption = {
              imagePath: 'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'
            };

            // Add markercluster_additional_options if any.
            if (map_settings.map_markercluster.markercluster_additional_options.length > 0) {
              const markeclusterAdditionalOptions = JSON.parse(map_settings.map_markercluster.markercluster_additional_options);
              // Merge markeclusterOption with markeclusterAdditionalOptions.
              $.extend(markeclusterOption, markeclusterAdditionalOptions);
            }

            // Define a markerCluster property, so other code can interact with
            // it.
            let markerCluster = [];
            const keys = Object.keys(self.map_data[mapid].markers);
            for (let k = 0; k < keys.length; k++) {
              markerCluster.push(self.map_data[mapid].markers[keys[k]]);
            }
            self.map_data[mapid].markerCluster = new MarkerClusterer(map, markerCluster, markeclusterOption);
          }

          // If the Map Initial State is defined by MapBounds,
          // and the map center is not forced.
          if (!self.mapBoundsAreNull(self.map_data[mapid].map_bounds) && !self.map_data[mapid].center_force) {
            map.fitBounds(self.map_data[mapid].map_bounds);
          }
          // Else if the Map Initial State is defined by Markers in the exact
          // same locations,.
          else if (self.map_data[mapid].markers.constructor === Object &&
            self.mapBoundsAreNull(self.map_data[mapid].map_bounds) &&
            // And the map center is not forced.
            !self.map_data[mapid].center_force) {
            map.setCenter(self.map_data[mapid].markers[Object.keys(self.map_data[mapid].markers)[0]].getPosition());
          }

        }

        google.maps.event.addListenerOnce(map, 'bounds_changed', function () {
          // Force the Map Zoom if requested.
          if (self.map_data[mapid].zoom_force) {
            self.map_data[mapid].map.setZoom(self.map_data[mapid].map_options.zoom);
          }
        });

        // At the beginning (once) ...
        google.maps.event.addListenerOnce(map, 'idle', function () {

          // Open the Feature infowindow, if so set.
          if (self.map_data[mapid].map_marker_and_infowindow.force_open) {
            // map.setCenter(features[0].getPosition());
            self.infowindow_open(mapid, features[0]);
          }

          // In case of map initial position not forced, and zooFiner not
          // null/neutral, adapt the Map Zoom and the Start Zoom accordingly.
          if (!self.map_data[mapid].zoom_force && self.map_data[mapid].map_zoom_and_pan.zoom.finer && self.map_data[mapid].map_zoom_and_pan.zoom.finer !== 0) {
            map.setOptions({
              zoom: map.getZoom() + parseInt(self.map_data[mapid].map_zoom_and_pan.zoom.finer)
            });
          }

          // Update map initial state after everything is settled.
          self.map_set_start_state(mapid, map.getCenter(), map.getZoom());

          // Trigger a custom event on Geofield Map initialized, with mapid.
          $(context).trigger('geofieldMapInit', mapid);
        });

      }
    },

    mapBoundsAreNull: function (mapBounds) {
      let north_east = mapBounds.getNorthEast();
      let south_west = mapBounds.getSouthWest();
      return north_east.toString() === south_west.toString();

    },

    map_set_start_state: function (mapid, center, zoom) {
      let self = this;
      self.map_data[mapid].map_start_center = center;
      self.map_data[mapid].map_start_zoom = zoom;
    },

    map_reset_control: function (controlDiv, mapid) {
      // Set CSS for the control border.
      let controlUI = document.createElement('div');
      controlUI.style.backgroundColor = '#fff';
      controlUI.style.boxShadow = 'rgba(0,0,0,.3) 0px 1px 4px -1px';
      controlUI.style.cursor = 'pointer';
      controlUI.title = Drupal.t('Click to reset the map to its initial state');
      controlUI.style.margin = '10px';
      controlUI.style.position = 'relative';
      controlUI.id = 'geofield-map--' + mapid + '--reset-control';
      controlDiv.appendChild(controlUI);

      // Set CSS for the control interior.
      let controlText = document.createElement('div');
      controlText.style.position = 'relative';
      controlText.innerHTML = Drupal.t('Reset Map');
      controlText.style.padding = '0 17px';
      controlText.style.display = 'table-cell';
      controlText.style.height = '40px';
      controlText.style.fontSize = '18px';
      controlText.style.color = 'rgb(86,86,86)';
      controlText.style.textAlign = 'center';
      controlText.style.verticalAlign = 'middle';
      controlUI.appendChild(controlText);

      // Setup the click event listeners: simply set the map to Chicago.
      controlUI.addEventListener('click', function () {
        Drupal.geoFieldMapFormatter.map_data[mapid].map.setCenter(Drupal.geoFieldMapFormatter.map_data[mapid].map_start_center);
        Drupal.geoFieldMapFormatter.map_data[mapid].map.setZoom(Drupal.geoFieldMapFormatter.map_data[mapid].map_start_zoom);
      });
      return controlUI;
    }

  };

})(jQuery, Drupal);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc