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