Gmaps4Rails appending markers to existing map - javascript

I am trying to asynchonios loading maps to my already rendered map:
handler = Gmaps.build('Google');
handler.buildMap({ provider: {
mapTypeId: google.maps.MapTypeId.ROADMAP,
styles: mapStyle_dark },
internal: {
id: 'map'}}, function () {
var json_array = $.getJSON("locations/async.json", function (data) {
return data;
});
var markers = handler.addMarkers(json_array);
handler.fitMapToBounds();
handler.getMap().setZoom(2);
});
My ajax response looks like this:
[{"lat":48.21042800000001,"lng":16.3822238,"infowindow":{"id":"53a0a0012d289ec127000020","image":"veq3r13nbgn5qg9c7zqk","name":"Blended Shisha Lounge Bar","categories":[{"name":"Bars","slug":"bars","icon":"bars"}],"coordinates":[16.3822238,48.21042800000001]},"picture":{"anchor":[17,17],"url":"/assets/map/icons/categories/bars-38.png","width":38,"height":38}}]
But the markers get not appended.
What I am doing wrong?

replace:
var json_array = $.getJSON("locations/async.json", function (data) {
return data;
});
var markers = handler.addMarkers(json_array);
with:
$.getJSON("locations/async.json", function (json_array) {
var markers = handler.addMarkers(json_array);
});

Related

Using google map in Vue / Laravel

Trying to implement google map into Vue component. But having a hard time. Actually, there is no error. But no map also :) Okay, what I tried so far down below.
In laravel blade I set my api.
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{env('GOOGLE_MAPS_API')}}&callback=initMap"></script>
Then in Vue component;
data() {
return {
mapName: "map",
//some other codes
}
},
mounted() {
this.fetchEstates();
},
methods: {
fetchEstates(page = 1) {
axios.get('/ajax', {
params: {
page
}}).then((response) => {
// console.log(response);
this.estates = response.data.data;
//some other codes....
//some other codes....
},
computed: {
//some other functions in computed...
//
initMap: function(){
var options =
{
zoom : 6,
center : {
lat:34.652500,
lng:135.506302
}
};
var map = new google.maps.Map(document.getElementById(this.mapName), options);
var marker = new google.maps.Marker({
map: map,
icon: 'imgs/marker.png',
url: "/pages/estates.id",
label: {
text: this.estates.price,
color: "#fff",
},
position: {
lat: this.estates.lat,
lng: this.estates.lng
}
});
google.maps.event.addListener(marker, 'click', function () {
window.location.href = this.url;
});
}
<div id="map"></div>
and last marker url id bind is in controller like this,
public function details($id)
{
$estates = allestates::where('id', $id)->first();
return view('pages.details', compact('estates'));
}
Do I missing something in Vue js? Thank you!
From our discussion in the comments, I realise that your issue is because this.estates is still not defined when initMap() is executed. Remember that you are using an asynchronous operation (via axios) to populate this.estates, so it is undefined at runtime. What you can do is:
Keep the map initialisation logic in initMap()
Move all the Google Map marker creation until after the axios promise has been resolved. You can abstract all that into another method, e.g. insertMarkers()
Also, remember that you need to define estates in the app/component data, otherwise it will not be reactive.
Here is an example:
data() {
return {
mapName: "map",
// Create the estate object first, otherwise it will not be reactive
estates: {}
}
},
mounted() {
this.fetchEstates();
this.initMap();
},
methods: {
fetchEstates: function(page = 1) {
axios.get('/ajax', {
params: {
page
}}).then((response) => {
this.estates = response.data.data;
// Once estates have been populated, we can insert markers
this.insertMarkers();
//pagination and stuff...
});
},
// Iniitialize map without creating markers
initMap: function(){
var mapOptions =
{
zoom : 6,
center : {
lat:34.652500,
lng:135.506302
}
};
var map = new google.maps.Map(document.getElementById(this.mapName), mapOptions);
},
// Helper method to insert markers
insertMarkers: function() {
var marker = new google.maps.Marker({
map: map,
icon: 'imgs/marker.png',
url: "/pages/estates.id",
label: {
text: this.estates.price,
color: "#fff",
},
position: {
lat: this.estates.lat,
lng: this.estates.lng
}
});
google.maps.event.addListener(marker, 'click', function () {
window.location.href = this.url;
});
}
},
Update: It also turns out that you have not addressed the issue of the data structure of this.estates. It appears that you are receiving an array from your endpoint instead of objects, so this.estates will return an array, and of course this.estates.lat will be undefined.
If you want to iterate through the entire array, you will have to use this.estates.forEach() to go through each individual estates while adding the marker, i.e.:
data() {
return {
mapName: "map",
// Create the estate object first, otherwise it will not be reactive
estates: {}
}
},
mounted() {
this.fetchEstates();
this.initMap();
},
methods: {
fetchEstates: function(page = 1) {
axios.get('/ajax', {
params: {
page
}}).then((response) => {
this.estates = response.data.data;
// Once estates have been populated, we can insert markers
this.insertMarkers();
//pagination and stuff...
});
},
// Iniitialize map without creating markers
initMap: function(){
var mapOptions =
{
zoom : 6,
center : {
lat:34.652500,
lng:135.506302
}
};
var map = new google.maps.Map(document.getElementById(this.mapName), mapOptions);
},
// Helper method to insert markers
insertMarkers: function() {
// Iterate through each individual estate
// Each estate will create a new marker
this.estates.forEach(estate => {
var marker = new google.maps.Marker({
map: map,
icon: 'imgs/marker.png',
url: "/pages/estates.id",
label: {
text: estate.price,
color: "#fff",
},
position: {
lat: estate.lat,
lng: estate.lng
}
});
google.maps.event.addListener(marker, 'click', function () {
window.location.href = this.url;
});
});
}
},
From what I can see in the screenshot you posted, this.estates is an array of objects? If that's the case you need to iterate through the array using forEach
this.estates.forEach((estate, index) => {
console.log(estate.lat);
//handle each estate object here
});
or use the first item in the array like so this.estates[0].lat, if you're only interested in the first item.

Google Maps Marker Animation, with KnockoutJs

I'm currently working on a map project with the Google Maps API, and KnockoutJS. I've managed to get most of my framework up and going, but the last piece of functionality is dodging me.
I'm trying to make it so when you click one of the pre-loaded locations on the left navigation bar, that it triggers the Google Maps marker animation, just like clicking on the actual marker does, as well as when filtering the list.
Here's my code so far:
// Define all variables to satisfy strict mode.
var document;
var setTimeout;
var alert;
var ko;
var google;
// Parsing for dynamic background & quote.
function parseQuote(response) {
"use strict";
document.getElementById("quote").innerHTML = response.quoteText;
document.getElementById("author").innerHTML = "Author - <b>" + response.quoteAuthor + "</b>";
}
// Specify all locations on map.
function model() {
"use strict";
var locations = [{
title: "The Hub",
lat: 39.521975,
lng: -119.822078,
id: "The Hub"
}, {
title: "The Jungle",
lat: 39.524982,
lng: -119.815983,
id: "The Jungle"
}, {
title: "Bibo Coffee Company",
lat: 39.536966,
lng: -119.811042,
id: "Bibo Coffee Company"
}, {
title: "Purple Bean",
lat: 39.531135,
lng: -119.833802,
id: "Purple Bean"
}, {
title: "Sips Coffee and Tea",
lat: 39.530438,
lng: -119.814742,
id: "Sips Coffee and Tea"
}];
return locations;
}
var listLocations = ko.observableArray(model());
// Initalize map location & position.
function initMap() {
"use strict";
var map = new google.maps.Map(document.getElementById("map"), {
center: {
lat: 39.529633,
lng: -119.813803
},
zoom: 14
});
// Define markers & content.
listLocations().forEach(function (data) {
var positionMk = new google.maps.LatLng(data.lat, data.lng);
var marker = new google.maps.Marker({
position: positionMk,
map: map,
title: data.title,
animation: google.maps.Animation.DROP
});
var infowindow = new google.maps.InfoWindow({
content: data.title
});
data.mapMarker = marker;
marker.addListener("click", function () {
data.triggerMarker(marker);
listLocations().forEach(function (place) {
if (data.title === place.title) {
place.openInfoWindow();
} else {
place.closeInfoWindow();
}
});
});
map.addListener("click", function () {
listLocations().forEach(function (place) {
place.closeInfoWindow();
});
});
var setMk = function (marker) {
infowindow.open(map, marker);
marker.setAnimation(google.maps.Animation.BOUNCE);
setTimeout(function () {
marker.setAnimation(null);
}, 750);
};
data.triggerMarker = setMk.bind();
var openMk = function () {
infowindow.open(map, marker);
};
data.openInfoWindow = openMk.bind();
var closeMk = function () {
infowindow.close(map, marker);
};
data.closeInfoWindow = closeMk.bind();
});
}
// Define ViewModel for list and sorting of list.
function ViewModel() {
"use strict";
var self = {};
self.placeList = ko.observableArray([]);
listLocations().forEach(function (place) {
place.visible = ko.observable(true);
self.placeList.push(place);
});
self.filterValue = ko.observable("");
self.filterList = ko.computed(function () {
listLocations().forEach(function (place) {
var searchParam = self.filterValue().toLowerCase();
var toBeSearched = place.title.toLowerCase();
place.visible(toBeSearched.indexOf(searchParam) > -1);
if (place.mapMarker) {
place.mapMarker.setVisible(toBeSearched.indexOf(searchParam) > -1);
}
if (place.visible() && searchParam && place.mapMarker) {
place.triggerMarker(place.mapMarker);
} else if (place.mapMarker) {
place.closeInfoWindow();
}
});
});
// Responsiveness for clicking locations on the list.
self.onClickListener = function (data) {
listLocations().forEach(function (place) {
if (data.title === place.title) {
place.openInfoWindow();
} else {
place.closeInfoWindow();
}
});
};
return self;
}
ko.applyBindings(new ViewModel());
// Error handling for API's.
function forismaticError() {
"use strict";
alert("Forismatic API is unreachable, please check your internet connection and try again.");
}
function googleMapsError() {
"use strict";
alert("Google Maps API is unreachable, please check your internet connection and try again.");
}
Any insight that can be offered into this would be appreciated! I feel like it's obvious, but my tired brain is failing me.
In addition, here's a quick JSFiddle of the entire project as well.
You just needed to copy the line of code that triggers the animation to your self.onClickListener function:
self.onClickListener = function (data) {
listLocations().forEach(function (place) {
if (data.title === place.title) {
place.openInfoWindow();
place.triggerMarker(place.mapMarker);
} else {
place.closeInfoWindow();
}
});
};

How to make dynamic markers in google maps with jSON?

I am trying to create dynamic markers to load information from my json file. For some reason, the json data never loads. When I try to load one marker, it works fine without the json data. I don't see what the error is. In the console, it says "TypeError: (intermediate value).error is not a function". Here is the code below.
html script link
<script src="https://maps.googleapis.com/maps/api/js?CLIENT ID HERE
&v=3.21&callback=initMap"
async defer></script>
External JS
var map;
function initMap() {
var myLatlng = {
lat: -25.363,
lng: 131.044
};
var centerZone = {
lat: 0,
lng: 0
};
map = new google.maps.Map(document.getElementById('map'), {
center: centerZone,
zoom: 3,
minZoom: 3
});
$.getJSON('data/data.json', function(data) {
$.each(data.markers, function(i, value) {
var myLatlng = new google.maps.LatLng(value.lat, value.lon);
alert(myLatlng);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title: value.lon
});
});
}.error(function(words) {
alert(words);
}));
var secretMessages = ['This', 'is', 'the', 'secret', 'message'];
/*marker.addListener('click', function() {
map.setZoom(6);
map.setCenter(marker.getPosition());
attachSecretMessage(marker, secretMessages[0]);
});*/
function attachSecretMessage(marker, secretMessage) {
var infowindow = new google.maps.InfoWindow({
content: secretMessage
});
marker.addListener('click', function() {
infowindow.open(marker.get('map'), marker);
});
}
// google.maps.event.addDomListener(window, 'load', initMap);
}
json data
{
"markers": [
{
"id": "1",
"name": "Mesoamerica",
"lat": "-25.363",
"lon": "131.044",
"zoomLevel": "6"
}
]
}
The json data will have more objects inside, this is just a sample of how I want it.
You need to wait until the JSON data loads before doing anything with it. I suggest placing everything that relies on the JSON file in a $.done() function, like this:
$.getJSON('data/data.json').done(function(data){
//everything else
});
Your browser will continue with the other lines of code while it's waiting for the $.getJSON function to return the data. That's why you're getting the "not a function" error; you're trying to call a function on something that doesn't exist and JS doesn't know what to do with it. If you place everything in $.done(), those lines won't execute until the JSON data has successfully been retrieved.

Angular-google-maps: How to show Title and Description dynamically on markers

I am using Angular-google-maps, HTML code follows
<ui-gmap-google-map center='mapData.map.center' zoom='mapData.map.zoom'
events="mapEvents">
<ui-gmap-markers models="mapData.map.markers" coords="'self'">
</ui-gmap-markers>
</ui-gmap-google-map>
in JS calling
angular.extend(this, $controller('MapsMixinController',
{$scope:$scope, map:mapData.data[0].map}));
MapsMixinController as follows. Calling this controller from js code. Markers are showing & on click able to mark.
MapsMixinController.js
/**
* Controller providing common behaviour for the other map controllers
*/
angular
.module('app')
.controller('MapsMixinController', ['$scope', 'GeolocationService', 'uiGmapGoogleMapApi', 'map',
function($scope, GeolocationService, GoogleMapApi, map) {
var _this = this;
$scope.mapEvents = {
click: function(mapModel, eventName, originalEventArgs) {
var e = originalEventArgs[0];
if (e.latLng) {
$scope.mapData.map.markers.push({
id: new Date().getTime(),
latitude: e.latLng.lat(),
longitude: e.latLng.lng()
});
// This event is outside angular boundary, hence we need to call $apply here
$scope.$apply();
}
}
};
// Returns a default map based on the position sent as parameter
this.getDefaultMap = function(position) {
return {
markers: [],
center: {
latitude: position.coords.latitude,
longitude: position.coords.longitude
},
zoom: 14
};
};
// Initialize the google maps api and configure the map
GoogleMapApi.then(function() {
GeolocationService().then(function(position) {
$scope.mapData.map = map || _this.getDefaultMap(position);
}, function() {
$scope.error = "Unable to set map data"; // TODO use translate
});
});
}
]);
How can I show title on mouse hover on markers? And on click how to show description on markers?
You can add title property alone with latitude and longtitude property while creating marker data.
/**
* Controller providing common behaviour for the other map controllers
*/
angular
.module('app')
.controller('MapsMixinController', ['$scope', 'GeolocationService', 'uiGmapGoogleMapApi', 'map',
function($scope, GeolocationService, GoogleMapApi, map) {
var _this = this;
$scope.mapEvents = {
click: function(mapModel, eventName, originalEventArgs) {
var e = originalEventArgs[0];
if (e.latLng) {
$scope.mapData.map.markers.push({
id: new Date().getTime(),
latitude: e.latLng.lat(),
longitude: e.latLng.lng(),
title: "Mouse over text"
});
// This event is outside angular boundary, hence we need to call $apply here
$scope.$apply();
}
}
};
// Returns a default map based on the position sent as parameter
this.getDefaultMap = function(position) {
return {
markers: [],
center: {
latitude: position.coords.latitude,
longitude: position.coords.longitude
},
zoom: 14
};
};
// Initialize the google maps api and configure the map
GoogleMapApi.then(function() {
GeolocationService().then(function(position) {
$scope.mapData.map = map || _this.getDefaultMap(position);
}, function() {
$scope.error = "Unable to set map data"; // TODO use translate
});
});
}
]);

How/where to keep google maps object in backbone to access it from multiple views

So I have a pretty easy app using Backbone.
I have collection of locations which I display.
I want to be able to click on one location and have the google location pin appear in the google map.
I actually have everything working but I hacked it to make it work.
I need the geocoder and the google map to be available from when I initialize it and also in the LocationSummaryView when it executes a search based on the address clicked. So to achieve this I passed the map and geocoder through all the view inits.
New to Backbone so just trying to work out best practices. I would like that all the google map code lives together.
AppView - I use require js which all works fine.
var AppView = Backbone.View.extend({
el: '#locationapp',
template: _.template(mainTemplate),
map:null,
geocoder:null,
initialize: function() {
this.collection = new LocationCollection();
this.collection.bind("reset", _.bind(this.fetched, this));
this.collection.fetch();
this.setUpMap();
},
setUpMap: function() {
var mapDiv = $("#map-canvas")[0];
var mapOptions = {
center: new google.maps.LatLng(37.7833, -122.4167),
zoom: 14
};
this.map = new google.maps.Map(mapDiv, mapOptions);
this.geocoder = new google.maps.Geocoder();
//hacky resizer I'll fix later
var $win = $(window);
var setMapSize = function() {
if ($win.width() >= 500) {
$(mapDiv).height($win.height());
} else {
$(mapDiv).height(300);
}
};
$win.resize(setMapSize);
setMapSize();
},
fetched: function() {
this.locationList = new LocationListView({collection:this.collection, map:this.map, geocoder:this.geocoder});
}
});
LocationListView
var LocationListView = Backbone.View.extend({
//el is top level div
el: '.location-search',
filter: null,
events: {
"keyup #location-name-search" : "filterList"
},
initialize: function(options) {
//You see what's going on here? just passing it on through
this.map = options.map;
this.geocoder = options.geocoder;
},
filterList: function(ev) {
this.filter = $(ev.target).val();
this.render();
},
render: function() {
console.log("rendering list");
var isMatchedLocation = true,
view = null,
title = '',
locations = '';
this.$(".location-list").empty();
//kill the render after it has cleared if the filter is empty
if (this.filter.length == 0) { return; }
//AUTOCOMPLETE THING IS HERE
//Just filtering which item views should be in the list
var filter = this.filter.toLowerCase();
var locationlist = this.collection.select(function(location) {
title = location.get("name").toLowerCase();
//sift out empty locations
locations = location.get("Locations") || false;
isMatchedLocation = title.indexOf(filter) !== -1 && locations != false;
if (isMatchedLocation === true) {
view = new LocationSummaryView({
model: location,
map: this.map,
geocoder: this.geocoder
});
this.$(".location-list").append(view.render().el);
}
return isMatchedLocation;
}, this);
},
});
LocationSummaryView
var LocationSummaryView = Backbone.View.extend({
tagName: 'li',
className: 'location-summary',
map: null,
geocoder: null,
template: _.template(summaryTemplate),
events: {
'click': 'onSelected'
},
initialize: function(options) {
//oh look at this familiar code
this.map = options.map;
this.geocoder = options.geocoder;
},
render: function() {
this.$el.html(this.template(this.model.attributes));
return this;
},
onSelected: function() {
var location = this.model.get("Locations");
this.setAddress(location);
},
setAddress: function(address) {
var fullAddress = address + " San Francisco, CA";
var self = this;
this.geocoder.geocode( { 'address': fullAddress}, function(results, status) {
// console.log(results);
if (status == google.maps.GeocoderStatus.OK) {
console.log(this);
self.map.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: self.map,
position: results[0].geometry.location
});
} else {
alert("Geocode was not successful for the following reason: " + status);
}
});
}
});

Categories

Resources