Skip to content

Instantly share code, notes, and snippets.

@DanielCardonaRojas
Created July 19, 2018 14:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save DanielCardonaRojas/c82a52462990dc1950b3cb55aa96d0e3 to your computer and use it in GitHub Desktop.
Save DanielCardonaRojas/c82a52462990dc1950b3cb55aa96d0e3 to your computer and use it in GitHub Desktop.
Elm google-map custom element
module Data.Coordinates exposing (..)
import Json.Decode as Decode exposing (Decoder, float, Value)
import Json.Encode as Encode
import Json.Decode.Pipeline as Pipeline exposing (required)
import Geolocation
type alias Coordinates =
{ latitude : Float
, longitude : Float
}
decode : Decoder Coordinates
decode =
Pipeline.decode Coordinates
|> required "latitude" float
|> required "longitude" float
encode : Coordinates -> Value
encode coordinates =
Encode.object
[ ("latitude", Encode.float coordinates.latitude)
, ("longitude", Encode.float coordinates.longitude)
]
fromGeolocation : Geolocation.Location -> Coordinates
fromGeolocation loc =
Coordinates loc.latitude loc.longitude
module GoogleMap exposing (..)
import Constants as Const
import Data.Coordinates as Coordinates exposing (Coordinates)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events as Events
import Json.Decode as Decode exposing (Decoder)
import Json.Encode as Encode exposing (Value)
import Utilities.Misc as Utils
-- This marker type should mirror the same properties described
-- here: https://developers.google.com/maps/documentation/javascript/markers
type alias Marker =
{ title : String
, position : {lat: Float, lng: Float}
, draggable : Bool
}
markerEncode : Marker -> Value
markerEncode m =
let
position =
Encode.object
[ ("lat", Encode.float m.position.lat)
, ("lng", Encode.float m.position.lng)
]
in
Encode.object
[ ("position", position)
, ("title", Encode.string m.title)
, ("draggable", Encode.bool m.draggable)
]
map : List (Attribute a) -> List Marker -> Html a
map attrs markers =
let
attributes =
[ attribute "api-key" Const.googleApiKey
, property "markers" <| Encode.list (List.map markerEncode markers)
]
|> List.append attrs
in
Html.node "google-map" attributes []
-- Google map Attributes
onMarkerDragEnd : (Coordinates -> msg) -> Attribute msg
onMarkerDragEnd tagger =
Decode.at [ "detail" ] Coordinates.decode
|> Decode.map tagger
|> Utils.traceDecoder "GoogleMapMarkerDragEnd"
|> Events.on "google-map-marker.dragend"
onMapDragEnd : (Coordinates -> msg) -> Attribute msg
onMapDragEnd tagger =
Decode.at [ "detail" ] Coordinates.decode
|> Decode.map tagger
|> Utils.traceDecoder "GoogleMapOnDragEnd"
|> Events.on "googlemap.dragend"
onMapDragStart : (Coordinates -> msg) -> Attribute msg
onMapDragStart tagger =
Decode.at [ "target" ] Coordinates.decode
|> Decode.map tagger
|> Utils.traceDecoder "GoogleMapOnDragStart"
|> Events.on "google-map-dragstart"
onMapClick : (Coordinates -> msg) -> Attribute msg
onMapClick tagger =
Decode.at [ "detail" ] Coordinates.decode
|> Decode.map tagger
|> Utils.traceDecoder "GoogleMapOnClick"
|> Events.on "googlemap.click"
import GoogleMapsLoader from 'google-maps'; // GoogleMap lib on npm
GoogleMapsLoader.KEY = '';
GoogleMapsLoader.LANGUAGE = 'es';
GoogleMapsLoader.LIBRARIES = ['geometry', 'places'];
class GoogleMap extends HTMLElement {
constructor(){
super();
this._onMapClick = this._onMapClick.bind(this);
this._onMapDragEnd = this._onMapDragEnd.bind(this);
this._onMarkerDragEnd = this._onMarkerDragEnd.bind(this);
this.addMarkers = this.addMarkers.bind(this);
this._markers = [];
this._googleMarkers = [];
this.google = null;
this._map = null;
}
static get observedAttributes() {
return ['latitude', 'longitude'];
}
// Marker property
set markers(ms) {
this._markers = ms;
if (!(this.google && this._map)) {
return
}
this.addMarkers(this._map, this.google);
}
get markers() {
return this._markers;
}
//Configuration options
get minZoom () {
const val = this.getAttribute('max-zoom');
return parseInt(val) || 0
}
get maxZoom () {
const val = this.getAttribute('min-zoom');
return parseInt(val) || 0
}
get streetViewControl() {
return this.getAttribute('street-view-control') == 'true' || false
}
get fitToMarkers() {
return this.hasAttribute('fit-to-markers');
}
get draggable() {
return this.getAttribute('draggable') == 'true' || false
}
get zoom() {
const val = this.getAttribute('zoom');
return parseInt(val) || 8
}
get zoomControl() {
return this.getAttribute('zoom-control') == 'false' || true
}
get latitude() {
const lat = parseFloat(this.getAttribute('latitude')) || 6.207775;
return lat
}
get longitude() {
const lng = parseFloat(this.getAttribute('longitude')) || -75.563024;
return lng
}
get center() {
const center = { lat: this.latitude, lng: this.longitude };
return center
}
get mapType() {
return this.getAttribute('map-type') || 'roadmap';
}
get apiKey() {
return this.getAttribute('api-key');
}
// Life Cycle
attributeChangedCallback(name, oldVal, newVal) {
console.log("Changed: " + name + " old: " + oldVal + " new: " + newVal);
if (name == 'latitude' && oldVal != null) {
const lat = parseFloat(newVal) || this._map.center.lat();
const newCenter = {lat: lat, lng: this._map.center.lng()};
this._map.setCenter(newCenter);
} else if (name == 'longitude' && oldVal != null) {
const lng = parseFloat(newVal) || this._map.center.lng();
const newCenter = {lat: this._map.center.lat(), lng: lng};
this._map.setCenter(newCenter);
}
}
connectedCallback() {
const shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.appendChild(this.template.content.cloneNode(true));
const elem = shadowRoot.getElementById('customMap');
var self = this;
GoogleMapsLoader.load(function(google) {
self.google = google
//const center = {lat: 6.2077114, lng: -75.5630352};
var map = new google.maps.Map(elem, {
center: self.center,
zoom: self.zoom,
zoomControl: self.zoomControl,
scaleControl: false,
fullscreenControl: false,
mapTypeControl: false,
fitToMarkers: self.fitToMarkers,
draggable: self.draggable,
streetViewControl: self.streetViewControl,
apiKey: self.apiKey,
mapTypeId: self.mapType
});
map.addListener('click', self._onMapClick);
map.addListener('dragend', self._onMapDragEnd);
self._map = map;
self.addMarkers(map, google);
});
}
// Markers
addMarkers(map, google) {
this.clearMarkers();
self = this;
this._googleMarkers = this._markers.map(function(m){
var marker = new google.maps.Marker(m)
marker.setMap(map);
marker.addListener('dragend', self._onMarkerDragEnd);
return marker
});
if (this.fitToMarkers) {
this.fitToMarkerBounds();
}
return this._googleMarkers
}
clearMarkers() {
this._googleMarkers.forEach(function(m){
if (m) {
m.setMap(null);
}
});
this._googleMarkers = [];
}
fitToMarkerBounds() { // Calculate the bounds to fit all markers
var latLngBounds = new this.google.maps.LatLngBounds();
this._markers.forEach(function(m){
latLngBounds.extend(m.position);
});
if (this._markers.length > 1) {
this._map.fitBounds(latLngBounds);
}
if (this._markers.length > 0) {
this._map.setCenter(latLngBounds.getCenter());
}
}
//Event handlers
_onMapClick(e) {
const customEvent = new CustomEvent('googlemap.click', {
bubbles: true,
composed: true,
detail: { 'latitude': e.latLng.lat(), 'longitude': e.latLng.lng() }
});
this.dispatchEvent(customEvent);
}
_onMapDragEnd(e) {
const customEvent = new CustomEvent('googlemap.dragend', {
bubbles: true,
composed: true,
detail: { 'latitude': this._map.center.lat(), 'longitude': this._map.center.lng() }
});
this.dispatchEvent(customEvent);
}
_onMarkerDragEnd(e) {
console.log("marker: dragend");
console.log(e);
const dragEvent = new CustomEvent('google-map-marker.dragend', {
bubbles: true,
composed: true,
detail: { 'latitude': e.latLng.lat(), 'longitude': e.latLng.lng() }
});
this.dispatchEvent(dragEvent);
}
// Template
get template() {
if (this._template) {return this._template;}
this._template = document.createElement('template');
let styles = document.createElement('style');
let content = document.createElement('div');
let imports = document.createElement('head');
content.id = 'customMap';
imports.innerHTML = `
`;
styles.innerHTML = `
div#customMap {
height: var(--google-map-height, 300px);
}
`;
content.innerHTML = `
`;
this._template.content.appendChild(styles);
this._template.content.appendChild(imports);
this._template.content.appendChild(content);
return this._template;
}
}
customElements.define('google-map', GoogleMap);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment