Google maps Places API V3 автозаполнение - выберите первый вариант при вводе

В моем окне ввода успешно реализована функция автозаполнения Google Maps Places V3 в соответствии с http://code.google.com/intl/sk-SK/apis/maps/documentation/javascript/places.html. Это работает хорошо, однако я хотел бы знать, как я могу выбрать первый вариант из предложенных, когда пользователь нажимает ввод. Думаю, мне понадобится немного магии JS, но я очень плохо знаком с JS и не знаю, с чего начать.

Заранее спасибо!

19 ответов

Решение

У меня была такая же проблема при внедрении автозаполнения на сайте, над которым я недавно работал. Это решение, которое я придумал:

$("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");
        }

    });
});

http://jsfiddle.net/dodger/pbbhH/

Вот решение, которое не делает запрос геокодирования, который может вернуть неверный результат: http://jsfiddle.net/amirnissim/2D6HW/

Имитирует down-arrow нажатие клавиши всякий раз, когда пользователь нажимает return внутри поля автозаполнения. Событие запускается до события возврата, поэтому он имитирует выбор пользователем первого предложения с помощью клавиатуры.

Вот код (протестирован на Chrome и 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>

Вот рабочий ответ на 2018 год.

Это сочетает в себе лучшие ответы на этой странице, использует только чистый JS и написан на простой ES6. Не требуется jQuery, 2-й запрос API или IIFE.

Во-первых, если у вас уже есть что-то вроде этого, чтобы идентифицировать поле вашего адреса:

const field = document.getElementById('address-field') 
const autoComplete = new google.maps.places.Autocomplete(field)
autoComplete.setTypes(['address'])

Затем добавьте это в следующую строку:

enableEnterKey(field)

А затем в другом месте в вашем скрипте, чтобы сохранить эту функциональность красивой и отдельной в вашем коде, добавьте это:

  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 suggestion_selected = document.getElementsByClassName('pac-item-selected').length > 0
          if (event.which === 13 && !suggestion_selected) {
            const e = JSON.parse(JSON.stringify(event))
            e.which = 40
            e.keyCode = 40
            _listener.apply(input, [e])
          }
          _listener.apply(input, [event])
        }
      }
      _addEventListener.apply(input, [type, listener])
    }

    input.addEventListener = addEventListenerWrapper
  }

Тебе должно быть хорошо идти. По сути, enableEnterKey() Функция фиксирует каждое нажатие клавиши возврата / ввода в input поле и имитирует нажатие клавиши со стрелкой вниз вместо. Он также сохраняет и привязывает слушателей и события, чтобы поддерживать все функциональные возможности ваших карт Google. Autocomplete(),

С очевидной благодарностью за более ранние ответы для большей части этого кода, в частности, Амирнисима и Александра Шварцмана.

Вот пример реального не хакерского решения. Он не использует взломы браузера и т. Д., Только методы из открытого API, предоставленного Google и задокументированного здесь: Google Maps API

Единственным недостатком является то, что дополнительные запросы к Google требуются, если пользователь не выбирает элемент из списка. Положительным моментом является то, что результат всегда будет правильным, поскольку запрос выполняется идентично запросу внутри автозаполнения. Вторым преимуществом является то, что, используя только общедоступные методы API и не полагаясь на внутреннюю HTML-структуру виджета автозаполнения, мы можем быть уверены, что наш продукт не сломается, если Google внесет изменения.

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);
    }
});

Кажется, есть гораздо лучшее и чистое решение: использовать google.maps.places.SearchBox вместо google.maps.places.Autocomplete, Код почти одинаков, просто получая первый из нескольких мест. При нажатии Enter выдается правильный список - поэтому он выходит из коробки и не требует взломов.

Смотрите пример HTML-страницы:

http://rawgithub.com/klokan/8408394/raw/5ab795fb36c67ad73c215269f61c7648633ae53e/places-enter-first-item.html

Соответствующий фрагмент кода:

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);
  }
});

Полный исходный код примера находится по адресу: https://gist.github.com/klokan/8408394

Для Google Places Autocomplete V3 лучшим решением для этого являются два запроса API.

Вот скрипка

Причина, по которой ни один из других ответов не был достаточным, заключается в том, что они либо использовали jquery для имитации событий (hacky), либо использовали либо Geocoder, либо окно поиска Google Places, которое не всегда соответствует результатам автозаполнения. Вместо этого мы будем использовать службу автозаполнения Google, как описано здесь, только с использованием javascript (без jquery).

Ниже подробно описано наиболее совместимое с браузерами решение, использующее собственные API Google для создания поля автозаполнения, а затем повторно запустите запрос, чтобы выбрать первый вариант.

<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>

Единственная проблема в том, что собственная реализация возвращает другую структуру данных, хотя информация та же самая. Отрегулируйте соответственно.

Что касается всех ваших ответов, я создал решение, которое идеально подходит для меня.

/**
 * 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>

Надеюсь, что это может кому-то помочь. Пожалуйста, не стесняйтесь обсуждать лучший способ сделать.

Я просто хочу написать небольшое улучшение для ответа amirnissim
Размещенный скрипт не поддерживает IE8, потому что "event.which" в IE8 кажется всегда пустым.
Для решения этой проблемы вам просто нужно дополнительно проверить "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/

Как насчет этого?

$("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;
});

Ни один из этих ответов, казалось, не работал для меня. Они получат общее местоположение, но на самом деле не смогут найти то место, которое я искал. Внутри.pac-item вы можете получить только адрес (исключая название места), выбрав $('. Pac-item:first'). Children()[2].textContent

Итак, вот мое решение:

$("#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);
        }
    });
}

Я знаю, что на этот вопрос уже был дан ответ, но подумал, что я добавлю свои 2 цента на случай, если у кого-то будет такая же проблема, как у меня.

@benregn @amirnissim Я думаю, что ошибка выбора происходит от:

var suggestion_selected = $(".pac-item.pac-selected").length > 0;

Класс pac-selected должно быть pac-item-selectedчто объясняет почему !suggestion_selected всегда принимайте значение true, что приводит к тому, что неверное местоположение выбирается при нажатии клавиши ввода после использования "keyup" или "keydown", чтобы выделить нужное местоположение.

Решение @Alexander- это то, что я искал. Но это вызывало ошибку -TypeError: a.stopPropagation is not a function.

Итак, я устроил мероприятие с KeyboardEvent. Вот рабочий код и версия Javascript, очень удобная для проектов React.js. Я также использовал это для своего проекта React.js.

(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);

Надеюсь, это может кому-то помочь,:)

Рабочее решение, которое слушает, если пользователь начал перемещаться по списку с помощью клавиатуры, вместо того, чтобы каждый раз вызывать ложную навигацию

https://codepen.io/callam/pen/RgzxZB

Вот важные биты

// 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;
    }

});

Я немного поработал над этим, и теперь я могу принудительно выбрать 1-й вариант из мест Google, используя угловой js и угловой модуль автозаполнения.
Благодаря кунза
мой код

<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>

Вот плункер
Спасибо.

Я исследовал это немного, так как у меня та же проблема. Что мне не понравилось в предыдущих решениях, так это то, что автозаполнение уже запустило сервис AutocompleteService, чтобы показать прогнозы. Поэтому прогнозы должны быть где-то и не должны загружаться снова.

Я узнал, что предсказания места инкл. place_id хранится в

Autocomplete.gm_accessors_.place.Kc.l

и вы сможете получить много данных из записей [0].data, Имхо, быстрее и лучше получить местоположение, используя place_id вместо адресных данных. Этот очень странный выбор объектов кажется мне не очень хорошим, хотя.

Знаете ли вы, есть ли лучший способ получить первый прогноз из автозаполнения?

Опираясь на ответ amimissim, я представляю небольшую альтернативу, использующую API Google для обработки событий кросс-браузерным способом (решение amimissim, похоже, не работает в IE8).

Я также должен был изменить pac-item.pac-selected в pac-item-refresh.pac-selected как кажется, результаты div класс изменился. Это делает прессинг ENTER на работу предложения (а не выбор следующего вниз).

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]);
});

Просто чистая версия javascript (без jquery) решения великого амирнисима:

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]);
    };

Еще одно решение, основанное на ответе Тони Брасунаса.

Хотя первоначальный вопрос дает мне отличные ответы, ему все еще не хватает некоторых функций, о которых люди также упоминали в комментариях, когда пользователь щелкает мышью или когда пользователь отправляет форму.

Это вводит два дополнительных событияblurиsubmit. Чтобы ввести эти события в мое любимое решение, я провел рефакторинг нескольких вещей.

      /**
  * 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]);
},

Итак, позвонивthis.enableFirstSelection(this.autocompleteField);Теперь я могу сделать выбор, когда пользователь нажимает Enter, убирает вкладку, щелкает, запускает отправку формы и так далее...

Наиболее важным объяснением этого решения является требование прослушивателя нажатия клавиш, поскольку оно используется другими событиями для запуска прослушивателя клавиатуры.
=>this._listener = listener;-против-const _listener = listener;

Надеюсь, это полезно для всех. Спасибо всем моим предшественникам за помощь в этом.

    /// <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>
Другие вопросы по тегам