KentoBF5587 / bookstore_visitor

0 stars 0 forks source link

GoogleMapAPIを利用した書店作成機能のアップデート(オートコンプリート) #19

Closed KentoBF5587 closed 3 months ago

KentoBF5587 commented 5 months ago

実装したいもの: places API (new)を使ったオートコンプリート機能

エラー内容:

TypeError: Cannot read properties of undefined (reading 'Autocomplete')
    at HTMLDocument.<anonymous>

が以下のコードで起こっています

function initialize() {
document.addEventListener('DOMContentLoaded', function() {

    let inputName = document.getElementById('Name');
    let inputAddress = document.getElementById('Address');

    let options = {
        types: ['establishment'],
        componentRestrictions: { country: 'JP'},
    };

    let autocompleteName = new google.maps.places.Autocomplete(inputName, options);
    let autocompleteAddress = new google.maps.places.Autocomplete(inputAddress, options);

    autocompleteName.addListener('place_changed', function() {
        let place = autocompleteName.getPlace();
        inputName.value = place.name;
        inputAddress.value = place.formatted_address;
    });

    autocompleteAddress.addListener('place_changed', function() {
        let place = autocompleteAddress.getPlace();
        inputName.value = place.name;
        inputAddress.value = place.formatted_address;
    });
});
}

initialize();

の、

let autocompleteName = new google.maps.places.Autocomplete(inputName, options);

の部分。

Uncaught (in promise) InvalidValueError

エラーの意味とエラー内容から推測される原因:

APIがうまく代入されていない? →検証ツールのElement部分を見ると期待されたAPIの値が確認できました

let inputNameとinputAddressが定義されていない? → autocomplete機能を実装したいビューである bookstores/_form.html.erbでは

<%= render "shared/error_messages", object: f.object %>
    <div class="mb-3">
      <%= f.label :name, class: "form-label" %>
      <%= f.text_field :name, id: "Name", class: "form-control" %>
    </div>
    <div class="mb-3">
      <%= f.label :address, class: "form-label" %>
      <%= f.text_field :address, id: "Address", class: "form-control" %>
    </div>

    <script>
    (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
      key: '<%= ENV['GOOGLE_MAPS_API_KEY'] %>',
      libraries: 'places',
      callback: 'initialize',
      // Add other bootstrap parameters as needed, using camel case.
      // Use the 'v' parameter to indicate the version to load (alpha, beta, weekly, etc.)
    });
    </script>

<!-- 以下略 -->

と書いてあるので、書き方は間違っていないと思われます。

必要なAPIキーが有効になっていない →maps javascript api, geocoding api, places api(new)は有効になっており、APIの制限の設定にもこの3つは含めていることは確認しました。

実装する際に参考にした資料: 44期さとはるさんのアプリおよびリポジトリ

GoogleMapPlatformの公式ドキュメント

Qiita記事 https://qiita.com/kaiyukun/items/08fac2a357852741f418 http://kwski.net/api/988/#google_vignette

エラーを解決するために調べた資料

ロボらんて君

Qiita記事 https://qiita.com/kamotetu/items/2f63b254b4995d35d3b7

※この質問をする前に、gonのエラーが出ていました。こちらの記事を参考に「gonのエラーが他のjavascriptファイルの実行の妨げになっている事もある」事を知りました。現在このエラーは解消されています。

javascript/map.js

let map;

async function initMap() {
  let position;

  if (typeof gon !== 'undefined' && gon.bookstore_lat && gon.bookstore_lng) {
    position = { lat: gon.bookstore_lat, lng: gon.bookstore_lng };
  } else {
    position = { lat: -25.344, lng: 131.031 };
  }

  const { Map } = await google.maps.importLibrary("maps");
  const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");

  // 地図の初期化
  map = new Map(document.getElementById("map"), {
    zoom: 15,
    center: position,
    mapId: "DEMO_MAP_ID",
  });

  // マーカーの初期化
  const marker = new AdvancedMarkerElement({
    map: map,
    position: position,
  });
}

initMap();
kenchasonakai commented 3 months ago

参考にしたとあるさとはるさんのリポジトリに書いてある

<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV["GOOGLE_API_KEY"] %>&libraries=places&callback=initialize"></script>

は下記の記載で代替可能という認識てあってますか?

    <script>
    (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
      key: '<%= ENV['GOOGLE_MAPS_API_KEY'] %>',
      libraries: 'places',
      callback: 'initialize',
      // Add other bootstrap parameters as needed, using camel case.
      // Use the 'v' parameter to indicate the version to load (alpha, beta, weekly, etc.)
    });
    </script>
KentoBF5587 commented 3 months ago

ありがとうございます!

両方地図が表示されたので、二つのコードは”簡易的なものか柔軟性のあるかの違い(ロボらんてくんより引用)”かと思っていました。

しかしさとはるさんの方のコードを書いていみると、入力欄にplaceholderの働きをする文章が出てきたので違うことが分かりました。試しに普通の地図を表示するコードを

<!-- 元のコードがENV["GOOGLE_MAPS_API_KEY"] 
<script src="https://maps.googleapis.com/maps/api/js?key=<%= ENV["GOOGLE_MAPS_API_KEY"] %>&libraries=places&callback=initialize"></script>

に差し替えたところinvaliderrorが2つ増えたので自分のコードだとlibrariesとcallbackがちゃんと働いていないのではないかと推測しています。

現在bookstores/_form.html.erbをさとはるさんの方のコードを差し替えた状態にして、autocomplete.jsの方はfunction initialize(){}を使わないコードに変えてみました。

document.addEventListener('DOMContentLoaded', function() {

    let inputName = document.getElementById('Name');
    let inputAddress = document.getElementById('Address');

    let options = {
        types: ['establishment'],
        componentRestrictions: { country: 'JP'},
    };

    let autocompleteName = new google.maps.places.Autocomplete(inputName, options);
    let autocompleteAddress = new google.maps.places.Autocomplete(inputAddress, options);

    autocompleteName.addListener('place_changed', function() {
        let place = autocompleteName.getPlace();
        inputName.value = place.name;
        inputAddress.value = place.formatted_address;
    });

    autocompleteAddress.addListener('place_changed', function() {
        let place = autocompleteAddress.getPlace();
        inputName.value = place.name;
        inputAddress.value = place.formatted_address;
    });
});

現在はinvalidvalueerrorが出ているのですが、これはautocomplete.jsの方ではなくmap.jsの書き方が影響を及ぼしているのでしょうか・・・?

Image from Gyazo

kenchasonakai commented 3 months ago

現在はinvalidvalueerrorが出ているのですが、これはautocomplete.jsの方ではなくmap.jsの書き方が影響を及ぼしているのでしょうか・・・?

JSのファイルの中身コメントアウトしたり読み込みさせないようにしてみて何が影響しているのか調べてみてください

KentoBF5587 commented 3 months ago

ありがとうございます!

結論から申しますと、検証中に文字を打つたびにApiNotActivatedMapErrorというエラーが出ていることが発見され、 "Maps javascript API"、"Geocoding API"、"Places API(NEW)"は有効化かつAPIキー制限下でも呼び出されるようにしているのになぜだと思っていました。

そこで"Places API(NEW)"を無効にして、"Places API"の方を使用できるように設定したところ以下のように実装ができました!

Image from Gyazo

ありがとうございます!

なお1つ目のInvalidvalueErrorはmapを表示するファイルでも、autocompleteを表示するファイルでもなく不明。

2つ目は、mapを表示するための

// 地図の初期化
  map = new Map(document.getElementById("map"), {
    zoom: 15,
    center: position,
    mapId: "DEMO_MAP_ID",
  });

こちらの部分でエラーが起きていました。

2つ目のエラーに関しては、地図を呼び出すページでない場合にエラーメッセージが表示されるようで、このエラーメッセージが消せなくても問題なくautocomplete機能は動いている現状です。

ひとまず機能そのものは実装できたので、次の機能の実装する流れで問題ないでしょうか?

kenchasonakai commented 3 months ago

ひとまず機能そのものは実装できたので、次の機能の実装する流れで問題ないでしょうか?

良いと思います

KentoBF5587 commented 3 months ago

承知しました、ありがとうございました!