web-dev-qa-db-ja.com

GoogleマップPlaces API V3オートコンプリート-入力時に最初のオプションを選択

http://code.google.com/intl/sk-SK/apis/maps/documentation/javascript/placesに従って、Google Maps Places V3オートコンプリート機能を入力ボックスに実装しました。 .html#places_autocomplete 。それはうまく機能しますが、ユーザーがEnterを押したときに提案から最初のオプションを選択する方法を知りたいです。 JSの魔法が必要になると思いますが、私はJSが非常に新しく、どこから始めればよいのかわかりません。

前もって感謝します!

67
Daniel Grezo

最近作業したサイトにオートコンプリートを実装するときに同じ問題が発生しました。これは私が思いついた解決策です:

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

43
dodger

間違った結果を返す可能性のあるジオコーディングリクエストを行わないソリューションを次に示します。 http://jsfiddle.net/amirnissim/2D6HW/

ユーザーがオートコンプリートフィールド内でreturnを押すたびに、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>
163
amirnissim

以下に、実際の非ハッキングソリューションの例を示します。ブラウザハックなどは使用せず、Googleが提供し、ここに文書化されているパブリックAPIのメソッドのみを使用します。 Google Maps API

唯一の欠点は、ユーザーがリストからアイテムを選択しない場合、Googleへの追加リクエストが必要になることです。利点は、クエリがオートコンプリート内のクエリと同じように実行されるため、結果が常に正しいことです。 2番目の利点は、パブリック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);
    }
});
21

2019年の有効な答えです

これは、このページのベストアンサーを組み合わせ、純粋なJSのみを使用し、簡単なES6で記述されています。 jQuery、2番目のAPIリクエスト、IIFEは必要ありません。

アイデアは、ユーザーがオートコンプリートフィールド内でreturnキーを押すたびに、↓(down-arrow)キー押下をシミュレートすることです(amirnnissimの答えと同様)。

まず、次のようなものを設定して住所フィールドを識別します。

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()のすべての機能を維持します。

このコードの大部分、特にamirnissimとAlexander Schwarzmanに対する以前の回答に明らかに感謝します。

17
Tony Brasunas

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

12

GoogleプレイスオートコンプリートV3の場合、これに対する最適なソリューションは2つのAPIリクエストです

これが fiddle です

他の回答がどれも不十分な理由は、jqueryを使用してイベントを模倣する(ハッキング)か、GeocoderまたはGoogleプレイス検索ボックスのいずれかを使用するためですオートコンプリートの結果と必ずしも一致しない。代わりに、ここで詳しく説明するように、JavaScriptのみ(jqueryは使用しない)のGoogleのオートコンプリートサービスを使用します

以下に、ネイティブGoogle APIを使用してオートコンプリートボックスを生成し、クエリを再実行して最初のオプションを選択する、最もクロスブラウザーに対応したソリューションの詳細を示します。

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

唯一の不満は、情報が同じでもネイティブ実装が異なるデータ構造を返すことです。それに応じて調整します。

9
AdamSchuld

これはどう?

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

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-フィドル: http://jsfiddle.net/QW59W/107/

2
Lars-Olof Kreim

これらの答えはどれも私には役に立たなかったようです。彼らは一般的な位置を取得しますが、実際に検索した実際の場所に移動しません。 .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セントを投じるだろうと考えました。

2
CodyEngel

あなたのすべての答えに関して、私は完璧に機能するソリューションを作成しました。

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

それが誰かに役立つことを願っています。最善の方法をお気軽にご相談ください。

2
Diego Galocha

@benregn @amirnissim選択エラーの原因は次のとおりです。

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

クラスpac-selectedpac-item-selectedである必要があります。これは、!suggestion_selectedが常にtrueと評価される理由を説明します。目的の場所を強調表示します。

1
Joe

この問題をいくつか回避し、angular jsおよびangular Autocompleteモジュールを使用して、Googleプラークから1番目のオプションを強制的に選択できるようになりました。
ありがとうございます 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 です
ありがとうございました。

1
Murali

amimissim's answer に基づいて、GoogleのAPIを使用してイベントをクロスブラウザーの方法で処理するわずかな代替案を紹介します(amimissimのソリューションはIE8では機能しないようです)。

結果divクラスが変更されたように見えるので、pac-item.pac-selectedpac-item-refresh.pac-selectedに変更する必要もありました。これにより、提案作業で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]);
});
0
alt

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

これが誰かを助けることを願っています:)

0
Roman M.

素晴らしいamirnissimのソリューションの純粋な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]);
    };

同じ問題があるので、これを少し調査しました。以前のソリューションで気に入らなかったのは、オートコンプリートが予測を表示するためにAutocompleteServiceを既に起動していることでした。したがって、予測はどこかにある必要があり、再度ロードしないでください。

場所の予測はinklであることがわかりました。 place_id

Autocomplete.gm_accessors_.place.Kc.l

レコード[0].dataから多くのデータを取得できます。私見、住所データの代わりにplace_idを使用して場所を取得する方がより速く、より良いです。この非常に奇妙なオブジェクト選択は、私にはあまり良くないようです。

オートコンプリートから最初の予測を取得するより良い方法がある場合、ご存知ですか?

0
Tobias Hartmann

ユーザーが毎回誤ったナビゲーションをトリガーするのではなく、キーボードでリストを下にナビゲートし始めたかどうかをリッスンするワーキングソリューション

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

});
0
Callam