// ==ClosureCompiler== // @compilation_level ADVANCED_OPTIMIZATIONS // @externs_url http://closure-compiler.googlecode.com/svn/trunk/contrib/externs/maps/google_maps_api_v3.js // @output_wrapper (function() {%output%})(); // ==/ClosureCompiler== /** * @license * Copyright 2013 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * A RichMarker that allows any HTML/DOM to be added to a map and be draggable. * * @param {Object.=} opt_options Optional properties to set. * @extends {google.maps.OverlayView} * @constructor */ function RichMarker(opt_options) { var options = opt_options || {}; /** * @type {boolean} * @private */ this.ready_ = false; /** * @type {boolean} * @private */ this.dragging_ = false; if (opt_options['visible'] == undefined) { opt_options['visible'] = true; } if (opt_options['shadow'] == undefined) { opt_options['shadow'] = '7px -3px 5px rgba(88,88,88,0.7)'; } if (opt_options['anchor'] == undefined) { opt_options['anchor'] = RichMarkerPosition['BOTTOM']; } this.setValues(options); } RichMarker.prototype = new google.maps.OverlayView(); window['RichMarker'] = RichMarker; /** * Returns the current visibility state of the marker. * * @return {boolean} The visiblity of the marker. */ RichMarker.prototype.getVisible = function() { return /** @type {boolean} */ (this.get('visible')); }; RichMarker.prototype['getVisible'] = RichMarker.prototype.getVisible; /** * Sets the visiblility state of the marker. * * @param {boolean} visible The visiblilty of the marker. */ RichMarker.prototype.setVisible = function(visible) { this.set('visible', visible); }; RichMarker.prototype['setVisible'] = RichMarker.prototype.setVisible; /** * The visible changed event. */ RichMarker.prototype.visible_changed = function() { if (this.ready_) { this.markerWrapper_.style['display'] = this.getVisible() ? '' : 'none'; this.draw(); } }; RichMarker.prototype['visible_changed'] = RichMarker.prototype.visible_changed; /** * Sets the marker to be flat. * * @param {boolean} flat If the marker is to be flat or not. */ RichMarker.prototype.setFlat = function(flat) { this.set('flat', !!flat); }; RichMarker.prototype['setFlat'] = RichMarker.prototype.setFlat; /** * If the makrer is flat or not. * * @return {boolean} True the marker is flat. */ RichMarker.prototype.getFlat = function() { return /** @type {boolean} */ (this.get('flat')); }; RichMarker.prototype['getFlat'] = RichMarker.prototype.getFlat; /** * Get the width of the marker. * * @return {Number} The width of the marker. */ RichMarker.prototype.getWidth = function() { return /** @type {Number} */ (this.get('width')); }; RichMarker.prototype['getWidth'] = RichMarker.prototype.getWidth; /** * Get the height of the marker. * * @return {Number} The height of the marker. */ RichMarker.prototype.getHeight = function() { return /** @type {Number} */ (this.get('height')); }; RichMarker.prototype['getHeight'] = RichMarker.prototype.getHeight; /** * Sets the marker's box shadow. * * @param {string} shadow The box shadow to set. */ RichMarker.prototype.setShadow = function(shadow) { this.set('shadow', shadow); this.flat_changed(); }; RichMarker.prototype['setShadow'] = RichMarker.prototype.setShadow; /** * Gets the marker's box shadow. * * @return {string} The box shadow. */ RichMarker.prototype.getShadow = function() { return /** @type {string} */ (this.get('shadow')); }; RichMarker.prototype['getShadow'] = RichMarker.prototype.getShadow; /** * Flat changed event. */ RichMarker.prototype.flat_changed = function() { if (!this.ready_) { return; } this.markerWrapper_.style['boxShadow'] = this.markerWrapper_.style['webkitBoxShadow'] = this.markerWrapper_.style['MozBoxShadow'] = this.getFlat() ? '' : this.getShadow(); }; RichMarker.prototype['flat_changed'] = RichMarker.prototype.flat_changed; /** * Sets the zIndex of the marker. * * @param {Number} index The index to set. */ RichMarker.prototype.setZIndex = function(index) { this.set('zIndex', index); }; RichMarker.prototype['setZIndex'] = RichMarker.prototype.setZIndex; /** * Gets the zIndex of the marker. * * @return {Number} The zIndex of the marker. */ RichMarker.prototype.getZIndex = function() { return /** @type {Number} */ (this.get('zIndex')); }; RichMarker.prototype['getZIndex'] = RichMarker.prototype.getZIndex; /** * zIndex changed event. */ RichMarker.prototype.zIndex_changed = function() { if (this.getZIndex() && this.ready_) { this.markerWrapper_.style.zIndex = this.getZIndex(); } }; RichMarker.prototype['zIndex_changed'] = RichMarker.prototype.zIndex_changed; /** * Whether the marker is draggable or not. * * @return {boolean} True if the marker is draggable. */ RichMarker.prototype.getDraggable = function() { return /** @type {boolean} */ (this.get('draggable')); }; RichMarker.prototype['getDraggable'] = RichMarker.prototype.getDraggable; /** * Sets the marker to be draggable or not. * * @param {boolean} draggable If the marker is draggable or not. */ RichMarker.prototype.setDraggable = function(draggable) { this.set('draggable', !!draggable); }; RichMarker.prototype['setDraggable'] = RichMarker.prototype.setDraggable; /** * Draggable property changed callback. */ RichMarker.prototype.draggable_changed = function() { if (this.ready_) { if (this.getDraggable()) { this.addDragging_(this.markerWrapper_); } else { this.removeDragListeners_(); } } }; RichMarker.prototype['draggable_changed'] = RichMarker.prototype.draggable_changed; /** * Gets the postiton of the marker. * * @return {google.maps.LatLng} The position of the marker. */ RichMarker.prototype.getPosition = function() { return /** @type {google.maps.LatLng} */ (this.get('position')); }; RichMarker.prototype['getPosition'] = RichMarker.prototype.getPosition; /** * Sets the position of the marker. * * @param {google.maps.LatLng} position The position to set. */ RichMarker.prototype.setPosition = function(position) { this.set('position', position); }; RichMarker.prototype['setPosition'] = RichMarker.prototype.setPosition; /** * Position changed event. */ RichMarker.prototype.position_changed = function() { this.draw(); }; RichMarker.prototype['position_changed'] = RichMarker.prototype.position_changed; /** * Gets the anchor. * * @return {google.maps.Size} The position of the anchor. */ RichMarker.prototype.getAnchor = function() { return /** @type {google.maps.Size} */ (this.get('anchor')); }; RichMarker.prototype['getAnchor'] = RichMarker.prototype.getAnchor; /** * Sets the anchor. * * @param {RichMarkerPosition|google.maps.Size} anchor The anchor to set. */ RichMarker.prototype.setAnchor = function(anchor) { this.set('anchor', anchor); }; RichMarker.prototype['setAnchor'] = RichMarker.prototype.setAnchor; /** * Anchor changed event. */ RichMarker.prototype.anchor_changed = function() { this.draw(); }; RichMarker.prototype['anchor_changed'] = RichMarker.prototype.anchor_changed; /** * Converts a HTML string to a document fragment. * * @param {string} htmlString The HTML string to convert. * @return {Node} A HTML document fragment. * @private */ RichMarker.prototype.htmlToDocumentFragment_ = function(htmlString) { var tempDiv = document.createElement('DIV'); tempDiv.innerHTML = htmlString; if (tempDiv.childNodes.length == 1) { return /** @type {!Node} */ (tempDiv.removeChild(tempDiv.firstChild)); } else { var fragment = document.createDocumentFragment(); while (tempDiv.firstChild) { fragment.appendChild(tempDiv.firstChild); } return fragment; } }; /** * Removes all children from the node. * * @param {Node} node The node to remove all children from. * @private */ RichMarker.prototype.removeChildren_ = function(node) { if (!node) { return; } var child; while (child = node.firstChild) { node.removeChild(child); } }; /** * Sets the content of the marker. * * @param {string|Node} content The content to set. */ RichMarker.prototype.setContent = function(content) { this.set('content', content); }; RichMarker.prototype['setContent'] = RichMarker.prototype.setContent; /** * Get the content of the marker. * * @return {string|Node} The marker content. */ RichMarker.prototype.getContent = function() { return /** @type {Node|string} */ (this.get('content')); }; RichMarker.prototype['getContent'] = RichMarker.prototype.getContent; /** * Sets the marker content and adds loading events to images */ RichMarker.prototype.content_changed = function() { if (!this.markerContent_) { // Marker content area doesnt exist. return; } this.removeChildren_(this.markerContent_); var content = this.getContent(); if (content) { if (typeof content == 'string') { content = content.replace(/^\s*([\S\s]*)\b\s*$/, '$1'); content = this.htmlToDocumentFragment_(content); } this.markerContent_.appendChild(content); var that = this; var images = this.markerContent_.getElementsByTagName('IMG'); for (var i = 0, image; image = images[i]; i++) { // By default, a browser lets a image be dragged outside of the browser, // so by calling preventDefault we stop this behaviour and allow the image // to be dragged around the map and now out of the browser and onto the // desktop. google.maps.event.addDomListener(image, 'mousedown', function(e) { if (that.getDraggable()) { if (e.preventDefault) { e.preventDefault(); } e.returnValue = false; } }); // Because we don't know the size of an image till it loads, add a // listener to the image load so the marker can resize and reposition // itself to be the correct height. google.maps.event.addDomListener(image, 'load', function() { that.draw(); }); } google.maps.event.trigger(this, 'domready'); } if (this.ready_) { this.draw(); } }; RichMarker.prototype['content_changed'] = RichMarker.prototype.content_changed; /** * Sets the cursor. * * @param {string} whichCursor What cursor to show. * @private */ RichMarker.prototype.setCursor_ = function(whichCursor) { if (!this.ready_) { return; } var cursor = ''; if (navigator.userAgent.indexOf('Gecko/') !== -1) { // Moz has some nice cursors :) if (whichCursor == 'dragging') { cursor = '-moz-grabbing'; } if (whichCursor == 'dragready') { cursor = '-moz-grab'; } if (whichCursor == 'draggable') { cursor = 'pointer'; } } else { if (whichCursor == 'dragging' || whichCursor == 'dragready') { cursor = 'move'; } if (whichCursor == 'draggable') { cursor = 'pointer'; } } if (this.markerWrapper_.style.cursor != cursor) { this.markerWrapper_.style.cursor = cursor; } }; /** * Start dragging. * * @param {Event} e The event. */ RichMarker.prototype.startDrag = function(e) { if (!this.getDraggable()) { return; } if (!this.dragging_) { this.dragging_ = true; var map = this.getMap(); this.mapDraggable_ = map.get('draggable'); map.set('draggable', false); // Store the current mouse position this.mouseX_ = e.clientX; this.mouseY_ = e.clientY; this.setCursor_('dragready'); // Stop the text from being selectable while being dragged this.markerWrapper_.style['MozUserSelect'] = 'none'; this.markerWrapper_.style['KhtmlUserSelect'] = 'none'; this.markerWrapper_.style['WebkitUserSelect'] = 'none'; this.markerWrapper_['unselectable'] = 'on'; this.markerWrapper_['onselectstart'] = function() { return false; }; this.addDraggingListeners_(); google.maps.event.trigger(this, 'dragstart'); } }; /** * Stop dragging. */ RichMarker.prototype.stopDrag = function() { if (!this.getDraggable()) { return; } if (this.dragging_) { this.dragging_ = false; this.getMap().set('draggable', this.mapDraggable_); this.mouseX_ = this.mouseY_ = this.mapDraggable_ = null; // Allow the text to be selectable again this.markerWrapper_.style['MozUserSelect'] = ''; this.markerWrapper_.style['KhtmlUserSelect'] = ''; this.markerWrapper_.style['WebkitUserSelect'] = ''; this.markerWrapper_['unselectable'] = 'off'; this.markerWrapper_['onselectstart'] = function() {}; this.removeDraggingListeners_(); this.setCursor_('draggable'); google.maps.event.trigger(this, 'dragend'); this.draw(); } }; /** * Handles the drag event. * * @param {Event} e The event. */ RichMarker.prototype.drag = function(e) { if (!this.getDraggable() || !this.dragging_) { // This object isn't draggable or we have stopped dragging this.stopDrag(); return; } var dx = this.mouseX_ - e.clientX; var dy = this.mouseY_ - e.clientY; this.mouseX_ = e.clientX; this.mouseY_ = e.clientY; var left = parseInt(this.markerWrapper_.style['left'], 10) - dx; var top = parseInt(this.markerWrapper_.style['top'], 10) - dy; this.markerWrapper_.style['left'] = left + 'px'; this.markerWrapper_.style['top'] = top + 'px'; var offset = this.getOffset_(); // Set the position property and adjust for the anchor offset var point = new google.maps.Point(left - offset.width, top - offset.height); var projection = this.getProjection(); this.setPosition(projection.fromDivPixelToLatLng(point)); this.setCursor_('dragging'); google.maps.event.trigger(this, 'drag'); }; /** * Removes the drag listeners associated with the marker. * * @private */ RichMarker.prototype.removeDragListeners_ = function() { if (this.draggableListener_) { google.maps.event.removeListener(this.draggableListener_); delete this.draggableListener_; } this.setCursor_(''); }; /** * Add dragability events to the marker. * * @param {Node} node The node to apply dragging to. * @private */ RichMarker.prototype.addDragging_ = function(node) { if (!node) { return; } var that = this; this.draggableListener_ = google.maps.event.addDomListener(node, 'mousedown', function(e) { that.startDrag(e); }); this.setCursor_('draggable'); }; /** * Add dragging listeners. * * @private */ RichMarker.prototype.addDraggingListeners_ = function() { var that = this; if (this.markerWrapper_.setCapture) { this.markerWrapper_.setCapture(true); this.draggingListeners_ = [ google.maps.event.addDomListener(this.markerWrapper_, 'mousemove', function(e) { that.drag(e); }, true), google.maps.event.addDomListener(this.markerWrapper_, 'mouseup', function() { that.stopDrag(); that.markerWrapper_.releaseCapture(); }, true) ]; } else { this.draggingListeners_ = [ google.maps.event.addDomListener(window, 'mousemove', function(e) { that.drag(e); }, true), google.maps.event.addDomListener(window, 'mouseup', function() { that.stopDrag(); }, true) ]; } }; /** * Remove dragging listeners. * * @private */ RichMarker.prototype.removeDraggingListeners_ = function() { if (this.draggingListeners_) { for (var i = 0, listener; listener = this.draggingListeners_[i]; i++) { google.maps.event.removeListener(listener); } this.draggingListeners_.length = 0; } }; /** * Get the anchor offset. * * @return {google.maps.Size} The size offset. * @private */ RichMarker.prototype.getOffset_ = function() { var anchor = this.getAnchor(); if (typeof anchor == 'object') { return /** @type {google.maps.Size} */ (anchor); } var offset = new google.maps.Size(0, 0); if (!this.markerContent_) { return offset; } var width = this.markerContent_.offsetWidth; var height = this.markerContent_.offsetHeight; switch (anchor) { case RichMarkerPosition['TOP_LEFT']: break; case RichMarkerPosition['TOP']: offset.width = -width / 2; break; case RichMarkerPosition['TOP_RIGHT']: offset.width = -width; break; case RichMarkerPosition['LEFT']: offset.height = -height / 2; break; case RichMarkerPosition['MIDDLE']: offset.width = -width / 2; offset.height = -height / 2; break; case RichMarkerPosition['RIGHT']: offset.width = -width; offset.height = -height / 2; break; case RichMarkerPosition['BOTTOM_LEFT']: offset.height = -height; break; case RichMarkerPosition['BOTTOM']: offset.width = -width / 2; offset.height = -height; break; case RichMarkerPosition['BOTTOM_RIGHT']: offset.width = -width; offset.height = -height; break; } return offset; }; /** * Adding the marker to a map. * Implementing the interface. */ RichMarker.prototype.onAdd = function() { if (!this.markerWrapper_) { this.markerWrapper_ = document.createElement('DIV'); this.markerWrapper_.style['position'] = 'absolute'; } if (this.getZIndex()) { this.markerWrapper_.style['zIndex'] = this.getZIndex(); } this.markerWrapper_.style['display'] = this.getVisible() ? '' : 'none'; if (!this.markerContent_) { this.markerContent_ = document.createElement('DIV'); this.markerWrapper_.appendChild(this.markerContent_); var that = this; google.maps.event.addDomListener(this.markerContent_, 'click', function(e) { google.maps.event.trigger(that, 'click', e); }); google.maps.event.addDomListener(this.markerContent_, 'mouseover', function(e) { google.maps.event.trigger(that, 'mouseover', e); }); google.maps.event.addDomListener(this.markerContent_, 'mouseout', function(e) { google.maps.event.trigger(that, 'mouseout', e); }); } this.ready_ = true; this.content_changed(); this.flat_changed(); this.draggable_changed(); var panes = this.getPanes(); if (panes) { panes.overlayMouseTarget.appendChild(this.markerWrapper_); } google.maps.event.trigger(this, 'ready'); }; RichMarker.prototype['onAdd'] = RichMarker.prototype.onAdd; /** * Impelementing the interface. */ RichMarker.prototype.draw = function() { if (!this.ready_ || this.dragging_) { return; } var projection = this.getProjection(); if (!projection) { // The map projection is not ready yet so do nothing return; } var latLng = /** @type {google.maps.LatLng} */ (this.get('position')); var pos = projection.fromLatLngToDivPixel(latLng); var offset = this.getOffset_(); this.markerWrapper_.style['top'] = (pos.y + offset.height) + 'px'; this.markerWrapper_.style['left'] = (pos.x + offset.width) + 'px'; var height = this.markerContent_.offsetHeight; var width = this.markerContent_.offsetWidth; if (width != this.get('width')) { this.set('width', width); } if (height != this.get('height')) { this.set('height', height); } }; RichMarker.prototype['draw'] = RichMarker.prototype.draw; /** * Removing a marker from the map. * Implementing the interface. */ RichMarker.prototype.onRemove = function() { if (this.markerWrapper_ && this.markerWrapper_.parentNode) { this.markerWrapper_.parentNode.removeChild(this.markerWrapper_); } this.removeDragListeners_(); }; RichMarker.prototype['onRemove'] = RichMarker.prototype.onRemove; /** * RichMarker Anchor positions * @enum {number} */ var RichMarkerPosition = { 'TOP_LEFT': 1, 'TOP': 2, 'TOP_RIGHT': 3, 'LEFT': 4, 'MIDDLE': 5, 'RIGHT': 6, 'BOTTOM_LEFT': 7, 'BOTTOM': 8, 'BOTTOM_RIGHT': 9 }; window['RichMarkerPosition'] = RichMarkerPosition;