google-map-marker.html 9.38 KB
<!-- Copyright (c) 2015 Google Inc. All rights reserved. -->

<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="../google-apis/google-maps-api.html">

<!--
The `google-map-marker` element represents a map marker. It is used as a
child of `google-map`.

<b>Example</b>:

    <google-map latitude="37.77493" longitude="-122.41942">
      <google-map-marker latitude="37.779" longitude="-122.3892"
          title="Go Giants!"></google-map-marker>
    </google-map>

<b>Example</b> - marker with info window (children create the window content):

    <google-map-marker latitude="37.77493" longitude="-122.41942">
      <img src="image.png">
    </google-map-marker>

<b>Example</b> - a draggable marker:

    <google-map-marker latitude="37.77493" longitude="-122.41942"
         draggable="true"></google-map-marker>

<b>Example</b> - hide a marker:

    <google-map-marker latitude="37.77493" longitude="-122.41942"
        hidden></google-map-marker>

-->

<dom-module id="google-map-marker">
  <style>
    :host {
      display: none;
    }
  </style>
  <template><content></content></template>
</dom-module>

<script>

(function() {
  Polymer({

    is: 'google-map-marker',

    /**
     * Fired when the marker icon was clicked. Requires the clickEvents attribute to be true.
     * @param {google.maps.MouseEvent} event The mouse event.
     * @event google-map-marker-click
     */
    /**
     * Fired when the marker icon was double clicked. Requires the clickEvents attribute to be true.
     * @param {google.maps.MouseEvent} event The mouse event.
     * @event google-map-marker-dblclick
     */
    /**
     * Fired for a mousedown on the marker. Requires the mouseEvents attribute to be true.
     * @event google-map-marker-mousedown
     * @param {google.maps.MouseEvent} event The mouse event.
     */
    /**
     * Fired when the DOM `mousemove` event is fired on the marker. Requires the mouseEvents
     * attribute to be true.
     * @event google-map-marker-mousemove
     * @param {google.maps.MouseEvent} event The mouse event.
     */
    /**
     * Fired when the mouse leaves the area of the marker icon. Requires the mouseEvents attribute to be
     * true.
     * @event google-map-marker-mouseout
     * @param {google.maps.MouseEvent} event The mouse event.
     */
    /**
     * Fired when the mouse enters the area of the marker icon. Requires the mouseEvents attribute to be
     * true.
     * @event google-map-marker-mouseover
     * @param {google.maps.MouseEvent} event The mouse event.
     */
    /**
     * Fired for a mouseup on the marker. Requires the mouseEvents attribute to be true.
     *
     * @event google-map-marker-mouseup
     * @param {google.maps.MouseEvent} event The mouse event.
     */
    /**
     * Fired for a rightclick on the marker. Requires the clickEvents attribute to be true.
     * @event google-map-marker-rightclick
     * @param {google.maps.MouseEvent} event The mouse event.
     */
    properties: {
      /**
       * A Google Maps marker object.
       * @type google.maps.Marker
       */
      marker: Object,

      /**
       * The Google map object.
       * @type google.maps.Map
       */
      map: {
        type: Object,
        observer: '_mapChanged'
      },

      /**
       * A Google Map Infowindow object.
       */
      info: {
        type: Object,
        value: null
      },

      /**
       * When true, marker *click events are automatically registered.
       */
      clickEvents: {
        type: Boolean,
        value: false,
        observer: '_clickEventsChanged'
      },

      /**
       * Image URL for the marker icon.
       * @type string|google.maps.Icon|google.maps.Symbol
       */
      icon: {
        type: Object,
        value: null,
        observer: '_iconChanged'
      },

      /**
       * When true, marker mouse* events are automatically registered.
       */
      mouseEvents: {
        type: Boolean,
        value: false,
        observer: '_mouseEventsChanged'
      },

      /**
       * Z-index for the marker icon.
       */
      zIndex: {
        type: Number,
        value: 0,
        observer: '_zIndexChanged'
      },

      /**
       * The marker's longitude coordinate.
       */
      longitude: {
        type: Number,
        value: null,
        reflectToAttribute: true
      },
      /**
       * The marker's latitude coordinate.
       */
      latitude: {
        type: Number,
        value: null,
        reflectToAttribute: true
      }
    },

    observers: [
      '_updatePosition(latitude, longitude)'
    ],

    detached: function() {
      if (this.marker) {
        this.marker.setMap(null);
      }
      if (this._contentObserver)
        this._contentObserver.disconnect();
    },

    attached: function() {
      // If element is added back to DOM, put it back on the map.
      if (this.marker) {
        this.marker.setMap(this.map);
      }
    },

    _updatePosition: function() {
      if (this.marker && this.latitude != null && this.longitude != null) {
        this.marker.setPosition({
          lat: parseFloat(this.latitude),
          lng: parseFloat(this.longitude)
        });
      }
    },

    _clickEventsChanged: function() {
      if (this.map) {
        if (this.clickEvents) {
          this._forwardEvent('click');
          this._forwardEvent('dblclick');
          this._forwardEvent('rightclick');
        } else {
          this._clearListener('click');
          this._clearListener('dblclick');
          this._clearListener('rightclick');
        }
      }
    },

    _mouseEventsChanged: function() {
      if (this.map) {
        if (this.mouseEvents) {
          this._forwardEvent('mousedown');
          this._forwardEvent('mousemove');
          this._forwardEvent('mouseout');
          this._forwardEvent('mouseover');
          this._forwardEvent('mouseup');
        } else {
          this._clearListener('mousedown');
          this._clearListener('mousemove');
          this._clearListener('mouseout');
          this._clearListener('mouseover');
          this._clearListener('mouseup');
        }
      }
    },

    _iconChanged: function() {
      if (this.marker) {
        this.marker.setIcon(this.icon);
      }
    },

    _zIndexChanged: function() {
      if (this.marker) {
        this.marker.setZIndex(this.zIndex);
      }
    },

    _mapChanged: function() {
      // Marker will be rebuilt, so disconnect existing one from old map and listeners.
      if (this.marker) {
        this.marker.setMap(null);
        google.maps.event.clearInstanceListeners(this.marker);
      }

      if (this.map && this.map instanceof google.maps.Map) {
        this._mapReady();
      }
    },

    _contentChanged: function() {
      if (this._contentObserver)
        this._contentObserver.disconnect();
      // Watch for future updates.
      this._contentObserver = new MutationObserver( this._contentChanged.bind(this));
      this._contentObserver.observe( this, {
        childList: true,
        subtree: true
      });

      var content = this.innerHTML.trim();
      if (content) {
        if (!this.info) {
          // Create a new infowindow
          this.info = new google.maps.InfoWindow();
          this.infoHandler_ = google.maps.event.addListener(this.marker, 'click', function() {
            this.info.open(this.map, this.marker);
          }.bind(this));
        }
        this.info.setContent(content);
      } else {
        if (this.info) {
          // Destroy the existing infowindow.  It doesn't make sense to have an empty one.
          google.maps.event.removeListener(this.infoHandler_);
          this.info = null;
        }
      }
    },

    _mapReady: function() {
      this._listeners = {};
      this.marker = new google.maps.Marker({
        map: this.map,
        position: {lat: this.latitude, lng: this.longitude},
        title: this.title,
        draggable: this.draggable,
        visible: !this.hidden,
        icon: this.icon,
        zIndex: this.zIndex
      });
      this._contentChanged();
      this._clickEventsChanged();
      this._contentChanged();
      this._mouseEventsChanged();
      setupDragHandler_.bind(this)();
    },

    _clearListener: function(name) {
      if (this._listeners[name]) {
        google.maps.event.removeListener(this._listeners[name]);
        this._listeners[name] = null;
      }
    },

    _forwardEvent: function(name) {
      this._listeners[name] = google.maps.event.addListener(this.marker, name, function(event) {
        this.fire('google-map-marker-' + name, event);
      }.bind(this));
    },

    attributeChanged: function(attrName, oldVal, newVal) {
      if (!this.marker) {
        return;
      }

      // Cannot use *Changed watchers for native properties.
      switch (attrName) {
        case 'hidden':
          this.marker.setVisible(!this.hidden);
          break;
        case 'draggable':
          this.marker.setDraggable(this.draggable);
          setupDragHandler_.bind(this)();
          break;
        case 'title':
          this.marker.setTitle(this.title);
          break;
      }
    }
  });

  function setupDragHandler_() {
    if (this.draggable) {
      this.dragHandler_ = google.maps.event.addListener(
          this.marker, 'dragend', onDragEnd_.bind(this));
    } else {
      google.maps.event.removeListener(this.dragHandler_);
      this.dragHandler_ = null;
    }
  }

  function onDragEnd_(e, details, sender) {
    this.latitude = e.latLng.lat();
    this.longitude = e.latLng.lng();
  }
})();
</script>