source : info-window.js

/**
 * @ngdoc directive
 * @name info-window
 * @param Attr2MapOptions {service}
 *   convert html attribute to Google map api options
 * @param $compile {service} $compile service
 * @description
 *  Defines infoWindow and provides compile method
 *
 *  Requires:  map directive
 *
 *  Restrict To:  Element
 *
 *  NOTE: this directive should **NOT** be used with `ng-repeat`
 *  because InfoWindow itself is a template, and a template must be
 *  reused by each marker, thus, should not be redefined repeatedly
 *  by `ng-repeat`.
 *
 * @attr {Boolean} visible
 *   Indicates to show it when map is initialized
 * @attr {Boolean} visible-on-marker
 *   Indicates to show it on a marker when map is initialized
 * @attr {Expression} geo-callback
 *   if position is an address, the expression is will be performed
 *   when geo-lookup is successful. e.g., geo-callback="showDetail()"
 * @attr {String} <InfoWindowOption> Any InfoWindow options,
 *   https://developers.google.com/maps/documentation/javascript/reference?csw=1#InfoWindowOptions
 * @attr {String} <InfoWindowEvent> Any InfoWindow events,
 *   https://developers.google.com/maps/documentation/javascript/reference
 * @example
 * Usage:
 *   <map MAP_ATTRIBUTES>
 *    <info-window id="foo" ANY_OPTIONS ANY_EVENTS"></info-window>
 *   </map>
 *
 * Example:
 *  <map center="41.850033,-87.6500523" zoom="3">
 *    <info-window id="1" position="41.850033,-87.6500523" >
 *      <div ng-non-bindable>
 *        Chicago, IL<br/>
 *        LatLng: {{chicago.lat()}}, {{chicago.lng()}}, <br/>
 *        World Coordinate: {{worldCoordinate.x}}, {{worldCoordinate.y}}, <br/>
 *        Pixel Coordinate: {{pixelCoordinate.x}}, {{pixelCoordinate.y}}, <br/>
 *        Tile Coordinate: {{tileCoordinate.x}}, {{tileCoordinate.y}} at Zoom Level {{map.getZoom()}}
 *      </div>
 *    </info-window>
 *  </map>
 */
/* global google */
(function() {
  'use strict';
  var infoWindow = function(Attr2MapOptions, $compile, $q, $templateRequest, $timeout, $parse, NgMap)  {
    var parser = Attr2MapOptions;
    var getInfoWindow = function(options, events, element) {
      var infoWindow;
      /**
       * set options
       */
      if (options.position && !(options.position instanceof google.maps.LatLng)) {
        delete options.position;
      }
      infoWindow = new google.maps.InfoWindow(options);
      /**
       * set events
       */
      for (var eventName in events) {
        if (eventName) {
          google.maps.event.addListener(infoWindow, eventName, events[eventName]);
        }
      }
      /**
       * set template and template-related functions
       * it must have a container element with ng-non-bindable
       */
      var templatePromise = $q(function(resolve) {
        if (angular.isString(element)) {
          $templateRequest(element).then(function (requestedTemplate) {
            resolve(angular.element(requestedTemplate).wrap('<div>').parent());
          }, function(message) {
            throw "info-window template request failed: " + message;
          });
        }
        else {
          resolve(element);
        }
      }).then(function(resolvedTemplate) {
        var template = resolvedTemplate.html().trim();
        if (angular.element(template).length != 1) {
          throw "info-window working as a template must have a container";
        }
        infoWindow.__template = template.replace(/\s?ng-non-bindable[='"]+/,"");
      });
      infoWindow.__open = function(map, scope, anchor) {
        templatePromise.then(function() {
          $timeout(function() {
            anchor && (scope.anchor = anchor);
            var el = $compile(infoWindow.__template)(scope);
            infoWindow.setContent(el[0]);
            scope.$apply();
            if (anchor && anchor.getPosition) {
              infoWindow.open(map, anchor);
            } else if (anchor && anchor instanceof google.maps.LatLng) {
              infoWindow.open(map);
              infoWindow.setPosition(anchor);
            } else {
              infoWindow.open(map);
            }
            $timeout(function() { // to avoid racing condition
              var infoWindowContainerEl = infoWindow.content.parentElement.parentElement.parentElement;
              infoWindowContainerEl.className = "ng-map-info-window";
            });
          });
        });
      };
      return infoWindow;
    };
    var linkFunc = function(scope, element, attrs, mapController) {
      mapController = mapController[0]||mapController[1];
      element.css('display','none');
      var orgAttrs = parser.orgAttributes(element);
      var filtered = parser.filter(attrs);
      var options = parser.getOptions(filtered, {scope: scope});
      var events = parser.getEvents(scope, filtered);
      var infoWindow = getInfoWindow(options, events, options.template || element);
      var address;
      if (options.position && !(options.position instanceof google.maps.LatLng)) {
        address = options.position;
      }
      if (address) {
        NgMap.getGeoLocation(address).then(function(latlng) {
          infoWindow.setPosition(latlng);
          infoWindow.__open(mapController.map, scope, latlng);
          var geoCallback = attrs.geoCallback;
          geoCallback && $parse(geoCallback)(scope);
        });
      }
      mapController.addObject('infoWindows', infoWindow);
      mapController.observeAttrSetObj(orgAttrs, attrs, infoWindow);
      mapController.showInfoWindow =
      mapController.map.showInfoWindow = mapController.showInfoWindow ||
        function(p1, p2, p3) { //event, id, marker
          var id = typeof p1 == 'string' ? p1 : p2;
          var marker = typeof p1 == 'string' ? p2 : p3;
          if (typeof marker == 'string') {
            //Check if markers if defined to avoid odd 'undefined' errors
            if (
              typeof mapController.map.markers != "undefined"
              && typeof mapController.map.markers[marker] != "undefined") {
                marker = mapController.map.markers[marker];
            } else if (
              //additionally check if that marker is a custom marker
              typeof mapController.map.customMarkers !== "undefined"
              && typeof mapController.map.customMarkers[marker] !== "undefined") {
                marker = mapController.map.customMarkers[marker];
            } else {
              //Better error output if marker with that id is not defined
              throw new Error("Cant open info window for id " + marker + ". Marker or CustomMarker is not defined")
            }
          }
          var infoWindow = mapController.map.infoWindows[id];
          var anchor = marker ? marker : (this.getPosition ? this : null);
          infoWindow.__open(mapController.map, scope, anchor);
          if(mapController.singleInfoWindow) {
            if(mapController.lastInfoWindow) {
              scope.hideInfoWindow(mapController.lastInfoWindow);
            }
            mapController.lastInfoWindow = id;
          }
        };
      mapController.hideInfoWindow =
      mapController.map.hideInfoWindow = mapController.hideInfoWindow ||
        function(p1, p2) {
          var id = typeof p1 == 'string' ? p1 : p2;
          var infoWindow = mapController.map.infoWindows[id];
          infoWindow.close();
        };
      //TODO DEPRECATED
      scope.showInfoWindow = mapController.map.showInfoWindow;
      scope.hideInfoWindow = mapController.map.hideInfoWindow;
      var map = infoWindow.mapId ? {id:infoWindow.mapId} : 0;
      NgMap.getMap(map).then(function(map) {
        infoWindow.visible && infoWindow.__open(map, scope);
        if (infoWindow.visibleOnMarker) {
          var markerId = infoWindow.visibleOnMarker;
          infoWindow.__open(map, scope, map.markers[markerId]);
        }
      });
    }; //link
    return {
      restrict: 'E',
      require: ['?^map','?^ngMap'],
      link: linkFunc
    };
  }; // infoWindow
  infoWindow.$inject =
    ['Attr2MapOptions', '$compile', '$q', '$templateRequest', '$timeout', '$parse', 'NgMap'];
  angular.module('ngMap').directive('infoWindow', infoWindow);
})();