אלמנט של רשימת מקומות (חיפוש מקומות)

PlaceListElement, שנקרא גם 'חיפוש מקומות', הוא אלמנט HTML שמייצג את התוצאות של חיפוש מקומות ברשימה. יש שתי דרכים להגדיר את האלמנט gmp-place-list:

בקשה לחיפוש בקרבת מקום

בדוגמה הבאה מוצג רכיב רשימת המקומות בתגובה לחיפוש בקרבת מקום. כדי לפשט את התהליך, מוצגים רק שלושה סוגי מקומות: בית קפה, מסעדה ותחנת טעינה לרכב חשמלי. כשבוחרים תוצאה, מוצג חלון מידע עם פרטי המקום שנבחר. כדי להוסיף את הרכיב 'רשימת מקומות' למפה, מוסיפים את הרכיב gmp-place-list לדף ה-HTML, כפי שמתואר בקטע הקוד הבא:

<gmp-map center="-37.813,144.963" zoom="10" map-id="DEMO_MAP_ID">
  <div class="overlay" slot="control-inline-start-block-start">
    <div class="controls">
      <select name="types" class="type-select">
        <option value="">Select a place type</option>
        <option value="cafe">Cafe</option>
        <option value="restaurant">Restaurant</option>
        <option value="electric_vehicle_charging_station">
          EV charging station
        </option>
      </select>
    </div>
    <div class="list-container">
      <gmp-place-list selectable style="display: none;"></gmp-place-list>
    </div>
  </div>
</gmp-map>

<gmp-place-details style="display: none;">
  <gmp-place-details-place-request></gmp-place-details-place-request>
  <gmp-place-all-content></gmp-place-all-content> 
</gmp-place-details>

מספר קריאות ל-querySelector משמשות לבחירת רכיבי הדף לאינטראקציה:

const map = document.querySelector("gmp-map");
const placeList = document.querySelector("gmp-place-list");
const typeSelect = document.querySelector(".type-select");
const placeDetails = document.querySelector("gmp-place-details");
const placeDetailsRequest = document.querySelector('gmp-place-details-place-request');

כשהמשתמש לוחץ על לחצן החיפוש, מתבצעת קריאה לפונקציה configureFromSearchNearbyRequest, והתוצאות מוצגות באלמנט Place List (הסמנים מתווספים בפונקציית העזרה addMarkers). בקטע הקוד הבא מוצג גם הקוד לטיפול באירועי קליקים ברשימת המקומות באמצעות האירוע gmp-placeselect:

placeDetails.addEventListener('gmp-load', (event) => {
    // Center the info window on the map.
    map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 });
});
typeSelect.addEventListener('change', (event) => {
    // First remove all existing markers.
    for (marker in markers) {
        markers[marker].map = null;
    }
    markers = {};
    if (typeSelect.value) {
        placeList.style.display = 'block';
        placeList.configureFromSearchNearbyRequest({
            locationRestriction: getContainingCircle(map.innerMap.getBounds()),
            includedPrimaryTypes: [typeSelect.value],
        }).then(addMarkers);
        // Handle user selection in Place Details.
        placeList.addEventListener('gmp-placeselect', ({ place }) => {
            markers[place.id].click();
        });
    }
});

הצגת דוגמת הקוד המלאה

JavaScript

const map = document.querySelector("gmp-map");
const placeList = document.querySelector("gmp-place-list");
const typeSelect = document.querySelector(".type-select");
const placeDetails = document.querySelector("gmp-place-details");
const placeDetailsRequest = document.querySelector('gmp-place-details-place-request');
let markers = {};
let infoWindow;
async function initMap() {
    await google.maps.importLibrary('places');
    const { LatLngBounds } = await google.maps.importLibrary('core');
    const { InfoWindow } = await google.maps.importLibrary('maps');
    const { spherical } = await google.maps.importLibrary('geometry');
    infoWindow = new InfoWindow;
    let marker;
    function getContainingCircle(bounds) {
        const diameter = spherical.computeDistanceBetween(bounds.getNorthEast(), bounds.getSouthWest());
        const calculatedRadius = diameter / 2;
        const cappedRadius = Math.min(calculatedRadius, 50000); // Radius cannot be more than 50000.
        return { center: bounds.getCenter(), radius: cappedRadius };
    }
    findCurrentLocation();
    map.innerMap.setOptions({
        mapTypeControl: false,
        clickableIcons: false,
    });
    placeDetails.addEventListener('gmp-load', (event) => {
        // Center the info window on the map.
        map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 });
    });
    typeSelect.addEventListener('change', (event) => {
        // First remove all existing markers.
        for (marker in markers) {
            markers[marker].map = null;
        }
        markers = {};
        if (typeSelect.value) {
            placeList.style.display = 'block';
            placeList.configureFromSearchNearbyRequest({
                locationRestriction: getContainingCircle(map.innerMap.getBounds()),
                includedPrimaryTypes: [typeSelect.value],
            }).then(addMarkers);
            // Handle user selection in Place Details.
            placeList.addEventListener('gmp-placeselect', ({ place }) => {
                markers[place.id].click();
            });
        }
    });
}
async function addMarkers() {
    const { AdvancedMarkerElement } = await google.maps.importLibrary('marker');
    const { LatLngBounds } = await google.maps.importLibrary('core');
    const bounds = new LatLngBounds();
    if (placeList.places.length > 0) {
        placeList.places.forEach((place) => {
            let marker = new AdvancedMarkerElement({
                map: map.innerMap,
                position: place.location
            });
            markers[place.id] = marker;
            bounds.extend(place.location);
            marker.addListener('gmp-click', (event) => {
                if (infoWindow.isOpen) {
                    infoWindow.close();
                }
                placeDetailsRequest.place = place.id;
                placeDetails.style.display = 'block';
                placeDetails.style.width = '350px';
                infoWindow.setOptions({
                    content: placeDetails,
                });
                infoWindow.open({
                    anchor: marker,
                    map: map.innerMap
                });
            });
            map.innerMap.setCenter(bounds.getCenter());
            map.innerMap.fitBounds(bounds);
        });
    }
}
async function findCurrentLocation() {
    const { LatLng } = await google.maps.importLibrary('core');
    if (navigator.geolocation) {
        navigator.geolocation.getCurrentPosition((position) => {
            const pos = new LatLng(position.coords.latitude, position.coords.longitude);
            map.innerMap.panTo(pos);
            map.innerMap.setZoom(14);
        }, () => {
            console.log('The Geolocation service failed.');
            map.innerMap.setZoom(14);
        });
    }
    else {
        console.log('Your browser doesn\'t support geolocation');
        map.innerMap.setZoom(14);
    }
}
initMap();

CSS

html,
  body {
      height: 100%;
      margin: 0;
  }

  body {
      display: flex;
      flex-direction: column;
      font-family: Arial, Helvetica, sans-serif;
  }

  h1 {
      font-size: large;
      text-align: center;
  }

  gmp-map {
      box-sizing: border-box;
      height: 600px;
  }

  .overlay {
      position: relative;
      top: 40px;
      margin: 20px;
      width: 400px;
  }

  .controls {
      display: flex;
      gap: 10px;
      margin-bottom: 10px;
      height: 32px;
  }

  .search-button {
      background-color: #5491f5;
      color: #fff;
      border: 1px solid #ccc;
      border-radius: 5px;
      width: 100px;
      cursor: pointer;
  }

  .type-select {
      border: 1px solid #ccc;
      border-radius: 5px;
      flex-grow: 1;
      padding: 0 10px;
  }

  .list-container {
      height: 400px;
      overflow: auto;
      border-radius: 10px;
  }

  gmp-place-list {
      background-color: #fff;
      font-size: large
  }

HTML

<!DOCTYPE html>
<html>
  <head>
    <title>Place List Nearby Search with Google Maps</title>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="style.css">
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <gmp-map center="-37.813,144.963" zoom="10" map-id="DEMO_MAP_ID">
      <div class="overlay" slot="control-inline-start-block-start">
        <div class="controls">
          <select name="types" class="type-select">
            <option value="">Select a place type</option>
            <option value="cafe">Cafe</option>
            <option value="restaurant">Restaurant</option>
            <option value="electric_vehicle_charging_station">
              EV charging station
            </option>
          </select>
        </div>
        <div class="list-container">
          <gmp-place-list selectable style="display: none;"></gmp-place-list>
        </div>
      </div>
    </gmp-map>

    <gmp-place-details style="display: none;">
      <gmp-place-details-place-request></gmp-place-details-place-request>
      <gmp-place-all-content></gmp-place-all-content> 
    </gmp-place-details>

    <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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "alpha"});</script>
  </body>
</html>

ניסיון של דוגמה

חיפוש לפי בקשת טקסט

בדוגמה הזו, הרכיב 'רשימת מקומות' מוצג בתגובה לחיפוש טקסט של משתמש. כשבוחרים תוצאה, מוצג חלון מידע עם פרטי המקום שנבחר. כדי להוסיף את האלמנט Place List למפה, מוסיפים אלמנט gmp-place-list לדף ה-HTML, כפי שמוצג בקטע הקוד הבא:

<gmp-map center="37.395641,-122.077627" zoom="10" map-id="DEMO_MAP_ID">
  <div class="overlay" slot="control-inline-start-block-start">
    <div class="text-search-container">
      <input type="text" id="textSearchInput" placeholder="Enter text search...">
      <button id="textSearchButton">Search</button>
    </div>
    <div class="list-container">
      <gmp-place-list selectable style="display: none;"></gmp-place-list>
    </div>
  </div>
  <gmp-place-details style="display: none;">
    <gmp-place-details-place-request></gmp-place-details-place-request>
    <gmp-place-all-content></gmp-place-all-content> 
  </gmp-place-details>
</gmp-map>

מספר קריאות ל-querySelector משמשות לבחירת רכיבי הדף לאינטראקציה:

const map = document.querySelector("gmp-map");
const placeList = document.querySelector("gmp-place-list");
const placeDetails = document.querySelector("gmp-place-details");
let marker = document.querySelector('gmp-advanced-marker');
const textSearchInput = document.getElementById('textSearchInput');
const textSearchButton = document.getElementById('textSearchButton');
const placeDetailsRequest = document.querySelector('gmp-place-details-place-request');

כשפונקציית החיפוש מופעלת אחרי שהמשתמש מזין שאילתת חיפוש, מתבצעת קריאה ל- configureFromSearchByTextRequest, ורכיב רשימת המקומות מציג את התוצאות (הסמנים מתווספים בפונקציית העזרה addMarkers). קטע הקוד הבא מציג את הקוד של הפונקציה:

async function searchByTextRequest() {
    if (textSearchInput.value !== "") {
        placeList.style.display = "block";
        placeList.configureFromSearchByTextRequest({
            locationRestriction: bounds,
            textQuery: textSearchInput.value,
        }).then(addMarkers);
        // Handle user selection in Place Details.
        placeList.addEventListener("gmp-placeselect", ({ place }) => {
            markers[place.id].click();
        });
    }
}

הצגת דוגמת הקוד המלאה

בדוגמה הבאה נעשה שימוש ברכיב ממשק המשתמש של רשימת המקומות כדי להציג מקומות על סמך חיפוש טקסט באמצעות configureFromSearchNearbyRequest, ומוסיפים למפה סמנים שניתן ללחוץ עליהם כדי להציג את פרטי המיקום שנבחר.

JavaScript

const map = document.querySelector("gmp-map");
const placeList = document.querySelector("gmp-place-list");
const placeDetails = document.querySelector("gmp-place-details");
let marker = document.querySelector('gmp-advanced-marker');
const textSearchInput = document.getElementById('textSearchInput');
const textSearchButton = document.getElementById('textSearchButton');
const placeDetailsRequest = document.querySelector('gmp-place-details-place-request');
let markers = {};
let infoWindow;
let center = { lat: 37.395641, lng: -122.077627 }; // Mountain View, CA.
let bounds;
async function initMap() {
    const { Map, InfoWindow } = await google.maps.importLibrary("maps");
    const { Place } = await google.maps.importLibrary("places");
    // Set bounds for location restriction.
    bounds = new google.maps.LatLngBounds({ lat: 37.37808200917261, lng: -122.13741583377849 }, { lat: 37.416676154341324, lng: -122.02261728794109 });
    infoWindow = new google.maps.InfoWindow;
    // Center the map
    map.innerMap.panTo(center);
    map.innerMap.setZoom(14);
    map.innerMap.setOptions({
        mapTypeControl: false,
        clickableIcons: false,
    });
    // Fire when the Place Details Element is loaded.
    placeDetails.addEventListener('gmp-load', (event) => {
        // Center the info window on the map.
        map.innerMap.fitBounds(placeDetails.place.viewport, { top: 500, left: 400 });
    });
    // Handle clicks on the search button.
    textSearchButton.addEventListener('click', searchByTextRequest);
    // Handle enter key on text input.
    textSearchInput.addEventListener('keydown', (event) => {
        if (event.key === 'Enter') {
            searchByTextRequest();
        }
    });
}
async function searchByTextRequest() {
    if (textSearchInput.value !== "") {
        placeList.style.display = "block";
        placeList.configureFromSearchByTextRequest({
            locationRestriction: bounds,
            textQuery: textSearchInput.value,
        }).then(addMarkers);
        // Handle user selection in Place Details.
        placeList.addEventListener("gmp-placeselect", ({ place }) => {
            markers[place.id].click();
        });
    }
}
async function addMarkers() {
    const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
    const { LatLngBounds } = await google.maps.importLibrary("core");
    const bounds = new LatLngBounds();
    // First remove all existing markers.
    for (marker in markers) {
        markers[marker].map = null;
    }
    markers = {};
    if (placeList.places.length > 0) {
        placeList.places.forEach((place) => {
            let marker = new AdvancedMarkerElement({
                map: map.innerMap,
                position: place.location,
            });
            markers[place.id] = marker;
            bounds.extend(place.location);
            marker.collisionBehavior = google.maps.CollisionBehavior.REQUIRED_AND_HIDES_OPTIONAL;
            marker.addListener('gmp-click', (event) => {
                if (infoWindow.isOpen) {
                    infoWindow.close();
                }
                // Set the Place Details request to the selected place.
                placeDetailsRequest.place = place.id;
                placeDetails.style.display = "block";
                placeDetails.style.width = "350px";
                infoWindow.setOptions({
                    content: placeDetails
                });
                infoWindow.open({
                    anchor: marker,
                    map: map.innerMap
                });
                placeDetails.addEventListener('gmp-load', () => {
                    map.innerMap.fitBounds(place.viewport, { top: 400, left: 400 });
                });
            });
            map.innerMap.setCenter(bounds.getCenter());
            map.innerMap.fitBounds(bounds);
        });
    }
}
initMap();

CSS

html,
  body {
      height: 100%;
      margin: 0;
  }

  gmp-map {
      box-sizing: border-box;
      height: 500px;
  }

  gmp-place-list {
    background-color: #fff;
    font-size: large
  }

  .overlay {
      position: relative;
      top: 20px;
      margin: 20px;
      width: 400px;
  }

  .list-container {
    height: 400px;
    overflow: auto;
    border-radius: 10px;
  }

  /* Styles for the text search container and its elements */
  .text-search-container {
    display: flex; /* Arrange input and button side-by-side */
    gap: 8px; /* Space between input and button */
    padding-bottom: 10px; /* Space below the search bar */
    border-bottom: 1px solid #eee; /* Separator line */
  }

  #textSearchInput {
    flex-grow: 1; /* Allow input to take available space */
    padding: 8px 12px;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 1rem;
  }

  #textSearchInput:focus {
    outline: none;
    border-color: #4a80e8; /* Highlight on focus */
    box-shadow: 0 0 0 2px rgba(74, 128, 232, 0.2);
  }

  #textSearchButton {
    padding: 8px 15px;
    background-color: #4a80e8; /* Example button color */
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
    transition: background-color 0.2s ease-in-out;
  }

  #textSearchButton:hover {
    background-color: #356ac0; /* Darker shade on hover */
  }

HTML

<!DOCTYPE html>
<html>
  <head>
    <title>Place List Text Search with Google Maps</title>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="style.css">
    <script type="module" src="./index.js"></script>
  </head>
  <body>
    <gmp-map center="37.395641,-122.077627" zoom="10" map-id="DEMO_MAP_ID">
      <div class="overlay" slot="control-inline-start-block-start">
        <div class="text-search-container">
          <input type="text" id="textSearchInput" placeholder="Enter text search...">
          <button id="textSearchButton">Search</button>
        </div>
        <div class="list-container">
          <gmp-place-list selectable style="display: none;"></gmp-place-list>
        </div>
      </div>
      <gmp-place-details style="display: none;">
        <gmp-place-details-place-request></gmp-place-details-place-request>
        <gmp-place-all-content></gmp-place-all-content> 
      </gmp-place-details>
    </gmp-map>
    <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: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8", v: "alpha"});</script>
  </body>
</html>

ניסיון של דוגמה