Подтвердить что ты не робот

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

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

Спасибо заранее!

4b9b3361

Ответ 1

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

$("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/

Ответ 2

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

Он моделирует нажатие down-arrow, когда пользователь нажимает return внутри поля автозаполнения. Событие запускается перед событием 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>

Ответ 3

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

Единственным недостатком является то, что дополнительные запросы в Google требуются, если пользователь не выбирает элемент из списка. Положительным моментом является то, что результат всегда будет правильным, так как запрос выполняется идентично запросу внутри автозаполнения. Второй потенциал заключается в том, что, используя только общедоступные методы API и не полагаясь на внутреннюю структуру HTML виджета AutoComplete, мы можем быть уверены, что наш продукт не сломается, если 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 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 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 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);
    }
});

Ответ 4

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

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

Идея состоит в том, чтобы имитировать нажатие клавиши ↓ (down-arrow) всякий раз, когда пользователь нажимает клавишу возврата в поле автозаполнения, подобно ответу amirnissim.

Сначала настройте что-то вроде этого, чтобы идентифицировать адресное поле:

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 suggestionSelected = document.getElementsByClassName('pac-item-selected').length
          if (event.key === 'Enter' && !suggestionSelected) {
            const e = JSON.parse(JSON.stringify(event))
            e.key = 'ArrowDown'
            e.code = 'ArrowDown'
            _listener.apply(input, [e])
          }
          _listener.apply(input, [event])
        }
      }
      _addEventListener.apply(input, [type, listener])
    }

    input.addEventListener = addEventListenerWrapper
  }

Тебе должно быть хорошо идти. По сути, функция фиксирует каждое нажатие клавиши в поле input и, если это enter, имитирует вместо этого нажатие клавиши down-arrow. Он также сохраняет и повторно связывает слушателей и события, чтобы поддерживать все функциональные возможности ваших Карт Google Autocomplete().

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

Ответ 5

Кажется, есть намного лучшее и чистое решение: использовать 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

Ответ 6

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

Вот fiddle

Причина, по которой ни один из других ответов не достаточен, заключается в том, что они либо использовали jquery для подражания событиям (хаки), либо использовали либо геокодер, либо поле поиска в Google Местах, которое не всегда соответствует результатам автозаполнения. Вместо этого мы будем использовать Google Autocomplete Service, как описано здесь, только с 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 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>

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

Ответ 7

Я просто хочу написать небольшое расширение для ответа amirnissim
Опубликованный script не поддерживает 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/

Ответ 8

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

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

Ответ 9

Ни один из этих ответов не работал у меня. Они получат общее местоположение, но на самом деле не будут катиться к тому месту, которое я искал. Внутри .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 цента на всякий случай, если у кого-то будет такая же проблема, как и я.

Ответ 10

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

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

Ответ 11

@benregn @amirnissim Я думаю, что ошибка выбора возникает из:

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

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

Ответ 12

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

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

Вот Plunker
Спасибо.

Ответ 13

Основываясь на ответ amimissim, я представляю небольшую альтернативу, используя Google API для обработки событий в режиме кросс-браузера (решение amimissim doesn похоже, работает в 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]);
});

Ответ 14

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

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

Ответ 15

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

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

Autocomplete.gm_accessors_.place.Kc.l

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

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

Ответ 16

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

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

});

Ответ 17

@Александр решение это то, что я искал. Но это вызывало ошибку - 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);

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