OLD | NEW |
(Empty) | |
| 1 <!-- Copyright (c) 2015 Google Inc. All rights reserved. --> |
| 2 |
| 3 <link rel="import" href="../polymer/polymer.html"> |
| 4 <link rel="import" href="../google-apis/google-maps-api.html"> |
| 5 <link rel="import" href="../iron-resizable-behavior/iron-resizable-behavior.html
"> |
| 6 <link rel="import" href="google-map-marker.html"> |
| 7 <!-- |
| 8 The `google-map` element renders a Google Map. |
| 9 |
| 10 <b>Example</b>: |
| 11 |
| 12 <style> |
| 13 google-map { |
| 14 height: 600px; |
| 15 } |
| 16 </style> |
| 17 <google-map latitude="37.77493" longitude="-122.41942"></google-map> |
| 18 |
| 19 <b>Example</b> - add markers to the map and ensure they're in view: |
| 20 |
| 21 <google-map latitude="37.77493" longitude="-122.41942" fit-to-markers> |
| 22 <google-map-marker latitude="37.779" longitude="-122.3892" |
| 23 draggable="true" title="Go Giants!"></google-map-marker> |
| 24 <google-map-marker latitude="37.777" longitude="-122.38911"></google-map-m
arker> |
| 25 </google-map> |
| 26 |
| 27 <b>Example</b>: |
| 28 |
| 29 <google-map disable-default-ui zoom="15"></google-map> |
| 30 <script> |
| 31 var map = document.querySelector('google-map'); |
| 32 map.latitude = 37.77493; |
| 33 map.longitude = -122.41942; |
| 34 map.addEventListener('google-map-ready', function(e) { |
| 35 alert('Map loaded!'); |
| 36 }); |
| 37 </script> |
| 38 |
| 39 <b>Example</b> - with Google directions, using data-binding inside another Polym
er element |
| 40 |
| 41 <google-map map="{{map}}"></google-map> |
| 42 <google-map-directions map="{{map}}" |
| 43 start-address="San Francisco" end-address="Mountain View"> |
| 44 </google-map-directions> |
| 45 |
| 46 @demo |
| 47 --> |
| 48 |
| 49 <dom-module id="google-map"> |
| 50 |
| 51 <style> |
| 52 :host { |
| 53 position: relative; |
| 54 display: block; |
| 55 height: 100%; |
| 56 } |
| 57 |
| 58 #map { |
| 59 position: absolute; |
| 60 top: 0; |
| 61 right: 0; |
| 62 bottom: 0; |
| 63 left: 0; |
| 64 } |
| 65 |
| 66 </style> |
| 67 <template> |
| 68 <google-maps-api |
| 69 api-key="[[apiKey]]" |
| 70 client-id="[[clientId]]" |
| 71 version="[[version]]" |
| 72 libraries="[[libraries]]" |
| 73 signed-in="[[signedIn]]" |
| 74 language="[[language]]" |
| 75 on-api-load="_mapApiLoaded"></google-maps-api> |
| 76 |
| 77 <div id="map"></div> |
| 78 |
| 79 <content id="markers" select="google-map-marker"></content> |
| 80 |
| 81 </template> |
| 82 </dom-module> |
| 83 |
| 84 <script> |
| 85 |
| 86 Polymer({ |
| 87 |
| 88 is: 'google-map', |
| 89 |
| 90 |
| 91 /** |
| 92 * Fired when the Maps API has fully loaded. |
| 93 * @event google-map-ready |
| 94 */ |
| 95 /** |
| 96 * Fired when the when the user clicks on the map (but not when they click o
n a marker, infowindow, or |
| 97 * other object). Requires the clickEvents attribute to be true. |
| 98 * @event google-map-click |
| 99 * @param {google.maps.MouseEvent} event The mouse event. |
| 100 */ |
| 101 /** |
| 102 * Fired when the user double-clicks on the map. Note that the google-map-cl
ick event will also fire, |
| 103 * right before this one. Requires the clickEvents attribute to be true. |
| 104 * @event google-map-dblclick |
| 105 * @param {google.maps.MouseEvent} event The mouse event. |
| 106 */ |
| 107 /** |
| 108 * Fired repeatedly while the user drags the map. Requires the dragEvents at
tribute to be true. |
| 109 * @event google-map-drag |
| 110 */ |
| 111 /** |
| 112 * Fired when the user stops dragging the map. Requires the dragEvents attri
bute to be true. |
| 113 * @event google-map-dragend |
| 114 */ |
| 115 /** |
| 116 * Fired when the user starts dragging the map. Requires the dragEvents attr
ibute to be true. |
| 117 * @event google-map-dragstart |
| 118 */ |
| 119 /** |
| 120 * Fired whenever the user's mouse moves over the map container. Requires th
e mouseEvents attribute to |
| 121 * be true. |
| 122 * @event google-map-mousemove |
| 123 * @param {google.maps.MouseEvent} event The mouse event. |
| 124 */ |
| 125 /** |
| 126 * Fired when the user's mouse exits the map container. Requires the mouseEv
ents attribute to be true. |
| 127 * @event google-map-mouseout |
| 128 * @param {google.maps.MouseEvent} event The mouse event. |
| 129 */ |
| 130 /** |
| 131 * Fired when the user's mouse enters the map container. Requires the mouseE
vents attribute to be true. |
| 132 * @event google-map-mouseover |
| 133 * @param {google.maps.MouseEvent} event The mouse event. |
| 134 */ |
| 135 /** |
| 136 * Fired when the DOM `contextmenu` event is fired on the map container. Req
uires the clickEvents |
| 137 * attribute to be true. |
| 138 * @event google-map-rightclick |
| 139 * @param {google.maps.MouseEvent} event The mouse event. |
| 140 */ |
| 141 properties: { |
| 142 /** |
| 143 * A Maps API key. To obtain an API key, see developers.google.com/maps/do
cumentation/javascript/tutorial#api_key. |
| 144 */ |
| 145 apiKey: String, |
| 146 |
| 147 /** |
| 148 * A Maps API for Business Client ID. To obtain a Maps API for Business Cl
ient ID, see developers.google.com/maps/documentation/business/. |
| 149 * If set, a Client ID will take precedence over an API Key. |
| 150 */ |
| 151 clientId: String, |
| 152 |
| 153 /** |
| 154 * A latitude to center the map on. |
| 155 */ |
| 156 latitude: { |
| 157 type: Number, |
| 158 value: 37.77493, |
| 159 notify: true, |
| 160 reflectToAttribute: true |
| 161 }, |
| 162 |
| 163 /** |
| 164 * A Maps API object. |
| 165 */ |
| 166 map: { |
| 167 type: Object, |
| 168 notify: true, |
| 169 value: null |
| 170 }, |
| 171 |
| 172 /** |
| 173 * A comma separated list (e.g. "places,geometry") of libraries to load |
| 174 * with this map. Defaults to "places". For more information see |
| 175 * https://developers.google.com/maps/documentation/javascript/libraries. |
| 176 */ |
| 177 libraries: { |
| 178 type: String, |
| 179 value: 'places' |
| 180 }, |
| 181 |
| 182 /** |
| 183 * A longitude to center the map on. |
| 184 */ |
| 185 longitude: { |
| 186 type: Number, |
| 187 value: -122.41942, |
| 188 notify: true, |
| 189 reflectToAttribute: true |
| 190 }, |
| 191 |
| 192 /** |
| 193 * A zoom level to set the map to. |
| 194 */ |
| 195 zoom: { |
| 196 type: Number, |
| 197 value: 10, |
| 198 observer: '_zoomChanged' |
| 199 }, |
| 200 |
| 201 /** |
| 202 * When set, prevents the map from tilting (when the zoom level and viewpo
rt supports it). |
| 203 */ |
| 204 noAutoTilt: { |
| 205 type: Boolean, |
| 206 value: false |
| 207 }, |
| 208 |
| 209 /** |
| 210 * Map type to display. One of 'roadmap', 'satellite', 'hybrid', 'terrain'
. |
| 211 */ |
| 212 mapType: { |
| 213 type: String, |
| 214 value: 'roadmap', // roadmap, satellite, hybrid, terrain, |
| 215 observer: '_mapTypeChanged' |
| 216 }, |
| 217 |
| 218 /** |
| 219 * Version of the Google Maps API to use. |
| 220 */ |
| 221 version: { |
| 222 type: String, |
| 223 value: '3.exp' |
| 224 }, |
| 225 |
| 226 /** |
| 227 * If set, removes the map's default UI controls. |
| 228 */ |
| 229 disableDefaultUi: { |
| 230 type: Boolean, |
| 231 value: false, |
| 232 observer: '_disableDefaultUiChanged' |
| 233 }, |
| 234 |
| 235 /** |
| 236 * If set, the zoom level is set such that all markers (google-map-marker
children) are brought into view. |
| 237 */ |
| 238 fitToMarkers: { |
| 239 type: Boolean, |
| 240 value: false, |
| 241 observer: '_fitToMarkersChanged' |
| 242 }, |
| 243 |
| 244 /** |
| 245 * If true, prevent the user from zooming the map interactively. |
| 246 */ |
| 247 disableZoom: { |
| 248 type: Boolean, |
| 249 value: false, |
| 250 observer: '_disableZoomChanged' |
| 251 }, |
| 252 |
| 253 /** |
| 254 * If set, custom styles can be applied to the map. |
| 255 * For style documentation see developers.google.com/maps/documentation/ja
vascript/reference#MapTypeStyle |
| 256 */ |
| 257 styles: { |
| 258 type: Object, |
| 259 value: function() { return {}; } |
| 260 }, |
| 261 |
| 262 /** |
| 263 * A maximum zoom level which will be displayed on the map. |
| 264 */ |
| 265 maxZoom: { |
| 266 type: Number, |
| 267 observer: '_maxZoomChanged' |
| 268 }, |
| 269 |
| 270 /** |
| 271 * A minimum zoom level which will be displayed on the map. |
| 272 */ |
| 273 minZoom: { |
| 274 type: Number, |
| 275 observer: '_minZoomChanged' |
| 276 }, |
| 277 |
| 278 /** |
| 279 * If true, sign-in is enabled. |
| 280 * See https://developers.google.com/maps/documentation/javascript/signedi
n#enable_sign_in |
| 281 */ |
| 282 signedIn: { |
| 283 type: Boolean, |
| 284 value: false |
| 285 }, |
| 286 |
| 287 /** |
| 288 * The localized language to load the Maps API with. For more information |
| 289 * see https://developers.google.com/maps/documentation/javascript/basics#
Language |
| 290 * |
| 291 * Note: the Maps API defaults to the preffered language setting of the br
owser. |
| 292 * Use this parameter to override that behavior. |
| 293 */ |
| 294 language: { |
| 295 type: String |
| 296 }, |
| 297 |
| 298 /** |
| 299 * When true, map *click events are automatically registered. |
| 300 */ |
| 301 clickEvents: { |
| 302 type: Boolean, |
| 303 value: false, |
| 304 observer: '_clickEventsChanged' |
| 305 }, |
| 306 |
| 307 /** |
| 308 * When true, map drag* events are automatically registered. |
| 309 */ |
| 310 dragEvents: { |
| 311 type: Boolean, |
| 312 value: false, |
| 313 observer: '_dragEventsChanged' |
| 314 }, |
| 315 |
| 316 /** |
| 317 * When true, map mouse* events are automatically registered. |
| 318 */ |
| 319 mouseEvents: { |
| 320 type: Boolean, |
| 321 value: false, |
| 322 observer: '_mouseEventsChanged' |
| 323 }, |
| 324 |
| 325 /** |
| 326 * Additional map options for google.maps.Map constructor. |
| 327 * Use to specify additional options we do not expose as |
| 328 * properties. |
| 329 * Ex: `<google-map additional-map-options='{"mapTypeId":"satellite"}'>` |
| 330 */ |
| 331 additionalMapOptions: { |
| 332 type: Object, |
| 333 value: function() { return {}; } |
| 334 } |
| 335 |
| 336 }, |
| 337 |
| 338 behaviors: [ |
| 339 Polymer.IronResizableBehavior |
| 340 ], |
| 341 |
| 342 listeners: { |
| 343 'iron-resize': 'resize' |
| 344 }, |
| 345 |
| 346 observers: [ |
| 347 '_debounceUpdateCenter(latitude, longitude)' |
| 348 ], |
| 349 |
| 350 created: function() { |
| 351 this.markers = []; |
| 352 }, |
| 353 |
| 354 attached: function() { |
| 355 this._initGMap(); |
| 356 }, |
| 357 |
| 358 detached: function() { |
| 359 if (this._mutationObserver) { |
| 360 this._mutationObserver.disconnect(); |
| 361 this._mutationObserver = null; |
| 362 } |
| 363 }, |
| 364 |
| 365 _initGMap: function() { |
| 366 if (this.map) { |
| 367 return; // already initialized |
| 368 } |
| 369 if (!(window.google && window.google.maps)) { |
| 370 return; // api not loaded |
| 371 } |
| 372 if (!this.isAttached) { |
| 373 return; // not attached |
| 374 } |
| 375 |
| 376 this.map = new google.maps.Map(this.$.map, this._getMapOptions()); |
| 377 this._listeners = {}; |
| 378 this._updateCenter(); |
| 379 this._updateMarkers(); |
| 380 this._addMapListeners(); |
| 381 this.fire('google-map-ready'); |
| 382 }, |
| 383 |
| 384 _mapApiLoaded: function() { |
| 385 this._initGMap(); |
| 386 }, |
| 387 |
| 388 _getMapOptions: function() { |
| 389 var mapOptions = { |
| 390 zoom: this.zoom, |
| 391 tilt: this.noAutoTilt ? 0 : 45, |
| 392 mapTypeId: this.mapType, |
| 393 disableDefaultUI: this.disableDefaultUi, |
| 394 disableDoubleClickZoom: this.disableZoom, |
| 395 scrollwheel: !this.disableZoom, |
| 396 styles: this.styles, |
| 397 maxZoom: Number(this.maxZoom), |
| 398 minZoom: Number(this.minZoom) |
| 399 }; |
| 400 |
| 401 // Only override the default if set. |
| 402 // We use getAttribute here because the default value of this.draggable =
false even when not set. |
| 403 if (this.getAttribute('draggable') != null) { |
| 404 mapOptions.draggable = this.draggable |
| 405 } |
| 406 for (var p in this.additionalMapOptions) |
| 407 mapOptions[p] = this.additionalMapOptions[p]; |
| 408 |
| 409 return mapOptions; |
| 410 }, |
| 411 |
| 412 // watch for future updates |
| 413 _observeMarkers: function() { |
| 414 // Watch for future updates. |
| 415 if (this._mutationObserver) { |
| 416 return; |
| 417 } |
| 418 this._mutationObserver = new MutationObserver( this._updateMarkers.bind(th
is)); |
| 419 this._mutationObserver.observe(this, { |
| 420 childList: true |
| 421 }); |
| 422 }, |
| 423 |
| 424 _updateMarkers: function() { |
| 425 var newMarkers = Array.prototype.slice.call( |
| 426 Polymer.dom(this.$.markers).getDistributedNodes()); |
| 427 |
| 428 // do not recompute if markers have not been added or removed |
| 429 if (newMarkers.length === this.markers.length) { |
| 430 var added = newMarkers.filter(function(m) { |
| 431 return this.markers && this.markers.indexOf(m) === -1; |
| 432 }.bind(this)); |
| 433 if (added.length === 0) { |
| 434 // set up observer first time around |
| 435 if (!this._mutationObserver) { |
| 436 this._observeMarkers(); |
| 437 } |
| 438 return; |
| 439 } |
| 440 } |
| 441 |
| 442 this._observeMarkers(); |
| 443 |
| 444 this.markers = newMarkers; |
| 445 |
| 446 // Set the map on each marker and zoom viewport to ensure they're in view. |
| 447 if (this.markers.length && this.map) { |
| 448 for (var i = 0, m; m = this.markers[i]; ++i) { |
| 449 m.map = this.map; |
| 450 } |
| 451 } |
| 452 if (this.fitToMarkers) { |
| 453 this._fitToMarkersChanged(); |
| 454 } |
| 455 }, |
| 456 |
| 457 /** |
| 458 * Clears all markers from the map. |
| 459 * |
| 460 * @method clear |
| 461 */ |
| 462 clear: function() { |
| 463 for (var i = 0, m; m = this.markers[i]; ++i) { |
| 464 m.marker.setMap(null); |
| 465 } |
| 466 }, |
| 467 |
| 468 /** |
| 469 * Explicitly resizes the map, updating its center. This is useful if the |
| 470 * map does not show after you have unhidden it. |
| 471 * |
| 472 * @method resize |
| 473 */ |
| 474 resize: function() { |
| 475 if (this.map) { |
| 476 |
| 477 // saves and restores latitude/longitude because resize can move the cen
ter |
| 478 var oldLatitude = this.latitude; |
| 479 var oldLongitude = this.longitude; |
| 480 google.maps.event.trigger(this.map, 'resize'); |
| 481 this.latitude = oldLatitude; // restore because resize can move our cen
ter |
| 482 this.longitude = oldLongitude; |
| 483 |
| 484 if (this.fitToMarkers) { // we might not have a center if we are doing f
it-to-markers |
| 485 this._fitToMarkersChanged(); |
| 486 } |
| 487 } |
| 488 }, |
| 489 |
| 490 _debounceUpdateCenter: function() { |
| 491 this.debounce('updateCenter', this._updateCenter); |
| 492 }, |
| 493 |
| 494 _updateCenter: function() { |
| 495 this.cancelDebouncer('updateCenter'); |
| 496 |
| 497 if (this.map && this.latitude !== undefined && this.longitude !== undefine
d) { |
| 498 // allow for latitude and longitude to be String-typed, but still Number
valued |
| 499 var lati = Number(this.latitude); |
| 500 if (isNaN(lati)) { |
| 501 throw new TypeError('latitude must be a number'); |
| 502 } |
| 503 var longi = Number(this.longitude); |
| 504 if (isNaN(longi)) { |
| 505 throw new TypeError('longitude must be a number'); |
| 506 } |
| 507 |
| 508 var newCenter = new google.maps.LatLng(lati, longi); |
| 509 var oldCenter = this.map.getCenter(); |
| 510 |
| 511 if (!oldCenter) { |
| 512 // If the map does not have a center, set it right away. |
| 513 this.map.setCenter(newCenter); |
| 514 } else { |
| 515 // Using google.maps.LatLng returns corrected lat/lngs. |
| 516 oldCenter = new google.maps.LatLng(oldCenter.lat(), oldCenter.lng()); |
| 517 |
| 518 // If the map currently has a center, slowly pan to the new one. |
| 519 if (!oldCenter.equals(newCenter)) { |
| 520 this.map.panTo(newCenter); |
| 521 } |
| 522 } |
| 523 } |
| 524 }, |
| 525 |
| 526 _zoomChanged: function() { |
| 527 if (this.map) { |
| 528 this.map.setZoom(Number(this.zoom)); |
| 529 } |
| 530 }, |
| 531 |
| 532 _clickEventsChanged: function() { |
| 533 if (this.map) { |
| 534 if (this.clickEvents) { |
| 535 this._forwardEvent('click'); |
| 536 this._forwardEvent('dblclick'); |
| 537 this._forwardEvent('rightclick'); |
| 538 } else { |
| 539 this._clearListener('click'); |
| 540 this._clearListener('dblclick'); |
| 541 this._clearListener('rightclick'); |
| 542 } |
| 543 } |
| 544 }, |
| 545 |
| 546 _dragEventsChanged: function() { |
| 547 if (this.map) { |
| 548 if (this.dragEvents) { |
| 549 this._forwardEvent('drag'); |
| 550 this._forwardEvent('dragend'); |
| 551 this._forwardEvent('dragstart'); |
| 552 } else { |
| 553 this._clearListener('drag'); |
| 554 this._clearListener('dragend'); |
| 555 this._clearListener('dragstart'); |
| 556 } |
| 557 } |
| 558 }, |
| 559 |
| 560 _mouseEventsChanged: function() { |
| 561 if (this.map) { |
| 562 if (this.mouseEvents) { |
| 563 this._forwardEvent('mousemove'); |
| 564 this._forwardEvent('mouseout'); |
| 565 this._forwardEvent('mouseover'); |
| 566 } else { |
| 567 this._clearListener('mousemove'); |
| 568 this._clearListener('mouseout'); |
| 569 this._clearListener('mouseover'); |
| 570 } |
| 571 } |
| 572 }, |
| 573 |
| 574 _maxZoomChanged: function() { |
| 575 if (this.map) { |
| 576 this.map.setOptions({maxZoom: Number(this.maxZoom)}); |
| 577 } |
| 578 }, |
| 579 |
| 580 _minZoomChanged: function() { |
| 581 if (this.map) { |
| 582 this.map.setOptions({minZoom: Number(this.minZoom)}); |
| 583 } |
| 584 }, |
| 585 |
| 586 _mapTypeChanged: function() { |
| 587 if (this.map) { |
| 588 this.map.setMapTypeId(this.mapType); |
| 589 } |
| 590 }, |
| 591 |
| 592 _disableDefaultUiChanged: function() { |
| 593 if (!this.map) { |
| 594 return; |
| 595 } |
| 596 this.map.setOptions({disableDefaultUI: this.disableDefaultUi}); |
| 597 }, |
| 598 |
| 599 _disableZoomChanged: function() { |
| 600 if (!this.map) { |
| 601 return; |
| 602 } |
| 603 this.map.setOptions({ |
| 604 disableDoubleClickZoom: this.disableZoom, |
| 605 scrollwheel: !this.disableZoom |
| 606 }); |
| 607 }, |
| 608 |
| 609 attributeChanged: function(attrName, oldVal, newVal) { |
| 610 if (!this.map) { |
| 611 return; |
| 612 } |
| 613 // Cannot use *Changed watchers for native properties. |
| 614 switch (attrName) { |
| 615 case 'draggable': |
| 616 this.map.setOptions({draggable: this.draggable}); |
| 617 break; |
| 618 } |
| 619 }, |
| 620 |
| 621 _fitToMarkersChanged: function() { |
| 622 // TODO(ericbidelman): respect user's zoom level. |
| 623 |
| 624 if (this.map && this.fitToMarkers) { |
| 625 var latLngBounds = new google.maps.LatLngBounds(); |
| 626 for (var i = 0, m; m = this.markers[i]; ++i) { |
| 627 latLngBounds.extend( |
| 628 new google.maps.LatLng(m.latitude, m.longitude)); |
| 629 } |
| 630 |
| 631 // For one marker, don't alter zoom, just center it. |
| 632 if (this.markers.length > 1) { |
| 633 this.map.fitBounds(latLngBounds); |
| 634 } |
| 635 |
| 636 this.map.setCenter(latLngBounds.getCenter()); |
| 637 } |
| 638 }, |
| 639 |
| 640 _addMapListeners: function() { |
| 641 google.maps.event.addListener(this.map, 'center_changed', function() { |
| 642 var center = this.map.getCenter(); |
| 643 this.latitude = center.lat(); |
| 644 this.longitude = center.lng(); |
| 645 }.bind(this)); |
| 646 |
| 647 google.maps.event.addListener(this.map, 'zoom_changed', function() { |
| 648 this.zoom = this.map.getZoom(); |
| 649 }.bind(this)); |
| 650 |
| 651 this._clickEventsChanged(); |
| 652 this._dragEventsChanged(); |
| 653 this._mouseEventsChanged(); |
| 654 }, |
| 655 |
| 656 _clearListener: function(name) { |
| 657 if (this._listeners[name]) { |
| 658 google.maps.event.removeListener(this._listeners[name]); |
| 659 this._listeners[name] = null; |
| 660 } |
| 661 }, |
| 662 |
| 663 _forwardEvent: function(name) { |
| 664 this._listeners[name] = google.maps.event.addListener(this.map, name, func
tion(event) { |
| 665 this.fire('google-map-' + name, event); |
| 666 }.bind(this)); |
| 667 } |
| 668 |
| 669 }); |
| 670 |
| 671 </script> |
OLD | NEW |