/** * @name KeyDragZoom for V3 * @version 2.0.5 [December 8, 2010] * @author: Nianwei Liu [nianwei at gmail dot com] & Gary Little [gary at luxcentral dot com] * @fileoverview This library adds a drag zoom capability to a V3 Google map. * When drag zoom is enabled, holding down a designated hot key (shift | ctrl | alt) * while dragging a box around an area of interest will zoom the map in to that area when * the mouse button is released. Optionally, a visual control can also be supplied for turning * a drag zoom operation on and off. * Only one line of code is needed: google.maps.Map.enableKeyDragZoom(); *

* NOTE: Do not use Ctrl as the hot key with Google Maps JavaScript API V3 since, unlike with V2, * it causes a context menu to appear when running on the Macintosh. *

* Note that if the map's container has a border around it, the border widths must be specified * in pixel units (or as thin, medium, or thick). This is required because of an MSIE limitation. *

NL: 2009-05-28: initial port to core API V3. *
NL: 2009-11-02: added a temp fix for -moz-transform for FF3.5.x using code from Paul Kulchenko (http://notebook.kulchenko.com/maps/gridmove). *
NL: 2010-02-02: added a fix for IE flickering on divs onmousemove, caused by scroll value when get mouse position. *
GL: 2010-06-15: added a visual control option. */ /*! * * 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. */ (function () { /*jslint browser:true */ /*global window,google */ /* Utility functions use "var funName=function()" syntax to allow use of the */ /* Dean Edwards Packer compression tool (with Shrink variables, without Base62 encode). */ /** * Converts "thin", "medium", and "thick" to pixel widths * in an MSIE environment. Not called for other browsers * because getComputedStyle() returns pixel widths automatically. * @param {string} widthValue The value of the border width parameter. */ var toPixels = function (widthValue) { var px; switch (widthValue) { case "thin": px = "2px"; break; case "medium": px = "4px"; break; case "thick": px = "6px"; break; default: px = widthValue; } return px; }; /** * Get the widths of the borders of an HTML element. * * @param {Node} h The HTML element. * @return {Object} The width object {top, bottom left, right}. */ var getBorderWidths = function (h) { var computedStyle; var bw = {}; if (document.defaultView && document.defaultView.getComputedStyle) { computedStyle = h.ownerDocument.defaultView.getComputedStyle(h, ""); if (computedStyle) { // The computed styles are always in pixel units (good!) bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0; bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0; bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0; return bw; } } else if (document.documentElement.currentStyle) { // MSIE if (h.currentStyle) { // The current styles may not be in pixel units so try to convert (bad!) bw.top = parseInt(toPixels(h.currentStyle.borderTopWidth), 10) || 0; bw.bottom = parseInt(toPixels(h.currentStyle.borderBottomWidth), 10) || 0; bw.left = parseInt(toPixels(h.currentStyle.borderLeftWidth), 10) || 0; bw.right = parseInt(toPixels(h.currentStyle.borderRightWidth), 10) || 0; return bw; } } // Shouldn't get this far for any modern browser bw.top = parseInt(h.style["border-top-width"], 10) || 0; bw.bottom = parseInt(h.style["border-bottom-width"], 10) || 0; bw.left = parseInt(h.style["border-left-width"], 10) || 0; bw.right = parseInt(h.style["border-right-width"], 10) || 0; return bw; }; // Page scroll values for use by getMousePosition. To prevent flickering on MSIE // they are calculated only when the document actually scrolls, not every time the // mouse moves (as they would be if they were calculated inside getMousePosition). var scroll = { x: 0, y: 0 }; var getScrollValue = function (e) { scroll.x = (typeof document.documentElement.scrollLeft !== "undefined" ? document.documentElement.scrollLeft : document.body.scrollLeft); scroll.y = (typeof document.documentElement.scrollTop !== "undefined" ? document.documentElement.scrollTop : document.body.scrollTop); }; getScrollValue(); /** * Get the position of the mouse relative to the document. * @param {Event} e The mouse event. * @return {Object} The position object {left, top}. */ var getMousePosition = function (e) { var posX = 0, posY = 0; e = e || window.event; if (typeof e.pageX !== "undefined") { posX = e.pageX; posY = e.pageY; } else if (typeof e.clientX !== "undefined") { // MSIE posX = e.clientX + scroll.x; posY = e.clientY + scroll.y; } return { left: posX, top: posY }; }; /** * Get the position of an HTML element relative to the document. * @param {Node} h The HTML element. * @return {Object} The position object {left, top}. */ var getElementPosition = function (h) { var posX = h.offsetLeft; var posY = h.offsetTop; var parent = h.offsetParent; // Add offsets for all ancestors in the hierarchy while (parent !== null) { // Adjust for scrolling elements which may affect the map position. // // See http://www.howtocreate.co.uk/tutorials/javascript/browserspecific // // "...make sure that every element [on a Web page] with an overflow // of anything other than visible also has a position style set to // something other than the default static..." if (parent !== document.body && parent !== document.documentElement) { posX -= parent.scrollLeft; posY -= parent.scrollTop; } // See http://groups.google.com/group/google-maps-js-api-v3/browse_thread/thread/4cb86c0c1037a5e5 // Example: http://notebook.kulchenko.com/maps/gridmove var m = parent; // This is the "normal" way to get offset information: var moffx = m.offsetLeft; var moffy = m.offsetTop; // This covers those cases where a transform is used: if (!moffx && !moffy && window.getComputedStyle) { var matrix = document.defaultView.getComputedStyle(m, null).MozTransform || document.defaultView.getComputedStyle(m, null).WebkitTransform; if (matrix) { if (typeof matrix === "string") { var parms = matrix.split(","); moffx += parseInt(parms[4], 10) || 0; moffy += parseInt(parms[5], 10) || 0; } } } posX += moffx; posY += moffy; parent = parent.offsetParent; } return { left: posX, top: posY }; }; /** * Set the properties of an object to those from another object. * @param {Object} obj The target object. * @param {Object} vals The source object. */ var setVals = function (obj, vals) { if (obj && vals) { for (var x in vals) { if (vals.hasOwnProperty(x)) { obj[x] = vals[x]; } } } return obj; }; /** * Set the opacity. If op is not passed in, this function just performs an MSIE fix. * @param {Node} h The HTML element. * @param {number} op The opacity value (0-1). */ var setOpacity = function (h, op) { if (typeof op !== "undefined") { h.style.opacity = op; } if (typeof h.style.opacity !== "undefined" && h.style.opacity !== "") { h.style.filter = "alpha(opacity=" + (h.style.opacity * 100) + ")"; } }; /** * @name KeyDragZoomOptions * @class This class represents the optional parameter passed into google.maps.Map.enableKeyDragZoom. * @property {string} [key] The hot key to hold down to activate a drag zoom, shift | ctrl | alt. * The default is shift. NOTE: Do not use Ctrl as the hot key with Google Maps JavaScript API V3 * since, unlike with V2, it causes a context menu to appear when running on the Macintosh. Also note that the * alt hot key refers to the Option key on a Macintosh. * @property {Object} [boxStyle] An object literal defining the css styles of the zoom box. * The default is {border: "4px solid #736AFF"}. * Border widths must be specified in pixel units (or as thin, medium, or thick). * @property {Object} [veilStyle] An object literal defining the css styles of the veil pane * which covers the map when a drag zoom is activated. The previous name for this property was * paneStyle but the use of this name is now deprecated. * The default is {backgroundColor: "gray", opacity: 0.25, cursor: "crosshair"}. * @property {boolean} [visualEnabled] A flag indicating whether a visual control is to be used. * The default is false. * @property {string} [visualClass] The name of the CSS class defining the styles for the visual * control. To prevent the visual control from being printed, set this property to the name of * a class, defined inside a @media print rule, which sets the CSS * display style to none. * @property {ControlPosition} [visualPosition] The position of the visual control. * The default position is on the left side of the map below other controls in the top left * — i.e., a position of google.maps.ControlPosition.LEFT_TOP. * @property {Size} [visualPositionOffset] The width and height values provided by this * property are the offsets (in pixels) from the location at which the control would normally * be drawn to the desired drawing location. The default is (35,0). * @property {number} [visualPositionIndex] The index of the visual control. * The index is for controlling the placement of the control relative to other controls at the * position given by visualPosition; controls with a lower index are placed first. * Use a negative value to place the control before any default controls. No index is * generally required; the default is null. * @property {String} [visualSprite] The URL of the sprite image used for showing the visual control * in the on, off, and hot (i.e., when the mouse is over the control) states. The three images * within the sprite must be the same size and arranged in on-hot-off order in a single row * with no spaces between images. * The default is http://maps.gstatic.com/mapfiles/ftr/controls/dragzoom_btn.png. * @property {Size} [visualSize] The width and height values provided by this property are * the size (in pixels) of each of the images within visualSprite. * The default is (20,20). * @property {Object} [visualTips] An object literal defining the help tips that appear when * the mouse moves over the visual control. The off property is the tip to be shown * when the control is off and the on property is the tip to be shown when the * control is on. * The default values are "Turn on drag zoom mode" and "Turn off drag zoom mode", respectively. */ /** * @name DragZoom * @class This class represents a drag zoom object for a map. The object is activated by holding down the hot key * or by turning on the visual control. * This object is created when google.maps.Map.enableKeyDragZoom is called; it cannot be created directly. * Use google.maps.Map.getDragZoomObject to gain access to this object in order to attach event listeners. * @param {Map} map The map to which the DragZoom object is to be attached. * @param {KeyDragZoomOptions} [opt_zoomOpts] The optional parameters. */ function DragZoom(map, opt_zoomOpts) { var me = this; var ov = new google.maps.OverlayView(); ov.onAdd = function () { me.init_(map, opt_zoomOpts); }; ov.draw = function () { }; ov.onRemove = function () { }; ov.setMap(map); this.prjov_ = ov; } /** * Initialize the tool. * @param {Map} map The map to which the DragZoom object is to be attached. * @param {KeyDragZoomOptions} [opt_zoomOpts] The optional parameters. */ DragZoom.prototype.init_ = function (map, opt_zoomOpts) { var i; var me = this; this.map_ = map; opt_zoomOpts = opt_zoomOpts || {}; this.key_ = opt_zoomOpts.key || "shift"; this.key_ = this.key_.toLowerCase(); this.borderWidths_ = getBorderWidths(this.map_.getDiv()); this.veilDiv_ = []; for (i = 0; i < 4; i++) { this.veilDiv_[i] = document.createElement("div"); // Prevents selection of other elements on the webpage // when a drag zoom operation is in progress: this.veilDiv_[i].onselectstart = function () { return false; }; // Apply default style values for the veil: setVals(this.veilDiv_[i].style, { backgroundColor: "gray", opacity: 0.25, cursor: "crosshair" }); // Apply style values specified in veilStyle parameter: setVals(this.veilDiv_[i].style, opt_zoomOpts.paneStyle); // Old option name was "paneStyle" setVals(this.veilDiv_[i].style, opt_zoomOpts.veilStyle); // New name is "veilStyle" // Apply mandatory style values: setVals(this.veilDiv_[i].style, { position: "absolute", overflow: "hidden", display: "none" }); // Workaround for Firefox Shift-Click problem: if (this.key_ === "shift") { this.veilDiv_[i].style.MozUserSelect = "none"; } setOpacity(this.veilDiv_[i]); // An IE fix: If the background is transparent it cannot capture mousedown // events, so if it is, change the background to white with 0 opacity. if (this.veilDiv_[i].style.backgroundColor === "transparent") { this.veilDiv_[i].style.backgroundColor = "white"; setOpacity(this.veilDiv_[i], 0); } this.map_.getDiv().appendChild(this.veilDiv_[i]); } this.visualEnabled_ = opt_zoomOpts.visualEnabled || false; this.visualClass_ = opt_zoomOpts.visualClass || ""; this.visualPosition_ = opt_zoomOpts.visualPosition || google.maps.ControlPosition.LEFT_TOP; this.visualPositionOffset_ = opt_zoomOpts.visualPositionOffset || new google.maps.Size(35, 0); this.visualPositionIndex_ = opt_zoomOpts.visualPositionIndex || null; this.visualSprite_ = opt_zoomOpts.visualSprite || "http://maps.gstatic.com/mapfiles/ftr/controls/dragzoom_btn.png"; this.visualSize_ = opt_zoomOpts.visualSize || new google.maps.Size(20, 20); this.visualTips_ = opt_zoomOpts.visualTips || {}; this.visualTips_.off = this.visualTips_.off || "Turn on drag zoom mode"; this.visualTips_.on = this.visualTips_.on || "Turn off drag zoom mode"; this.boxDiv_ = document.createElement("div"); // Apply default style values for the zoom box: setVals(this.boxDiv_.style, { border: "4px solid #736AFF" }); // Apply style values specified in boxStyle parameter: setVals(this.boxDiv_.style, opt_zoomOpts.boxStyle); // Apply mandatory style values: setVals(this.boxDiv_.style, { position: "absolute", display: "none" }); setOpacity(this.boxDiv_); this.map_.getDiv().appendChild(this.boxDiv_); this.boxBorderWidths_ = getBorderWidths(this.boxDiv_); this.listeners_ = [ google.maps.event.addDomListener(document, "keydown", function (e) { me.onKeyDown_(e); }), google.maps.event.addDomListener(document, "keyup", function (e) { me.onKeyUp_(e); }), google.maps.event.addDomListener(this.veilDiv_[0], "mousedown", function (e) { me.onMouseDown_(e); }), google.maps.event.addDomListener(this.veilDiv_[1], "mousedown", function (e) { me.onMouseDown_(e); }), google.maps.event.addDomListener(this.veilDiv_[2], "mousedown", function (e) { me.onMouseDown_(e); }), google.maps.event.addDomListener(this.veilDiv_[3], "mousedown", function (e) { me.onMouseDown_(e); }), google.maps.event.addDomListener(document, "mousedown", function (e) { me.onMouseDownDocument_(e); }), google.maps.event.addDomListener(document, "mousemove", function (e) { me.onMouseMove_(e); }), google.maps.event.addDomListener(document, "mouseup", function (e) { me.onMouseUp_(e); }), google.maps.event.addDomListener(window, "scroll", getScrollValue) ]; this.hotKeyDown_ = false; this.mouseDown_ = false; this.dragging_ = false; this.startPt_ = null; this.endPt_ = null; this.mapWidth_ = null; this.mapHeight_ = null; this.mousePosn_ = null; this.mapPosn_ = null; if (this.visualEnabled_) { this.buttonDiv_ = this.initControl_(this.visualPositionOffset_); if (this.visualPositionIndex_ !== null) { this.buttonDiv_.index = this.visualPositionIndex_; } this.map_.controls[this.visualPosition_].push(this.buttonDiv_); this.controlIndex_ = this.map_.controls[this.visualPosition_].length - 1; } }; /** * Initializes the visual control and returns its DOM element. * @param {Size} offset The offset of the control from its normal position. * @return {Node} The DOM element containing the visual control. */ DragZoom.prototype.initControl_ = function (offset) { var control; var image; var me = this; control = document.createElement("div"); control.className = this.visualClass_; control.style.position = "relative"; control.style.overflow = "hidden"; control.style.height = this.visualSize_.height + "px"; control.style.width = this.visualSize_.width + "px"; control.title = this.visualTips_.off; image = document.createElement("img"); image.src = this.visualSprite_; image.style.position = "absolute"; image.style.left = -(this.visualSize_.width * 2) + "px"; image.style.top = 0 + "px"; control.appendChild(image); control.onclick = function (e) { me.hotKeyDown_ = !me.hotKeyDown_; if (me.hotKeyDown_) { me.buttonDiv_.firstChild.style.left = -(me.visualSize_.width * 0) + "px"; me.buttonDiv_.title = me.visualTips_.on; me.activatedByControl_ = true; google.maps.event.trigger(me, "activate"); } else { me.buttonDiv_.firstChild.style.left = -(me.visualSize_.width * 2) + "px"; me.buttonDiv_.title = me.visualTips_.off; google.maps.event.trigger(me, "deactivate"); } me.onMouseMove_(e); // Updates the veil }; control.onmouseover = function () { me.buttonDiv_.firstChild.style.left = -(me.visualSize_.width * 1) + "px"; }; control.onmouseout = function () { if (me.hotKeyDown_) { me.buttonDiv_.firstChild.style.left = -(me.visualSize_.width * 0) + "px"; me.buttonDiv_.title = me.visualTips_.on; } else { me.buttonDiv_.firstChild.style.left = -(me.visualSize_.width * 2) + "px"; me.buttonDiv_.title = me.visualTips_.off; } }; control.ondragstart = function () { return false; }; setVals(control.style, { cursor: "pointer", marginTop: offset.height + "px", marginLeft: offset.width + "px" }); return control; }; /** * Returns true if the hot key is being pressed when an event occurs. * @param {Event} e The keyboard event. * @return {boolean} Flag indicating whether the hot key is down. */ DragZoom.prototype.isHotKeyDown_ = function (e) { var isHot; e = e || window.event; isHot = (e.shiftKey && this.key_ === "shift") || (e.altKey && this.key_ === "alt") || (e.ctrlKey && this.key_ === "ctrl"); if (!isHot) { // Need to look at keyCode for Opera because it // doesn't set the shiftKey, altKey, ctrlKey properties // unless a non-modifier event is being reported. // // See http://cross-browser.com/x/examples/shift_mode.php // Also see http://unixpapa.com/js/key.html switch (e.keyCode) { case 16: if (this.key_ === "shift") { isHot = true; } break; case 17: if (this.key_ === "ctrl") { isHot = true; } break; case 18: if (this.key_ === "alt") { isHot = true; } break; } } return isHot; }; /** * Returns true if the mouse is on top of the map div. * The position is captured in onMouseMove_. * @return {boolean} */ DragZoom.prototype.isMouseOnMap_ = function () { var mousePosn = this.mousePosn_; if (mousePosn) { var mapPosn = this.mapPosn_; var mapDiv = this.map_.getDiv(); return mousePosn.left > mapPosn.left && mousePosn.left < (mapPosn.left + mapDiv.offsetWidth) && mousePosn.top > mapPosn.top && mousePosn.top < (mapPosn.top + mapDiv.offsetHeight); } else { // if user never moved mouse return false; } }; /** * Show the veil if the hot key is down and the mouse is over the map, * otherwise hide the veil. */ DragZoom.prototype.setVeilVisibility_ = function () { var i; if (this.map_ && this.hotKeyDown_ && this.isMouseOnMap_()) { var mapDiv = this.map_.getDiv(); this.mapWidth_ = mapDiv.offsetWidth - (this.borderWidths_.left + this.borderWidths_.right); this.mapHeight_ = mapDiv.offsetHeight - (this.borderWidths_.top + this.borderWidths_.bottom); if (this.activatedByControl_) { // Veil covers entire map (except control) var left = parseInt(this.buttonDiv_.style.left, 10) + this.visualPositionOffset_.width; var top = parseInt(this.buttonDiv_.style.top, 10) + this.visualPositionOffset_.height; var width = this.visualSize_.width; var height = this.visualSize_.height; // Left veil rectangle: this.veilDiv_[0].style.top = "0px"; this.veilDiv_[0].style.left = "0px"; this.veilDiv_[0].style.width = left + "px"; this.veilDiv_[0].style.height = this.mapHeight_ + "px"; // Right veil rectangle: this.veilDiv_[1].style.top = "0px"; this.veilDiv_[1].style.left = (left + width) + "px"; this.veilDiv_[1].style.width = (this.mapWidth_ - (left + width)) + "px"; this.veilDiv_[1].style.height = this.mapHeight_ + "px"; // Top veil rectangle: this.veilDiv_[2].style.top = "0px"; this.veilDiv_[2].style.left = left + "px"; this.veilDiv_[2].style.width = width + "px"; this.veilDiv_[2].style.height = top + "px"; // Bottom veil rectangle: this.veilDiv_[3].style.top = (top + height) + "px"; this.veilDiv_[3].style.left = left + "px"; this.veilDiv_[3].style.width = width + "px"; this.veilDiv_[3].style.height = (this.mapHeight_ - (top + height)) + "px"; for (i = 0; i < this.veilDiv_.length; i++) { this.veilDiv_[i].style.display = "block"; } } else { this.veilDiv_[0].style.left = "0px"; this.veilDiv_[0].style.top = "0px"; this.veilDiv_[0].style.width = this.mapWidth_ + "px"; this.veilDiv_[0].style.height = this.mapHeight_ + "px"; for (i = 1; i < this.veilDiv_.length; i++) { this.veilDiv_[i].style.width = "0px"; this.veilDiv_[i].style.height = "0px"; } for (i = 0; i < this.veilDiv_.length; i++) { this.veilDiv_[i].style.display = "block"; } } } else { for (i = 0; i < this.veilDiv_.length; i++) { this.veilDiv_[i].style.display = "none"; } } }; /** * Handle key down. Show the veil if the hot key has been pressed. * @param {Event} e The keyboard event. */ DragZoom.prototype.onKeyDown_ = function (e) { if (this.map_ && !this.hotKeyDown_ && this.isHotKeyDown_(e)) { this.mapPosn_ = getElementPosition(this.map_.getDiv()); this.hotKeyDown_ = true; this.activatedByControl_ = false; this.setVeilVisibility_(); /** * This event is fired when the hot key is pressed. * @name DragZoom#activate * @event */ google.maps.event.trigger(this, "activate"); } if (this.visualEnabled_ && this.isHotKeyDown_(e)) { this.buttonDiv_.style.display = "none"; } }; /** * Get the google.maps.Point of the mouse position. * @param {Event} e The mouse event. * @return {Point} The mouse position. */ DragZoom.prototype.getMousePoint_ = function (e) { var mousePosn = getMousePosition(e); var p = new google.maps.Point(); p.x = mousePosn.left - this.mapPosn_.left - this.borderWidths_.left; p.y = mousePosn.top - this.mapPosn_.top - this.borderWidths_.top; p.x = Math.min(p.x, this.mapWidth_); p.y = Math.min(p.y, this.mapHeight_); p.x = Math.max(p.x, 0); p.y = Math.max(p.y, 0); return p; }; /** * Handle mouse down. * @param {Event} e The mouse event. */ DragZoom.prototype.onMouseDown_ = function (e) { if (this.map_ && this.hotKeyDown_) { this.mapPosn_ = getElementPosition(this.map_.getDiv()); this.dragging_ = true; this.startPt_ = this.endPt_ = this.getMousePoint_(e); this.boxDiv_.style.width = this.boxDiv_.style.height = "0px"; var prj = this.prjov_.getProjection(); var latlng = prj.fromContainerPixelToLatLng(this.startPt_); if (this.visualEnabled_) { this.buttonDiv_.style.display = "none"; } /** * This event is fired when the drag operation begins. * The parameter passed is the geographic position of the starting point. * @name DragZoom#dragstart * @param {LatLng} latlng The geographic position of the starting point. * @event */ google.maps.event.trigger(this, "dragstart", latlng); } }; /** * Handle mouse down at the document level. * @param {Event} e The mouse event. */ DragZoom.prototype.onMouseDownDocument_ = function (e) { this.mouseDown_ = true; }; /** * Handle mouse move. * @param {Event} e The mouse event. */ DragZoom.prototype.onMouseMove_ = function (e) { this.mousePosn_ = getMousePosition(e); if (this.dragging_) { this.endPt_ = this.getMousePoint_(e); var left = Math.min(this.startPt_.x, this.endPt_.x); var top = Math.min(this.startPt_.y, this.endPt_.y); var width = Math.abs(this.startPt_.x - this.endPt_.x); var height = Math.abs(this.startPt_.y - this.endPt_.y); // For benefit of MSIE 7/8 ensure following values are not negative: var boxWidth = Math.max(0, width - (this.boxBorderWidths_.left + this.boxBorderWidths_.right)); var boxHeight = Math.max(0, height - (this.boxBorderWidths_.top + this.boxBorderWidths_.bottom)); // Left veil rectangle: this.veilDiv_[0].style.top = "0px"; this.veilDiv_[0].style.left = "0px"; this.veilDiv_[0].style.width = left + "px"; this.veilDiv_[0].style.height = this.mapHeight_ + "px"; // Right veil rectangle: this.veilDiv_[1].style.top = "0px"; this.veilDiv_[1].style.left = (left + width) + "px"; this.veilDiv_[1].style.width = (this.mapWidth_ - (left + width)) + "px"; this.veilDiv_[1].style.height = this.mapHeight_ + "px"; // Top veil rectangle: this.veilDiv_[2].style.top = "0px"; this.veilDiv_[2].style.left = left + "px"; this.veilDiv_[2].style.width = width + "px"; this.veilDiv_[2].style.height = top + "px"; // Bottom veil rectangle: this.veilDiv_[3].style.top = (top + height) + "px"; this.veilDiv_[3].style.left = left + "px"; this.veilDiv_[3].style.width = width + "px"; this.veilDiv_[3].style.height = (this.mapHeight_ - (top + height)) + "px"; // Selection rectangle: this.boxDiv_.style.top = top + "px"; this.boxDiv_.style.left = left + "px"; this.boxDiv_.style.width = boxWidth + "px"; this.boxDiv_.style.height = boxHeight + "px"; this.boxDiv_.style.display = "block"; /** * This event is fired repeatedly while the user drags a box across the area of interest. * The southwest and northeast point are passed as parameters of type google.maps.Point * (for performance reasons), relative to the map container. Also passed is the projection object * so that the event listener, if necessary, can convert the pixel positions to geographic * coordinates using google.maps.MapCanvasProjection.fromContainerPixelToLatLng. * @name DragZoom#drag * @param {Point} southwestPixel The southwest point of the selection area. * @param {Point} northeastPixel The northeast point of the selection area. * @param {MapCanvasProjection} prj The projection object. * @event */ google.maps.event.trigger(this, "drag", new google.maps.Point(left, top + height), new google.maps.Point(left + width, top), this.prjov_.getProjection()); } else if (!this.mouseDown_) { this.mapPosn_ = getElementPosition(this.map_.getDiv()); this.setVeilVisibility_(); } }; /** * Handle mouse up. * @param {Event} e The mouse event. */ DragZoom.prototype.onMouseUp_ = function (e) { var z; var me = this; this.mouseDown_ = false; if (this.dragging_) { if ((this.getMousePoint_(e).x === this.startPt_.x) && (this.getMousePoint_(e).y === this.startPt_.y)) { this.onKeyUp_(e); // Cancel event return; } var left = Math.min(this.startPt_.x, this.endPt_.x); var top = Math.min(this.startPt_.y, this.endPt_.y); var width = Math.abs(this.startPt_.x - this.endPt_.x); var height = Math.abs(this.startPt_.y - this.endPt_.y); // Google Maps API bug: setCenter() doesn't work as expected if the map has a // border on the left or top. The code here includes a workaround for this problem. var kGoogleCenteringBug = true; if (kGoogleCenteringBug) { left += this.borderWidths_.left; top += this.borderWidths_.top; } var prj = this.prjov_.getProjection(); var sw = prj.fromContainerPixelToLatLng(new google.maps.Point(left, top + height)); var ne = prj.fromContainerPixelToLatLng(new google.maps.Point(left + width, top)); var bnds = new google.maps.LatLngBounds(sw, ne); // Sometimes fitBounds causes a zoom OUT, so restore original zoom level if this happens. z = this.map_.getZoom(); //this.map_.fitBounds(bnds); if (this.map_.getZoom() < z) { this.map_.setZoom(z); } // Redraw box after zoom: var swPt = prj.fromLatLngToContainerPixel(sw); var nePt = prj.fromLatLngToContainerPixel(ne); if (kGoogleCenteringBug) { swPt.x -= this.borderWidths_.left; swPt.y -= this.borderWidths_.top; nePt.x -= this.borderWidths_.left; nePt.y -= this.borderWidths_.top; } this.boxDiv_.style.left = swPt.x + "px"; this.boxDiv_.style.top = nePt.y + "px"; this.boxDiv_.style.width = (Math.abs(nePt.x - swPt.x) - (this.boxBorderWidths_.left + this.boxBorderWidths_.right)) + "px"; this.boxDiv_.style.height = (Math.abs(nePt.y - swPt.y) - (this.boxBorderWidths_.top + this.boxBorderWidths_.bottom)) + "px"; // Hide box asynchronously after 1 second: setTimeout(function () { me.boxDiv_.style.display = "none"; }, 1000); this.dragging_ = false; this.onMouseMove_(e); // Updates the veil /** * This event is fired when the drag operation ends. * The parameter passed is the geographic bounds of the selected area. * Note that this event is not fired if the hot key is released before the drag operation ends. * @name DragZoom#dragend * @param {LatLngBounds} bnds The geographic bounds of the selected area. * @event */ google.maps.event.trigger(this, "dragend", bnds); // if the hot key isn't down, the drag zoom must have been activated by turning // on the visual control. In this case, finish up by simulating a key up event. if (!this.isHotKeyDown_(e)) { this.onKeyUp_(e); } } }; /** * Handle key up. * @param {Event} e The keyboard event. */ DragZoom.prototype.onKeyUp_ = function (e) { var i; if (this.map_ && this.hotKeyDown_) { this.hotKeyDown_ = false; if (this.dragging_) { this.boxDiv_.style.display = "none"; this.dragging_ = false; } for (i = 0; i < this.veilDiv_.length; i++) { this.veilDiv_[i].style.display = "none"; } if (this.visualEnabled_) { this.buttonDiv_.firstChild.style.left = -(this.visualSize_.width * 2) + "px"; this.buttonDiv_.title = this.visualTips_.off; this.buttonDiv_.style.display = ""; } /** * This event is fired when the hot key is released. * @name DragZoom#deactivate * @event */ google.maps.event.trigger(this, "deactivate"); } }; /** * @name google.maps.Map * @class These are new methods added to the Google Maps JavaScript API V3's * Map * class. */ /** * Enables drag zoom. The user can zoom to an area of interest by holding down the hot key * (shift | ctrl | alt ) while dragging a box around the area or by turning * on the visual control then dragging a box around the area. * @param {KeyDragZoomOptions} opt_zoomOpts The optional parameters. */ google.maps.Map.prototype.enableKeyDragZoom = function (opt_zoomOpts) { this.dragZoom_ = new DragZoom(this, opt_zoomOpts); }; /** * Disables drag zoom. */ google.maps.Map.prototype.disableKeyDragZoom = function () { var i; var d = this.dragZoom_; if (d) { for (i = 0; i < d.listeners_.length; ++i) { google.maps.event.removeListener(d.listeners_[i]); } this.getDiv().removeChild(d.boxDiv_); for (i = 0; i < d.veilDiv_.length; i++) { this.getDiv().removeChild(d.veilDiv_[i]); } if (d.visualEnabled_) { // Remove the custom control: this.controls[d.visualPosition_].removeAt(d.controlIndex_); } d.prjov_.setMap(null); this.dragZoom_ = null; } }; /** * Returns true if the drag zoom feature has been enabled. * @return {boolean} */ google.maps.Map.prototype.keyDragZoomEnabled = function () { return this.dragZoom_ !== null; }; /** * Returns the DragZoom object which is created when google.maps.Map.enableKeyDragZoom is called. * With this object you can use google.maps.event.addListener to attach event listeners * for the "activate", "deactivate", "dragstart", "drag", and "dragend" events. * @return {DragZoom} */ google.maps.Map.prototype.getDragZoomObject = function () { return this.dragZoom_; }; })();