home *** CD-ROM | disk | FTP | other *** search
/ Image Zone / ImageZone.iso / setup.exe / Graphics / html / js / extinfowindow.js < prev    next >
Encoding:
JavaScript  |  2009-12-10  |  23.9 KB  |  655 lines

  1. /*
  2. * ExtInfoWindow Class, v1.0 
  3. *  Copyright (c) 2007, Joe Monahan (http://www.seejoecode.com)
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *       http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. *
  15. * This class lets you add an info window to the map which mimics GInfoWindow
  16. * and allows for users to skin it via CSS.  Additionally it has options to
  17. * pull in HTML content from an ajax request, triggered when a user clicks on
  18. * the associated marker.
  19. */
  20.  
  21.  
  22. /**
  23.  * Creates a new ExtInfoWindow that will initialize by reading styles from css
  24.  *
  25.  * @constructor
  26.  * @param {GMarker} marker The marker associated with the info window
  27.  * @param {String} windowId The DOM Id we will use to reference the info window
  28.  * @param {String} html The HTML contents
  29.  * @param {Object} opt_opts A contianer for optional arguments:
  30.  *    {String} ajaxUrl The Url to hit on the server to request some contents 
  31.  *    {Number} paddingX The padding size in pixels that the info window will leave on 
  32.  *                    the left and right sides of the map when panning is involved.
  33.  *    {Number} paddingY The padding size in pixels that the info window will leave on 
  34.  *                    the top and bottom sides of the map when panning is involved.
  35.  *    {Number} beakOffset The repositioning offset for when aligning the beak element. 
  36.  *                    This is used to make sure the beak lines up correcting if the 
  37.  *                    info window styling containers a border.
  38.  */
  39. function ExtInfoWindow(marker, windowId, html, opt_opts) {
  40.   this.html_ = html;
  41.   this.marker_ = marker;
  42.   this.infoWindowId_ = windowId;
  43.  
  44.   this.options_ = opt_opts == null ? {} : opt_opts;
  45.   this.ajaxUrl_ = this.options_.ajaxUrl == null ? null : this.options_.ajaxUrl;
  46.   this.callback_ = this.options_.ajaxCallback == null ? null : this.options_.ajaxCallback;
  47.  
  48.   this.borderSize_ = this.options_.beakOffset == null ? 0 : this.options_.beakOffset;
  49.   this.paddingX_ = this.options_.paddingX == null ? 0 + this.borderSize_ : this.options_.paddingX + this.borderSize_;
  50.   this.paddingY_ = this.options_.paddingY == null ? 0 + this.borderSize_ : this.options_.paddingY + this.borderSize_;
  51.  
  52.   this.map_ = null;
  53.  
  54.   this.container_ = document.createElement('div');
  55.   this.container_.style.position = 'relative';
  56.   this.container_.style.display = 'none';
  57.  
  58.   this.contentDiv_ = document.createElement('div');
  59.   this.contentDiv_.id = this.infoWindowId_ + '_contents';
  60.   this.contentDiv_.innerHTML = this.html_;
  61.   this.contentDiv_.style.display = 'block';
  62.   this.contentDiv_.style.visibility = 'hidden';
  63.  
  64.   this.wrapperDiv_ = document.createElement('div');
  65. };
  66.  
  67. //use the GOverlay class
  68. ExtInfoWindow.prototype = new GOverlay();
  69.  
  70. /**
  71.  * Called by GMap2's addOverlay method.  Creates the wrapping div for our info window and adds
  72.  * it to the relevant map pane.  Also binds mousedown event to a private function so that they
  73.  * are not passed to the underlying map.  Finally, performs ajax request if set up to use ajax
  74.  * in the constructor.
  75.  * @param {GMap2} map The map that has had this extInfoWindow is added to it.
  76.  */
  77. ExtInfoWindow.prototype.initialize = function(map) {
  78.   this.map_ = map;
  79.  
  80.   this.defaultStyles = {
  81.     containerWidth: this.map_.getSize().width / 2,
  82.     borderSize: 1
  83.   };
  84.  
  85.   this.wrapperParts = {
  86.     tl:{t:0, l:0, w:0, h:0, domElement: null},
  87.     t:{t:0, l:0, w:0, h:0, domElement: null},
  88.     tr:{t:0, l:0, w:0, h:0, domElement: null},
  89.     l:{t:0, l:0, w:0, h:0, domElement: null},
  90.     r:{t:0, l:0, w:0, h:0, domElement: null},
  91.     bl:{t:0, l:0, w:0, h:0, domElement: null},
  92.     b:{t:0, l:0, w:0, h:0, domElement: null},
  93.     br:{t:0, l:0, w:0, h:0, domElement: null},
  94.     beak:{t:0, l:0, w:0, h:0, domElement: null},
  95.     close:{t:0, l:0, w:0, h:0, domElement: null}
  96.   };
  97.  
  98.   for (var i in this.wrapperParts ) {
  99.     var tempElement = document.createElement('div');
  100.     tempElement.id = this.infoWindowId_ + '_' + i;
  101.     tempElement.style.visibility = 'hidden';
  102.     document.body.appendChild(tempElement);
  103.     tempElement = document.getElementById(this.infoWindowId_ + '_' + i);
  104.     var tempWrapperPart = eval('this.wrapperParts.' + i);    
  105.     tempWrapperPart.w = parseInt(this.getStyle_(tempElement, 'width'));
  106.     tempWrapperPart.h = parseInt(this.getStyle_(tempElement, 'height'));
  107.     document.body.removeChild(tempElement);
  108.   }
  109.   for (var i in this.wrapperParts) {
  110.     if (i == 'close' ) {
  111.       //first append the content so the close button is layered above it
  112.       this.wrapperDiv_.appendChild(this.contentDiv_);
  113.     }
  114.     var wrapperPartsDiv = null;
  115.     if (this.wrapperParts[i].domElement == null) {
  116.       wrapperPartsDiv = document.createElement('div');
  117.       this.wrapperDiv_.appendChild(wrapperPartsDiv);
  118.     } else {
  119.       wrapperPartsDiv = this.wrapperParts[i].domElement;
  120.     }
  121.     wrapperPartsDiv.id = this.infoWindowId_ + '_' + i;
  122.     wrapperPartsDiv.style.position = 'absolute';
  123.     wrapperPartsDiv.style.width = this.wrapperParts[i].w + 'px';
  124.     wrapperPartsDiv.style.height = this.wrapperParts[i].h + 'px';
  125.     wrapperPartsDiv.style.top = this.wrapperParts[i].t + 'px';
  126.     wrapperPartsDiv.style.left = this.wrapperParts[i].l + 'px';
  127.     this.wrapperParts[i].domElement = wrapperPartsDiv;
  128.   }
  129.   
  130.   this.map_.getPane(G_MAP_FLOAT_PANE).appendChild(this.container_);
  131.   this.container_.id = this.infoWindowId_;
  132.   var containerWidth  = this.getStyle_(document.getElementById(this.infoWindowId_), 'width');
  133.   this.container_.style.width = (containerWidth == null ? this.defaultStyles.containerWidth : containerWidth);
  134.  
  135.   this.map_.getContainer().appendChild(this.contentDiv_);
  136.   this.contentWidth = this.getDimensions_(this.container_).width;
  137.   this.contentDiv_.style.width = this.contentWidth + 'px';
  138.   this.contentDiv_.style.position = 'absolute';
  139.  
  140.   this.container_.appendChild(this.wrapperDiv_);
  141.  
  142.   GEvent.bindDom(this.container_, 'mousedown', this,this.onClick_);
  143.   GEvent.bindDom(this.container_, 'dblclick', this,this.onClick_);
  144.   GEvent.bindDom(this.container_, 'DOMMouseScroll', this, this.onClick_);
  145.   
  146.  
  147.   GEvent.trigger(this.map_, 'extinfowindowopen');
  148.   if (this.ajaxUrl_ != null ) {
  149.     this.ajaxRequest_(this.ajaxUrl_);
  150.   }
  151. };
  152.  
  153. /**
  154.  * Private function to steal mouse click events to prevent it from returning to the map.
  155.  * Without this links in the ExtInfoWindow would not work, and you could click to zoom or drag 
  156.  * the map behind it.
  157.  * @private
  158.  * @param {MouseEvent} e The mouse event caught by this function
  159.  */
  160. ExtInfoWindow.prototype.onClick_ = function(e) {
  161.   if(navigator.userAgent.toLowerCase().indexOf('msie') != -1 && document.all) {
  162.     window.event.cancelBubble = true;
  163.     window.event.returnValue = false;
  164.   } else {
  165.     //e.preventDefault();
  166.     e.stopPropagation();
  167.   }
  168. };
  169.  
  170. /**
  171.  * Remove the extInfoWindow container from the map pane. 
  172.  */
  173. ExtInfoWindow.prototype.remove = function() {
  174.   if (this.map_.getExtInfoWindow() != null) {
  175.     GEvent.trigger(this.map_, 'extinfowindowbeforeclose');
  176.     
  177.     GEvent.clearInstanceListeners(this.container_);
  178.     if (this.container_.outerHTML) {
  179.       this.container_.outerHTML = ''; //prevent pseudo-leak in IE
  180.     }
  181.     if (this.container_.parentNode) {
  182.       this.container_.parentNode.removeChild(this.container_);
  183.     }
  184.     this.container_ = null;
  185.     GEvent.trigger(this.map_, 'extinfowindowclose');
  186.     this.map_.setExtInfoWindow_(null);
  187.   }
  188. };
  189.  
  190. /**
  191.  * Return a copy of this overlay, for the parent Map to duplicate itself in full. This
  192.  * is part of the Overlay interface and is used, for example, to copy everything in the 
  193.  * main view into the mini-map.
  194.  * @return {GOverlay}
  195.  */
  196. ExtInfoWindow.prototype.copy = function() {
  197.   return new ExtInfoWindow(this.marker_, this.infoWindowId_, this.html_, this.options_);
  198. };
  199.  
  200. /**
  201.  * Draw extInfoWindow and wrapping decorators onto the map.  Resize and reposition
  202.  * the map as necessary. 
  203.  * @param {Boolean} force Will be true when pixel coordinates need to be recomputed.
  204.  */
  205. ExtInfoWindow.prototype.redraw = function(force) {
  206.   if (!force || this.container_ == null) return;
  207.  
  208.   //set the content section's height, needed so  browser font resizing does not affect the window's dimensions
  209.   var contentHeight = this.contentDiv_.offsetHeight;
  210.   this.contentDiv_.style.height = contentHeight + 'px';
  211.  
  212.   //reposition contents depending on wrapper parts.
  213.   //this is necessary for content that is pulled in via ajax
  214.   this.contentDiv_.style.left = this.wrapperParts.l.w + 'px';
  215.   this.contentDiv_.style.top = this.wrapperParts.tl.h + 'px';
  216.   this.contentDiv_.style.visibility = 'visible';
  217.  
  218.   //Finish configuring wrapper parts that were not set in initialization
  219.   this.wrapperParts.tl.t = 0;
  220.   this.wrapperParts.tl.l = 0;
  221.   this.wrapperParts.t.l = this.wrapperParts.tl.w;
  222.   this.wrapperParts.t.w = (this.wrapperParts.l.w + this.contentWidth + this.wrapperParts.r.w) - this.wrapperParts.tl.w - this.wrapperParts.tr.w;
  223.   this.wrapperParts.t.h = this.wrapperParts.tl.h;
  224.   this.wrapperParts.tr.l = this.wrapperParts.t.w + this.wrapperParts.tl.w;
  225.   this.wrapperParts.l.t = this.wrapperParts.tl.h;
  226.   this.wrapperParts.l.h = contentHeight;
  227.   this.wrapperParts.r.l = this.contentWidth + this.wrapperParts.l.w;
  228.   this.wrapperParts.r.t = this.wrapperParts.tr.h;
  229.   this.wrapperParts.r.h = contentHeight;
  230.   this.wrapperParts.bl.t = contentHeight + this.wrapperParts.tl.h;
  231.   this.wrapperParts.b.l = this.wrapperParts.bl.w;
  232.   this.wrapperParts.b.t = contentHeight + this.wrapperParts.tl.h;
  233.   this.wrapperParts.b.w = (this.wrapperParts.l.w + this.contentWidth + this.wrapperParts.r.w) - this.wrapperParts.bl.w - this.wrapperParts.br.w;
  234.   this.wrapperParts.b.h = this.wrapperParts.bl.h;
  235.   this.wrapperParts.br.l = this.wrapperParts.b.w + this.wrapperParts.bl.w;
  236.   this.wrapperParts.br.t = contentHeight + this.wrapperParts.tr.h;
  237.   this.wrapperParts.close.l = this.wrapperParts.tr.l +this.wrapperParts.tr.w - this.wrapperParts.close.w - this.borderSize_;
  238.   this.wrapperParts.close.t = this.borderSize_;
  239.   this.wrapperParts.beak.l = this.borderSize_ + (this.contentWidth / 2) - (this.wrapperParts.beak.w / 2);
  240.   this.wrapperParts.beak.t = this.wrapperParts.bl.t + this.wrapperParts.bl.h - this.borderSize_;
  241.  
  242.   //create the decoration wrapper DOM objects
  243.   //append the styled info window to the container
  244.   for (var i in this.wrapperParts) {
  245.     if (i == 'close' ) {
  246.       //first append the content so the close button is layered above it
  247.       this.wrapperDiv_.insertBefore(this.contentDiv_, this.wrapperParts[i].domElement);
  248.     }
  249.     var wrapperPartsDiv = null;
  250.     if (this.wrapperParts[i].domElement == null) {
  251.       wrapperPartsDiv = document.createElement('div');
  252.       this.wrapperDiv_.appendChild(wrapperPartsDiv);
  253.     } else {
  254.       wrapperPartsDiv = this.wrapperParts[i].domElement;
  255.     }
  256.     wrapperPartsDiv.id = this.infoWindowId_ + '_' + i;
  257.     wrapperPartsDiv.style.position='absolute';
  258.     wrapperPartsDiv.style.width = this.wrapperParts[i].w + 'px';
  259.     wrapperPartsDiv.style.height = this.wrapperParts[i].h + 'px';
  260.     wrapperPartsDiv.style.top = this.wrapperParts[i].t + 'px';
  261.     wrapperPartsDiv.style.left = this.wrapperParts[i].l + 'px';
  262.     this.wrapperParts[i].domElement = wrapperPartsDiv;
  263.   }
  264.  
  265.   //add event handler for the close box
  266.   var currentMarker = this.marker_;
  267.   var thisMap = this.map_;
  268.   GEvent.addDomListener(this.wrapperParts.close.domElement, 'click', 
  269.     function() {
  270.       thisMap.closeExtInfoWindow();
  271.     }
  272.   );
  273.  
  274.   //position the container on the map, over the marker
  275.   var pixelLocation = this.map_.fromLatLngToDivPixel(this.marker_.getPoint());
  276.   this.container_.style.position = 'absolute';
  277.   var markerIcon = this.marker_.getIcon();
  278.   this.container_.style.left = (pixelLocation.x 
  279.     - (this.contentWidth / 2) 
  280.     - markerIcon.iconAnchor.x 
  281.     + markerIcon.infoWindowAnchor.x
  282.   ) + 'px';
  283.  
  284.   this.container_.style.top = (pixelLocation.y
  285.     - this.wrapperParts.bl.h
  286.     - contentHeight
  287.     - this.wrapperParts.tl.h
  288.     - this.wrapperParts.beak.h
  289.     - markerIcon.iconAnchor.y
  290.     + markerIcon.infoWindowAnchor.y
  291.     + this.borderSize_
  292.   ) + 'px';
  293.  
  294.   this.container_.style.display = 'block';
  295.  
  296.   if(this.map_.getExtInfoWindow() != null) {
  297.     this.repositionMap_();
  298.   }
  299. };
  300.  
  301. /**
  302.  * Determine the dimensions of the contents to recalculate and reposition the 
  303.  * wrapping decorator elements accordingly.
  304.  */
  305. ExtInfoWindow.prototype.resize = function(){
  306.   
  307.   //Create temporary DOM node for new contents to get new height
  308.   //This is done because if you manipulate this.contentDiv_ directly it causes visual errors in IE6
  309.   var tempElement = this.contentDiv_.cloneNode(true);
  310.   tempElement.id = this.infoWindowId_ + '_tempContents';
  311.   tempElement.style.visibility = 'hidden';    
  312.   tempElement.style.height = 'auto';
  313.   document.body.appendChild(tempElement);
  314.   tempElement = document.getElementById(this.infoWindowId_ + '_tempContents');
  315.   var contentHeight = tempElement.offsetHeight;
  316.   document.body.removeChild(tempElement);
  317.  
  318.   //Set the new height to eliminate visual defects that can be caused by font resizing in browser
  319.   this.contentDiv_.style.height = contentHeight + 'px';
  320.  
  321.   var contentWidth = this.contentDiv_.offsetWidth;
  322.   var pixelLocation = this.map_.fromLatLngToDivPixel(this.marker_.getPoint());
  323.  
  324.   var oldWindowHeight = this.wrapperParts.t.domElement.offsetHeight + this.wrapperParts.l.domElement.offsetHeight + this.wrapperParts.b.domElement.offsetHeight;    
  325.   var oldWindowPosTop = this.wrapperParts.t.domElement.offsetTop;
  326.  
  327.   //resize info window to look correct for new height
  328.   this.wrapperParts.l.domElement.style.height = contentHeight + 'px';
  329.   this.wrapperParts.r.domElement.style.height = contentHeight + 'px';
  330.   var newPosTop = this.wrapperParts.b.domElement.offsetTop - contentHeight;
  331.   this.wrapperParts.l.domElement.style.top = newPosTop + 'px';
  332.   this.wrapperParts.r.domElement.style.top = newPosTop + 'px';
  333.   this.contentDiv_.style.top = newPosTop + 'px';
  334.   windowTHeight = parseInt(this.wrapperParts.t.domElement.style.height);
  335.   newPosTop -= windowTHeight;
  336.   this.wrapperParts.close.domElement.style.top = newPosTop + this.borderSize_ + 'px';
  337.   this.wrapperParts.tl.domElement.style.top = newPosTop + 'px';
  338.   this.wrapperParts.t.domElement.style.top = newPosTop + 'px';
  339.   this.wrapperParts.tr.domElement.style.top = newPosTop + 'px';
  340.  
  341.   this.repositionMap_();
  342. };
  343.  
  344. /**
  345.  * Check to see if the displayed extInfoWindow is positioned off the viewable 
  346.  * map region and by how much.  Use that information to pan the map so that 
  347.  * the extInfoWindow is completely displayed.
  348.  * @private
  349.  */
  350. ExtInfoWindow.prototype.repositionMap_ = function(){
  351.   //pan if necessary so it shows on the screen
  352.   var mapNE = this.map_.fromLatLngToDivPixel(
  353.     this.map_.getBounds().getNorthEast()
  354.   );
  355.   var mapSW = this.map_.fromLatLngToDivPixel(
  356.     this.map_.getBounds().getSouthWest()
  357.   );
  358.   var markerPosition = this.map_.fromLatLngToDivPixel(
  359.     this.marker_.getPoint()
  360.   );
  361.  
  362.   var panX = 0;
  363.   var panY = 0;
  364.   var paddingX = this.paddingX_;
  365.   var paddingY = this.paddingY_;
  366.   var infoWindowAnchor = this.marker_.getIcon().infoWindowAnchor;
  367.   var iconAnchor = this.marker_.getIcon().iconAnchor;
  368.  
  369.   //test top of screen    
  370.   var windowT = this.wrapperParts.t.domElement;
  371.   var windowL = this.wrapperParts.l.domElement;
  372.   var windowB = this.wrapperParts.b.domElement;
  373.   var windowR = this.wrapperParts.r.domElement;
  374.   var windowBeak = this.wrapperParts.beak.domElement;
  375.  
  376.   var offsetTop = markerPosition.y - ( -infoWindowAnchor.y + iconAnchor.y +  this.getDimensions_(windowBeak).height + this.getDimensions_(windowB).height + this.getDimensions_(windowL).height + this.getDimensions_(windowT).height + this.paddingY_);
  377.   if (offsetTop < mapNE.y) {
  378.     panY = mapNE.y - offsetTop;
  379.   } else {
  380.     //test bottom of screen
  381.     var offsetBottom = markerPosition.y + this.paddingY_;
  382.     if (offsetBottom >= mapSW.y) {
  383.       panY = -(offsetBottom - mapSW.y);
  384.     }
  385.   }
  386.  
  387.   //test right of screen
  388.   var offsetRight = Math.round(markerPosition.x + this.getDimensions_(this.container_).width/2 + this.getDimensions_(windowR).width + this.paddingX_ + infoWindowAnchor.x - iconAnchor.x);
  389.   if (offsetRight > mapNE.x) {
  390.     panX = -( offsetRight - mapNE.x);
  391.   } else {
  392.     //test left of screen
  393.     var offsetLeft = - (Math.round( (this.getDimensions_(this.container_).width/2 - this.marker_.getIcon().iconSize.width/2) + this.getDimensions_(windowL).width + this.borderSize_ + this.paddingX_) - markerPosition.x - infoWindowAnchor.x + iconAnchor.x);
  394.     if( offsetLeft < mapSW.x) {
  395.       panX = mapSW.x - offsetLeft;
  396.     }
  397.   }
  398.  
  399.   if (panX != 0 || panY != 0 && this.map_.getExtInfoWindow() != null ) {
  400.     this.map_.panBy(new GSize(panX,panY));
  401.   }
  402. };
  403.  
  404. /**
  405.  * Private function that handles performing an ajax request to the server.  The response
  406.  * information is assumed to be HTML and is placed inside this extInfoWindow's contents region.
  407.  * Last, check to see if the height has changed, and resize the extInfoWindow accordingly.
  408.  * @private
  409.  * @param {String} url The Url of where to make the ajax request on the server
  410.  */
  411. ExtInfoWindow.prototype.ajaxRequest_ = function(url){
  412.   var thisMap = this.map_;
  413.   var thisCallback = this.callback_;
  414.   GDownloadUrl(url, function(response, status){
  415.     var infoWindow = document.getElementById(thisMap.getExtInfoWindow().infoWindowId_ + '_contents');
  416.     if (response == null || status == -1 ) {
  417.       infoWindow.innerHTML = '<span class="error">ERROR: The Ajax request failed to get HTML content from "' + url + '"</span>';
  418.     } else {
  419.       infoWindow.innerHTML = response;
  420.     }
  421.     if (thisCallback != null ) {
  422.       thisCallback();
  423.     }
  424.     thisMap.getExtInfoWindow().resize();
  425.     GEvent.trigger(thisMap, 'extinfowindowupdate');
  426.   });
  427. };
  428.  
  429. /**
  430.  * Private function derived from Prototype.js to get a given element's
  431.  * height and width
  432.  * @private
  433.  * @param {Object} element The DOM element that will have height and 
  434.  *                    width will be calculated for it.
  435.  * @return {Object} Object with keys: width, height
  436.  */
  437. ExtInfoWindow.prototype.getDimensions_ = function(element) {
  438.   var display = this.getStyle_(element, 'display');
  439.   if (display != 'none' && display != null) { // Safari bug
  440.     return {width: element.offsetWidth, height: element.offsetHeight};
  441.   }
  442.  
  443.   // All *Width and *Height properties give 0 on elements with display none,
  444.   // so enable the element temporarily
  445.   var els = element.style;
  446.   var originalVisibility = els.visibility;
  447.   var originalPosition = els.position;
  448.   var originalDisplay = els.display;
  449.   els.visibility = 'hidden';
  450.   els.position = 'absolute';
  451.   els.display = 'block';
  452.   var originalWidth = element.clientWidth;
  453.   var originalHeight = element.clientHeight;
  454.   els.display = originalDisplay;
  455.   els.position = originalPosition;
  456.   els.visibility = originalVisibility;
  457.   return {width: originalWidth, height: originalHeight};
  458. };
  459.  
  460. /**
  461.  * Private function derived from Prototype.js to get a given element's
  462.  * value that is associated with the passed style
  463.  * @private
  464.  * @param {Object} element The DOM element that will be checked.
  465.  * @param {String} style The style name that will be have it's value returned.
  466.  * @return {Object}
  467.  */
  468. ExtInfoWindow.prototype.getStyle_ = function(element, style) {
  469.   var found = false;
  470.   style = this.camelize_(style);
  471.   var value = element.style[style];
  472.   if (!value) {
  473.     if (document.defaultView && document.defaultView.getComputedStyle) {
  474.       var css = document.defaultView.getComputedStyle(element, null);
  475.       value = css ? css[style] : null;
  476.     } else if (element.currentStyle) {
  477.       value = element.currentStyle[style];
  478.     }
  479.   }
  480.   if((value == 'auto') && (style == 'width' || style == 'height') && (this.getStyle_(element, 'display') != 'none')) {
  481.     if( style == 'width' ) {
  482.       value = element.offsetWidth;
  483.     }else {
  484.       value = element.offsetHeight;
  485.     }
  486.   }
  487.   return (value == 'auto') ? null : value;
  488. };
  489.  
  490. /**
  491.  * Private function pulled from Prototype.js that will change a hyphened
  492.  * style name into camel case.
  493.  * @private
  494.  * @param {String} element The string that will be parsed and made into camel case
  495.  * @return {String}
  496.  */
  497. ExtInfoWindow.prototype.camelize_ = function(element) {
  498.   var parts = element.split('-'), len = parts.length;
  499.   if (len == 1) return parts[0];
  500.   var camelized = element.charAt(0) == '-'
  501.     ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
  502.     : parts[0];
  503.  
  504.   for (var i = 1; i < len; i++) {
  505.     camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
  506.   }
  507.   return camelized;
  508. };
  509.  
  510. GMap.prototype.ExtInfoWindowInstance_ = null;
  511. GMap.prototype.ClickListener_ = null;
  512. GMap.prototype.InfoWindowListener_ = null;
  513.  
  514. /**
  515.  * Creates a new instance of ExtInfoWindow for the GMarker.  Register the newly created 
  516.  * instance with the map, ensuring only one window is open at a time. If this is the first
  517.  * ExtInfoWindow ever opened, add event listeners to the map to close the ExtInfoWindow on 
  518.  * zoom and click, to mimic the default GInfoWindow behavior.
  519.  *
  520.  * @param {GMap} map The GMap2 object where the ExtInfoWindow will open
  521.  * @param {String} cssId The id we will use to reference the info window
  522.  * @param {String} html The HTML contents
  523.  * @param {Object} opt_opts A contianer for optional arguments:
  524.  *    {String} ajaxUrl The Url to hit on the server to request some contents 
  525.  *    {Number} paddingX The padding size in pixels that the info window will leave on 
  526.  *                    the left and right sides of the map when panning is involved.
  527.  *    {Number} paddingX The padding size in pixels that the info window will leave on 
  528.  *                    the top and bottom sides of the map when panning is involved.
  529.  *    {Number} beakOffset The repositioning offset for when aligning the beak element. 
  530.  *                    This is used to make sure the beak lines up correcting if the 
  531.  *                    info window styling containers a border.
  532.  */
  533.  
  534. GMarker.prototype.openExtInfoWindow = function(map, cssId, html, opt_opts) {
  535.  
  536.     if (map == null) {
  537.  
  538.         throw 'Error in GMarker.openExtInfoWindow: map cannot be null';
  539.  
  540.         return false;
  541.  
  542.     }
  543.  
  544.     if (cssId == null || cssId == '') {
  545.  
  546.         throw 'Error in GMarker.openExtInfoWindow: must specify a cssId';
  547.  
  548.         return false;
  549.  
  550.     }
  551.  
  552.  
  553.  
  554.     map.closeInfoWindow();
  555.  
  556.     if (map.getExtInfoWindow() != null) {
  557.  
  558.         map.closeExtInfoWindow();
  559.  
  560.     }
  561.  
  562.     if (map.getExtInfoWindow() == null) {
  563.  
  564.         map.setExtInfoWindow_(new ExtInfoWindow(
  565.  
  566.       this,
  567.  
  568.       cssId,
  569.  
  570.       html,
  571.  
  572.       opt_opts
  573.  
  574.     ));
  575.  
  576.         if (map.ClickListener_ == null) {
  577.  
  578.             //listen for map click, close ExtInfoWindow if open
  579.  
  580.             map.ClickListener_ = GEvent.addListener(map, 'click',
  581.  
  582.       function(event) {
  583.  
  584.           if (!event && map.getExtInfoWindow() != null) {
  585.  
  586.               map.closeExtInfoWindow();
  587.  
  588.           }
  589.  
  590.       }
  591.  
  592.       );
  593.  
  594.         }
  595.  
  596.         if (map.InfoWindowListener_ == null) {
  597.  
  598.             //listen for default info window open, close ExtInfoWindow if open
  599.  
  600.             map.InfoWindowListener_ = GEvent.addListener(map, 'infowindowopen',
  601.  
  602.       function(event) {
  603.  
  604.           if (map.getExtInfoWindow() != null) {
  605.  
  606.               map.closeExtInfoWindow();
  607.  
  608.           }
  609.  
  610.       }
  611.  
  612.       );
  613.  
  614.         }
  615.  
  616.         map.addOverlay(map.getExtInfoWindow());
  617.  
  618.     }
  619.  
  620. };
  621.  
  622. /**
  623.  * Remove the ExtInfoWindow instance
  624.  * @param {GMap2} map The map where the GMarker and ExtInfoWindow exist
  625.  */
  626. GMarker.prototype.closeExtInfoWindow = function(map) {
  627.   if( map.getExtInfWindow() != null ){
  628.     map.closeExtInfoWindow();
  629.   }
  630. };
  631.  
  632. /**
  633.  * Get the ExtInfoWindow instance from the map
  634.  */
  635. GMap2.prototype.getExtInfoWindow = function(){
  636.   return this.ExtInfoWindowInstance_;
  637. };
  638. /**
  639.  * Set the ExtInfoWindow instance for the map
  640.  * @private
  641.  */
  642. GMap2.prototype.setExtInfoWindow_ = function( extInfoWindow ){
  643.   this.ExtInfoWindowInstance_ = extInfoWindow;
  644. }
  645. /**
  646.  * Remove the ExtInfoWindow from the map
  647.  */
  648. GMap2.prototype.closeExtInfoWindow = function(){
  649.   if( this.getExtInfoWindow() != null ){
  650.     this.ExtInfoWindowInstance_.remove();
  651.   }
  652. };
  653.