georgebellos/real_estate

View on GitHub
app/assets/javascripts/gmaps4rails/gmaps4rails.base.js.coffee

Summary

Maintainability
Test Coverage
Gmaps = {}

Gmaps.triggerOldOnload = ->
  Gmaps.oldOnload() if typeof(Gmaps.oldOnload) == 'function'

Gmaps.loadMaps = ->
  #loop through all variable names.
  #there should only be maps inside so it trigger their load function
  for key, value of Gmaps
    searchLoadIncluded = key.search(/load/)
    if searchLoadIncluded == -1
      load_function_name = "load_" + key
      Gmaps[load_function_name]()

window.Gmaps = Gmaps

class @Gmaps4Rails

  constructor: ->
    #map config
    @map =  null               #DEPRECATED: will still contain a copy of serviceObject below as transition
    @serviceObject = null      #contains the map we're working on
    @visibleInfoWindow = null  #contains the current opened infowindow
    @userLocation = null       #contains user's location if geolocalization was performed and successful

    #empty slots
    @geolocationSuccess = -> false  #triggered when geolocation succeeds. Can be customized.
    @geolocationFailure = -> false  #triggered when geolocation fails. If customized, must be like= function(navigator_handles_geolocation){} where 'navigator_handles_geolocation' is a boolean
    @callback           = -> false  #to let user set a custom callback function
    @customClusterer    = -> false  #to let user set custom clusterer pictures
    @infobox            = -> false  #to let user use custom infoboxes
    @jsTemplate         = false     #to let user create infowindows client side

    @default_map_options =
      id: 'map'
      draggable: true
      detect_location: false  # should the browser attempt to use geolocation detection features of HTML5?
      center_on_user: false   # centers map on the location detected through the browser
      center_latitude: 0
      center_longitude: 0
      zoom: 7
      maxZoom: null
      minZoom: null
      auto_adjust : true      # adjust the map to the markers if set to true
      auto_zoom: true         # zoom given by auto-adjust
      bounds: []              # adjust map to these limits. Should be [{"lat": , "lng": }]
      raw: {}                  # raw json to pass additional options

    @default_markers_conf =
      #Marker config
      title: ""
      #MarkerImage config
      picture : ""
      width: 22
      length: 32
      draggable: false         # how to modify: <%= gmaps( "markers" => { "data" => @object.to_gmaps4rails, "options" => { "draggable" => true }}) %>
      #clustering config
      do_clustering: false     # do clustering if set to true
      randomize: false         # Google maps can't display two markers which have the same coordinates. This randomizer enables to prevent this situation from happening.
      max_random_distance: 100 # in meters. Each marker coordinate could be altered by this distance in a random direction
      list_container: null     # id of the ul that will host links to all markers
      offset: 0                # used when adding_markers to an existing map. Because new markers are concated with previous one, offset is here to prevent the existing from being re-created.
      raw: {}                  # raw json to pass additional options

    #Stored variables
    @markers = []            # contains all markers. A marker contains the following: {"description": , "longitude": , "title":, "latitude":, "picture": "", "width": "", "length": "", "sidebar": "", "serviceObject": google_marker}
    @boundsObject = null     # contains current bounds from markers, polylines etc...
    @polygons = []           # contains raw data, array of arrays (first element could be a hash containing options)
    @polylines = []          # contains raw data, array of arrays (first element could be a hash containing options)
    @circles = []            # contains raw data, array of hash
    @markerClusterer = null  # contains all marker clusterers
    @markerImages = []

    #Polyline Styling
    @polylines_conf =         #default style for polylines
      strokeColor: "#FF0000"
      strokeOpacity: 1
      strokeWeight: 2
      clickable: false
      zIndex: null

  #tnitializes the map
  initialize : ->
    @serviceObject = @createMap()
    @map = @serviceObject #beware, soon deprecated
    if (@map_options.detect_location == true or @map_options.center_on_user == true)
      @findUserLocation(this)
    #resets sidebar if needed
    @resetSidebarContent()

  findUserLocation : (map_object) ->
    if (navigator.geolocation)
      #try to retrieve user's position
      positionSuccessful = (position) ->
        map_object.userLocation = map_object.createLatLng(position.coords.latitude, position.coords.longitude)
        #change map's center to focus on user's geoloc if asked
        if(map_object.map_options.center_on_user == true)
          map_object.centerMapOnUser()
        map_object.geolocationSuccess()
      positionFailure = ->
        map_object.geolocationFailure(true)

      navigator.geolocation.getCurrentPosition( positionSuccessful, positionFailure)
    else
      #failure but the navigator doesn't handle geolocation
      map_object.geolocationFailure(false)


  #////////////////////////////////////////////////////
  #//////////////////// DIRECTIONS ////////////////////
  #////////////////////////////////////////////////////

  create_direction : ->
    directionsDisplay = new google.maps.DirectionsRenderer()
    directionsService = new google.maps.DirectionsService()

    directionsDisplay.setMap(@serviceObject)
    #display panel only if required
    if @direction_conf.display_panel
      directionsDisplay.setPanel(document.getElementById(@direction_conf.panel_id))

    directionsDisplay.setOptions
      suppressMarkers:     false
      suppressInfoWindows: false
      suppressPolylines:   false

    request =
      origin:             @direction_conf.origin
      destination:        @direction_conf.destination
      waypoints:          @direction_conf.waypoints
      optimizeWaypoints:  @direction_conf.optimizeWaypoints
      unitSystem:         google.maps.DirectionsUnitSystem[@direction_conf.unitSystem]
      avoidHighways:      @direction_conf.avoidHighways
      avoidTolls:         @direction_conf.avoidTolls
      region:             @direction_conf.region
      travelMode:         google.maps.DirectionsTravelMode[@direction_conf.travelMode]
      language:           "en"

    directionsService.route request, (response, status) ->
      if (status == google.maps.DirectionsStatus.OK)
        directionsDisplay.setDirections(response)

  #////////////////////////////////////////////////////
  #///////////////////// CIRCLES //////////////////////
  #////////////////////////////////////////////////////

  #Loops through all circles
  #Loops through all circles and draws them
  create_circles : ->
    for circle in @circles
      @create_circle circle

  create_circle : (circle) ->
    #by convention, default style configuration could be integrated in the first element
    if circle == @circles[0]
      @circles_conf.strokeColor   = circle.strokeColor   if circle.strokeColor?
      @circles_conf.strokeOpacity = circle.strokeOpacity if circle.strokeOpacity?
      @circles_conf.strokeWeight  = circle.strokeWeight  if circle.strokeWeight?
      @circles_conf.fillColor     = circle.fillColor     if circle.fillColor?
      @circles_conf.fillOpacity   = circle.fillOpacity   if circle.fillOpacity?

    if circle.lat? and circle.lng?
      # always check if a config is given, if not, use defaults
      # NOTE: is there a cleaner way to do this? Maybe a hash merge of some sort?
      newCircle = new google.maps.Circle
        center:        @createLatLng(circle.lat, circle.lng)
        strokeColor:   circle.strokeColor   || @circles_conf.strokeColor
        strokeOpacity: circle.strokeOpacity || @circles_conf.strokeOpacity
        strokeWeight:  circle.strokeWeight  || @circles_conf.strokeWeight
        fillOpacity:   circle.fillOpacity   || @circles_conf.fillOpacity
        fillColor:     circle.fillColor     || @circles_conf.fillColor
        clickable:     circle.clickable     || @circles_conf.clickable
        zIndex:        circle.zIndex        || @circles_conf.zIndex
        radius:        circle.radius

      circle.serviceObject = newCircle
      newCircle.setMap(@serviceObject)

  # clear circles
  clear_circles : ->
    for circle in @circles
      @clear_circle circle

  clear_circle : (circle) ->
    circle.serviceObject.setMap(null)

  hide_circles : ->
    for circle in @circles
      @hide_circle circle

  hide_circle : (circle) ->
    circle.serviceObject.setMap(null)

  show_circles : ->
    for circle in @circles
      @show_circle @circle

  show_circle : (circle) ->
    circle.serviceObject.setMap(@serviceObject)

  #////////////////////////////////////////////////////
  #///////////////////// POLYGONS /////////////////////
  #////////////////////////////////////////////////////

  #polygons is an array of arrays. It loops.
  create_polygons : ->
    for polygon in @polygons
      @create_polygon(polygon)

  #creates a single polygon, triggered by create_polygons
  create_polygon : (polygon) ->
    polygon_coordinates = []

    #Polygon points are in an Array, that's why looping is necessary
    for point in polygon
      latlng = @createLatLng(point.lat, point.lng)
      polygon_coordinates.push(latlng)
      #first element of an Array could contain specific configuration for this particular polygon. If no config given, use default
      if point == polygon[0]
        strokeColor   = point.strokeColor   || @polygons_conf.strokeColor
        strokeOpacity = point.strokeOpacity || @polygons_conf.strokeOpacity
        strokeWeight  = point.strokeWeight  || @polygons_conf.strokeWeight
        fillColor     = point.fillColor     || @polygons_conf.fillColor
        fillOpacity   = point.fillOpacity   || @polygons_conf.fillOpacity
        clickable     = point.clickable     || @polygons_conf.clickable
        
    #Construct the polygon
    new_poly = new google.maps.Polygon
      paths:          polygon_coordinates
      strokeColor:    strokeColor
      strokeOpacity:  strokeOpacity
      strokeWeight:   strokeWeight
      fillColor:      fillColor
      fillOpacity:    fillOpacity
      clickable:      clickable
      map:            @serviceObject

    #save polygon in list
    polygon.serviceObject = new_poly

  

  #////////////////////////////////////////////////////
  #///////////////////// MARKERS //////////////////////
  #////////////////////////////////////////////////////

  #creates, clusterizes and adjusts map
  create_markers : ->
    @createServiceMarkersFromMarkers()
    @clusterize()

  #create google.maps Markers from data provided by user
  createServiceMarkersFromMarkers : ->
    for marker, index in @markers
      if not @markers[index].serviceObject?
        #extract options, test if value passed or use default
        Lat = @markers[index].lat
        Lng = @markers[index].lng

        #alter coordinates if randomize is true
        if @markers_conf.randomize
          LatLng = @randomize(Lat, Lng)
          #retrieve coordinates from the array
          Lat = LatLng[0]
          Lng = LatLng[1]

        #save object
        @markers[index].serviceObject = @createMarker
          "marker_picture":   if @markers[index].picture  then @markers[index].picture else @markers_conf.picture
          "marker_width":     if @markers[index].width    then @markers[index].width   else @markers_conf.width
          "marker_height":    if @markers[index].height   then @markers[index].height  else @markers_conf.length
          "marker_title":     if @markers[index].title    then @markers[index].title   else null
          "marker_anchor":    if @markers[index].marker_anchor  then @markers[index].marker_anchor  else null
          "shadow_anchor":    if @markers[index].shadow_anchor  then @markers[index].shadow_anchor  else null
          "shadow_picture":   if @markers[index].shadow_picture then @markers[index].shadow_picture else null
          "shadow_width":     if @markers[index].shadow_width   then @markers[index].shadow_width   else null
          "shadow_height":    if @markers[index].shadow_height  then @markers[index].shadow_height  else null
          "marker_draggable": if @markers[index].draggable      then @markers[index].draggable      else @markers_conf.draggable
          "rich_marker":      if @markers[index].rich_marker    then @markers[index].rich_marker    else null
          "zindex":           if @markers[index].zindex         then @markers[index].zindex         else null
          "Lat":              Lat
          "Lng":              Lng
          "index":            index

        #add infowindowstuff if enabled
        @createInfoWindow(@markers[index])
        #create sidebar if enabled
        @createSidebar(@markers[index])

    @markers_conf.offset = @markers.length

  #creates Image Anchor Position or return null if nothing passed
  createImageAnchorPosition : (anchorLocation) ->
    if (anchorLocation == null)
      return null
    else
      return @createPoint(anchorLocation[0], anchorLocation[1])


  #replace old markers with new markers on an existing map
  replaceMarkers : (new_markers, adjustBounds = true) ->
    @clearMarkers()
    #reset previous markers
    @markers = new Array
    #reset current bounds
    @boundsObject = @createLatLngBounds() if adjustBounds
    #reset sidebar content if exists
    @resetSidebarContent()
    #add new markers
    @markers_conf.offset = 0
    @addMarkers(new_markers, adjustBounds)

  #add new markers to on an existing map
  addMarkers : (new_markers, adjustBounds = true) ->
    #update the list of markers to take into account
    @markers = @markers.concat(new_markers)
    #put markers on the map
    @create_markers()
    @adjustMapToBounds() if adjustBounds

  #////////////////////////////////////////////////////
  #///////////////////// SIDEBAR //////////////////////
  #////////////////////////////////////////////////////

  #//creates sidebar
  createSidebar : (marker_container) ->
    if (@markers_conf.list_container)
      ul = document.getElementById(@markers_conf.list_container)
      li = document.createElement('li')
      aSel = document.createElement('a')
      aSel.href = 'javascript:void(0);'
      html = if marker_container.sidebar? then marker_container.sidebar else "Marker"
      aSel.innerHTML = html
      currentMap = this
      aSel.onclick = @sidebar_element_handler(currentMap, marker_container.serviceObject, 'click')
      li.appendChild(aSel)
      ul.appendChild(li)

  #moves map to marker clicked + open infowindow
  sidebar_element_handler : (currentMap, marker, eventType) ->
    return () ->
      currentMap.map.panTo(marker.position)
      google.maps.event.trigger(marker, eventType)


  resetSidebarContent : ->
    if @markers_conf.list_container isnt null
      ul = document.getElementById(@markers_conf.list_container)
      ul.innerHTML = ""

  #////////////////////////////////////////////////////
  #////////////////// MISCELLANEOUS ///////////////////
  #////////////////////////////////////////////////////

  #to make the map fit the different LatLng points
  adjustMapToBounds : ->
    #FIRST_STEP: retrieve all bounds
    #create the bounds object only if necessary
    if @map_options.auto_adjust or @map_options.bounds isnt null
      @boundsObject = @createLatLngBounds()

      #if autodjust is true, must get bounds from markers polylines etc...
      if @map_options.auto_adjust
        #from markers
        @extendBoundsWithMarkers()

        #from polylines:
        @updateBoundsWithPolylines()

        #from polygons:
        @updateBoundsWithPolygons()

        #from circles
        @updateBoundsWithCircles()

      #in every case, I've to take into account the bounds set up by the user
      @extendMapBounds()

      #SECOND_STEP: ajust the map to the bounds
      @adaptMapToBounds()

  #////////////////////////////////////////////////////
  #/////////////////// POLYLINES //////////////////////
  #////////////////////////////////////////////////////

  #replace old markers with new markers on an existing map
  replacePolylines : (new_polylines) ->
    #reset previous polylines and kill them from map
    @destroy_polylines()
    #set new polylines
    @polylines = new_polylines
    #create
    @create_polylines()
    #.... and adjust map boundaries
    @adjustMapToBounds()

  destroy_polylines : ->
    for polyline in @polylines
      #delete polylines from map
      polyline.serviceObject.setMap(null)
    #empty array
    @polylines = []

  #polylines is an array of arrays. It loops.
  create_polylines : ->
    for polyline in @polylines
      @create_polyline polyline

  #////////////////////////////////////////////////////
  #///////////////// Basic functions //////////////////
  #///////////////////tests coded//////////////////////

  #//basic function to check existence of a variable
  exists : (var_name) ->
    return (var_name    != "" and typeof var_name != "undefined")


  #randomize
  randomize : (Lat0, Lng0) ->
    #distance in meters between 0 and max_random_distance (positive or negative)
    dx = @markers_conf.max_random_distance * @random()
    dy = @markers_conf.max_random_distance * @random()
    Lat = parseFloat(Lat0) + (180/Math.PI)*(dy/6378137)
    Lng = parseFloat(Lng0) + ( 90/Math.PI)*(dx/6378137)/Math.cos(Lat0)
    return [Lat, Lng]

  mergeObjectWithDefault : (object1, object2) ->
    copy_object1 = {}
    for key, value of object1
      copy_object1[key] = value

    for key, value of object2
      unless copy_object1[key]?
        copy_object1[key] = value
    return copy_object1

  mergeWithDefault : (objectName) ->
    default_object = @["default_" + objectName]
    object = @[objectName]
    @[objectName] = @mergeObjectWithDefault(object, default_object)
    return true

  #gives a value between -1 and 1
  random : -> return(Math.random() * 2 -1)