Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS Google Maps Custom InfoWindow

I've been using code from http://gmaps-samples-v3.googlecode.com/svn/trunk/infowindow_custom/infowindow-custom.html, which is currently google's best example of how to create custom InfoWindow's in Maps API v3. I've been working on it and so far I've got it close to working except for one thing, it the div container the text content won't expand to fit the content, so it just drops off instead of expanding the bubble. if I give the content container a fixed pixel width it works fine but I can't get it to expand depending on the amount of text in it.

I've been stuck on this one for a while. Any help would be greatly appreciated!

Here is the HTML page

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>Gayborhood Map Test</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
  html { height: 100% }
  body { height: 100%; margin: 0px; padding: 0px }
  #map_canvas { width: 900px;
    height: 400px;
    margin: 200px auto 0 auto; }
</style>
<link rel="stylesheet" type="text/css" href="map.css" />
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
<script type="text/javascript" src="InfoBox.js"></script>
<script type="text/javascript">
  function initialize() {
    var latlng = new google.maps.LatLng(39.947137,-75.161824);
    var myOptions = {
      zoom: 16,
      center: latlng,
      mapTypeId: google.maps.MapTypeId.HYBRID
    };

    var gayborhood;

    var map = new google.maps.Map(document.getElementById("map_canvas"),
        myOptions);

    var gayborhoodcoords = [
       new google.maps.LatLng(39.9492017, -75.1631272),
       new google.maps.LatLng(39.945423, -75.1639561),
       new google.maps.LatLng(39.9450064, -75.160579),
       new google.maps.LatLng(39.9487765, -75.1597468),
       new google.maps.LatLng(39.9492017, -75.1631272)
    ];

    gayborhood = new google.maps.Polygon({
       paths: gayborhoodcoords,
       strokeColor: "#00ff00",
       strokeOpacity: 0.8,
       strokeWeight: 2,
       fillColor: "#00ff00",
       fillOpacity: 0.35
    });

    gayborhood.setMap(map);

    var image = 'red_icon.png';
    var myLatLng = new google.maps.LatLng(39.948883,-75.162246);
    var redMarker = new google.maps.Marker({
      position: myLatLng,
      map: map,
      icon: image
    });

   var contentString = '<h4>Woody\'s Bar</h4>';

   /*var infowindow = new google.maps.InfoWindow({
    content: contentString,
    disableAutoPan: true
   });*/

   google.maps.event.addListener(redMarker, 'mouseover', function() {
      var infoBox = new InfoBox({marker: redMarker, map: map});
   });
   /*google.maps.event.addListener(redMarker, 'mouseout', function() {
      infowindow.close();
   });*/
  }

</script>
</head>
<body onload="initialize()">
  <div id="map_canvas"></div>
</body>
</html>

Here's the InfoBox.js:

    /* An InfoBox is like an info window, but it displays
 * under the marker, opens quicker, and has flexible styling.
 * @param {GLatLng} latlng Point to place bar at
 * @param {Map} map The map on which to display this InfoBox.
 * @param {Object} opts Passes configuration options - content,
 *   offsetVertical, offsetHorizontal, className, height, width
 */
function InfoBox(opts) {
  google.maps.OverlayView.call(this);
  this.marker_ = opts.marker
  this.latlng_ = opts.marker.getPosition();
  this.map_ = opts.map;
  this.offsetVertical_ = -65;
  this.offsetHorizontal_ = -20;
  this.height_ = 50;
  //this.width_ = 159;

  var me = this;
  this.boundsChangedListener_ =
    google.maps.event.addListener(this.map_, "bounds_changed", function() {
      return me.panMap.apply(me);
    });

  // Once the properties of this OverlayView are initialized, set its map so
  // that we can display it.  This will trigger calls to panes_changed and
  // draw.
  this.setMap(this.map_);
}

/* InfoBox extends GOverlay class from the Google Maps API
 */
InfoBox.prototype = new google.maps.OverlayView();

/* Creates the DIV representing this InfoBox
 */
InfoBox.prototype.remove = function() {
  if (this.div_) {
    this.div_.parentNode.removeChild(this.div_);
    this.div_ = null;
  }
};

/* Redraw the Bar based on the current projection and zoom level
 */
InfoBox.prototype.draw = function() {
  // Creates the element if it doesn't exist already.
  this.createElement();
  if (!this.div_) return;

  // Calculate the DIV coordinates of two opposite corners of our bounds to
  // get the size and position of our Bar
  var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
  if (!pixPosition) return;

  // Now position our DIV based on the DIV coordinates of our bounds
  //this.div_.style.width = this.width_ + "px";
  this.div_.style.left = (pixPosition.x + this.offsetHorizontal_) + "px";
  this.div_.style.height = this.height_ + "px";
  this.div_.style.top = (pixPosition.y + this.offsetVertical_) + "px";
  this.div_.style.display = 'block';
};

/* Creates the DIV representing this InfoBox in the floatPane.  If the panes
 * object, retrieved by calling getPanes, is null, remove the element from the
 * DOM.  If the div exists, but its parent is not the floatPane, move the div
 * to the new pane.
 * Called from within draw.  Alternatively, this can be called specifically on
 * a panes_changed event.
 */
InfoBox.prototype.createElement = function() {
  var panes = this.getPanes();
  var div = this.div_;
  if (!div) {
    // This does not handle changing panes.  You can set the map to be null and
    // then reset the map to move the div.
    div = this.div_ = document.createElement("div");
    div.className = "infobox";
    //div.style.width = this.width_ + "px";
    //div.style.height = this.height_ + "px";
    var leftDiv = document.createElement("div");
    leftDiv.className = "bubbleLeftDiv";
    var containerDiv = document.createElement("div");
    containerDiv.className = "infoboxContainer";
    var contentDiv = document.createElement("div");
    contentDiv.className = "infoboxContent";

    var title = "Much longer title than woody's"

    //var infoboxWidth = ( title.length*10 - (title.length) - 40) + "px"
    //containerDiv.style.width = infoboxWidth;
    //this.width_ = infoboxWidth + 47;
    contentDiv.innerHTML = "<h3>" + title + "</h3>";
    var rightDiv = document.createElement("div");
    rightDiv.className = "bubbleRightDiv";

    function removeInfoBox(ib) {
      return function() {
        ib.setMap(null);
      };
    }

    google.maps.event.addListener(this.marker_, 'mouseout', removeInfoBox(this));

    div.appendChild(leftDiv)
    div.appendChild(containerDiv);
    containerDiv.appendChild(contentDiv);
    div.appendChild(rightDiv);
    div.style.display = 'none';
    panes.floatPane.appendChild(div);
    this.panMap();
  } else if (div.parentNode != panes.floatPane) {
    // The panes have changed.  Move the div.
    div.parentNode.removeChild(div);
    panes.floatPane.appendChild(div);
  } else {
    // The panes have not changed, so no need to create or move the div.
  }
}

/* Pan the map to fit the InfoBox.
 */
InfoBox.prototype.panMap = function() {
  // if we go beyond map, pan map
  var map = this.map_;
  var bounds = map.getBounds();
  if (!bounds) return;

  // The position of the infowindow
  var position = this.latlng_;

  // The dimension of the infowindow
  var iwWidth = this.width_;
  var iwHeight = this.height_;

  // The offset position of the infowindow
  var iwOffsetX = this.offsetHorizontal_;
  var iwOffsetY = this.offsetVertical_;

  // Padding on the infowindow
  var padX = 40;
  var padY = 40;

  // The degrees per pixel
  var mapDiv = map.getDiv();
  var mapWidth = mapDiv.offsetWidth;
  var mapHeight = mapDiv.offsetHeight;
  var boundsSpan = bounds.toSpan();
  var longSpan = boundsSpan.lng();
  var latSpan = boundsSpan.lat();
  var degPixelX = longSpan / mapWidth;
  var degPixelY = latSpan / mapHeight;

  // The bounds of the map
  var mapWestLng = bounds.getSouthWest().lng();
  var mapEastLng = bounds.getNorthEast().lng();
  var mapNorthLat = bounds.getNorthEast().lat();
  var mapSouthLat = bounds.getSouthWest().lat();

  // The bounds of the infowindow
  var iwWestLng = position.lng() + (iwOffsetX - padX) * degPixelX;
  var iwEastLng = position.lng() + (iwOffsetX + iwWidth + padX) * degPixelX;
  var iwNorthLat = position.lat() - (iwOffsetY - padY) * degPixelY;
  var iwSouthLat = position.lat() - (iwOffsetY + iwHeight + padY) * degPixelY;

  // calculate center shift
  var shiftLng =
      (iwWestLng < mapWestLng ? mapWestLng - iwWestLng : 0) +
      (iwEastLng > mapEastLng ? mapEastLng - iwEastLng : 0);
  var shiftLat =
      (iwNorthLat > mapNorthLat ? mapNorthLat - iwNorthLat : 0) +
      (iwSouthLat < mapSouthLat ? mapSouthLat - iwSouthLat : 0);

  // The center of the map
  var center = map.getCenter();

  // The new map center
  var centerX = center.lng() - shiftLng;
  var centerY = center.lat() - shiftLat;

  // center the map to the new shifted center
  map.setCenter(new google.maps.LatLng(centerY, centerX));

  // Remove the listener after panning is complete.
  google.maps.event.removeListener(this.boundsChangedListener_);
  this.boundsChangedListener_ = null;
};

And here's the CSS:

    .infobox {
    border: 0px none;
    position: absolute;
    width: auto;
    height: auto;
}

.infoboxContent {
    font-family: arial, helvetica, sans-serif;
    font-size: 15px;
    padding: 0px;
    margin: 9px 0px 0px -24px;
    position: absolute;
    z-index: 105;
}

.infoboxContainer {
    background: url('infowindow_bg.png') repeat-x;
    height: 50px;
    margin-left: 47px;
}

.bubbleLeftDiv {
    width: 47px;
    height: 50px;
    background: url('infowindow_left.png') no-repeat;
    position: absolute;
    z-index: 102;
}

.bubbleRightDiv {
    width: 26px;
    height: 50px;
    background: url('infowindow_right.png') no-repeat;
    position: absolute;
    right: -26px;
    top: 0px;
}

.clear { clear: both; }

Thank you!!

like image 205
Andrew Avatar asked May 05 '11 02:05

Andrew


1 Answers

I've faced the same problem. The approach that worked for me was to dynamically determine the dimensions of the content and set the height and width of the InfoBox correctly. The problem that I encountered with this was that before the content is inserted into the DOM it doesn't have (correct) dimension values. As a result my approach was the following:

  1. Create the DOM content element that needs to be inserted
  2. Insert it in a temp container
  3. Get dimensions of temp container
  4. Remove temp container
  5. Insert content in InfoBox and set its height and width based on temp container dimensions

Here is an example done with the jQuery framework:

    var temp = $("<div class='temp'></div>").html(content).hide().appendTo("body");
    var dimentions = {
        width : temp.outerWidth(true),
        height : temp.outerHeight(true)
    };
    temp.remove();

    var overlayProjection = this.getProjection();
    var top_left = overlayProjection.fromLatLngToDivPixel(this.point_);        

    var dimensions= $.extend({}, dimensions, {
        y : top_left.y - dimensions.height,
        x : top_left.x - dimensions.width/2
    });

    var div = this.div_;
    $(div).css({
        "top": dimensions.y + 'px',
        "left" : dimensions.x + 'px',
        "width" : dimensions.width + 'px',
        "height" : dimensions.height + 'px'
    }).html(content);

Hope that helps!

like image 75
planewalker Avatar answered Oct 22 '22 18:10

planewalker