rails 5 turbolinks 5 and google maps how to? - javascript

I'm using rails 5 with turbolinks 5 and trying to get google places autocomplete address form to work using the example from google's website found here
I only want to load the javascript and google maps library on this page.
What would be the correct way to get the following javascript to work with rails 5 and turbolinks 5.
<script>
// This example displays an address form, using the autocomplete feature
// of the Google Places API to help users fill in the information.
// This example requires the Places library. Include the libraries=places
// parameter when you first load the API. For example:
// <script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places">
var placeSearch, autocomplete;
var componentForm = {
street_number: 'short_name',
route: 'long_name',
locality: 'long_name',
administrative_area_level_1: 'short_name',
country: 'long_name',
postal_code: 'short_name'
};
function initAutocomplete() {
// Create the autocomplete object, restricting the search to geographical
// location types.
autocomplete = new google.maps.places.Autocomplete(
/** #type {!HTMLInputElement} */(document.getElementById('autocomplete')),
{types: ['geocode']});
// When the user selects an address from the dropdown, populate the address
// fields in the form.
autocomplete.addListener('place_changed', fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
var place = autocomplete.getPlace();
for (var component in componentForm) {
document.getElementById(component).value = '';
document.getElementById(component).disabled = false;
}
// Get each component of the address from the place details
// and fill the corresponding field on the form.
for (var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if (componentForm[addressType]) {
var val = place.address_components[i][componentForm[addressType]];
document.getElementById(addressType).value = val;
}
}
}
// Bias the autocomplete object to the user's geographical location,
// as supplied by the browser's 'navigator.geolocation' object.
function geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy
});
autocomplete.setBounds(circle.getBounds());
});
}
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&libraries=places&callback=initAutocomplete"
async defer></script>

I solved Turbolinks 5 and Google Maps API problem just with data-turbolinks-eval="false" attribute on my google maps script tag.
... application.html.erb
<head>
<!-- Other HTML page Head content -->
<title><%= content_for?(:title) ? yield(:title) : "Untitled" %></title>
<script async src="https://maps.googleapis.com/maps/api/js?key=YOUR_GOOGLE_MAPS_API_KEY" data-turbolinks-eval="false"></script>
<%= javascript_include_tag "application", 'data-turbolinks-track' => 'reload' %>
</head>
and in your coffee/js files use:
coffee:
$(document).on "turbolinks:load", ->
map = new google.maps.Map $('map').get(0), mapOptions
js:
$(document).on("turbolinks:load", function(){
map = new google.maps.Map($('map').get(0), mapOptions)
})

Try this too....
google.maps.event.addDomListener(window, 'turbolinks:load', initialize);

I was not happy with other answers as Google Maps throws a warning if loaded multiple times, e.g. while re-visiting a page, and disabling Turbolinks defeats its purpose. Also using Google Maps JavaScript API's callback argument forces you to pollute window name space.
Instead we can get Google Maps scripts, e.g. with jQuery, if and only if it is not loaded yet. Here is the CoffeeScript that I use with Rails 4.2.10:
initMap = ->
...
$(document).on 'turbolinks:load', ->
// if you would like to limit what views are affected
// see https://brandonhilkert.com/blog/organizing-javascript-in-rails-application-with-turbolinks/
// return unless $('.plots.map').length > 0
if typeof(google)=='undefined' or typeof(google.maps)=='undefined'
$.get
url: "https://maps.googleapis.com/maps/api/js?key=<use_your_key>&libraries=geometry"
dataType: 'script'
success: initMap
else
initMap()

As per the documentation:
Turbolinks evaluates script elements in a page’s body each time it renders the page. You can use inline body scripts to set up per-page JavaScript state or bootstrap client-side models. To install behavior, or to perform more complex operations when the page changes, avoid script elements and use the turbolinks:load event instead.
But the reason it wasn't working for me was because in layouts/application.html.erb I had:
<%= javascript_include_tag "application", 'data-turbolinks-track' => true %>
when it should be:
<%= javascript_include_tag "application", 'data-turbolinks-track': 'reload' %>

Related

How to build a theme using jaredpalmer/presspack with Google Maps integration?

Introducing the problem
Building this theme using jaredpalmer/presspack for docker, I'm struggling for a few days now as I try to insert this pair of Google Maps instances within an element that will be called in every page. The maps won't load and Chrome tells me my function is not there.
Some context
Presspack is serving the DB and uses yarn to make a environment. It keeps scripts separately for general purposes and specific ones, as it has a common.js - where I code everything it should load on every page - and a .js - which loads only on specific pages. I'm working on common now, since I believe this contact section will be used on every page and blog post of this site. The calling function for this section is WP basic <?php get_template_part( 'content', 'contato-std' ); ?>
I've added a code in functions.php to load the API key script after the footer of my pages.
It's important to mention that I've tried the js code I'm using in common.js on another HTML only environment.
My files
common.js
export default {
init() {
// JavaScript to be fired on all pages
console.log('common');
},
finalize() {
// JavaScript to be fired on all pages, after page specific JS is fired
var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile) { // Tudo o que for pra navegadores mobile
(...)
} else { // Tudo o que for pra navegadores padrão
(...)
// GOOGLE MAPS LOAD
var markers = [{
GPS1: -15.7954901,
GPS2: -47.8926766,
client_address: "Corpus - Fisioterapia & Pilates, Unidade Asa Sul"
}];
function initialize() {
initMap();
initMap2();
};
function initMap() {
var latlng = new google.maps.LatLng(-15.7954901, -47.8926766); // default location
var myOptions = {
zoom: 16,
center: latlng,
mapTypeId: google.maps.MapTypeId.MAP,
mapTypeControl: true
};
var map = new google.maps.Map(document.getElementById('mapaAsaSul'), myOptions);
var infowindow = new google.maps.InfoWindow(),
marker, lat, lng;
for (i = 0; i < markers.length; i++) {
lat = (markers[i].GPS1);
lng = (markers[i].GPS2);
name = (markers[i].client_address);
marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
name: name,
map: map
});
google.maps.event.addListener(marker, 'click', function(e) {
infowindow.setContent(this.name);
infowindow.open(map, this);
}.bind(marker));
}
}
var markers2 = [{
GPS1: -15.7187167,
GPS2: -47.8867326,
client_address: "Corpus - Fisioterapia & Pilates, Unidade Lago norte"
}];
function initMap2() {
var latlng = new google.maps.LatLng(-15.7187167, -47.8867326); // default location
var myOptions = {
zoom: 16,
center: latlng,
mapTypeId: google.maps.MapTypeId.MAP,
mapTypeControl: true
};
var map = new google.maps.Map(document.getElementById('mapaLagoNorte'), myOptions);
var infowindow = new google.maps.InfoWindow(),
marker, lat, lng;
for (i = 0; i < markers2.length; i++) {
lat = (markers2[i].GPS1);
lng = (markers2[i].GPS2);
name = (markers2[i].client_address);
marker = new google.maps.Marker({
position: new google.maps.LatLng(lat, lng),
name: name,
map: map
});
google.maps.event.addListener(marker, 'click', function(e) {
infowindow.setContent(this.name);
infowindow.open(map, this);
}.bind(marker));
}
}
},
};
function.php
<?php
...
function add_google_maps() {
wp_enqueue_script(
'my-google-maps',
'http://maps.googleapis.com/maps/api/js?key=KEY&callback=initialize',
NULL,
NULL,
true
);
add_filter( 'script_loader_tag', function ( $tag, $handle ) {
if ( 'my-google-maps' !== $handle )
return $tag;
return str_replace( ' src', ' async defer src', $tag );
}, 10, 2 );
}
add_action('wp_enqueue_scripts', 'add_google_maps');
?>
The JS handling the routing from common.js and .js is index.js
import jQuery from 'jquery';
import './style.scss';
import Router from './util/Router';
import common from './routes/common';
import home from './routes/home';
/**
* Populate Router instance with DOM routes
* #type {Router} routes - An instance of our router
*/
const routes = new Router({
/** All pages */
common,
/** Home page */
home
/** About Us page, note the change from about-us to aboutUs. */
});
/** Load Events */
jQuery(document).ready(() => routes.loadEvents());
Bad response
It should load both instances of maps within the respective div's #mapaAsaSul and #mapaLagoNorte, but it won't and Chrome's console returns this:
(index):1
Uncaught (in promise) Xc {message: "initialize is not a function", name: "InvalidValueError", stack: "Error↵ at new Xc (http://maps.googleapis.com/ma…KEY&callback=initialize:125:107"}message: "initialize is not a function"name: "InvalidValueError"stack: "Error↵ at new Xc (http://maps.googleapis.com/maps/api/js?key=KEY&callback=initialize:56:227)↵ at Object._.Yc (http://maps.googleapis.com/maps/api/js?key=KEY&callback=initialize:56:337)↵ at Uh (http://maps.googleapis.com/maps/api/js?key=KEY&callback=initialize:125:221)↵ at http://maps.googleapis.com/maps/api/js?key=KEY&callback=initialize:125:107"__proto__: Error
Promise.then (async)
Vh # js?key=KEY&callback=initialize:125
google.maps.Load # js?key=KEY&callback=initialize:21
(anonymous) # js?key=KEY&callback=initialize:212
(anonymous) # js?key=KEY&callback=initialize:212
This initialize is written in common.js. The file is listed in console, red by webpack (which presspack uses).
I'm running this #localhost so won't be able to provide a link for this page.
And I'm sorry if I've done any mistakes formatting this post or provide wrong info. Not really experienced at creating posts here in stack. :(
Any thoughts and ideas on this problem will be most welcome.
Edit: corrected that mistake as Evan suggested in the comments, where the calling was for maps.google.com and not maps.googleapis.com
Edit2: As Evan kept helping me on the comment section, I've made some additions to my code as follows.
The callback function that was returning a problem in console, was removed and I replaced it as a new callback for initialize function that appears on common.js.
var markers = [{(...)}];
function initialize() {(...)};
function initMap() {(...)};
var markers = [{(...)}];
function initMap2() {(...)};
google.maps.event.addDomListener(window, 'load', initialize); //This is the new callback for my maps
This did solved something, since the first map appeared. Still, there was still some error which console would point at:
"Uncaught ReferenceError: i is not defined at initMap (common.js?a395:77) at initialize (common.js?a395:56)" pointing at this line of common.js: for (i = 0; i < markers.length; i++) {.
I didn't get it. Just guessed that it had something to do with my markers code, since the first map appeared after the callback being made in common.js but with no marker.
Then, Evan pointed that if i was initialized globally, that error made sense. So he suggested changing the code to for (let i = 0; i < markers.length; i++) {, which did it for me. Adding it to both instances of markers in the code, made both maps appear properly and with markers.
Thanks for your help, Evan!
First of all, the JavaScript API should be called via https://maps.googleapis.com/, not http://maps.google.com/.
The "initialize is not a function" scope error can be worked around by loading the Maps API script synchronously on page load, e.g. by using google.maps.event.addDomListener(window, 'load', initialize), but in general the recommended approach is to load the API asynchronously via a callback:
<script async defer src="https://maps.googleapis.com/maps/api/js?key=KEY&callback=initialize"></script>
Then there's an issue with both markers loops, e.g. for (i = 0; i < markers.length; i++), as they're missing i initialization. Try using for (let i = 0; i < markers.length; i++).
You can find here a working jsbin that shows both your maps and markers loading successfully after the above code changes.

Reinitializing initAutocomplete Google Maps API Places autocomplete upon dynamically-loaded HTML input text element target

On initial page load this script:
<script src="https://maps.googleapis.com/maps/api/js?key=KEY&libraries=places&callback=initAutocomplete" async defer></script>
along with the functions I've shared below are loading to support Google Maps autocomplete (JS) with Places library.
Autocomplete functionality works if the target input text element loads initially with the page; however, when I dynamically load it later (as part of a click event) ($(element).html('<input type="text" id="location" />');) the autocomplete doesn't work.
I've tried to reinitialize the initAutocomplete by calling the function again after loading the new "location" element (initAutocomplete()), but still not working. Is it possible to reinitialize initAutocomplete() for a dynamically generated autocomplete target (<input type="text" id="location" />)?
var placeSearch, autocomplete;
var componentForm = {
street_number: 'short_name',
route: 'long_name',
locality: 'long_name',
administrative_area_level_1: 'short_name',
country: 'long_name',
postal_code: 'short_name'
};
function initAutocomplete() {
// Create the autocomplete object, restricting the search to geographical
// location types.
autocomplete = new google.maps.places.Autocomplete(
/** #type {!HTMLInputElement} */(document.getElementById('location')),
{types: ['geocode']});
// When the user selects an address from the dropdown, populate the address
// fields in the form.
autocomplete.addListener('place_changed', fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
var place = autocomplete.getPlace();
for (var component in componentForm) {
document.getElementById(component).value = '';
document.getElementById(component).disabled = false;
}
// Get each component of the address from the place details
// and fill the corresponding field on the form.
for (var i = 0; i < place.address_components.length; i++) {
var addressType = place.address_components[i].types[0];
if (componentForm[addressType]) {
var val = place.address_components[i][componentForm[addressType]];
document.getElementById(addressType).value = val;
}
}
document.getElementById("lat").value = place.geometry.location.lat();
document.getElementById("lng").value = place.geometry.location.lng();
}
// Bias the autocomplete object to the user's geographical location,
// as supplied by the browser's 'navigator.geolocation' object.
function geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function(position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy
});
autocomplete.setBounds(circle.getBounds());
});
}
}

jquery and google places autocomplete

I would like to add the Google Places address autocomplete search to a form. I see the example code HERE. I am using jquery and have my ready function in a separate js file. I have put the example functions into that external js file and it is being loaded.
I have my API key in the Google API script tag. but the callback=initAutocomplete js function is not being called.
My script section looks like -- yes, I have a real API key:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"
integrity="..." crossorigin="anonymous"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=<MY-API-KEY>&libraries=places&callback=initAutocomplete"
async defer></script>
<script src="js/index.js'"></script>
The index.js file looks mostly like:
$(function () {
...
$("#addressLookup").on('focus', geolocate());
});
var autocomplete;
function initAutocomplete() {
// Create the autocomplete object, restricting the search to geographical
// location types.
autocomplete = new google.maps.places.Autocomplete(
/** #type {!HTMLInputElement} */(document.getElementById('addressLookup')),
{types: ['geocode']});
// When the user selects an address from the dropdown, populate the address
// fields in the form.
autocomplete.addListener('place_changed', fillInAddress);
}
function fillInAddress() {
// Get the place details from the autocomplete object.
var place = autocomplete.getPlace();
console.log("fillInAddress: place");
console.dir(place);
}
// Bias the autocomplete object to the user's geographical location,
// as supplied by the browser's 'navigator.geolocation' object.
function geolocate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(function (position) {
var geolocation = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
var circle = new google.maps.Circle({
center: geolocation,
radius: position.coords.accuracy
});
autocomplete.setBounds(circle.getBounds());
});
}
}
I don't see anything barfing in the js console log. I do not see the Google stuff loading in the Developer Tools "Network" panel.
What have I missed?
I think you should change scripts order
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"
integrity="..." crossorigin="anonymous"></script>
<script src="js/index.js'"></script>
<script src="https://maps.googleapis.com/maps/api/js?key=<MY-API-KEY>&libraries=places&callback=initAutocomplete"
async defer></script>

Rails accessing javascript variable in template - Google map

I try something : I have a gMap with markers.
In my index I initialise my google map, put markers etc. Everything is working.
On the left of my page I show the info of the 5 lasts markers on the maps (name, date etc.).
So for that I call a template named _timeline.
When I click one element I do an action -> Move the map. But For now, everytime I do this action, the map is reloaded (and markers are gone). I just want to see the map moving to center the marker.
But let's see my code, this is probably more simple :
my index.html.erb
<div class="row">
<% #statuses_t.each do |status| %>
<%= render partial: "statuses/timeline", locals: { status: status } %>
<% end %>
</div>
<script type="text/javascript">
handler = Gmaps.build('Google');
handler.buildMap({
provider: {
disableDefaultUI: true
// pass in other Google Maps API options here
},
internal: {
id: 'map'
}
},
function(){
markers = handler.addMarkers((<%=raw #hash.to_json %>));
handler.bounds.extendWith(markers);
handler.fitMapToBounds();
}
);
</script>
handler is the gMap
in statuses/timeline
... # some things
<script type="text/javascript">
$(<%="timeline_div#{status.id}"%>).on("click", function() {
var centerpoint = new google.maps.LatLng(<%=status.latitude%>, <%=status.longitude%>);
gMap = new google.maps.Map(document.getElementById('map'));
gMap.setZoom(13);
gMap.setCenter(centerpoint); // HERE
gMap.setMapTypeId(google.maps.MapTypeId.ROADMAP);
});
</script>
this create a new map when a click on the item, the probleme is : I don't want to reload a page, I just want to change re-center the map with setCenter.
So I want to use the handler variable from Index in my template.
How can I do that ?
So
My variable vas already global ( template has the variable )
Thanks to comments, I had to do handler.map.centerOn(<%=status.latitude%>, <%=status.longitude%>) and that's work

Gmaps4rails: What's the proper way to enable clustering after adding markers via AJAX?

I tried to search the answer from docs, but couldn't find..
How can I enable clustering in Gmaps4rails when I add markers on the map via AJAX. My view has this:
<%= gmaps( map_options: { zoom: 15, auto_adjust: false } ) %>
and in my javascript I add the markers like this:
$.getJSON(path, { lat: lat, lng: lng, user_lat: user_lat, user_lng: user_lng }, function(markers_json) {
Gmaps.map.replaceMarkers(markers_json);
});
I'd just like to set the do_clustering option to true, but because I don't have markers json in the map gmaps() call, I can't add the markers options either.
Tell gmaps4rails directly:
Gmaps.map.markers_conf.do_clustering = true;
And include this script:
<script type="text/javascript" src="http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.9/src/markerclusterer_packed.js"></script>

Categories

Resources