BartlomiejSkwira
6/3/2013 - 10:11 AM

Bootstraps Popover as Google Maps custom InfoWindow

Bootstraps Popover as Google Maps custom InfoWindow

gm.event.addListener(marker, 'click', ->
  infBox = new CustomInfoWindow { latlng: marker.getPosition()
  map: map
  title: pin['name'] if pin['name']
  content: pin_content(pin) }
)
class CustomInfoWindow extends google.maps.OverlayView

  constructor: (opts) ->
    google.maps.OverlayView.call this
    @latlng_ = opts.latlng
    @map_ = opts.map
    @offsetVertical_ = -199
    @offsetHorizontal_ = -132
    @height_ = 165
    @width_ = 266
    @title = opts.title
    @content = opts.content
    me = this
    @boundsChangedListener_ = google.maps.event.addListener(@map_, "bounds_changed", ->
      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.
    @setMap @map_


  # Creates the DIV representing this InfoBox
  remove: ->
    if @div_
      @div_.parentNode.removeChild @div_
      @div_ = null

  # Redraw the Bar based on the current projection and zoom level
  draw: ->

    # Creates the element if it doesn't exist already.
    @createElement()
    return  unless @div_

    # Calculate the DIV coordinates of two opposite corners of our bounds to
    # get the size and position of our Bar
    pixPosition = @getProjection().fromLatLngToDivPixel(@latlng_)
    return  unless pixPosition

    # Now position our DIV based on the DIV coordinates of our bounds
    @div_.style.width = @width_ + "px"
    @div_.style.left = (pixPosition.x + @offsetHorizontal_) + "px"
    @div_.style.height = @height_ + "px"
    @div_.style.top = (pixPosition.y + @offsetVertical_) + "px"
    @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.
  createElement: ->
    panes = @getPanes()
    div = @div_
    unless div
      # This does not handle changing panes.  You can set the map to be null and
      # then reset the map to move the div.
      removeInfoBox = (ib) ->
        ->
          ib.setMap null
      div = @div_ = document.createElement("div")
      div.style.border = "0px none"
      div.className = 'popover top'
      div.style.position = "absolute"
      div.style.width = @width_ + "px"
      div.style.height = @height_ + "px"
      contentDiv = document.createElement("div")
      contentDiv.className = 'popover-content'
      contentDiv.innerHTML = @content
      topDiv = document.createElement("div")
      topDiv.className = 'popover-title'
      topDiv.style.position = 'relative'
      topDiv.innerHTML = "<span>#{@title}</span>"
      closeImg = document.createElement("img")
      closeImg.style.width = "32px"
      closeImg.style.height = "32px"
      closeImg.style.cursor = "pointer"
      closeImg.style.position = "absolute"
      closeImg.style.top = "1px"
      closeImg.style.right = "5px"
      closeImg.src = "http://gmaps-samples.googlecode.com/svn/trunk/images/closebigger.gif"
      bottomDiv = document.createElement 'div'
      bottomDiv.className = 'arrow'
      bottomDiv.style.bottom = '-10px'
      topDiv.appendChild closeImg
      google.maps.event.addDomListener closeImg, "click", removeInfoBox(this)
      div.appendChild bottomDiv
      div.appendChild topDiv
      div.appendChild contentDiv
      div.style.display = "none"
      panes.floatPane.appendChild div
      @panMap()
    else unless div.parentNode is 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.
  panMap: ->

    # if we go beyond map, pan map
    map = @map_
    bounds = map.getBounds()
    return  unless bounds

    # The position of the infowindow
    position = @latlng_

    # The dimension of the infowindow
    iwWidth = @width_
    iwHeight = @height_

    # The offset position of the infowindow
    iwOffsetX = @offsetHorizontal_
    iwOffsetY = @offsetVertical_

    # Padding on the infowindow
    padX = 40
    padY = 40

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

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

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

    # calculate center shift
    shiftLng = ((if iwWestLng < mapWestLng then mapWestLng - iwWestLng else 0)) + ((if iwEastLng > mapEastLng then mapEastLng - iwEastLng else 0))
    shiftLat = ((if iwNorthLat > mapNorthLat then mapNorthLat - iwNorthLat else 0)) + ((if iwSouthLat < mapSouthLat then mapSouthLat - iwSouthLat else 0))

    # The center of the map
    center = map.getCenter()

    # The new map center
    centerX = center.lng() - shiftLng
    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 @boundsChangedListener_
    @boundsChangedListener_ = null

window.CustomInfoWindow = CustomInfoWindow