Google Maps API Autocomplete search without selecting from dropdown [duplicate] - javascript

This question already has answers here:
Google Maps API: How do you ensure that the Google Maps Autocomplete text is an actual address before submitting? [duplicate]
(2 answers)
Closed 2 years ago.
I am using Google Maps API and the Autocomplete search feature. Currently, you must begin typing a location(city, state, zip, etc.) and then select a result from the dropdown for the map to center at that location. However, I would like to make this fool proof and set it up so that if someone types just a city or just a state and hits "enter" without selecting an Autocomplete result, it will still work. For instance, if someone types "New York" and hits "enter" without selecting a result in the dropdown, it will still center on New York.
I'm assuming this is accomplished by grabbing the first result from the Autocomplete dropdown if one is not selected manually.
I've added an event listener for the submission of the form, but not sure how to pass the first result into the "place" variable.
Here is my code:
function searchBar(map) {
var input = document.getElementById('search-input');
var searchform = document.getElementById('search-form');
var place;
var options = {componentRestrictions: {country: 'us'}};
var autocomplete = new google.maps.places.Autocomplete(input, options);
//Add listener to detect autocomplete selection
google.maps.event.addListener(autocomplete, 'place_changed', function () {
place = autocomplete.getPlace();
center = new google.maps.LatLng(place.geometry.location.lat(),place.geometry.location.lng());
map.setCenter(center);
map.setZoom(5);
});
//Add listener to search
searchform.addEventListener('submit', function() {
center = new google.maps.LatLng(place.geometry.location.lat(),place.geometry.location.lng());
map.setCenter(center);
map.setZoom(5);
});
//Reset the inpout box on click
input.addEventListener('click', function(){
input.value = "";
});
};
And here is a JSFiddle for experimentation.

There is no implemented way to access the places in the dropdown. Basically what you see in the dropdown are not places, there are only predictions. The details for a prediction will be loaded when you select a prediction.
So you must select a prediction programmatically.
How to achieve it:
Another way than clicking on a prediction is to use the down-key, the keycode is 40. The API listens to keydown-events of the input.
So when you hit enter:
trigger keydown with a keycode 40(down)
trigger keydown with a keycode 13(enter)
google.maps.event.addDomListener(input,'keydown',function(e){
if(e.keyCode===13 && !e.triggered){
google.maps.event.trigger(this,'keydown',{keyCode:40})
google.maps.event.trigger(this,'keydown',{keyCode:13,triggered:true})
}
});
Note: to avoid a infinite loop for the enter I've added a custom property triggered, so I'm able to bypass the triggered keydown inside the function.
Demo: http://jsfiddle.net/4nr4tdwz/1/

I added a filter to avoid keydown being triggered when user has already select any result from the autocomplete:
google.maps.event.addDomListener(input,'keydown',function(e){
if(e.keyCode===13 && $('.pac-item-selected').length == 0 && !e.triggered){
google.maps.event.trigger(this,'keydown',{keyCode:40})
google.maps.event.trigger(this,'keydown',{keyCode:13,triggered:true})
}
});

Helper function for setting initial/default location programmatically - e.g. from geolocation API.
var triggerLocation = function(autocompleteInput, addressString) {
var $locationInput = $(autocompleteInput).val(addressString).trigger('focus');
var maxTries = 500;
var autocompleteSelectionInterval = setInterval(function() {
if (!--maxTries) {
clearInterval(autocompleteSelectionInterval);
}
if ($('.pac-container .pac-item').length) {
google.maps.event.trigger($locationInput[0], 'keydown', {
keyCode : 40
});
google.maps.event.trigger($locationInput[0], 'keydown', {
keyCode : 13
});
clearInterval(autocompleteSelectionInterval);
}
}, 10);
};
Example:
var autocomplete = new google.maps.places.Autocomplete($('#location')[0]);
triggerLocation('#location', 'New York');

Related

How to prevent selecting google autocomplete to put my value to the input?

I'm using Google Autocomplete and want to put street number + route value to the input field after selecting necessary location. I'm using this code
let options = { types: ['address'] };
let autocomplete = new google.maps.places.Autocomplete(element, options);
autocomplete.addListener('place_changed', function () {
let place = autocomplete.getPlace();
let components = new AutoCompleteAddressComponent(place.address_components).parse();
/* getStreet is returning address_components.street_number.short_name
and address_components.route.long_name */
let streetName = components.getStreet().trim();
$(element).val(streetName);
});
And how this code is working:
User it typing something in the input
Google is suggesting location from where users are selecting necessary one
Input is reflecting on the user action and putting data. For example 777 Brockton Avenue, Abington, MA, USA
After this place_changed is firing and 777 Brockton Avenue, Abington, MA, USA replacing to 777 Brockton Avenue
I don't like that 4-th point where users can see how data is replacing from long address to the short one.
Is there any way to show street number + route right after selecting location from the dropdown? I've tried to play with an Autocomplete method options by providing different types, but nothing helps me. I also need to get all data like country, state, city, post_code in the autocomplete.getPlace() object (I'm pre-filling these fields on the site).
I've found solution by using Google Places Autocomplete Service and completely redesign of the Autocomplete widget
Here is a source code if someone will try to implement this service:
registerGoogleAutoComplete = function(input) {
let options = { types: ['address'] };
const autocomplete = new google.maps.places.AutocompleteService();
// building container for the prediction list
const predictionList = this.predictionListMarkup(input);
$(input).parent().append(predictionList);
/* listening the input changing event and send data to the Google Service
to get prediction list */
input.addEventListener('input', function () {
// display list if something have
if (input.value) {
predictionList.style.display = 'block';
/* here is a main call of the getPlacePredictions where your input
value is sending to the Google Service
(input option of the getPlacePredictions method) */
autocomplete.getPlacePredictions({
input: input.value,
types: options.types,
}, function (predictions, status) {
self.displayPredictionSuggestions(predictions, status, predictionList, input);
});
} else {
// hide if not
predictionList.style.display = 'none';
}
});
}
/**
* #param autocompleteFormField
* #returns {HTMLUListElement}
*/
this.predictionListMarkup = function (autocompleteFormField) {
const predictionsWrapperDiv = document.createElement(`ul`);
predictionsWrapperDiv.classList.add(`pac-container`, `pac-logo`);
predictionsWrapperDiv.style.display = 'none';
predictionsWrapperDiv.style.width = autocompleteFormField.clientWidth + 'px';
return predictionsWrapperDiv;
};
this.displayPredictionSuggestions = function (predictions, status, predictionList, autocompleteFormField) {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
predictionList.style.display = `none`;
return;
}
predictionList.innerHTML = '';
/* this is a list of all predictions from the Google API. Each prediction
is a default Place object of the Google. So you can parse `address_component` and pre-fill whatever you want */
for (let prediction of predictions) {
self.predictionBuilder(prediction, predictionList, autocompleteFormField);
}
// here you can bind all necessary events like keyDownPress, keyUpPress to control your prediction list more
}
// this is a each prediction element UI building function
this.predictionBuilder = function (prediction, predictionList, autocompleteFormField) {
// we are creating li item to the ul container
const predictionListItem = document.createElement(`li`);
predictionListItem.classList.add(`pac-item`);
let text = prediction.description;
/* prediction has an matched_substrings, using which you can make matched string words bold (as Google is doing it) */
if (prediction.matched_substrings.length > 0) {
let tmp = text;
prediction.matched_substrings.forEach(function (item) {
let substrText = tmp.substring(item.offset, item.length + item.offset);
text = text.replace(substrText, '<span class="pac-matched">' + substrText + '</span>');
})
}
let node = document.createElement('div');
node.innerHTML = text;
predictionListItem.appendChild(node);
/* add 'onClick' event listener to handle user selecting action
where you can act the same is `place_changed` event of the Autocomplete service */
predictionListItem.addEventListener(`click`, function () {
self.autocompleteServiceListener(prediction, predictionList, autocompleteFormField);
});
predictionList.appendChild(predictionListItem);
};
P.s: Later I'll try to write a small component/widget for this

Restricting search based on a particular country in the Google Maps Places API

I am using the Google Maps' Places API for autocompletion of location as user types. But how can I restrict the suggestions to a particular country?
Here is my javascript:
function initAutocomplete() {
// Create the search box and link it to the UI element.
var input = document.getElementById('autocomplete-input');
var searchBox = new google.maps.places.SearchBox(input);
// [START region_getplaces]
// Listen for the event fired when the user selects a prediction and retrieve
// more details for that place.
searchBox.addListener('places_changed', function() {
var places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
});
}
Fixed it by changing SearchBox to Autocomplete and passing an options with the requirements..Here is the code if someone needs.
function initAutocomplete() {
// Create the search box and link it to the UI element.
var input = document.getElementById('autocomplete-input');
var options = {
componentRestrictions: {country: 'fr'}
};
var searchBox = new google.maps.places.Autocomplete(input, options);
// [START region_getplaces]
// Listen for the event fired when the user selects a prediction and retrieve
// more details for that place.
searchBox.addListener('places_changed', function() {
var places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
});
}
Note: I'm using Google Maps API v3 (v3.30.13)
#Abhilash's approach to the problem was nice to try with. But with his answer, I faced a problem: the autocomplete was working fine, but it didn't triggering the places_changed method. And there's no console error too.
Then with the answer by #geocodezip in another thread, I figured out that, the event fired up with Autocomplete is not places_changed, but place_changed (note the difference of singular form).
And there are other changes required if you want to proceed with place_changed, because with place_changed it return only a single place, not an array of places. So:
instead of .getPlaces() you have to use .getPlace()
instead of places.forEach(function(place) { ... } you have to work with place directly.
var input = document.getElementById('pac-input');
var restrictOptions = {
componentRestrictions: {country: 'bd'}
};
var searchBox = new google.maps.places.Autocomplete(input, restrictOptions);
searchBox.addListener('place_changed', function() {
var place = searchBox.getPlace();
if (place.length == 0) {
return;
}
if (!place.geometry) {
console.log("No details available for input: '" + place.name + "'");
return;
}
// ...
});
Caveat
The limitation is: with .SearchBox() google places search work even with random string, but with .Autocomplete() random string cannot find out any .geometry, hence cannot pinpoint to any matched area on the map. With .SearchBox() if you type "Name of a place", hit enter, it can point to Placename, Locality. But with .Autocomplete() without choosing from the autosuggestion[s] it cannot trigger the place's .geometry. The following console.log(place) displays the difference between random string and autocomplete chosen item:
Aftermath
I's not satisfied with the outcome how .Autocomplete() worked for me. So until having a nicer solution, I decided to stick to the .SearchBox() method - though it's not restricting the search suggestions. But binding with bounds_changed it binds the search result a little bit, though it's not strict and satisfactory, but at least something's better than nothing. ¯\_(ツ)_/¯
// Bias the SearchBox results towards current map's viewport.
front_end_map.addListener('bounds_changed', function() {
searchBox.setBounds(front_end_map.getBounds());
});
I, desperately am, looking forward for a bulletproof solution...

Bootstrap TypeAhead.js prevent invalid entry

I am using the bootstrap typeahead.js feature for autocomplete capability for 'state' lookup. How can I best prevent individuals from typing all (and not successfully picking Alaska) before submitting the form?
Typeahead.js does not currently support this behavior.
You can use other libraries better suited for restricted options, for example Select2
However I have working fork of Typeahead that supports this.
See the JQuery example page here:
https://github.com/Svakinn/typeahead.js/blob/typeaheadSimple/Examples.md
Depending on your requirements you could enforce picking from valid options only and clearing any invalid input.
Start with creating a custom source which tracks search results:
var sync = false;
var current_results = [];
var current_q = '';
var selected_val = '';
function search(q, sync) {
current_q = q;
window.sync = sync;
engine.search(q, cust_sync);
}
function cust_sync(datums) {
current_results = datums;
sync(datums);
}
$('#typeahead').typeahead(
{
highlight: true,
minLength: 0
},
{
source: search // custom search engine
}
);
Then bind select and change event handlers.
$('#typeahead').bind('typeahead:change', function(ev) {
var current_val = $('#typeahead').val();
// user moved cursor out of input without selecting from the menu
if(current_val != selected_val){
// current query has search results
if(current_results.length)
// change input to the first valid search result
$('#typeahead').typeahead('val', current_results[0].name);
// no results for this query - clear input
else
$('#typeahead').typeahead('val', '');
}
});
$('#typeahead').bind('typeahead:select', function(ev, suggestion) {
selected_val = $('#typeahead').val(); // keep track of a selected value
});

Input shows previous value, after setting value

I am making a form with yandex maps integration. I need the value of input to be changed after user picks some point on map. Here is code:
function setPlace(c){
//c - coordinates
var geocoder = ymaps.geocode(c, {kind: "locality"});
geocoder.then(function(res){
var city = res.geoObjects.get(0).properties.get("name");
var inpCity = document.getElementById(idCity);
inpCity.value = city;
});
}
So the problem is I am setting value of input properly (checked in console). But on the page input always shows previous chosen value.

Store input value as a var and display it

This is the code I am working from:
http://jsfiddle.net/DYfbg/12/
If you click "map" then try typing a placename it gives you autocomplete suggestions. Then if you click on one of these suggestions it tells you the co-ordinates in an alert box.
What I want to do is capture what is in the input box when the autocomplete option is clicked, and then store it as a variable. Then in the alert box, along with the co-ordinates, I want to print the input box variable into it.
I know this all sounds pretty simple but I am new to javascript so I would really appreciate some input.
This is the segment of code:
google.maps.event.addListener(autocomplete, 'place_changed', function() {
var place = autocomplete.getPlace();
**// Store input box as variable**
if (!place.geometry) {
current_place = null;
alert('Cannot find place');
return;
} else {
current_place = place;
alert('Place is at:' + place.geometry.location);
**// Print input box varaible aswell**
} });
EDIT: This is as close as I could get without getting stuck:
// Set Autocomplete Location Variable equal to #loc input
$("#loc").val(ac_location);
// Print Autocomplete Variable
alert(ac_location);
In the "place" object returned with
var place = autocomplete.getPlace();
you have all info you need
Add a console.log and you'll see all infos returned.
For example:
var place = autocomplete.getPlace();
console.log(place);
window.myGlobalVar = place.name;
Edit based on notes below:
it seems original value of inputbox is actually stored in this property:
autocomplete.gm_accessors_.place.lf.d

Categories