Javascript Google 地图 Places API V3 自动完成 - 在输入时选择第一个选项

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7865446/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-24 03:53:28  来源:igfitidea点击:

Google maps Places API V3 autocomplete - select first option on enter

javascriptgoogle-mapsautocompletegoogle-maps-api-3

提问by Daniel Grezo

I have successfuly implemented Google Maps Places V3 autocomplete feature on my input box as per http://code.google.com/intl/sk-SK/apis/maps/documentation/javascript/places.html#places_autocomplete. 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.

我已经按照http://code.google.com/intl/sk-SK/apis/maps/documentation/javascript/places.html#places_autocomplete在我的输入框中成功实现了 Google Maps Places V3 自动完成功能。它工作得很好,但是我很想知道如何让它在用户按下 Enter 键时从建议中选择第一个选项。我想我需要一些 JS 魔法,但我对 JS 很陌生,不知道从哪里开始。

Thanks in advance!

提前致谢!

采纳答案by dodger

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

    });
});

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

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

回答by amirnissim

Here is a solution that does not make a geocoding request that may return an incorrect result: http://jsfiddle.net/amirnissim/2D6HW/

这是一个不发出可能返回错误结果的地理编码请求的解决方案:http: //jsfiddle.net/amirnissim/2D6HW/

It simulates a down-arrowkeypress whenever the user hits returninside the autocomplete field. The event is triggered before the returnevent so it simulates the user selecting the first suggestion using the keyboard.

down-arrow每当用户点击return自动完成字段时,它就会模拟按键。该事件在return事件之前触发,因此它模拟用户使用键盘选择第一个建议。

Here is the code (tested on Chrome and Firefox) :

这是代码(在 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>

回答by Tomasz Matuszczyk

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

这是一个真实的、非 hacky 的解决方案的例子。它不使用任何浏览器黑客等,只是来自 Google 提供的公共 API 的方法,并在此处记录: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.

唯一的缺点是,如果用户未从列表中选择项目,则需要向 Google 提出额外请求。好处是结果总是正确的,因为查询的执行方式与 AutoComplete 中的查询相同。第二个好处是,通过只使用公共 API 方法而不依赖于 AutoComplete 小部件的内部 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);
    }
});

回答by Tony Brasunas

Here's the answer for 2020.

这是2020年的答案。

This combines the best answers on this page, uses only pure JS, and is written in straightforward ES6. No jQuery, 2nd API request, or IIFE needed.

这结合了此页面上的最佳答案,仅使用纯 JS,并用简单的 ES6 编写。不需要 jQuery、第二个 API 请求或 IIFE。

The idea is to simulate a ↓ (down-arrow) keypress whenever the user hits return inside the autocomplete field, akin to amirnissim's answer.

这个想法是down-arrow在用户在自动完成字段内点击返回时模拟 ↓ ( ) 按键,类似于 amirnissim 的答案。

First, set up something like this to identify your address field:

首先,设置如下内容以识别您的地址字段:

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, 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 = 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
  }

You should be good to go. Essentially, the function captures each keypress in the inputfield and if it's an enter, simulates instead a down-arrowkeypress. It also stores and rebinds listeners and events to maintain all functionality of your Google Maps Autocomplete().

你应该很高兴去。本质上,该函数会捕获input字段中的每个按键,如果是enter,则模拟down-arrow按键。它还存储和重新绑定侦听器和事件以维护您的 Google 地图的所有功能Autocomplete()

With thanks to earlier answers for much of this code, particular amirnissim and Alexander Schwarzman.

感谢之前对大部分代码的回答,特别是 amirnissim 和 Alexander Schwarzman。

回答by Klokan Technologies

It seems there is a much better and clean solution: To use google.maps.places.SearchBoxinstead 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.

似乎有一个更好更干净的解决方案:使用google.maps.places.SearchBox代替google.maps.places.Autocomplete. 代码几乎相同,只是从多个地方获取第一个。在按 Enter 时,将返回正确的列表 - 因此它开箱即用,无需进行黑客攻击。

See the example HTML page:

请参阅示例 HTML 页面:

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

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

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

示例的完整源代码位于:https: //gist.github.com/klokan/8408394

回答by AdamSchuld

For Google Places Autocomplete V3, the best solution for this is two API requests.

对于 Google Places Autocomplete V3,最好的解决方案是两个 API 请求。

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)

其他答案都不够的原因是因为他们要么使用 jquery 来模拟事件(hacky),要么使用 Geocoder 或 Google Places 搜索框,这些并不总是与自动完成结果匹配。相反,我们要做的是使用谷歌的自动完成服务,如这里详述的那样,只使用 javascript(没有 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.

下面详细介绍了使用原生 Google API 生成自动完成框,然后重新运行查询以选择第一个选项的最跨浏览器兼容的解决方案。

<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?libraries=places&language=en"></script>

Javascript

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

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.

唯一的抱怨是本机实现返回了不同的数据结构,尽管信息是相同的。相应调整。

回答by Diego Galocha

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.

希望它可以帮助某人。请随时讨论最好的方法。

回答by Gangadhar JANNU

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

回答by CodyEngel

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

这些答案似乎都不适合我。他们会得到大致位置,但实际上不会平移到我搜索的实际位置。在 .pac-item 中,您实际上可以通过选择 $('.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.

我知道这个问题已经得到了回答,但我想我会投入 2 美分,以防其他人遇到和我一样的问题。

回答by Lars-Olof Kreim

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

我只是想为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/

JS-小提琴:http: //jsfiddle.net/QW59W/107/