/*
* This class lets you add a control to the map which will let the user

*  zoom by dragging a rectangle.

*  More info on original GZoom at http://earthcode.com

*

* Back Button functionality provides the user with a one click means to return the map state 

*  to its state prior to the DragZoom.  Sequential DragZooms are backed out in reverse order.

*/



/**

 * Constructor for DragZoomControl, which takes 3 option hashes and

 *  uses them to customize the control.

 * @param {opts_boxStyle} Named optional arguments:

 *   opts_boxStyle.opacity {Number} Opacity from 0-1

 *   opts_boxStyle.fillColor {String} Hex value of fill color

 *   opts_boxStyle.border {String} CSS-style declaration of border

 * @param {opts_other} Named optional arguments:

 *   opts_other.buttonHTML {String} The zoom button HTML in non-activated state

 *   opts_other.buttonStartingStyle {Object} A hash of css styles for the 

 *     zoom button which are common to both un-activated and activated state

 *   opts_other.buttonStyle {Object} A hash of css styles for the zoom button 

 *     which will be applied when the button is in un-activated state.

 *   opts_other.rightMouseZoomOutEnabled {Boolean} Whether to zoom out when a drag

 *     with the right mouse button occurs.

 *   opts_other.buttonZoomingHTML {String} HTML which is placed in the 

 *     zoom button when the button is activated. 

 *   opts_other.buttonZoomingStyle {Object} A hash of css styles for the 

 *    zoom button which will be applied when the button is activated.

 *   opts_other.overlayRemoveTime {Number} The number of milliseconds to wait before

 *     removing the rectangle indicating the zoomed-in area after the zoom has happened.

 *   opts_other.stickyZoomEnabled {Boolean} Whether or not the control stays in 

 *     "zoom mode" until turned off. When true, the user can zoom repeatedly, 

 *     until clicking on the zoom button again to turn zoom mode off.

 *   opts_other.backButtonEnabled {Boolean} enables Back Button functionality

 *   opts_other.backButtonHTML {String} The back button HTML

 *   opts_other.backButtonStyle {Object} A hash of css styles for the back button

 *     which will be applied when the button is created.

 *   opts_other.minDragSize {Number} The minimum size of the rectangle when it is

 *     released for a zoom to happen.

 * @param {opts_callbacks} Named optional arguments:

 *   opts_callbacks.buttonclick {Function} Called when the DragZoom is activated 

 *     by clicking on the "zoom" button. 

 *   opts_callbacks.dragstart {Function} Called when user starts to drag a rectangle.

 *     Callback args are x,y -- the PIXEL values, relative to the upper-left-hand 

 *     corner of the map, where the user began dragging.

 *   opts_callbacks.dragging {Function} Called repeatedly while the user is dragging.

 *     Callback args are startX,startY, currentX,currentY -- the PIXEL values of the 

 *     start of the drag, and the current drag point, respectively.

 *   opts_callbacks.dragend {Function} Called when the user releases the mouse button 

 *     after dragging the rectangle. Callback args are: NW {GLatLng}, NE {GLatLng}, 

 *     SE {GLatLng}, SW {GLatLng}, NW {GPoint}, NE {GPoint}, SE {GPoint}, SW {GPoint}.

 *     The first 4 are the latitudes/longitudes; the last 4 are the pixel coords on the map.

 *   opts_callbacks.backbuttonclick {Function} Called when the back button is activated 

 *     after the map context is restored. Callback args: methodCall (boolean) set true if

 *     this backbuttonclick was to restore context set by the mathod call, else false.

 * Method

 *    this.saveMapContext(text) Call to push map context onto the backStack and set the button text 

 *    this.initiateZoom() Call to simulate clicking the dragZoom button

 *    this.initiateZoomBack() Call to simulate clicking the dragZoom back button

 **/

function DragZoomControl(opts_boxStyle, opts_other, opts_callbacks) {

  // Holds all information needed globally

  // Not all globals are initialized here

  this.globals = {

    draggingOn: false,

    cornerTopDiv: null,

    cornerRightDiv: null,

    cornerBottomDiv: null,

    cornerLeftDiv: null,

    mapPosition: null,

    outlineDiv: null,

    mapWidth: 0,

    mapHeight: 0,

    mapRatio: 0,

    startX: 0,

    startY: 0,

    borderCorrection: 0

  };



  //box style options

  this.globals.style = {

    opacity: .2,

    fillColor: "#000",

    border: "2px solid blue"

  };



  var style = this.globals.style;

  for (var s in opts_boxStyle) {

    style[s]=opts_boxStyle[s];

  }



  var borderStyleArray = style.border.split(' ');

  style.outlineWidth = parseInt(borderStyleArray[0].replace(/\D/g,''));

  style.outlineColor = borderStyleArray[2];

  style.alphaIE = 'alpha(opacity=' + (style.opacity * 100) + ')';

 

  // map context stack for back button

  this.globals.backStack = [];



  // Other options

  this.globals.options={

    buttonHTML: 'zoom ...',

    buttonStartingStyle: 

      {width: '52px', border: '1px solid black', padding: '2px'},

    buttonStyle: {background: '#FFF'},

    backButtonHTML: 'zoom back',

    backButtonStyle: {background: '#FFF', display: 'none'},

    buttonZoomingHTML: 'Drag a region on the map',

    buttonZoomingStyle: {background: '#FF0'},

    overlayRemoveTime: 6000,

    backButtonEnabled: false,

    stickyZoomEnabled: false,

    rightMouseZoomOutEnabled: false,

    minDragSize: 0

  };

	

  for (var s in opts_other) {

    this.globals.options[s] = opts_other[s]

  }



  // callbacks: buttonclick, dragstart, dragging, dragend, backbuttonclick 

  if (opts_callbacks == null) {

    opts_callbacks = {}

  }

  this.globals.callbacks = opts_callbacks;

};



DragZoomControl.prototype = new GControl();



/**

 * Methods

 */



/**

 * Method called to save the map context before the zoom.

 * Back Button functionality:	

 * @param {text} text string for the back button

 */

DragZoomControl.prototype.saveMapContext = function(text) {

  if (this.globals.options.backButtonEnabled) {

    this.saveBackContext_(text,true);

    this.globals.backButtonDiv.style.display = 'block';

  }	

};



/**

 * Method called to initiate a dragZoom as if the user had clicked the dragZoom button.

 */

DragZoomControl.prototype.initiateZoom = function() {this.buttonclick_()};



/**

 * Method called to initiate a dragZoom back operation as if the user had clicked the dragZoom back button.

 * Back Button functionality:	

 */

DragZoomControl.prototype.initiateZoomBack = function() {if (this.globals.options.backButtonEnabled) this.backbuttonclick_()};	



/**

 * Creates a new button to control gzoom and appends to the button container div.

 * @param {DOM Node} buttonContainerDiv created in main .initialize code

 */

DragZoomControl.prototype.initButton_ = function(buttonContainerDiv) {

  var G = this.globals;

  var buttonDiv = document.createElement('div');

  buttonDiv.innerHTML = G.options.buttonHTML;

  DragZoomUtil.style([buttonDiv], {cursor: 'pointer', zIndex:200});

  DragZoomUtil.style([buttonDiv], G.options.buttonStartingStyle);

  DragZoomUtil.style([buttonDiv], G.options.buttonStyle);

  buttonContainerDiv.appendChild(buttonDiv);

  return buttonDiv;

};



/**												

 * Creates a second new button to control backup zoom and appends to the button container div.

 * @param {DOM Node} buttonContainerDiv created in main .initialize code

 */

DragZoomControl.prototype.initBackButton_ = function(buttonContainerDiv) {

  var G = this.globals;

  var backButtonDiv = document.createElement('div');

  backButtonDiv.innerHTML = G.options.backButtonHTML;

  DragZoomUtil.style([backButtonDiv], {cursor: 'pointer', zIndex:200});

  DragZoomUtil.style([backButtonDiv], G.options.buttonStartingStyle);

  DragZoomUtil.style([backButtonDiv], G.options.backButtonStyle);

  buttonContainerDiv.appendChild(backButtonDiv);

  return backButtonDiv;

};



/**

 * Sets button mode to zooming or otherwise, changes CSS & HTML.

 * @param {String} mode Either "zooming" or not.

 */

DragZoomControl.prototype.setButtonMode_ = function(mode){

  var G = this.globals;

  if (mode == 'zooming') {

    G.buttonDiv.innerHTML = G.options.buttonZoomingHTML;

    DragZoomUtil.style([G.buttonDiv], G.options.buttonStartingStyle);

    DragZoomUtil.style([G.buttonDiv], G.options.buttonZoomingStyle);

  } else {

    G.buttonDiv.innerHTML = G.options.buttonHTML;

    DragZoomUtil.style([G.buttonDiv], G.options.buttonStartingStyle);

    DragZoomUtil.style([G.buttonDiv], G.options.buttonStyle);

  }

};



/**

 * Is called by GMap2's addOverlay method. Creates the zoom control

 * divs and appends to the map div.

 * @param {GMap2} map The map that has had this DragZoomControl added to it.

 * @return {DOM Object} Div that holds the gzoomcontrol button

 */ 

DragZoomControl.prototype.initialize = function(map) {

  var G = this.globals;

  var me = this;

  var mapDiv = map.getContainer();

 

  // Create div for both buttons	

  var buttonContainerDiv = document.createElement("div");	

  DragZoomUtil.style([buttonContainerDiv], {cursor: 'pointer', zIndex: 0});



  // create and init the zoom button

  //DOM:button

  var buttonDiv = this.initButton_(buttonContainerDiv);



  // create and init the back button				

  //DOM:button

  var backButtonDiv = this.initBackButton_(buttonContainerDiv);

  

  // Add the two buttons to the map 					

  mapDiv.appendChild(buttonContainerDiv);

 

  //DOM:map covers

  var zoomDiv = document.createElement("div");

  var DIVS_TO_CREATE = ['outlineDiv', 'cornerTopDiv', 'cornerLeftDiv', 'cornerRightDiv', 'cornerBottomDiv'];

  for (var i=0; i<DIVS_TO_CREATE.length; i++) {

    var id = DIVS_TO_CREATE[i];

    var div = document.createElement("div");

    DragZoomUtil.style([div], {position: 'absolute', display: 'none'});

    zoomDiv.appendChild(div);

    G[id] = div;

  }

  DragZoomUtil.style([zoomDiv], {position: 'absolute', display: 'none', overflow: 'hidden', cursor: 'crosshair', zIndex: 101});

  mapDiv.appendChild(zoomDiv);

  

  // add event listeners

  GEvent.addDomListener(buttonDiv, 'click', function(e) {

    me.buttonclick_(e);

  });

  GEvent.addDomListener(backButtonDiv, 'click', function(e) {

    me.backbuttonclick_(e);

  });

  GEvent.addDomListener(zoomDiv, 'mousedown', function(e) {

    me.coverMousedown_(e);

  });

  GEvent.addDomListener(document, 'mousemove', function(e) {

    me.drag_(e);

  });

  GEvent.addDomListener(document, 'mouseup', function(e) {

    me.mouseup_(e);

  });

  

  // get globals

  G.mapPosition = DragZoomUtil.getElementPosition(mapDiv);

  G.buttonDiv = buttonDiv;

  G.backButtonDiv = backButtonDiv;

  G.mapCover = zoomDiv;

  G.map = map;



  G.borderCorrection = G.style.outlineWidth * 2;	

  this.setDimensions_();

  

  //styles

  this.initStyles_();



  // disable text selection on map cover

  G.mapCover.onselectstart = function() {return false}; 

    

  return buttonContainerDiv;

};



/**

 * Required by GMaps API for controls. 

 * @return {GControlPosition} Default location for control

 */

DragZoomControl.prototype.getDefaultPosition = function() {

  return new GControlPosition(G_ANCHOR_TOP_LEFT, new GSize(3, 120));

};



/**

 * Function called when mousedown event is captured.

 * @param {Object} e 

 */

DragZoomControl.prototype.coverMousedown_ = function(e){

  var G = this.globals;

  var pos = this.getRelPos_(e);

  G.startX = pos.left;

  G.startY = pos.top;



  if (e.which) {

    var rightMouse = (e.which != 1);

  } else if (e.button) {

    var rightMouse = (e.button != 1);

  }

  G.draggingRightMouse = rightMouse;

  

  DragZoomUtil.style([G.mapCover], {background: 'transparent', opacity: 1, filter: 'alpha(opacity=100)'});

  DragZoomUtil.style([G.outlineDiv], {left: G.startX + 'px', top: G.startY + 'px', display: 'block', width: '1px', height: '1px'});

  G.draggingOn = true;



  G.cornerTopDiv.style.top = (G.startY - G.mapHeight) + 'px';

  G.cornerTopDiv.style.display ='block';

  G.cornerLeftDiv.style.left = (G.startX - G.mapWidth) +'px';

  G.cornerLeftDiv.style.top = G.startY + 'px';

  G.cornerLeftDiv.style.display = 'block';



  G.cornerRightDiv.style.left = G.startX + 'px';

  G.cornerRightDiv.style.top = G.startY + 'px';

  G.cornerRightDiv.style.display = 'block';

  G.cornerBottomDiv.style.left = G.startX + 'px';

  G.cornerBottomDiv.style.top = G.startY + 'px';

  G.cornerBottomDiv.style.width = '0px';

  G.cornerBottomDiv.style.display = 'block';



  // invoke the callback if provided

  if (G.callbacks.dragstart != null) {

    G.callbacks.dragstart(G.startX, G.startY);

  }



  return false;

};



/**

 * Function called when drag event is captured

 * @param {Object} e 

 */

DragZoomControl.prototype.drag_ = function(e){

  var G = this.globals;

  if(G.draggingOn) {

    var pos = this.getRelPos_(e);

    var rect = this.getRectangle_(G.startX, G.startY, pos, G.mapRatio);



    if (rect.left) {

      addX = -rect.width;			

    } else { 

      addX = 0;

    }



    if (rect.top) {

      addY = -rect.height;

    } else {

      addY = 0;

    }



    DragZoomUtil.style([G.outlineDiv], {left: G.startX + addX + 'px', top: G.startY + addY + 'px', display: 'block', width: '1px', height: '1px'});	

	

    G.outlineDiv.style.width = rect.width + "px";

    G.outlineDiv.style.height = rect.height + "px";



    G.cornerTopDiv.style.height = ((G.startY + addY) - (G.startY - G.mapHeight)) + 'px';

    G.cornerLeftDiv.style.top = (G.startY + addY) + 'px';

    G.cornerLeftDiv.style.width = ((G.startX + addX) - (G.startX - G.mapWidth)) + 'px';

    G.cornerRightDiv.style.top = G.cornerLeftDiv.style.top;

    G.cornerRightDiv.style.left = (G.startX + addX + rect.width + G.borderCorrection) + 'px';

    G.cornerBottomDiv.style.top = (G.startY + addY + rect.height + G.borderCorrection) + 'px';

    G.cornerBottomDiv.style.left = (G.startX - G.mapWidth + ((G.startX + addX) - (G.startX - G.mapWidth))) + 'px';

    G.cornerBottomDiv.style.width = (rect.width + G.borderCorrection) + 'px';

		

    // invoke callback if provided

    if (G.callbacks.dragging != null) {

      G.callbacks.dragging(G.startX, G.startY, rect.endX, rect.endY)

    }

		

    return false;

  }  

};



/** 

 * Function called when mouseup event is captured

 * @param {Event} e

 */

DragZoomControl.prototype.mouseup_ = function(e){

  var G = this.globals;

  if (G.draggingOn) {

    var pos = this.getRelPos_(e);

    G.draggingOn = false;

    

    var rect = this.getRectangle_(G.startX, G.startY, pos, G.mapRatio);



    if (rect.left) rect.endX = rect.startX - rect.width;

    if (rect.top) rect.endY = rect.startY - rect.height;

	

    this.resetDragZoom_();



    if (rect.width >= G.options.minDragSize && rect.height >= G.options.minDragSize) {

      var nwpx = new GPoint(rect.startX, rect.startY);

      var nepx = new GPoint(rect.endX, rect.startY);

      var sepx = new GPoint(rect.endX, rect.endY);

      var swpx = new GPoint(rect.startX, rect.endY);

      var nw = G.map.fromContainerPixelToLatLng(nwpx); 

      var ne = G.map.fromContainerPixelToLatLng(nepx); 

      var se = G.map.fromContainerPixelToLatLng(sepx); 

      var sw = G.map.fromContainerPixelToLatLng(swpx); 



      var zoomAreaPoly = new GPolyline([nw, ne, se, sw, nw], G.style.outlineColor, G.style.outlineWidth + 1,.4);



      try{

        G.map.addOverlay(zoomAreaPoly);

        setTimeout (function() {G.map.removeOverlay(zoomAreaPoly)}, G.options.overlayRemoveTime);  

      }catch(e) {}



      var polyBounds = zoomAreaPoly.getBounds();

      var ne = polyBounds.getNorthEast();

      var sw = polyBounds.getSouthWest();

      var se = new GLatLng(sw.lat(), ne.lng());

      var nw = new GLatLng(ne.lat(), sw.lng());

      if (G.options.rightMouseZoomOutEnabled && G.draggingRightMouse) {

        var mapSpan = G.map.getBounds().toSpan();

        var polySpan = polyBounds.toSpan();

        var dSize = Math.max(mapSpan.lat()/polySpan.lat(), mapSpan.lng()/polySpan.lng());

        var zoomLevel = G.map.getZoom() - Math.ceil(Math.log(dSize, 2));

      } else {

        var zoomLevel = G.map.getBoundsZoomLevel(polyBounds);

      }

      var center = polyBounds.getCenter();

      G.map.setCenter(center, zoomLevel);



      // invoke callback if provided

      if (G.callbacks.dragend != null) {

        G.callbacks.dragend(nw, ne, se, sw, nwpx, nepx, sepx, swpx);

      }

    }



    //re-init if sticky

    if (G.options.stickyZoomEnabled) {

      //GLog.write("stickyZoomEnabled, re-initting");

      this.initCover_();

      if (G.options.backButtonEnabled) this.saveBackContext_(G.options.backButtonHTML,false); // save the map context for back button

      G.backButtonDiv.style.display='none';

    }

  }

};



/**

 * Set the cover sizes according to the size of the map

 */

DragZoomControl.prototype.setDimensions_ = function() {

  var G = this.globals;

  var mapSize = G.map.getSize();

  G.mapWidth  = mapSize.width;

  G.mapHeight = mapSize.height;

  G.mapRatio  = G.mapHeight / G.mapWidth;

  // set left:0px in next <div>s in case we inherit text-align:center from map <div> in IE.

  DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv], 

      {top: '0px', left: '0px', width: G.mapWidth + 'px', height: G.mapHeight +'px'});

};



/**

 * Initializes styles based on global parameters

 */

DragZoomControl.prototype.initStyles_ = function(){

  var G = this.globals;

  DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv], 

    {filter: G.style.alphaIE, opacity: G.style.opacity, background:G.style.fillColor});

  G.outlineDiv.style.border = G.style.border;  

};



/**

 * Function called when the zoom button's click event is captured.

 */

DragZoomControl.prototype.buttonclick_ = function(){

  var G = this.globals;	

  G.backButtonDiv.style.display='none';

  if (G.mapCover.style.display == 'block') { // reset if clicked before dragging 

    this.resetDragZoom_();

    if (G.options.backButtonEnabled) {  

      this.restoreBackContext_();  // pop the backStack on a button reset

      if (G.backStack.length==0) G.backButtonDiv.style.display='none';

    }

  } else {

    this.initCover_();

    if ( G.options.backButtonEnabled ) this.saveBackContext_(G.options.backButtonHTML,false); // save the map context for back button

  }

};



/**

 * Back Button functionality:	

 * Function called when the back button's click event is captured.

 * calls the function to set the map context back to where it was before the zoom.

 */

DragZoomControl.prototype.backbuttonclick_ = function(){

  var G = this.globals;	

  if (G.options.backButtonEnabled && G.backStack.length > 0) {

    this.restoreBackContext_();

    // invoke the callback if provided

    if (G.callbacks['backbuttonclick'] != null) {

      G.callbacks.backbuttonclick(G.methodCall);

    }

  }

};



/** 

 * Back Button functionality:	

 * Saves the map context and pushes it on the backStack for later use by the back button

 */

DragZoomControl.prototype.saveBackContext_ = function(text,methodCall) {

  var G = this.globals;

  var backFrame = {};

  backFrame["center"] = G.map.getCenter();

  backFrame["zoom"] = G.map.getZoom();

  backFrame["maptype"] = G.map.getCurrentMapType();

  backFrame["text"] = G.backButtonDiv.innerHTML; // this saves the previous button text

  backFrame["methodCall"] = methodCall; //This determines if it was an internal or method call

  G.backStack.push(backFrame);

  G.backButtonDiv.innerHTML = text;

  // Back Button is turned on in resetDragZoom_()

};



/** 

 * Back Button functionality:	

 * Pops the previous map context off of the backStack and restores the map to that context

 */

DragZoomControl.prototype.restoreBackContext_ = function() {

  var G = this.globals;

  var backFrame = G.backStack.pop();

  G.map.setCenter(backFrame["center"],backFrame["zoom"],backFrame["maptype"]);

  G.backButtonDiv.innerHTML = backFrame["text"];

  G.methodCall = backFrame["methodCall"];

  if (G.backStack.length==0) G.backButtonDiv.style.display = 'none'; // if we're at the top of the stack, hide the back botton

};



/**

 * Shows the cover over the map

 */

DragZoomControl.prototype.initCover_ = function(){

  var G = this.globals;

  G.mapPosition = DragZoomUtil.getElementPosition(G.map.getContainer());

  this.setDimensions_();

  this.setButtonMode_('zooming');

  DragZoomUtil.style([G.mapCover], {display: 'block', background: G.style.fillColor});

  DragZoomUtil.style([G.outlineDiv], {width: '0px', height: '0px'});



  //invoke callback if provided

  if(G.callbacks['buttonclick'] != null){

    G.callbacks.buttonclick();

  }

};



/**

 * Gets position of the mouse relative to the map

 * @param {Object} e

 */

DragZoomControl.prototype.getRelPos_ = function(e) {

  var pos = DragZoomUtil.getMousePosition(e);

  var G = this.globals;

  return {top: (pos.top - G.mapPosition.top), 

          left: (pos.left - G.mapPosition.left)};

};



/**

 * Figures out the rectangle the user's trying to draw

 * @param {Number} startX 

 * @param {Number} startY

 * @param {Object} pos

 * @param {Number} ratio

 * @return {Object} Describes the rectangle

 */

DragZoomControl.prototype.getRectangle_ = function(startX, startY, pos, ratio){

  var left = false;

  var top = false;

  var dX = pos.left - startX;

  var dY = pos.top - startY;	

  if (dX < 0) {

    dX = dX * -1;

    left = true;

  }

  if (dY < 0) {

    dY = dY * -1;

    top = true;

  }

  delta = dX > dY ? dX : dY;



  return {

    startX: startX,

    startY: startY,

    endX: startX + delta,

    endY: startY + parseInt(delta * ratio),

    width: delta,

    height: parseInt(delta * ratio),

    left:left,

    top:top

  }

};



/** 

 * Resets CSS and button display when drag zoom done

 */

DragZoomControl.prototype.resetDragZoom_ = function() {

  var G = this.globals;

  DragZoomUtil.style([G.mapCover, G.cornerTopDiv, G.cornerRightDiv, G.cornerBottomDiv, G.cornerLeftDiv], 

    {display: 'none', opacity: G.style.opacity, filter: G.style.alphaIE});

  G.outlineDiv.style.display = 'none';	

  this.setButtonMode_('normal');

  if (G.options.backButtonEnabled  && (G.backStack.length > 0)) G.backButtonDiv.style.display = 'block'; // show the back button

};



/* utility functions in DragZoomUtil.namespace */

var DragZoomUtil={};



/**

 * Alias function for getting element by id

 * @param {String} sId

 * @return {Object} DOM object with sId id

 */

DragZoomUtil.gE = function(sId) {

  return document.getElementById(sId);

};



/**

 * A general-purpose function to get the absolute position

 * of the mouse.

 * @param {Object} e  Mouse event

 * @return {Object} Describes position

 */

DragZoomUtil.getMousePosition = function(e) {

  var posX = 0;

  var posY = 0;

  if (!e) var e = window.event;

  if (e.pageX || e.pageY) {

    posX = e.pageX;

    posY = e.pageY;

  } else if (e.clientX || e.clientY){

    posX = e.clientX + 

      (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);

    posY = e.clientY + 

      (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);

  }

  return {left: posX, top: posY};  

};



/**

 * Gets position of element

 * @param {Object} element

 * @return {Object} Describes position

 */

DragZoomUtil.getElementPosition = function(element) {

  var leftPos = element.offsetLeft;          // initialize var to store calculations

  var topPos = element.offsetTop;            // initialize var to store calculations

  var parElement = element.offsetParent;     // identify first offset parent element  

  while (parElement != null ) {                // move up through element hierarchy

    leftPos += parElement.offsetLeft;      // appending left offset of each parent

    topPos += parElement.offsetTop;  

    parElement = parElement.offsetParent;  // until no more offset parents exist

  }

  return {left: leftPos, top: topPos};

};



/**

 * Applies styles to DOM objects 

 * @param {String/Object} elements Either comma-delimited list of ids 

 *   or an array of DOM objects

 * @param {Object} styles Hash of styles to be applied

 */

DragZoomUtil.style = function(elements, styles){

  if (typeof(elements) == 'string') {

    elements = DragZoomUtil.getManyElements(elements);

  }

  for (var i = 0; i < elements.length; i++){

    for (var s in styles) { 

      elements[i].style[s] = styles[s];

    }

  }

};



/**

 * Gets DOM elements array according to list of IDs

 * @param {String} elementsString Comma-delimited list of IDs

 * @return {Array} Array of DOM elements corresponding to s

 */

DragZoomUtil.getManyElements = function(idsString){		

  var idsArray = idsString.split(',');

  var elements = [];

  for (var i = 0; i < idsArray.length; i++){

    elements[elements.length] = DragZoomUtil.gE(idsArray[i])

  };

  return elements;

};


