1/** 2 * @name Key Drag Zoom for V3 3 * @version 1.0 4 * @author: Nianwei Liu [nianwei at gmail dot com] & Gary Little [gary at luxcentral dot com] 5 * @fileoverview This library adds a drag zoom capability to a Google map. 6 * When drag zoom is enabled, holding down a user-defined hot key <code>(shift | ctrl | alt)</code> 7 * while dragging a box around an area of interest will zoom the map 8 * to that area when the hot key is released. 9 * Only one line of code is needed: <code>google.maps.Map.enableKeyDragZoom();</code> 10 * <p> 11 * Note that if the map's container has a border around it, the border widths must be specified 12 * in pixel units (or as thin, medium, or thick). This is required because of an MSIE limitation. 13 * <p>NL: 2009-05-28: initial port to core API V3. 14 */ 15/*! 16 * 17 * Licensed under the Apache License, Version 2.0 (the "License"); 18 * you may not use this file except in compliance with the License. 19 * You may obtain a copy of the License at 20 * 21 * http://www.apache.org/licenses/LICENSE-2.0 22 * 23 * Unless required by applicable law or agreed to in writing, software 24 * distributed under the License is distributed on an "AS IS" BASIS, 25 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 * See the License for the specific language governing permissions and 27 * limitations under the License. 28 */ 29(function () { 30 /*jslint browser:true */ 31 /*global google */ 32 /** 33 * Converts 'thin', 'medium', and 'thick' to pixel widths 34 * in an MSIE environment. Not called for other browsers 35 * because getComputedStyle() returns pixel widths automatically. 36 * @param {String} widthValue 37 */ 38 var toPixels = function (widthValue) { 39 var px; 40 switch (widthValue) { 41 case 'thin': 42 px = "2px"; 43 break; 44 case 'medium': 45 px = "4px"; 46 break; 47 case 'thick': 48 px = "6px"; 49 break; 50 default: 51 px = widthValue; 52 } 53 return px; 54 }; 55 /** 56 * Get the widths of the borders of an HTML element. 57 * 58 * @param {Object} h HTML element 59 * @return {Object} widths object (top, bottom left, right) 60 */ 61 var getBorderWidths = function (h) { 62 var computedStyle; 63 var bw = {}; 64 if (document.defaultView && document.defaultView.getComputedStyle) { 65 computedStyle = h.ownerDocument.defaultView.getComputedStyle(h, ""); 66 if (computedStyle) { 67 // The computed styles are always in pixel units (good!) 68 bw.top = parseInt(computedStyle.borderTopWidth, 10) || 0; 69 bw.bottom = parseInt(computedStyle.borderBottomWidth, 10) || 0; 70 bw.left = parseInt(computedStyle.borderLeftWidth, 10) || 0; 71 bw.right = parseInt(computedStyle.borderRightWidth, 10) || 0; 72 return bw; 73 } 74 } else if (document.documentElement.currentStyle) { // MSIE 75 if (h.currentStyle) { 76 // The current styles may not be in pixel units so try to convert (bad!) 77 bw.top = parseInt(toPixels(h.currentStyle.borderTopWidth), 10) || 0; 78 bw.bottom = parseInt(toPixels(h.currentStyle.borderBottomWidth), 10) || 0; 79 bw.left = parseInt(toPixels(h.currentStyle.borderLeftWidth), 10) || 0; 80 bw.right = parseInt(toPixels(h.currentStyle.borderRightWidth), 10) || 0; 81 return bw; 82 } 83 } 84 // Shouldn't get this far for any modern browser 85 bw.top = parseInt(h.style["border-top-width"], 10) || 0; 86 bw.bottom = parseInt(h.style["border-bottom-width"], 10) || 0; 87 bw.left = parseInt(h.style["border-left-width"], 10) || 0; 88 bw.right = parseInt(h.style["border-right-width"], 10) || 0; 89 return bw; 90 }; 91 92 /** 93 * Get the position of the mouse relative to the document. 94 * @param {Object} e Mouse event 95 * @return {Object} left & top position 96 */ 97 var getMousePosition = function (e) { 98 var posX = 0, posY = 0; 99 e = e || window.event; 100 if (typeof e.pageX !== "undefined") { 101 posX = e.pageX; 102 posY = e.pageY; 103 } else if (typeof e.clientX !== "undefined") { 104 posX = e.clientX + 105 (typeof document.documentElement.scrollLeft !== "undefined" ? document.documentElement.scrollLeft : document.body.scrollLeft); 106 posY = e.clientY + 107 (typeof document.documentElement.scrollTop !== "undefined" ? document.documentElement.scrollTop : document.body.scrollTop); 108 } 109 return { 110 left: posX, 111 top: posY 112 }; 113 }; 114 115 /** 116 * Get the position of an HTML element relative to the document. 117 * @param {Object} h HTML element 118 * @return {Object} left & top position 119 */ 120 var getElementPosition = function (h) { 121 var posX = h.offsetLeft; 122 var posY = h.offsetTop; 123 var parent = h.offsetParent; 124 // Add offsets for all ancestors in the hierarchy 125 while (parent !== null) { 126 // Adjust for scrolling elements which may affect the map position. 127 // 128 // See http://www.howtocreate.co.uk/tutorials/javascript/browserspecific 129 // 130 // "...make sure that every element [on a Web page] with an overflow 131 // of anything other than visible also has a position style set to 132 // something other than the default static..." 133 if (parent !== document.body && parent !== document.documentElement) { 134 posX -= parent.scrollLeft; 135 posY -= parent.scrollTop; 136 } 137 posX += parent.offsetLeft; 138 posY += parent.offsetTop; 139 parent = parent.offsetParent; 140 } 141 return { 142 left: posX, 143 top: posY 144 }; 145 }; 146 /** 147 * Set the properties of an object to those from another object. 148 * @param {Object} obj target object 149 * @param {Object} vals source object 150 */ 151 var setVals = function (obj, vals) { 152 if (obj && vals) { 153 for (var x in vals) { 154 if (vals.hasOwnProperty(x)) { 155 obj[x] = vals[x]; 156 } 157 } 158 } 159 return obj; 160 }; 161 /** 162 * Set the opacity. If op is not passed in, this function just performs an MSIE fix. 163 * @param {Node} div 164 * @param {Number} op (0-1) 165 */ 166 var setOpacity = function (div, op) { 167 if (typeof op !== 'undefined') { 168 div.style.opacity = op; 169 } 170 if (typeof div.style.opacity !== 'undefined') { 171 div.style.filter = "alpha(opacity=" + (div.style.opacity * 100) + ")"; 172 } 173 }; 174 175 /** 176 * @name KeyDragZoomOptions 177 * @class This class represents the optional parameter passed into <code>google.maps.Map.enableDragBoxZoom</code>. 178 * @property {String} [key] the hot key to hold down to activate a drag zoom, <code>shift | ctrl | alt</code>. 179 * The default is <code>shift</code>. 180 * @property {Object} [boxStyle] the css style of the zoom box. 181 * The default is <code>
181{border: 'thin solid #FF0000'}</code>. 182 * Border widths must be specified in pixel units (or as thin, medium, or thick). 183 * @property {Object} [paneStyle] the css style of the pane which overlays the map when a drag zoom is activated. 184 * The default is <code>{backgroundColor: 'white', opacity: 0.0, cursor: 'crosshair'}</code>. 185 */ 186 /** 187 * @name DragZoom 188 * @class This class represents a drag zoom object for a map. The object is activated by holding down the hot key. 189 * This object is created when <code>google.maps.Map.enableKeyDragZoom</code> is called; it cannot be created directly. 190 * Use <code>google.maps.Map.getDragZoomObject</code> to gain access to this object in order to attach event listeners. 191 * @param {google.maps.Map} map 192 * @param {KeyDragZoomOptions} opt_zoomOpts 193 */ 194 function DragZoom(map, opt_zoomOpts) { 195 var ov = new google.maps.OverlayView(); 196 var me = this; 197 ov.onAdd = function () { 198 me.init_(map, opt_zoomOpts); 199 }; 200 ov.draw = function () { 201 }; 202 ov.onRemove = function () { 203 }; 204 ov.setMap(map); 205 this.prjov_ = ov; 206 } 207 /** 208 * Init the tool. 209 * @param {google.maps.Map} map 210 * @param {KeyDragZoomOptions} opt_zoomOpts 211 */ 212 DragZoom.prototype.init_ = function (map, opt_zoomOpts) { 213 this.map_ = map; 214 opt_zoomOpts = opt_zoomOpts || {}; 215 this.key_ = opt_zoomOpts.key || 'shift'; 216 this.key_ = this.key_.toLowerCase(); 217 this.borderWidths_ = getBorderWidths(this.map_.getDiv());//Container()); 218 this.paneDiv_ = document.createElement("div"); 219 this.paneDiv_.onselectstart = function () { 220 return false; 221 }; 222 // default style 223 setVals(this.paneDiv_.style, { 224 backgroundColor: 'white', 225 opacity: 0.0, 226 cursor: 'crosshair' 227 }); 228 // allow overwrite 229 setVals(this.paneDiv_.style, opt_zoomOpts.paneStyle); 230 // stuff that cannot be overwritten 231 setVals(this.paneDiv_.style, { 232 position: 'absolute', 233 overflow: 'hidden', 234 zIndex: 10001, 235 display: 'none' 236 }); 237 if (this.key_ === 'shift') { // Workaround for Firefox Shift-Click problem 238 this.paneDiv_.style.MozUserSelect = "none"; 239 } 240 setOpacity(this.paneDiv_); 241 // An IE fix: if the background is transparent, it cannot capture mousedown events 242 if (this.paneDiv_.style.backgroundColor === 'transparent') { 243 this.paneDiv_.style.backgroundColor = 'white'; 244 setOpacity(this.paneDiv_, 0); 245 } 246 this.map_.getDiv().appendChild(this.paneDiv_);//Container() 247 this.boxDiv_ = document.createElement('div'); 248 setVals(this.boxDiv_.style, { 249 border: 'thin solid #FF0000' 250 }); 251 setVals(this.boxDiv_.style, opt_zoomOpts.boxStyle); 252 setVals(this.boxDiv_.style, { 253 position: 'absolute', 254 display: 'none' 255 }); 256 setOpacity(this.boxDiv_); 257 this.map_.getDiv().appendChild(this.boxDiv_); 258 this.boxBorderWidths_ = getBorderWidths(this.boxDiv_); 259 var me = this; 260 this.keyDownListener_ = google.maps.event.addDomListener(document, 'keydown', function (e) { 261 me.onKeyDown_(e); 262 }); 263 this.keyUpListener_ = google.maps.event.addDomListener(document, 'keyup', function (e) { 264 me.onKeyUp_(e); 265 }); 266 this.mouseDownListener_ = google.maps.event.addDomListener(this.paneDiv_, 'mousedown', function (e) { 267 me.onMouseDown_(e); 268 }); 269 this.mouseDownListenerDocument_ = google.maps.event.addDomListener(document, 'mousedown', function (e) { 270 me.onMouseDownDocument_(e); 271 }); 272 this.mouseMoveListener_ = google.maps.event.addDomListener(document, 'mousemove', function (e) { 273 me.onMouseMove_(e); 274 }); 275 this.mouseUpListener_ = google.maps.event.addDomListener(document, 'mouseup', function (e) { 276 me.onMouseUp_(e); 277 }); 278 279 this.hotKeyDown_ = false; 280 this.dragging_ = false; 281 this.startPt_ = null; 282 this.endPt_ = null; 283 this.boxMaxX_ = null; 284 this.boxMaxY_ = null; 285 this.mousePosn_ = null; 286 this.mapPosn_ = getElementPosition(this.map_.getDiv()); 287 this.mouseDown_ = false; 288 }; 289 290 /** 291 * Returns true if the hot key is being pressed when an event occurs. 292 * @param {Event} e 293 * @return {Boolean} 294 */ 295 DragZoom.prototype.isHotKeyDown_ = function (e) { 296 var isHot; 297 e = e || window.event; 298 isHot = (e.shiftKey && this.key_ === 'shift') || (e.altKey && this.key_ === 'alt') || (e.ctrlKey && this.key_ === 'ctrl'); 299 if (!isHot) { 300 // Need to look at keyCode for Opera because it 301 // doesn't set the shiftKey, altKey, ctrlKey properties 302 // unless a non-modifier event is being reported. 303 // 304 // See http://cross-browser.com/x/examples/shift_mode.php 305 // Also see http://unixpapa.com/js/key.html 306 switch (e.keyCode) { 307 case 16: 308 if (this.key_ === 'shift') { 309 isHot = true; 310 } 311 break; 312 case 17: 313 if (this.key_ === 'ctrl') { 314 isHot = true; 315 } 316 break; 317 case 18: 318 if (this.key_ === 'alt') { 319 isHot = true; 320 } 321 break; 322 } 323 } 324 return isHot; 325 }; 326 327 /** 328 * Checks if the mouse is on top of the map. The position is captured 329 * in onMouseMove_. 330 * @return true if mouse is on top of the map div. 331 */ 332 DragZoom.prototype.isMouseOnMap_ = function () { 333 var mousePos = this.mousePosn_; 334 if (mousePos) { 335 var mapPos = this.mapPosn_; 336 var mapDiv = this.map_.getDiv(); 337 return mousePos.left > mapPos.left && mousePos.left < mapPos.left + mapDiv.offsetWidth && 338 mousePos.top > mapPos.top && mousePos.top < mapPos.top + mapDiv.offsetHeight; 339 } else { 340 // if user never moved mouse 341 return false;
342 } 343 }; 344 345 /** 346 * Show or hide the overlay pane, depending on whether the mouse is over the map. 347 */ 348 DragZoom.prototype.setPaneVisibility_ = function () { 349 if (this.map_ && this.hotKeyDown_ && this.isMouseOnMap_()) { 350 var mapDiv = this.map_.getDiv(); 351 this.paneDiv_.style.left = 0 + 'px'; 352 this.paneDiv_.style.top = 0 + 'px'; 353 this.paneDiv_.style.width = mapDiv.offsetWidth - (this.borderWidths_.left + this.borderWidths_.right) + 'px'; 354 this.paneDiv_.style.height = mapDiv.offsetHeight - (this.borderWidths_.top + this.borderWidths_.bottom) + 'px'; 355 this.paneDiv_.style.display = 'block'; 356 this.boxMaxX_ = parseInt(this.paneDiv_.style.width, 10) - (this.boxBorderWidths_.left + this.boxBorderWidths_.right); 357 this.boxMaxY_ = parseInt(this.paneDiv_.style.height, 10) - (this.boxBorderWidths_.top + this.boxBorderWidths_.bottom); 358 } else { 359 this.paneDiv_.style.display = 'none'; 360 } 361 }; 362 /** 363 * Handle key down. Activate the tool only if the mouse is on top of the map. 364 * @param {Event} e 365 */ 366 DragZoom.prototype.onKeyDown_ = function (e) { 367 var me = this; 368 if (this.map_ && !this.hotKeyDown_ && this.isHotKeyDown_(e)) { 369 me.hotKeyDown_ = true; 370 me.setPaneVisibility_(); 371 /** 372 * This event is fired when the hot key is pressed. 373 * @name DragZoom#activate 374 * @event 375 */ 376 google.maps.event.trigger(me, 'activate'); 377 } 378 }; 379 /** 380 * Get the <code>google.maps.Point</code> of the mouse position. 381 * @param {Object} e 382 * @return {google.maps.Point} point 383 * @private 384 */ 385 DragZoom.prototype.getMousePoint_ = function (e) { 386 var mousePosn = getMousePosition(e); 387 var p = new google.maps.Point(); 388 p.x = mousePosn.left - this.mapPosn_.left - this.borderWidths_.left; 389 p.y = mousePosn.top - this.mapPosn_.top - this.borderWidths_.top; 390 p.x = Math.min(p.x, this.boxMaxX_); 391 p.y = Math.min(p.y, this.boxMaxY_); 392 p.x = Math.max(p.x, 0); 393 p.y = Math.max(p.y, 0); 394 return p; 395 }; 396 /** 397 * Handle mouse down. 398 * @param {Event} e 399 */ 400 DragZoom.prototype.onMouseDown_ = function (e) { 401 if (this.map_ && this.hotKeyDown_) { 402 this.mapPosn_ = getElementPosition(this.map_.getDiv()); 403 this.dragging_ = true; 404 this.startPt_ = this.endPt_ = this.getMousePoint_(e); 405 var prj = this.prjov_.getProjection(); 406 var latlng = prj.fromDivPixelToLatLng(this.startPt_); 407 /** 408 * This event is fired when the drag operation begins. 409 * @name DragZoom#dragstart 410 * @param {GLatLng} startLatLng 411 * @event 412 */ 413 google.maps.event.trigger(this, 'dragstart', latlng); 414 } 415 }; 416 /** 417 * Handle mouse down at the document level. 418 * @param {Event} e 419 */ 420 DragZoom.prototype.onMouseDownDocument_ = function (e) { 421 this.mouseDown_ = true; 422 }; 423 /** 424 * Handle mouse move. 425 * @param {Event} e 426 */ 427 DragZoom.prototype.onMouseMove_ = function (e) { 428 this.mousePosn_ = getMousePosition(e); 429 if (this.dragging_) { 430 this.endPt_ = this.getMousePoint_(e); 431 var left = Math.min(this.startPt_.x, this.endPt_.x); 432 var top = Math.min(this.startPt_.y, this.endPt_.y); 433 var width = Math.abs(this.startPt_.x - this.endPt_.x); 434 var height = Math.abs(this.startPt_.y - this.endPt_.y); 435 this.boxDiv_.style.left = left + 'px'; 436 this.boxDiv_.style.top = top + 'px'; 437 this.boxDiv_.style.width = width + 'px'; 438 this.boxDiv_.style.height = height + 'px'; 439 this.boxDiv_.style.display = 'block'; 440 /** 441 * This event is repeatedly fired while the user drags the box. The southwest and northeast 442 * point are passed as parameters of type <code>google.maps.Point</code> (for performance reasons), 443 * relative to the map container. Note: the event listener is responsible 444 * for converting Pixel to LatLng, if necessary. 445 * @name DragZoom#drag 446 * @param {google.maps.Point} southwestPixel 447 * @param {google.maps.Point} northeastPixel 448 * @event 449 */ 450 google.maps.event.trigger(this, 'drag', new google.maps.Point(left, top + height), new google.maps.Point(left + width, top)); 451 } else if (!this.mouseDown_) { 452 this.setPaneVisibility_(); 453 } 454 }; 455 /** 456 * Handle mouse up. 457 * @param {Event} e 458 */ 459 DragZoom.prototype.onMouseUp_ = function (e) { 460 this.mouseDown_ = false;
461 if (this.dragging_) { 462 var left = Math.min(this.startPt_.x, this.endPt_.x); 463 var top = Math.min(this.startPt_.y, this.endPt_.y); 464 var width = Math.abs(this.startPt_.x - this.endPt_.x); 465 var height = Math.abs(this.startPt_.y - this.endPt_.y); 466 var prj = this.prjov_.getProjection(); 467 // 2009-05-29: since V3 does not have fromContainerPixel, 468 //needs find offset here 469 var containerPos = getElementPosition(this.map_.getDiv()); 470 var mapPanePos = getElementPosition(this.prjov_.getPanes().mapPane); 471 left = left + (containerPos.left - mapPanePos.left); 472 top = top + (containerPos.top - mapPanePos.top); 473 var sw = prj.fromDivPixelToLatLng(new google.maps.Point(left, top + height)); 474 var ne = prj.fromDivPixelToLatLng(new google.maps.Point(left + width, top)); 475 var bnds = new google.maps.LatLngBounds(sw, ne); 476 this.map_.fitBounds(bnds); 477 this.dragging_ = false; 478 this.boxDiv_.style.display = 'none'; 479 /** 480 * This event is fired when the drag operation ends. 481 * Note that the event is not fired if the hot key is released before the drag operation ends. 482 * @name DragZoom#dragend 483 * @param {GLatLngBounds} newBounds 484 * @event 485 */ 486 google.maps.event.trigger(this, 'dragend', bnds); 487 } 488 }; 489 490 /** 491 * Handle key up. 492 * @param {Event} e 493 */ 494 DragZoom.prototype.onKeyUp_ = function (e) { 495 if (this.map_ && this.hotKeyDown_) { 496 this.hotKeyDown_ = false; 497 this.dragging_ = false; 498 this.boxDiv_.style.display = 'none'; 499 this.paneDiv_.style.display = "none"; 500 /** 501 * This event is fired while the user release the key 502 * @name DragZoom#deactivate 503 * @event 504 */ 505 google.maps.event.trigger(this, 'deactivate'); 506 } 507 }; 508 509 510 511 512 /** 513 * @name google.maps.Map 514 * @class These are new methods added to the Google Maps API's 515 * <a href = 'http://code.google.com/apis/maps/documentation/v3/reference.html#Map'>Map</a> 516 * class. 517 */ 518 /** 519 * Enable drag zoom. The user can zoom to an area of interest by holding down the hot key 520 * <code>(shift | ctrl | alt )</code> while dragging a box around the area. 521 * @param {KeyDragZoomOptions} opt_zoomOpts 522 */ 523 524 google.maps.Map.prototype.enableKeyDragZoom = function (opt_zoomOpts) { 525 this.dragZoom_ = new DragZoom(this, opt_zoomOpts); 526 }; 527 /** 528 * Disable drag zoom. 529 */ 530 google.maps.Map.prototype.disableKeyDragZoom = function () { 531 var d = this.dragZoom_; 532 if (d) { 533 google.maps.event.removeListener(d.mouseDownListener_); 534 google.maps.event.removeListener(d.mouseDownListenerDocument_); 535 google.maps.event.removeListener(d.mouseMoveListener_); 536 google.maps.event.removeListener(d.mouseUpListener_); 537 google.maps.event.removeListener(d.keyUpListener_); 538 google.maps.event.removeListener(d.keyDownListener_); 539 this.getDiv().removeChild(d.boxDiv_); 540 this.getDiv().removeChild(d.paneDiv_); 541 this.dragZoom_ = null; 542 } 543 }; 544 /** 545 * Returns true if the drag zoom feature has been enabled. 546 * @return {Boolean} 547 */ 548 google.maps.Map.prototype.keyDragZoomEnabled = function () { 549 return this.dragZoom_ !== null; 550 }; 551 /** 552 * Returns the DragZoom object which is created when <code>google.maps.Map.enableKeyDragZoom</code> is called. 553 * With this object you can use <code>google.maps.event.addListener</code> to attach event listeners 554 * for the 'activate', 'deactivate', 'dragstart', 'drag', and 'dragend' events. 555 * @return {DragZoom} 556 */ 557 google.maps.Map.prototype.getDragZoomObject = function () { 558 return this.dragZoom_; 559 }; 560})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.