I have successfuly implemented Google Maps Places V3 autocomplete feature on my input box as per http://web.archive.org/web/20120225114154/http://code.google.com:80/intl/sk-SK/apis/maps/documentation/javascript/places.html. It works nicely, however I would love to know how can I make it select the first option from the suggestions when a user presses enter. I guess I would need some JS magic, but I am very much new to JS and don't know where to start.

- 20,030
- 7
- 43
- 238

- 1,414
- 1
- 12
- 12
-
nice question. i was thinking about this today. my only worry is forcing the user to do anything they maybe wont want to do. Some big websites don't force selection of the first option is there are more than one option visible, they only force when there is only one option showing, which seems better to me. – luke_mclachlan Feb 10 '15 at 22:06
19 Answers
Here is a solution that does not make a geocoding request that may return an incorrect result: http://jsfiddle.net/amirnissim/2D6HW/
It simulates a down-arrow
keypress whenever the user hits return
inside the autocomplete field. The ↓ event is triggered before the return event so it simulates the user selecting the first suggestion using the keyboard.
Here is the code (tested on Chrome and Firefox) :
<script src='https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js'></script>
<script src="https://maps.googleapis.com/maps/api/js?sensor=false&libraries=places"></script>
<script>
var pac_input = document.getElementById('searchTextField');
(function pacSelectFirst(input) {
// store the original event binding function
var _addEventListener = (input.addEventListener) ? input.addEventListener : input.attachEvent;
function addEventListenerWrapper(type, listener) {
// Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected,
// and then trigger the original listener.
if (type == "keydown") {
var orig_listener = listener;
listener = function(event) {
var suggestion_selected = $(".pac-item-selected").length > 0;
if (event.which == 13 && !suggestion_selected) {
var simulated_downarrow = $.Event("keydown", {
keyCode: 40,
which: 40
});
orig_listener.apply(input, [simulated_downarrow]);
}
orig_listener.apply(input, [event]);
};
}
_addEventListener.apply(input, [type, listener]);
}
input.addEventListener = addEventListenerWrapper;
input.attachEvent = addEventListenerWrapper;
var autocomplete = new google.maps.places.Autocomplete(input);
})(pac_input);
</script>

- 3,173
- 2
- 24
- 19
I had the same issue when implementing autocomplete on a site I worked on recently. This is the solution I came up with:
$("input").focusin(function () {
$(document).keypress(function (e) {
if (e.which == 13) {
var firstResult = $(".pac-container .pac-item:first").text();
var geocoder = new google.maps.Geocoder();
geocoder.geocode({"address":firstResult }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat(),
lng = results[0].geometry.location.lng(),
placeName = results[0].address_components[0].long_name,
latlng = new google.maps.LatLng(lat, lng);
$(".pac-container .pac-item:first").addClass("pac-selected");
$(".pac-container").css("display","none");
$("#searchTextField").val(firstResult);
$(".pac-container").css("visibility","hidden");
moveMarker(placeName, latlng);
}
});
} else {
$(".pac-container").css("visibility","visible");
}
});
});

- 3
- 1

- 4,687
- 1
- 17
- 7
-
16The problem with this method is it doesn't return the same address as the first autocomplete result. It performs a geocode on the display address of the first autocomplete result which may be an entirely different address. For example, the first autocomplete result for input "jfk" is the correct address (JFK Access Road, New York, NY, United States) but the geocode result for the address "JFK Airport, New York, NY" (which is the display address of the first autocomplete) will produce the address of what appears to be a hotel (144-02 135th Ave, Queens, NY 11436, USA). – Reed G. Law Jun 26 '12 at 00:59
-
1In my case, I did the following: `var firstResult = $(".pac-container .pac-item:first").text();var stringMatched = $(".pac-container .pac-item:first").find(".pac-item-query").text(); firstResult = firstResult.replace(stringMatched, stringMatched + " ");` And that solved the issue – Christian Martinez Apr 29 '15 at 19:58
-
Can't you just do .click() on that first item to trigger the same processing as you would. – Antony D'Andrea Jul 23 '18 at 14:14
-
Can you please tell me how can i add space after ".pac-item-query" because now when i click on ENTER the space is removed – Chetan Apr 22 '19 at 12:15
-
Thank you! thank you!! thank you!!! I didn't know you can use Geocoder.geocode() to get the address_components. You've saved me hours of work! Thank you again! – Sam Oct 30 '19 at 12:10
A working answer for 2020.
I've combined the best answers on this page and written it in straightforward ES6. No jQuery, 2nd API request, or IIFE needed.
Basically, we simulate a ↓ (down-arrow
) keypress whenever the user hits return inside the autocomplete field.
First, assuming in your HTML you have something like <input id="address-field">
, set up the identification of your address field like this:
const field = document.getElementById('address-field')
const autoComplete = new google.maps.places.Autocomplete(field)
autoComplete.setTypes(['address'])
Then add this on the next line:
enableEnterKey(field)
And then elsewhere in your script, to keep this functionality separate in your code if you'd like to, add the function:
function enableEnterKey(input) {
/* Store original event listener */
const _addEventListener = input.addEventListener
const addEventListenerWrapper = (type, listener) => {
if (type === 'keydown') {
/* Store existing listener function */
const _listener = listener
listener = (event) => {
/* Simulate a 'down arrow' keypress if no address has been selected */
const suggestionSelected = document.getElementsByClassName('pac-item-selected').length
if (event.key === 'Enter' && !suggestionSelected) {
const e = new KeyboardEvent('keydown', {
key: 'ArrowDown',
code: 'ArrowDown',
keyCode: 40,
})
_listener.apply(input, [e])
}
_listener.apply(input, [event])
}
}
_addEventListener.apply(input, [type, listener])
}
input.addEventListener = addEventListenerWrapper
}
You should be good to go. Essentially, the function captures each keypress in the input
field and if it's an enter
, simulates instead a down-arrow
keypress. It also stores and rebinds listeners and events to maintain all functionality of your Google Maps Autocomplete()
.
With thanks to earlier answers for much of this code, particular amirnissim and Alexander Schwarzman.

- 4,012
- 3
- 39
- 51
-
1You forgot to declare enableEnterKey as a function. `function enableEnterKey(input) {}` Other than that it works flawlessly, thank you! – richardj May 07 '18 at 08:38
-
Added that, thanks. Could also use `const enableEnterKey = (input) => {` if you want to use an arrow function. It depends on whether you're using classes and constructors or not, but either way it's more complete now. – Tony Brasunas May 08 '18 at 16:41
-
1Tweaked to handle tab keys also, by adding a range, i.e.if (event.which >= 9 && event.which <= 13 && !suggestion_selected) { – Isabelle Jul 09 '18 at 20:43
-
@Isabelle That's a nice addition if you want additional keys to work – Tony Brasunas Aug 01 '18 at 02:59
-
-
And plus why do you use .which? It should be .key = "Enter" and e.key="Down arrow". "If you are staying in vanilla Javascript, please note keyCode is now deprecated and will be dropped:" https://stackoverflow.com/questions/4471582/keycode-vs-which – Philipp Mochine Nov 01 '18 at 08:43
-
1
-
-
2@Shadrix And now updated to modern browser key identifiers as well. – Tony Brasunas Mar 20 '19 at 23:57
-
5This was not working for me at the beginning. I changed the way the mocked keydown event is created and the solution worked as a charm: `const e = new KeyboardEvent("keydown", { key: "ArrowDown", code: "ArrowDown", keyCode: 40 });` – sebbab Oct 02 '19 at 08:43
-
1@sebbab Thanks, added that improvement. Agreed that's a more modern and universal way of mocking the event. – Tony Brasunas Oct 08 '20 at 18:04
-
1In Typscript I had to add this `Object.defineProperty(e, 'keyCode', { get: () => 40, });` because keyCode is not defined on KeyboardEventInit. – maidi Nov 10 '20 at 11:50
-
To enable this event `onBlur` I've stored the `_listener` from the `keyDown` and applied it to `blur`. – Tim Vermaelen Nov 15 '22 at 10:47
Here is an example of a real, non-hacky, solution. It doesn't use any browser hacks etc, just methods from the public API provided by Google and documented here: Google Maps API
The only downside is that additional requests to Google are required if the user doesn't select an item from the list. The upside is that the result will always be correct as the query is performed identically to the query inside the AutoComplete. Second upside is that by only using public API methods and not relying on the internal HTML structure of the AutoComplete widget, we can be sure that our product won't break if Google makes changes.
var input = /** @type {HTMLInputElement} */(document.getElementById('searchTextField'));
var autocomplete = new google.maps.places.Autocomplete(input);
// These are my options for the AutoComplete
autocomplete.setTypes(['(cities)']);
autocomplete.setComponentRestrictions({'country': 'es'});
google.maps.event.addListener(autocomplete, 'place_changed', function() {
result = autocomplete.getPlace();
if(typeof result.address_components == 'undefined') {
// The user pressed enter in the input
// without selecting a result from the list
// Let's get the list from the Google API so that
// we can retrieve the details about the first result
// and use it (just as if the user had actually selected it)
autocompleteService = new google.maps.places.AutocompleteService();
autocompleteService.getPlacePredictions(
{
'input': result.name,
'offset': result.name.length,
// I repeat the options for my AutoComplete here to get
// the same results from this query as I got in the
// AutoComplete widget
'componentRestrictions': {'country': 'es'},
'types': ['(cities)']
},
function listentoresult(list, status) {
if(list == null || list.length == 0) {
// There are no suggestions available.
// The user saw an empty list and hit enter.
console.log("No results");
} else {
// Here's the first result that the user saw
// in the list. We can use it and it'll be just
// as if the user actually selected it
// themselves. But first we need to get its details
// to receive the result on the same format as we
// do in the AutoComplete.
placesService = new google.maps.places.PlacesService(document.getElementById('placesAttribution'));
placesService.getDetails(
{'reference': list[0].reference},
function detailsresult(detailsResult, placesServiceStatus) {
// Here's the first result in the AutoComplete with the exact
// same data format as you get from the AutoComplete.
console.log("We selected the first item from the list automatically because the user didn't select anything");
console.log(detailsResult);
}
);
}
}
);
} else {
// The user selected a result from the list, we can
// proceed and use it right away
console.log("User selected an item from the list");
console.log(result);
}
});

- 229
- 2
- 2
-
2your solution is not correct as it breaks when `place_changed` event is not fired. That is when none option is selected. that means when user presses enter or tab keys or even click elsewhere than in an option. – sabotero Mar 05 '15 at 09:38
-
Has anyone found a way to update the 'place' of the autocomplete object with this implementation? Currently, if this is used `autocomplete.getPlace()` results in `undefined`. – Ulad Kasach May 29 '17 at 20:49
-
To answer Ulad's question, simply move the `AutocompleteService` code outside of the 'place_changed' event handler, and instead set state that 'place_changed' will set to true if the user selects a value. If the state has not changed when expected (I'm using on bur on the input field), then fire the `AutocompleteService` with input as search key. The main problem I've found with using the service however is that the results returned are not guaranteed to be the same or in the same order as the completion API, so you must implement your own prediction UI with the service results – verboze Jun 13 '18 at 19:51
It seems there is a much better and clean solution: To use google.maps.places.SearchBox
instead of google.maps.places.Autocomplete
.
A code is almost the same, just getting the first from multiple places. On pressing the Enter the the correct list is returned - so it runs out of the box and there is no need for hacks.
See the example HTML page:
The relevant code snippet is:
var searchBox = new google.maps.places.SearchBox(document.getElementById('searchinput'));
google.maps.event.addListener(searchBox, 'places_changed', function() {
var place = searchBox.getPlaces()[0];
if (!place.geometry) return;
if (place.geometry.viewport) {
map.fitBounds(place.geometry.viewport);
} else {
map.setCenter(place.geometry.location);
map.setZoom(16);
}
});
The complete source code of the example is at: https://gist.github.com/klokan/8408394

- 1,754
- 14
- 22
-
6it works exactly the same as `Autocomplete` for pressing Enter so... it doesn't work. – klimat Nov 21 '16 at 17:05
-
7It should be noted that you can't set componentRestrictions options for SearchBox. So if you need to restrict the suggestions to a specific country this is not an alternative. – Felix Wienberg Dec 13 '16 at 16:54
For Google Places Autocomplete V3, the best solution for this is two API requests.
Here is the fiddle
The reason why none of the other answers sufficed is because they either used jquery to mimic events (hacky) or used either Geocoder or Google Places Search box which does not always match autocomplete results. Instead, what we will do is is uses Google's Autocomplete Service as detailed here with only javascript (no jquery)
Below is detailed the most cross browser compatible solution using native Google APIs to generate the autocomplete box and then rerun the query to select the first option.
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places&language=en"></script>
Javascript
// For convenience, although if you are supporting IE8 and below
// bind() is not supported
var $ = document.querySelector.bind(document);
function autoCallback(predictions, status) {
// *Callback from async google places call
if (status != google.maps.places.PlacesServiceStatus.OK) {
// show that this address is an error
pacInput.className = 'error';
return;
}
// Show a successful return
pacInput.className = 'success';
pacInput.value = predictions[0].description;
}
function queryAutocomplete(input) {
// *Uses Google's autocomplete service to select an address
var service = new google.maps.places.AutocompleteService();
service.getPlacePredictions({
input: input,
componentRestrictions: {
country: 'us'
}
}, autoCallback);
}
function handleTabbingOnInput(evt) {
// *Handles Tab event on delivery-location input
if (evt.target.id == "pac-input") {
// Remove active class
evt.target.className = '';
// Check if a tab was pressed
if (evt.which == 9 || evt.keyCode == 9) {
queryAutocomplete(evt.target.value);
}
}
}
// ***** Initializations ***** //
// initialize pac search field //
var pacInput = $('#pac-input');
pacInput.focus();
// Initialize Autocomplete
var options = {
componentRestrictions: {
country: 'us'
}
};
var autocomplete = new google.maps.places.Autocomplete(pacInput, options);
// ***** End Initializations ***** //
// ***** Event Listeners ***** //
google.maps.event.addListener(autocomplete, 'place_changed', function () {
var result = autocomplete.getPlace();
if (typeof result.address_components == 'undefined') {
queryAutocomplete(result.name);
} else {
// returns native functionality and place object
console.log(result.address_components);
}
});
// Tabbing Event Listener
if (document.addEventListener) {
document.addEventListener('keydown', handleTabbingOnInput, false);
} else if (document.attachEvent) { // IE8 and below
document.attachEvent("onsubmit", handleTabbingOnInput);
}
// search form listener
var standardForm = $('#search-shop-form');
if (standardForm.addEventListener) {
standardForm.addEventListener("submit", preventStandardForm, false);
} else if (standardForm.attachEvent) { // IE8 and below
standardForm.attachEvent("onsubmit", preventStandardForm);
}
// ***** End Event Listeners ***** //
HTML
<form id="search-shop-form" class="search-form" name="searchShopForm" action="/impl_custom/index/search/" method="post">
<label for="pac-input">Delivery Location</label>
<input id="pac-input" type="text" placeholder="Los Angeles, Manhattan, Houston" autocomplete="off" />
<button class="search-btn btn-success" type="submit">Search</button>
</form>
The only gripe is that the native implementation returns a different data structure although the information is the same. Adjust accordingly.

- 833
- 9
- 15
-
Good call on the `AutocompleteService` API. I've used in conjunction with the `blur` event to account for mobile users tapping out. – Josh Nov 26 '15 at 21:05
-
1That is the cleanest and most reliable answer on this question. If you need some more details about the address like GPS coordinates (and get the same object as the autocomplete), you can make another call to `getDetails()` on [`Google.maps.places.PlacesService`](https://developers.google.com/maps/documentation/javascript/reference?hl=fr#PlacesService) – Tdy Aug 01 '16 at 23:03
-
Just a warning: getDetails is expensive. The additional monetary cost is 6 times as high than for Autocomplete itself. – bart Jan 31 '19 at 10:45
Regarding to all your answers, I have created a solution that works perfectly for me.
/**
* Function that add the google places functionality to the search inputs
* @private
*/
function _addGooglePlacesInputsAndListeners() {
var self = this;
var input = document.getElementById('searchBox');
var options = {
componentRestrictions: {country: "es"}
};
self.addInputEventListenersToAvoidAutocompleteProblem(input);
var searchBox = new google.maps.places.Autocomplete(input, options);
self.addPlacesChangedListener(searchBox, self.SimulatorMapStorage.map);
}
/**
* A problem exists with google.maps.places.Autocomplete when the user write an address and doesn't selectany options that autocomplete gives him so we have to add some events to the two inputs that we have to simulate the behavior that it should have. First, we get the keydown 13 (Enter) and if it's not a suggested option, we simulate a keydown 40 (keydownArrow) to select the first option that Autocomplete gives. Then, we dispatch the event to complete the request.
* @param input
* @private
*/
function _addInputEventListenersToAvoidAutocompleteProblem(input) {
input.addEventListener('keydown', function(event) {
if (event.keyCode === 13 && event.which === 13) {
var suggestion_selected = $(".pac-item-selected").length > 0;
if (!suggestion_selected) {
var keyDownArrowEvent = new Event('keydown');
keyDownArrowEvent.keyCode = 40;
keyDownArrowEvent.which = keyDownArrowEvent.keyCode;
input.dispatchEvent(keyDownArrowEvent);
}
}
});
}
<input id="searchBox" class="search-input initial-input" type="text" autofocus>
Hope that it can help to someone. Please, feel free to discuss the best way to do.

- 310
- 4
- 8
I just want to write an small enhancement for the answer of amirnissim
The script posted doesn't support IE8, because "event.which" seems to be always empty in IE8.
To solve this problem you just need to additionally check for "event.keyCode":
listener = function (event) {
if (event.which == 13 || event.keyCode == 13) {
var suggestion_selected = $(".pac-item.pac-selected").length > 0;
if(!suggestion_selected){
var simulated_downarrow = $.Event("keydown", {keyCode:40, which:40})
orig_listener.apply(input, [simulated_downarrow]);
}
}
orig_listener.apply(input, [event]);
};
JS-Fiddle: http://jsfiddle.net/QW59W/107/

- 1
- 1

- 280
- 1
- 8
How about this?
$("input").keypress(function(event) {
var firstValue = null;
if (event.keyCode == 13 || event.keyCode == 9) {
$(event.target).blur();
if ($(".pac-container .pac-item:first span:eq(3)").text() == "") {
firstValue = $(".pac-container .pac-item:first .pac-item-query").text();
} else {
firstValue = $(".pac-container .pac-item:first .pac-item-query").text() + ", " + $(".pac-container .pac-item:first span:eq(3)").text();
}
event.target.value = firstValue;
} else
return true;
});

- 4,136
- 6
- 29
- 49
None of these answers seemed to work for me. They'd get the general location but wouldn't actually pan to the actual place I searched for. Within the .pac-item you can actually get just the address (name of place excluded) by selecting $('.pac-item:first').children()[2].textContent
So here is my solution:
$("#search_field").on("keyup", function(e) {
if(e.keyCode == 13) {
searchPlaces();
}
});
function searchPlaces() {
var $firstResult = $('.pac-item:first').children();
var placeName = $firstResult[1].textContent;
var placeAddress = $firstResult[2].textContent;
$("#search_field").val(placeName + ", " + placeAddress);
var geocoder = new google.maps.Geocoder();
geocoder.geocode({"address":placeAddress }, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
var lat = results[0].geometry.location.lat(),
lng = results[0].geometry.location.lng(),
placeName = results[0].address_components[0].long_name,
latlng = new google.maps.LatLng(lat, lng);
map.panTo(latlng);
}
});
}
I know this question was already answered but figured I'd throw in my 2 cents just in case anyone else was having the same problem as me.

- 1,501
- 14
- 22
-
$('.pac-item:first') - it's not necessarily the first item in the list that's the one selected by the user unfortunately – Malcolm Swaine May 16 '21 at 07:20
@benregn @amirnissim I think the selection error comes from:
var suggestion_selected = $(".pac-item.pac-selected").length > 0;
The class pac-selected
should be pac-item-selected
, which explains why !suggestion_selected
always evaluate to true, causing the incorrect location to be selected when the enter key is pressed after using 'keyup' or 'keydown' to highlight the desired location.

- 11
- 1
I did some work around this and now I can force select 1st option from google placces using angular js and angular Autocomplete module.
Thanks to kuhnza
my code
<form method="get" ng-app="StarterApp" ng-controller="AppCtrl" action="searchresults.html" id="target" autocomplete="off">
<br/>
<div class="row">
<div class="col-md-4"><input class="form-control" tabindex="1" autofocus g-places-autocomplete force-selection="true" ng-model="user.fromPlace" placeholder="From Place" autocomplete="off" required>
</div>
<div class="col-md-4"><input class="form-control" tabindex="2" g-places-autocomplete force-selection="true" placeholder="To Place" autocomplete="off" ng-model="user.toPlace" required>
</div>
<div class="col-md-4"> <input class="btn btn-primary" type="submit" value="submit"></div></div><br /><br/>
<input class="form-control" style="width:40%" type="text" name="sourceAddressLat" placeholder="From Place Lat" id="fromLat">
<input class="form-control" style="width:40%"type="text" name="sourceAddressLang" placeholder="From Place Long" id="fromLong">
<input class="form-control" style="width:40%"type="text" name="sourceAddress" placeholder="From Place City" id="fromCity">
<input class="form-control" style="width:40%"type="text" name="destinationAddressLat" placeholder="To Place Lat" id="toLat">
<input class="form-control" style="width:40%"type="text" name="destinationAddressLang" placeholder="To Place Long"id="toLong">
<input class="form-control" style="width:40%"type="text" name="destinationAddress"placeholder="To Place City" id="toCity">
</form>
Here is a Plunker
Thank you.

- 409
- 11
- 17
I investigated this a bit since I have the same Issue. What I did not like about the previous solutions was, that the autocomplete already fired the AutocompleteService to show the predictions. Therefore, the predictions should be somewhere and should not be loaded again.
I found out that the predictions of place inkl. place_id is stored in
Autocomplete.gm_accessors_.place.Kc.l
and you will be able to get a lot of data from the records [0].data
. Imho, it's faster and better to get the location by using the place_id instead of address data. This very strange object selection appears not very good to me, tho.
Do you know, if there is a better way to retrieve the first prediction from the autocomplete?

- 1,233
- 9
- 12
-
1That's very dangerous indeed, as currently the field names have been changed. At this moment, there's an array of prediction objects in `autocomplete.gm_accessors_.place.Zc.predictions`, where `autocomplete` is the autocomplete object. The prediction objects have human-readable keys. I wish there was a documented accessor to get at this array. Maybe there is. – bart Jan 31 '19 at 11:34
-
I was forced to do something similar (typescript) let formattedPediction = autocomplete.gm_accessors_?.place?.We?.formattedPrediction; – Malcolm Swaine May 16 '21 at 08:25
Working Solution that listens to if the user has started to navigate down the list with the keyboard rather than triggering the false navigation each time
https://codepen.io/callam/pen/RgzxZB
Here are the important bits
// search input
const searchInput = document.getElementById('js-search-input');
// Google Maps autocomplete
const autocomplete = new google.maps.places.Autocomplete(searchInput);
// Has user pressed the down key to navigate autocomplete options?
let hasDownBeenPressed = false;
// Listener outside to stop nested loop returning odd results
searchInput.addEventListener('keydown', (e) => {
if (e.keyCode === 40) {
hasDownBeenPressed = true;
}
});
// GoogleMaps API custom eventlistener method
google.maps.event.addDomListener(searchInput, 'keydown', (e) => {
// Maps API e.stopPropagation();
e.cancelBubble = true;
// If enter key, or tab key
if (e.keyCode === 13 || e.keyCode === 9) {
// If user isn't navigating using arrows and this hasn't ran yet
if (!hasDownBeenPressed && !e.hasRanOnce) {
google.maps.event.trigger(e.target, 'keydown', {
keyCode: 40,
hasRanOnce: true,
});
}
}
});
// Clear the input on focus, reset hasDownBeenPressed
searchInput.addEventListener('focus', () => {
hasDownBeenPressed = false;
searchInput.value = '';
});
// place_changed GoogleMaps listener when we do submit
google.maps.event.addListener(autocomplete, 'place_changed', function() {
// Get the place info from the autocomplete Api
const place = autocomplete.getPlace();
//If we can find the place lets go to it
if (typeof place.address_components !== 'undefined') {
// reset hasDownBeenPressed in case they don't unfocus
hasDownBeenPressed = false;
}
});

- 949
- 1
- 14
- 22
-
1This only real solution with readable code and in vanilla JavaScript. Thanks Callam! – Maarten de Graaf Sep 09 '20 at 13:00
-
This seems to give an error `TypeError: a.stopPropagation is not a function error` Any thoughts on this? – RobbTe Dec 22 '21 at 13:15
@Alexander
's solution is the one which I was looking for.
But it was causing an error - TypeError: a.stopPropagation is not a function
.
So I made the event with KeyboardEvent
.
Here's the working code and Javascript version is very convenient for React.js projects. I also used this for my React.js project.
(function selectFirst(input) {
let _addEventListener = input.addEventListener
? input.addEventListener
: input.attachEvent;
function addEventListenerWrapper(type, listener) {
if (type === 'keydown') {
console.log('keydown');
let orig_listener = listener;
listener = event => {
let suggestion_selected =
document.getElementsByClassName('pac-item-selected').length > 0;
if (event.keyCode === 13 && !suggestion_selected) {
let simulated_downarrow = new KeyboardEvent('keydown', {
bubbles: true,
cancelable: true,
keyCode: 40
});
orig_listener.apply(input, [simulated_downarrow]);
}
orig_listener.apply(input, [event]);
};
}
_addEventListener.apply(input, [type, listener]);
}
if (input.addEventListener) input.addEventListener = addEventListenerWrapper;
else if (input.attachEvent) input.attachEvent = addEventListenerWrapper;
})(addressInput);
this.autocomplete = new window.google.maps.places.Autocomplete(addressInput, options);
Hope this can help someone, :)

- 101
- 1
- 11
-
I used this solution in React with the package (react-google-maps/api). I added this solution in onLoad prop of Autocomplete and had this working smoothly.. – vassilis ntovantzis May 31 '21 at 15:03
Building on amimissim's answer, I present a slight alternative, utilising Google's API to handle the events in a cross browser way (amimissim's solution doesn't seem to work in IE8).
I also had to change pac-item.pac-selected
to pac-item-refresh.pac-selected
as it seems the results div class has changed. This makes pressing ENTER
on a suggestion work (rather than selecting the next one down).
var input = document.getElementById('MyFormField');
var autocomplete = new google.maps.places.Autocomplete(input);
google.maps.event.addListener(autocomplete, 'keydown', function(event) {
var suggestion_selected = $(".pac-item-refesh.pac-selected").length > 0;
if (event.which == 13 && !suggestion_selected) {
var simulated_downarrow = $.Event("keydown", {
keyCode: 40,
which: 40
});
this.apply(autocomplete, [simulated_downarrow]);
}
this.apply(autocomplete, [event]);
});
Just a pure javascript version (without jquery) of the great amirnissim's solution:
listener = function(event) {
var suggestion_selected = document.getElementsByClassName('.pac-item-selected').length > 0;
if (event.which === 13 && !suggestion_selected) {
var e = JSON.parse(JSON.stringify(event));
e.which = 40;
e.keyCode = 40;
orig_listener.apply(input, [e]);
}
orig_listener.apply(input, [event]);
};

- 1,639
- 1
- 13
- 17
-
1Minor bug here. `getElementsByClassName('.pac-item-selected')` should be `getElementsByClassName('pac-item-selected')` – Tony Brasunas Apr 06 '18 at 20:09
-
Thanks for the javascript version. This is the one which I was looking for. But It's causing `TypeError: a.stopPropagation is not a function` error – Roman M. Jul 09 '19 at 00:55
/// <reference types="@types/googlemaps" />
import {ChangeDetectorRef, Component, ElementRef, EventEmitter, Inject, Input, NgZone, OnInit, Output, ViewChild} from '@angular/core';
import {MapsAPILoader, MouseEvent} from '@agm/core';
import { Address } from 'src/@core/interfaces/address.model';
import { NotificationService } from 'src/@core/services/notification.service';
// import {} from 'googlemaps';
declare var google: any;
// @ts-ignore
@Component({
selector: 'app-search-address',
templateUrl: './search-address.component.html',
styleUrls: ['./search-address.component.scss']
})
export class SearchAddressComponent implements OnInit {
@Input('label') label: string;
@Input('addressObj') addressObj: Address = {};
zoom: number;
isSnazzyInfoWindowOpened = false;
private geoCoder;
// @ts-ignore
@Output() onAddressSelected = new EventEmitter<any>();
@Input('defaultAddress') defaultAddress = '';
@ViewChild('search', {static: true})
public searchElementRef: ElementRef = null;
constructor(
private mapsAPILoader: MapsAPILoader,
private ngZone: NgZone,
private notify: NotificationService,
@Inject(ChangeDetectorRef) private changeDetectorRef: ChangeDetectorRef
) { }
ngOnInit() {
// console.log('addressObj# ', this.addressObj);
if (this.defaultAddress !== '') {
this.searchElementRef.nativeElement.value = this.defaultAddress;
}
// load Places Autocomplete
this.mapsAPILoader.load().then(() => {
if (this.addressObj.address) {
this.setZoom();
} else {
this.setCurrentLocation();
}
this.geoCoder = new google.maps.Geocoder;
const autocomplete = new google.maps.places.Autocomplete(this.searchElementRef.nativeElement, {
types: ['address']
});
autocomplete.setTypes(['(cities)']);
autocomplete.setComponentRestrictions({'country': 'in'});
autocomplete.addListener('place_changed', () => {
this.ngZone.run(() => {
// get the place result
const place: google.maps.places.PlaceResult = autocomplete.getPlace();
// verify result
if (place.geometry === undefined || place.geometry === null) {
return;
}
// set latitude, longitude and zoom
this.addressObj.latitude = place.geometry.location.lat();
this.addressObj.longitude = place.geometry.location.lng();
this.getAddress(this.addressObj.latitude, this.addressObj.longitude);
this.zoom = 12;
});
});
});
}
setZoom() {
this.zoom = 8;
}
// Get Current Location Coordinates
private setCurrentLocation() {
if ('geolocation' in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
this.addressObj.latitude = position.coords.latitude;
this.addressObj.longitude = position.coords.longitude;
this.zoom = 8;
this.getAddress(this.addressObj.latitude, this.addressObj.longitude);
});
}
}
markerDragEnd($event: MouseEvent) {
this.addressObj.latitude = $event.coords.lat;
this.addressObj.longitude = $event.coords.lng;
this.getAddress(this.addressObj.latitude, this.addressObj.longitude);
}
getAddress(latitude, longitude) {
this.addressObj.latitude = latitude;
this.addressObj.longitude = longitude;
this.geoCoder.geocode({ location: { lat: latitude, lng: longitude } }, (results, status) => {
if (status === 'OK') {
if (results[0]) {
console.log('results ', results);
this.zoom = 12;
this.addressObj.address = results[0].formatted_address;
this.showSnazzyInfoWindow();
this.addressObj.placeId = results[0].place_id;
for(let i = 0; i < results[0].address_components.length; i++) {
if (results[0].address_components[i].types[0] == 'locality') {
this.addressObj.city = results[0].address_components[i].long_name;
}
if (results[0].address_components[i].types[0] == 'administrative_area_level_1') {
this.addressObj.region = results[0].address_components[i].long_name;
}
if (results[0].address_components[i].types[0] == 'country') {
this.addressObj.country = results[0].address_components[i].long_name;
}
if (results[0].address_components[i].types[0] == 'postal_code') {
this.addressObj.zip = results[0].address_components[i].long_name;
}
}
this.transmitData();
} else {
this.notify.showMessage('No results found', 3000, 'OK');
}
} else {
this.notify.showMessage('Google maps location failed due to: ' + status, 3000, 'OK');
}
});
}
transmitData() {
// console.log(this.addressObj);
this.onAddressSelected.emit(this.addressObj);
}
toggleSnazzyInfoWindow() {
this.isSnazzyInfoWindowOpened = !this.isSnazzyInfoWindowOpened;
}
showSnazzyInfoWindow() {
this.isSnazzyInfoWindowOpened = true;
}
}
<mat-form-field class="full-width pt-2 flex-auto w-full">
<input matInput [(ngModel)]="addressObj.address" type="text" (keydown.enter)="$event.preventDefault()" placeholder="{{label ? label : 'Location'}}" autocorrect="off" autocapitalize="off" spellcheck="off" type="text" #search>
</mat-form-field>
<agm-map
[latitude]="addressObj.latitude"
[longitude]="addressObj.longitude"
[zoom]="zoom">
<agm-marker
[latitude]="addressObj.latitude"
[longitude]="addressObj.longitude"
[markerDraggable]="true"
(dragEnd)="markerDragEnd($event)">
</agm-marker>
</agm-map>

- 1,038
- 11
- 16
Yet another solution based on Tony Brasunas' answer.
Although the original question yields great answers to me it still lacks some functionality, which people have also mentioned in the comments, when the user clicks away or when the user submits the form.
This introduces two additional events blur
and submit
.
To introduce these events into my favourite solution I refactored a few things.
/**
* Google Autocomplete auto first select
* What we really need is a [lat,lng] pair to enhance validation of our form
* @event keydown : stores the eventListener in memory
* @event blur : pass the eventListener to a wrapper
* @event submit : pass the eventListener to a wrapper
* @param {Object} input HTMLElement
*/
enableFirstSelection: function (input) {
const originalEventListener = input.addEventListener;
const addEventListenerWrapper = (type, listener) => {
if (type === 'keydown') {
this._listener = listener;
listener = (event) => this.createKeyboardEventWrapperByKey(event, 'Enter', this._listener);
}
if (type === 'blur' || type === 'submit') {
const _listener = listener;
listener = (event) => this.createKeyboardEventWrapperByType(event, 'Enter', this._listener, _listener);
}
originalEventListener.apply(input, [type, listener]);
};
input.addEventListener = addEventListenerWrapper;
},
/**
* Simulate a 'down arrow' keypress if no address has been selected
* Re-apply the original event
* @param {Object} ev wrapped keyboardEvent
* @param {String} key value of the key represented by the event
* @param {Object} kbListener stored keyboardEvent listener
*/
createKeyboardEventWrapperByKey: function (ev, key, kbListener) {
const suggestionSelected = document.getElementsByClassName('pac-item-selected').length;
if (ev.key === key && !suggestionSelected) {
const e = new KeyboardEvent('keydown', {
key: 'ArrowDown',
code: 'ArrowDown',
keyCode: 40,
});
kbListener.apply(ev.currentTarget, [e]);
}
if (ev.type === 'keydown') {
kbListener.apply(ev.currentTarget, [ev]);
}
},
/**
* Simulate a 'down arrow' keypress when the user switches focus
* Re-apply the original event
* @param {Object} ev wrapped event
* @param {String} key value of the key represented by the event
* @param {Object} kbListener stored keyboardEvent listener
* @param {Object} typeListener stored typeEvent listener
*/
createKeyboardEventWrapperByType: function (ev, key, kbListener, typeListener) {
const fakeTypeEvent = { key: key, currentTarget: ev.currentTarget, type: ev.type };
this.createKeyboardEventWrapperByKey(fakeTypeEvent, key, kbListener);
typeListener.apply(ev.currentTarget, [ev]);
},
So by calling this.enableFirstSelection(this.autocompleteField);
I can now make a selection when the user presses Enter, tabs away, clicks out, triggers the form submit and so on...
The most important explanation to this solution is the requirement of the keydown listener as it is used by the other events to trigger the keyboard listener.
=> this._listener = listener;
-vs- const _listener = listener;
Hopefully this is useful to anyone. Thanks to all my predecessors for helping me out on this one.

- 6,869
- 1
- 25
- 39