1. Hinweis
In diesem Codelab erfahren Sie, wie Sie mit dem Places UI Kit der Google Maps Platform eine vollständig interaktive Anwendung für die lokale Suche erstellen.
Vorbereitung
- Ein Google Cloud-Projekt mit den erforderlichen APIs und konfigurierten Anmeldedaten.
- Grundkenntnisse in HTML und CSS.
- Kenntnisse von modernem JavaScript
- Einen modernen Webbrowser, z. B. die aktuelle Version von Chrome.
- Ein Texteditor Ihrer Wahl.
Aufgaben
- Eine Zuordnungsanwendung mit einer JavaScript-Klasse strukturieren
- Karte mit Webkomponenten anzeigen
- Mit dem Place Search-Element können Sie eine Textsuche durchführen und die Ergebnisse anzeigen.
- Benutzerdefinierte
AdvancedMarkerElement
-Kartenmarkierungen programmatisch erstellen und verwalten. - Das Element „Ortsdetails“ anzeigen, wenn ein Nutzer einen Ort auswählt.
- Mit der Geocoding API können Sie eine dynamische und nutzerfreundliche Benutzeroberfläche erstellen.
Voraussetzungen
- Ein Google Cloud-Projekt mit aktivierter Abrechnung
- Ein Google Maps Platform API-Schlüssel
- Eine Karten-ID
- Die folgenden APIs sind aktiviert:
- Maps JavaScript API
- UI-Kit für Places
- Geocoding API
2. Einrichten
Im nächsten Schritt müssen Sie die Maps JavaScript API, das Places UI Kit und die Geocoding API aktivieren.
Google Maps Platform einrichten
Wenn Sie noch kein Google Cloud-Konto und kein Projekt mit aktivierter Abrechnung haben, lesen Sie bitte den Leitfaden Erste Schritte mit Google Maps Platform, um ein Rechnungskonto und ein Projekt zu erstellen.
- Klicken Sie in der Cloud Console auf das Drop-down-Menü für das Projekt und wählen Sie das Projekt aus, das Sie für dieses Codelab verwenden möchten.
- Aktivieren Sie die für dieses Codelab erforderlichen APIs und SDKs der Google Maps Platform im Google Cloud Marketplace. Folgen Sie dazu der Anleitung in diesem Video oder dieser Dokumentation.
- Generieren Sie einen API-Schlüssel in der Cloud Console auf der Seite Anmeldedaten. Folgen Sie dazu dieser Anleitung oder dieser Dokumentation. Für alle Anfragen an die Google Maps Platform ist ein API-Schlüssel erforderlich.
3. Die Anwendungsshell und eine funktionierende Karte
In diesem ersten Schritt erstellen wir das vollständige visuelle Layout für unsere Anwendung und richten eine übersichtliche, klassenbasierte Struktur für unser JavaScript ein. Das ist eine solide Grundlage, auf der wir aufbauen können. Am Ende dieses Abschnitts haben Sie eine Seite mit benutzerdefinierten Stilen, auf der eine interaktive Karte angezeigt wird.
HTML-Datei erstellen
Erstellen Sie zuerst eine Datei mit dem Namen index.html
. Diese Datei enthält die vollständige Struktur unserer Anwendung, einschließlich des Headers, der Suchfilter, der Seitenleiste, des Kartencontainers und der erforderlichen Webkomponenten.
Kopieren Sie den folgenden Code in index.html
. Ersetzen Sie YOUR_API_KEY_HERE
durch Ihren eigenen Google Maps Platform API-Schlüssel und DEMO_MAP_ID
durch Ihre eigene Google Maps Platform-Karten-ID.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Local Search App</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Google Fonts: Roboto -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<!-- GMP Bootstrap Loader -->
<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: "YOUR_API_KEY_HERE",
v: "weekly",
libraries: "places,maps,marker,geocoding"
});
</script>
<link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
<!-- Header for search controls -->
<header class="top-header">
<div class="logo">
<svg viewBox="0 0 24 24" width="28" height="28"><path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z" fill="currentColor"></path></svg>
<span>PlaceFinder</span>
</div>
<div class="search-container">
<input
type="text"
id="query-input"
placeholder="e.g., burger in New York"
value="burger"
/>
<button id="search-button" aria-label="Search">Search</button>
</div>
<div class="filter-container">
<label class="open-now-label">
<input type="checkbox" id="open-now-filter"> Open Now
</label>
<select id="rating-filter" aria-label="Minimum rating">
<option value="0" selected>Any rating</option>
<option value="1">1+ ★</option>
<option value="2">2+ ★★</option>
<option value="3">3+ ★★★</option>
<option value="4">4+ ★★★★</option>
<option value="5">5 ★★★★★</option>
</select>
<select id="price-filter" aria-label="Price level">
<option value="0" selected>Any Price</option>
<option value="1">$</option>
<option value="2">$$</option>
<option value="3">$$$</option>
<option value="4">$$$$</option>
</select>
</div>
</header>
<!-- Main content area -->
<div class="app-container">
<!-- Left Panel: Results -->
<div class="sidebar">
<div class="results-header">
<h2 id="results-header-text">Results</h2>
</div>
<div class="results-container">
<gmp-place-search id="place-search-list" class="hidden" selectable>
<gmp-place-all-content></gmp-place-all-content>
<gmp-place-text-search-request></gmp-place-text-search-request>
</gmp-place-search>
<div id="placeholder-message" class="placeholder">
<p>Your search results will appear here.</p>
</div>
<div id="loading-spinner" class="spinner-overlay">
<div class="spinner"></div>
</div>
</div>
</div>
<!-- Right Panel: Map -->
<div class="map-container">
<gmp-map
center="40.758896,-73.985130"
zoom="13"
map-id="DEMO_MAP_ID"
>
</gmp-map>
<div id="details-container">
<gmp-place-details-compact>
<gmp-place-details-place-request></gmp-place-details-place-request>
<gmp-place-all-content></gmp-place-all-content>
</gmp-place-details-compact>
</div>
</div>
</div>
<script src="script.js"></script>
</body>
</html>
CSS-Datei erstellen
Erstellen Sie als Nächstes eine Datei mit dem Namen style.css
. Wir fügen jetzt alle erforderlichen Formatierungen hinzu, um von Anfang an ein sauberes, modernes Erscheinungsbild zu schaffen. Dieses CSS steuert das allgemeine Layout, die Farben, die Schriftarten und das Erscheinungsbild aller UI-Elemente.
Kopieren Sie den folgenden Code in style.css
:
/* style.css */
:root {
--primary-color: #1a73e8;
--text-color: #202124;
--text-color-light: #5f6368;
--background-color: #f8f9fa;
--panel-background: #ffffff;
--border-color: #dadce0;
--shadow-color: rgba(0, 0, 0, 0.1);
}
body {
font-family: 'Roboto', sans-serif;
margin: 0;
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background-color: var(--background-color);
color: var(--text-color);
}
.hidden {
display: none !important;
}
.top-header {
display: flex;
align-items: center;
padding: 12px 24px;
border-bottom: 1px solid var(--border-color);
background-color: var(--panel-background);
gap: 24px;
flex-shrink: 0;
}
.logo {
display: flex;
align-items: center;
gap: 8px;
font-size: 22px;
font-weight: 700;
color: var(--primary-color);
}
.search-container {
display: flex;
flex-grow: 1;
max-width: 720px;
}
.search-container input {
width: 100%;
padding: 12px 16px;
border: 1px solid var(--border-color);
border-radius: 8px 0 0 8px;
font-size: 16px;
transition: box-shadow 0.2s ease;
}
.search-container input:focus {
outline: none;
border-color: var(--primary-color);
box-shadow: 0 0 0 2px rgba(26, 115, 232, 0.2);
}
.search-container button {
padding: 0 20px;
border: 1px solid var(--primary-color);
border-radius: 0 8px 8px 0;
background-color: var(--primary-color);
color: white;
cursor: pointer;
font-size: 16px;
font-weight: 500;
transition: background-color 0.2s ease;
}
.search-container button:hover {
background-color: #185abc;
}
.filter-container {
display: flex;
gap: 12px;
align-items: center;
}
.filter-container select, .open-now-label {
padding: 10px 14px;
border: 1px solid var(--border-color);
border-radius: 8px;
background-color: var(--panel-background);
font-size: 14px;
cursor: pointer;
transition: border-color 0.2s ease;
}
.filter-container select:hover, .open-now-label:hover {
border-color: #c0c2c5;
}
.open-now-label {
display: flex;
align-items: center;
gap: 8px;
white-space: nowrap;
}
.app-container {
display: flex;
flex-grow: 1;
overflow: hidden;
}
.sidebar {
width: 35%;
min-width: 380px;
max-width: 480px;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border-color);
background-color: var(--panel-background);
overflow: hidden;
}
.results-header {
padding: 16px 24px;
border-bottom: 1px solid var(--border-color);
flex-shrink: 0;
}
.results-header h2 {
margin: 0;
font-size: 18px;
font-weight: 500;
}
.results-container {
flex-grow: 1;
position: relative;
overflow-y: auto;
overflow-x: hidden;
}
.placeholder {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 2rem;
box-sizing: border-box;
}
.placeholder p {
color: var(--text-color-light);
font-size: 1.1rem;
}
gmp-place-search {
width: 100%;
}
.map-container {
flex-grow: 1;
position: relative;
}
gmp-map {
width: 100%;
height: 100%;
}
.spinner-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 100;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
}
.spinner-overlay.visible {
opacity: 1;
visibility: visible;
}
.spinner {
width: 48px;
height: 48px;
border: 4px solid #e0e0e0;
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
gmp-place-details-compact {
width: 350px;
display: none;
border: none;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
gmp-place-details-compact::after {
content: '';
position: absolute;
bottom: -12px;
left: 50%;
transform: translateX(-50%);
width: 24px;
height: 12px;
background-color: var(--panel-background);
clip-path: polygon(50% 100%, 0 0, 100% 0);
}
JavaScript-Anwendungsklasse erstellen
Erstellen Sie schließlich eine Datei mit dem Namen script.js
. Wir strukturieren unsere Anwendung in einer JavaScript-Klasse namens PlaceFinderApp
. So bleibt unser Code übersichtlich und der Status wird sauber verwaltet.
In diesem ersten Code wird die Klasse definiert, alle HTML-Elemente im constructor
gefunden und eine init()
-Methode zum Laden der Google Maps Platform-Bibliotheken erstellt.
Kopieren Sie den folgenden Code in script.js
:
// script.js
class PlaceFinderApp {
constructor() {
// Get all DOM element references
this.queryInput = document.getElementById('query-input');
this.priceFilter = document.getElementById('price-filter');
this.ratingFilter = document.getElementById('rating-filter');
this.openNowFilter = document.getElementById('open-now-filter');
this.searchButton = document.getElementById('search-button');
this.placeSearch = document.getElementById('place-search-list');
this.gMap = document.querySelector('gmp-map');
this.loadingSpinner = document.getElementById('loading-spinner');
this.resultsHeaderText = document.getElementById('results-header-text');
this.placeholderMessage = document.getElementById('placeholder-message');
this.placeDetailsWidget = document.querySelector('gmp-place-details-compact');
this.placeDetailsRequest = this.placeDetailsWidget.querySelector('gmp-place-details-place-request');
this.searchRequest = this.placeSearch.querySelector('gmp-place-text-search-request');
// Initialize instance variables
this.map = null;
this.geocoder = null;
this.markers = {};
this.detailsPopup = null;
this.PriceLevel = null;
this.isSearchInProgress = false;
// Start the application
this.init();
}
async init() {
// Import libraries
await google.maps.importLibrary("maps");
const { Place, PriceLevel } = await google.maps.importLibrary("places");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
const { Geocoder } = await google.maps.importLibrary("geocoding");
// Make classes available to the instance
this.PriceLevel = PriceLevel;
this.AdvancedMarkerElement = AdvancedMarkerElement;
this.map = this.gMap.innerMap;
this.geocoder = new Geocoder();
// We will add more initialization logic here in later steps.
}
}
// Wait for the DOM to be ready, then create an instance of our app.
window.addEventListener('DOMContentLoaded', () => {
new PlaceFinderApp();
});
Einschränkungen für API-Schlüssel
Möglicherweise müssen Sie Ihrem API-Schlüssel eine neue Einschränkung hinzufügen, damit dieses Codelab funktioniert. Weitere Informationen und Anleitungen dazu finden Sie unter API-Schlüssel einschränken.
Ergebnisse prüfen
Öffnen Sie die Datei index.html
in Ihrem Webbrowser. Sie sollten eine Seite mit einer Kopfzeile mit einer Suchleiste und Filtern, einer Seitenleiste mit der Meldung „Ihre Suchergebnisse werden hier angezeigt“ und einer großen Karte sehen, die auf New York City zentriert ist. Zu diesem Zeitpunkt sind die Suchsteuerelemente noch nicht funktionsfähig.
4. Suchfunktion implementieren
In diesem Abschnitt implementieren wir die Kernsuchfunktion, um unsere Anwendung zum Leben zu erwecken. Wir schreiben den Code, der ausgeführt wird, wenn ein Nutzer auf die Schaltfläche „Suchen“ klickt. Wir werden diese Funktion von Anfang an mit Best Practices entwickeln, um Nutzerinteraktionen reibungslos zu verarbeiten und häufige Fehler wie Race Conditions zu vermeiden.
Am Ende dieses Schritts können Sie auf die Suchschaltfläche klicken und sehen, wie ein Ladesymbol angezeigt wird, während die Anwendung Daten im Hintergrund abruft.
Suchmethode erstellen
Definieren Sie zuerst die Methode performSearch
in der Klasse PlaceFinderApp
. Diese Funktion ist das Herzstück unserer Suchlogik. Außerdem führen wir eine Instanzvariable, isSearchInProgress
, als „Gatekeeper“ ein. So wird verhindert, dass der Nutzer eine neue Suche startet, während eine andere bereits läuft. Das kann zu Fehlern führen.
Die Logik in performSearch
mag komplex erscheinen. Deshalb sehen wir sie uns genauer an:
- Zuerst wird geprüft, ob bereits eine Suche läuft. Wenn ja, passiert nichts.
- Das Flag
isSearchInProgress
wird auftrue
gesetzt, um die Funktion zu „sperren“. - Der Ladespinner wird angezeigt und die Benutzeroberfläche wird für neue Ergebnisse vorbereitet.
- Dadurch wird die
textQuery
-Property der Suchanfrage aufnull
festgelegt. Dies ist ein wichtiger Schritt, damit die Webkomponente erkennt, dass eine neue Anfrage eingeht. - Sie verwendet eine
setTimeout
mit einer0
-Verzögerung. Mit dieser Standard-JavaScript-Technik wird der Rest unseres Codes für die Ausführung in der nächsten Browseraufgabe geplant. So wird sichergestellt, dass die Komponente zuerst dennull
-Wert verarbeitet hat. Auch wenn der Nutzer zweimal nach genau demselben sucht, wird immer eine neue Suche ausgelöst.
Event-Listener hinzufügen
Als Nächstes müssen wir unsere performSearch
-Methode aufrufen, wenn der Nutzer mit der App interagiert. Wir erstellen eine neue Methode, attachEventListeners
, um den gesamten Code für die Ereignisverarbeitung an einem Ort zu speichern. Wir fügen jetzt einen Listener für das click
-Ereignis der Suchschaltfläche hinzu. Außerdem fügen wir einen Platzhalter für ein weiteres Ereignis, gmp-load
, hinzu, das wir im nächsten Schritt verwenden.
JavaScript-Datei aktualisieren
Aktualisieren Sie Ihre script.js
-Datei mit dem folgenden Code. Die neuen oder geänderten Abschnitte sind die Methode attachEventListeners
und die Methode performSearch
.
// script.js
class PlaceFinderApp {
constructor() {
// Get all DOM element references
this.queryInput = document.getElementById('query-input');
this.priceFilter = document.getElementById('price-filter');
this.ratingFilter = document.getElementById('rating-filter');
this.openNowFilter = document.getElementById('open-now-filter');
this.searchButton = document.getElementById('search-button');
this.placeSearch = document.getElementById('place-search-list');
this.gMap = document.querySelector('gmp-map');
this.loadingSpinner = document.getElementById('loading-spinner');
this.resultsHeaderText = document.getElementById('results-header-text');
this.placeholderMessage = document.getElementById('placeholder-message');
this.placeDetailsWidget = document.querySelector('gmp-place-details-compact');
this.placeDetailsRequest = this.placeDetailsWidget.querySelector('gmp-place-details-place-request');
this.searchRequest = this.placeSearch.querySelector('gmp-place-text-search-request');
// Initialize instance variables
this.map = null;
this.geocoder = null;
this.markers = {};
this.detailsPopup = null;
this.PriceLevel = null;
this.isSearchInProgress = false;
// Start the application
this.init();
}
async init() {
// Import libraries
await google.maps.importLibrary("maps");
const { Place, PriceLevel } = await google.maps.importLibrary("places");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
const { Geocoder } = await google.maps.importLibrary("geocoding");
// Make classes available to the instance
this.PriceLevel = PriceLevel;
this.AdvancedMarkerElement = AdvancedMarkerElement;
this.map = this.gMap.innerMap;
this.geocoder = new Geocoder();
// Call the new method to set up listeners
this.attachEventListeners();
}
// NEW: Method to set up all event listeners
attachEventListeners() {
this.searchButton.addEventListener('click', this.performSearch.bind(this));
// We will add the gmp-load listener in the next step
}
// NEW: Core search method
async performSearch() {
// Exit if a search is already in progress
if (this.isSearchInProgress) {
return;
}
// Set the lock
this.isSearchInProgress = true;
// Show the placeholder and spinner
this.placeholderMessage.classList.add('hidden');
this.placeSearch.classList.remove('hidden');
this.showLoading(true);
// Force a state change by clearing the query first.
this.searchRequest.textQuery = null;
// Defer setting the real properties to the next event loop cycle.
setTimeout(async () => {
const rawQuery = this.queryInput.value.trim();
// If the query is empty, release the lock and hide the spinner
if (!rawQuery) {
this.showLoading(false);
this.isSearchInProgress = false;
return;
};
// For now, we just set the textQuery. We'll add filters later.
this.searchRequest.textQuery = rawQuery;
this.searchRequest.locationRestriction = this.map.getBounds();
}, 0);
}
// NEW: Helper method to show/hide the spinner
showLoading(visible) {
this.loadingSpinner.classList.toggle('visible', visible);
}
}
// Wait for the DOM to be ready, then create an instance of our app.
window.addEventListener('DOMContentLoaded', () => {
new PlaceFinderApp();
});
Ergebnisse prüfen
Speichern Sie die Datei script.js
und aktualisieren Sie index.html
in Ihrem Browser. Die Seite sollte genauso aussehen wie zuvor. Klicken Sie nun in der Kopfzeile auf die Schaltfläche „Suchen“.
Sie sollten zwei Dinge sehen:
- Die Platzhalter-Meldung „Ihre Suchergebnisse werden hier angezeigt“ verschwindet.
- Das rotierende Ladesymbol wird angezeigt und dreht sich weiter.
Der Spinner dreht sich endlos, weil wir noch nicht festgelegt haben, wann er anhalten soll. Das werden wir im nächsten Abschnitt tun, wenn wir die Ergebnisse präsentieren. So wird bestätigt, dass unsere Suchfunktion richtig ausgelöst wird.
5. Ergebnisse anzeigen und Markierungen hinzufügen
Nachdem der Suchauslöser funktioniert, müssen die Ergebnisse auf dem Bildschirm angezeigt werden. Der Code in diesem Abschnitt verbindet die Suchlogik mit der Benutzeroberfläche. Sobald das Place Search Element das Laden der Daten abgeschlossen hat, wird die Suchsperre aufgehoben, der Lade-Spinner ausgeblendet und für jedes Ergebnis eine Markierung auf der Karte angezeigt.
Auf das Ende der Suche warten
Das Place Search Element löst ein gmp-load
-Ereignis aus, wenn Daten erfolgreich abgerufen wurden. Das ist das perfekte Signal für uns, um die Ergebnisse zu verarbeiten.
Fügen Sie zuerst einen Event-Listener für dieses Ereignis in unserer attachEventListeners
-Methode hinzu.
Methoden für die Markierungsbehandlung erstellen
Als Nächstes erstellen wir zwei neue Hilfsmethoden: clearMarkers
und addMarkers
.
- Mit
clearMarkers()
werden alle Markierungen aus einer vorherigen Suche entfernt. addMarkers()
wird von unseremgmp-load
-Listener aufgerufen. Die Liste der von der Suche zurückgegebenen Orte wird durchlaufen und für jeden Ort wird ein neuesAdvancedMarkerElement
-Objekt erstellt. Hier wird auch der Ladespinner ausgeblendet und dieisSearchInProgress
-Sperre aufgehoben, wodurch der Suchvorgang abgeschlossen wird.
Beachten Sie, dass wir Markierungen in einem Objekt (this.markers
) speichern und die Orts-ID als Schlüssel verwenden. So können wir Markierungen verwalten und später eine bestimmte Markierung finden.
Schließlich müssen wir clearMarkers()
zu Beginn jeder neuen Suche aufrufen. Am besten ist es, wenn Sie das in performSearch
tun.
JavaScript-Datei aktualisieren
Aktualisieren Sie die Datei script.js
mit den neuen Methoden und den Änderungen an attachEventListeners
und performSearch
.
// script.js
class PlaceFinderApp {
constructor() {
// Get all DOM element references
this.queryInput = document.getElementById('query-input');
this.priceFilter = document.getElementById('price-filter');
this.ratingFilter = document.getElementById('rating-filter');
this.openNowFilter = document.getElementById('open-now-filter');
this.searchButton = document.getElementById('search-button');
this.placeSearch = document.getElementById('place-search-list');
this.gMap = document.querySelector('gmp-map');
this.loadingSpinner = document.getElementById('loading-spinner');
this.resultsHeaderText = document.getElementById('results-header-text');
this.placeholderMessage = document.getElementById('placeholder-message');
this.placeDetailsWidget = document.querySelector('gmp-place-details-compact');
this.placeDetailsRequest = this.placeDetailsWidget.querySelector('gmp-place-details-place-request');
this.searchRequest = this.placeSearch.querySelector('gmp-place-text-search-request');
// Initialize instance variables
this.map = null;
this.geocoder = null;
this.markers = {};
this.detailsPopup = null;
this.PriceLevel = null;
this.isSearchInProgress = false;
// Start the application
this.init();
}
async init() {
// Import libraries
await google.maps.importLibrary("maps");
const { Place, PriceLevel } = await google.maps.importLibrary("places");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
const { Geocoder } = await google.maps.importLibrary("geocoding");
// Make classes available to the instance
this.PriceLevel = PriceLevel;
this.AdvancedMarkerElement = AdvancedMarkerElement;
this.map = this.gMap.innerMap;
this.geocoder = new Geocoder();
this.attachEventListeners();
}
attachEventListeners() {
this.searchButton.addEventListener('click', this.performSearch.bind(this));
// NEW: Listen for when the search component has loaded results
this.placeSearch.addEventListener('gmp-load', this.addMarkers.bind(this));
}
// NEW: Method to clear markers from a previous search
clearMarkers() {
for (const marker of Object.values(this.markers)) {
marker.map = null;
}
this.markers = {};
}
// NEW: Method to add markers for new search results
addMarkers() {
// Release the lock and hide the spinner
this.isSearchInProgress = false;
this.showLoading(false);
const places = this.placeSearch.places;
if (!places || places.length === 0) return;
// Create a new marker for each place result
for (const place of places) {
if (!place.location || !place.id) continue;
const marker = new this.AdvancedMarkerElement({
map: this.map,
position: place.location,
title: place.displayName,
});
// Store marker by its place ID for access later
this.markers[place.id] = marker;
}
}
async performSearch() {
if (this.isSearchInProgress) {
return;
}
this.isSearchInProgress = true;
this.placeholderMessage.classList.add('hidden');
this.placeSearch.classList.remove('hidden');
this.showLoading(true);
// NEW: Clear old markers before starting a new search
this.clearMarkers();
this.searchRequest.textQuery = null;
setTimeout(async () => {
const rawQuery = this.queryInput.value.trim();
if (!rawQuery) {
this.showLoading(false);
this.isSearchInProgress = false;
return;
};
this.searchRequest.textQuery = rawQuery;
this.searchRequest.locationRestriction = this.map.getBounds();
}, 0);
}
showLoading(visible) {
this.loadingSpinner.classList.toggle('visible', visible);
}
}
window.addEventListener('DOMContentLoaded', () => {
new PlaceFinderApp();
});
Ergebnisse prüfen
Speichern Sie Ihre Dateien und aktualisieren Sie die Seite in Ihrem Browser. Klicke auf "Suchen".
Das Ladesymbol sollte nun kurz angezeigt und dann wieder ausgeblendet werden. In der Seitenleiste wird eine Liste mit Orten angezeigt, die für den Suchbegriff relevant sind. Auf der Karte sollten entsprechende Markierungen zu sehen sein. Wenn Sie auf die Markierungen klicken, passiert noch nichts. Wir fügen die Interaktivität im nächsten Abschnitt hinzu.
6. Suchfilter und Listeninteraktivität aktivieren
In unserer Anwendung können jetzt Suchergebnisse angezeigt werden, sie ist aber noch nicht interaktiv. In diesem Abschnitt werden alle Nutzersteuerungen zum Leben erweckt. Wir aktivieren die Filter, ermöglichen die Suche mit der Eingabetaste und verknüpfen die Elemente in der Ergebnisliste mit den entsprechenden Orten auf der Karte.
Nach Abschluss dieses Schritts reagiert die Anwendung vollständig auf Nutzereingaben.
Suchfilter aktivieren
Zuerst wird die Methode performSearch
aktualisiert, um die Werte aus allen Filtersteuerelementen in der Kopfzeile zu lesen. Für jeden Filter (Preis, Bewertung und „Jetzt geöffnet“) wird die entsprechende Eigenschaft für das searchRequest
-Objekt festgelegt, bevor die Suche ausgeführt wird.
Event-Listener für alle Steuerelemente hinzufügen
Als Nächstes erweitern wir die attachEventListeners
-Methode. Wir fügen Listener für das change
-Ereignis in jedem Filtersteuerelement sowie einen keydown
-Listener in der Sucheingabe hinzu, um zu erkennen, wenn der Nutzer die Eingabetaste drückt. Alle diese neuen Listener rufen die Methode performSearch
auf.
Ergebnisliste mit der Karte verbinden
Um eine nahtlose Nutzererfahrung zu ermöglichen, sollte die Karte auf den entsprechenden Ort fokussiert werden, wenn ein Nutzer in der Seitenleiste auf ein Element in der Ergebnisliste klickt.
Eine neue Methode, handleResultClick
, wartet auf das gmp-select
-Ereignis, das vom Place Search Element ausgelöst wird, wenn auf ein Element geklickt wird. Mit dieser Funktion wird der Standort des zugehörigen Orts ermittelt und die Karte wird sanft dorthin geschwenkt.
Dazu muss das Attribut selectable
in Ihrer gmp-place-search
-Komponente in index.html
vorhanden sein.
<gmp-place-search id="place-search-list" class="hidden" selectable>
<gmp-place-all-content></gmp-place-all-content>
<gmp-place-text-search-request></gmp-place-text-search-request>
</gmp-place-search>
JavaScript-Datei aktualisieren
Aktualisieren Sie Ihre script.js
-Datei mit dem folgenden vollständigen Code. Diese Version enthält die neue Methode handleResultClick
und die aktualisierte Logik in attachEventListeners
und performSearch
.
// script.js
class PlaceFinderApp {
constructor() {
// Get all DOM element references
this.queryInput = document.getElementById('query-input');
this.priceFilter = document.getElementById('price-filter');
this.ratingFilter = document.getElementById('rating-filter');
this.openNowFilter = document.getElementById('open-now-filter');
this.searchButton = document.getElementById('search-button');
this.placeSearch = document.getElementById('place-search-list');
this.gMap = document.querySelector('gmp-map');
this.loadingSpinner = document.getElementById('loading-spinner');
this.resultsHeaderText = document.getElementById('results-header-text');
this.placeholderMessage = document.getElementById('placeholder-message');
this.placeDetailsWidget = document.querySelector('gmp-place-details-compact');
this.placeDetailsRequest = this.placeDetailsWidget.querySelector('gmp-place-details-place-request');
this.searchRequest = this.placeSearch.querySelector('gmp-place-text-search-request');
// Initialize instance variables
this.map = null;
this.geocoder = null;
this.markers = {};
this.detailsPopup = null;
this.PriceLevel = null;
this.isSearchInProgress = false;
// Start the application
this.init();
}
async init() {
// Import libraries
await google.maps.importLibrary("maps");
const { Place, PriceLevel } = await google.maps.importLibrary("places");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
const { Geocoder } = await google.maps.importLibrary("geocoding");
// Make classes available to the instance
this.PriceLevel = PriceLevel;
this.AdvancedMarkerElement = AdvancedMarkerElement;
this.map = this.gMap.innerMap;
this.geocoder = new Geocoder();
this.attachEventListeners();
}
// UPDATED: All event listeners are now attached
attachEventListeners() {
// Listen for the 'Enter' key press in the search input
this.queryInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.performSearch();
}
});
// Listen for a sidebar result click
this.placeSearch.addEventListener('gmp-select', this.handleResultClick.bind(this));
this.placeSearch.addEventListener('gmp-load', this.addMarkers.bind(this));
this.searchButton.addEventListener('click', this.performSearch.bind(this));
this.priceFilter.addEventListener('change', this.performSearch.bind(this));
this.ratingFilter.addEventListener('change', this.performSearch.bind(this));
this.openNowFilter.addEventListener('change', this.performSearch.bind(this));
}
clearMarkers() {
for (const marker of Object.values(this.markers)) {
marker.map = null;
}
this.markers = {};
}
addMarkers() {
this.isSearchInProgress = false;
this.showLoading(false);
const places = this.placeSearch.places;
if (!places || places.length === 0) return;
for (const place of places) {
if (!place.location || !place.id) continue;
const marker = new this.AdvancedMarkerElement({
map: this.map,
position: place.location,
title: place.displayName,
});
this.markers[place.id] = marker;
}
}
// NEW: Function to handle clicks on the results list
handleResultClick(event) {
const place = event.place;
if (!place || !place.location) return;
// Pan the map to the selected place
this.map.panTo(place.location);
}
// UPDATED: Search function now includes all filters
async performSearch() {
if (this.isSearchInProgress) {
return;
}
this.isSearchInProgress = true;
this.placeholderMessage.classList.add('hidden');
this.placeSearch.classList.remove('hidden');
this.showLoading(true);
this.clearMarkers();
this.searchRequest.textQuery = null;
setTimeout(async () => {
const rawQuery = this.queryInput.value.trim();
if (!rawQuery) {
this.showLoading(false);
this.isSearchInProgress = false;
return;
};
this.searchRequest.textQuery = rawQuery;
this.searchRequest.locationRestriction = this.map.getBounds();
// Add filter values to the request
const selectedPrice = this.priceFilter.value;
let priceLevels = [];
switch (selectedPrice) {
case "1": priceLevels = [this.PriceLevel.INEXPENSIVE]; break;
case "2": priceLevels = [this.PriceLevel.MODERATE]; break;
case "3": priceLevels = [this.PriceLevel.EXPENSIVE]; break;
case "4": priceLevels = [this.PriceLevel.VERY_EXPENSIVE]; break;
default: priceLevels = null; break;
}
this.searchRequest.priceLevels = priceLevels;
const selectedRating = parseFloat(this.ratingFilter.value);
this.searchRequest.minRating = selectedRating > 0 ? selectedRating : null;
this.searchRequest.isOpenNow = this.openNowFilter.checked ? true : null;
}, 0);
}
showLoading(visible) {
this.loadingSpinner.classList.toggle('visible', visible);
}
}
window.addEventListener('DOMContentLoaded', () => {
new PlaceFinderApp();
});
Ergebnisse prüfen
Speichern Sie die Datei script.js
und aktualisieren Sie die Seite. Die Anwendung sollte jetzt sehr interaktiv sein.
Gehen Sie so vor:
- Die Suche durch Drücken der Eingabetaste im Suchfeld funktioniert.
- Wenn Sie einen der Filter (Preis, Bewertung, Jetzt geöffnet) ändern, wird eine neue Suche gestartet und die Ergebnisse werden aktualisiert.
- Wenn Sie in der Seitenleiste auf ein Element in der Ergebnisliste klicken, wird die Karte jetzt sanft zum Standort dieses Elements geschwenkt.
Im nächsten Abschnitt implementieren wir die Detailkarte, die angezeigt wird, wenn auf eine Markierung geklickt wird.
7. „Place Details“-Element implementieren
Unsere Anwendung ist jetzt vollständig interaktiv, aber es fehlt eine wichtige Funktion: die Möglichkeit, weitere Informationen zu einem ausgewählten Ort aufzurufen. In diesem Abschnitt implementieren wir das Element „Ortsdetails“, das angezeigt wird, wenn ein Nutzer auf eine Markierung auf der Karte klickt oder ein Element im Element „Ortssuche“ auswählt.
Wiederverwendbaren Container für Detailkarten erstellen
Am effizientesten lassen sich Ortsdetails auf der Karte anzeigen, wenn Sie einen einzelnen, wiederverwendbaren Container erstellen. Wir verwenden einen AdvancedMarkerElement
als Container. Der Inhalt ist das ausgeblendete gmp-place-details-compact
-Widget, das wir bereits in unserem index.html
haben.
Eine neue Methode, initDetailsPopup
, übernimmt das Erstellen dieser wiederverwendbaren Markierung. Sie wird einmal beim Laden der Anwendung erstellt und ist anfangs ausgeblendet. Wir fügen in dieser Methode auch einen Listener für die Hauptkarte hinzu, damit die Detailkarte ausgeblendet wird, wenn Sie auf eine beliebige Stelle auf der Karte klicken.
Klickverhalten für Markierungen aktualisieren
Als Nächstes müssen wir festlegen, was passiert, wenn ein Nutzer auf eine Ortsmarkierung klickt. Der 'click'
-Listener in der addMarkers
-Methode ist jetzt für das Anzeigen der Detailkarte verantwortlich.
Wenn auf eine Markierung geklickt wird, führt der Listener folgende Aktionen aus:
- Schwenken Sie die Karte zur Position der Markierung.
- Aktualisieren Sie die Detailkarte mit den Informationen für diesen Ort.
- Positionieren Sie die Detailkarte am Standort der Markierung und machen Sie sie sichtbar.
Klick auf Liste mit Klick auf Markierung verknüpfen
Schließlich aktualisieren wir die Methode handleResultClick
. Anstatt die Karte nur zu verschieben, wird jetzt programmatisch das click
-Ereignis für die entsprechende Markierung ausgelöst. Dieses leistungsstarke Muster ermöglicht es uns, dieselbe Logik für beide Interaktionen wiederzuverwenden, wodurch unser Code sauber und wartungsfreundlich bleibt.
JavaScript-Datei aktualisieren
Aktualisieren Sie Ihre script.js
-Datei mit dem folgenden Code. Die neuen oder geänderten Abschnitte sind die Methode initDetailsPopup
und die aktualisierten Methoden addMarkers
und handleResultClick
.
// script.js
class PlaceFinderApp {
constructor() {
// Get all DOM element references
this.queryInput = document.getElementById('query-input');
this.priceFilter = document.getElementById('price-filter');
this.ratingFilter = document.getElementById('rating-filter');
this.openNowFilter = document.getElementById('open-now-filter');
this.searchButton = document.getElementById('search-button');
this.placeSearch = document.getElementById('place-search-list');
this.gMap = document.querySelector('gmp-map');
this.loadingSpinner = document.getElementById('loading-spinner');
this.resultsHeaderText = document.getElementById('results-header-text');
this.placeholderMessage = document.getElementById('placeholder-message');
this.placeDetailsWidget = document.querySelector('gmp-place-details-compact');
this.placeDetailsRequest = this.placeDetailsWidget.querySelector('gmp-place-details-place-request');
this.searchRequest = this.placeSearch.querySelector('gmp-place-text-search-request');
// Initialize instance variables
this.map = null;
this.geocoder = null;
this.markers = {};
this.detailsPopup = null;
this.PriceLevel = null;
this.isSearchInProgress = false;
// Start the application
this.init();
}
async init() {
// Import libraries
await google.maps.importLibrary("maps");
const { Place, PriceLevel } = await google.maps.importLibrary("places");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
const { Geocoder } = await google.maps.importLibrary("geocoding");
// Make classes available to the instance
this.PriceLevel = PriceLevel;
this.AdvancedMarkerElement = AdvancedMarkerElement;
this.map = this.gMap.innerMap;
this.geocoder = new Geocoder();
// NEW: Call the method to initialize the details card
this.initDetailsPopup();
this.attachEventListeners();
}
attachEventListeners() {
this.queryInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.performSearch();
}
});
this.placeSearch.addEventListener('gmp-select', this.handleResultClick.bind(this));
this.placeSearch.addEventListener('gmp-load', this.addMarkers.bind(this));
this.searchButton.addEventListener('click', this.performSearch.bind(this));
this.priceFilter.addEventListener('change', this.performSearch.bind(this));
this.ratingFilter.addEventListener('change', this.performSearch.bind(this));
this.openNowFilter.addEventListener('change', this.performSearch.bind(this));
}
// NEW: Method to set up the reusable details card
initDetailsPopup() {
this.detailsPopup = new this.AdvancedMarkerElement({
content: this.placeDetailsWidget,
map: null,
zIndex: 100
});
this.map.addListener('click', () => { this.detailsPopup.map = null; });
}
clearMarkers() {
for (const marker of Object.values(this.markers)) {
marker.map = null;
}
this.markers = {};
}
// UPDATED: The marker's click listener now shows the details card
addMarkers() {
this.isSearchInProgress = false;
this.showLoading(false);
const places = this.placeSearch.places;
if (!places || places.length === 0) return;
for (const place of places) {
if (!place.location || !place.id) continue;
const marker = new this.AdvancedMarkerElement({
map: this.map,
position: place.location,
title: place.displayName,
});
// Add the click listener to show the details card
marker.addListener('click', (event) => {
event.stop();
this.map.panTo(place.location);
this.placeDetailsRequest.place = place;
this.placeDetailsWidget.style.display = 'block';
this.detailsPopup.position = place.location;
this.detailsPopup.map = this.map;
});
this.markers[place.id] = marker;
}
}
// UPDATED: This now triggers the marker's click event
handleResultClick(event) {
const place = event.place;
if (!place || !place.id) return;
const marker = this.markers[place.id];
if (marker) {
// Programmatically trigger the marker's click event
marker.click();
}
}
async performSearch() {
if (this.isSearchInProgress) return;
this.isSearchInProgress = true;
this.placeholderMessage.classList.add('hidden');
this.placeSearch.classList.remove('hidden');
this.showLoading(true);
this.clearMarkers();
// Hide the details card when a new search starts
if (this.detailsPopup) this.detailsPopup.map = null;
this.searchRequest.textQuery = null;
setTimeout(async () => {
const rawQuery = this.queryInput.value.trim();
if (!rawQuery) {
this.showLoading(false);
this.isSearchInProgress = false;
return;
};
this.searchRequest.textQuery = rawQuery;
this.searchRequest.locationRestriction = this.map.getBounds();
const selectedPrice = this.priceFilter.value;
let priceLevels = [];
switch (selectedPrice) {
case "1": priceLevels = [this.PriceLevel.INEXPENSIVE]; break;
case "2": priceLevels = [this.PriceLevel.MODERATE]; break;
case "3": priceLevels = [this.PriceLevel.EXPENSIVE]; break;
case "4": priceLevels = [this.PriceLevel.VERY_EXPENSIVE]; break;
default: priceLevels = null; break;
}
this.searchRequest.priceLevels = priceLevels;
const selectedRating = parseFloat(this.ratingFilter.value);
this.searchRequest.minRating = selectedRating > 0 ? selectedRating : null;
this.searchRequest.isOpenNow = this.openNowFilter.checked ? true : null;
}, 0);
}
showLoading(visible) {
this.loadingSpinner.classList.toggle('visible', visible);
}
}
window.addEventListener('DOMContentLoaded', () => {
new PlaceFinderApp();
});
Ergebnisse prüfen
Speichern Sie die Datei script.js
und aktualisieren Sie die Seite. Die Anwendung sollte jetzt Details auf Anfrage anzeigen.
Gehen Sie so vor:
- Wenn Sie auf eine Markierung auf der Karte klicken, wird die Karte jetzt zentriert und eine formatierte Detailkarte über der Markierung geöffnet.
- Wenn Sie in der Seitenleiste auf ein Element in der Ergebnisliste klicken, passiert genau dasselbe.
- Wenn Sie auf die Karte außerhalb der Karte klicken, wird sie geschlossen.
- Wenn Sie eine neue Suche starten, wird auch jede geöffnete Detailkarte geschlossen.
8. Letzte Korrekturen vornehmen
Unsere Anwendung ist jetzt voll funktionsfähig, aber es gibt noch ein paar letzte Details, die wir hinzufügen können, um die Nutzerfreundlichkeit noch weiter zu verbessern. In diesem letzten Abschnitt implementieren wir zwei wichtige Funktionen: eine dynamische Kopfzeile, die einen besseren Kontext für die Suchergebnisse bietet, und eine automatische Formatierung für die Suchanfrage des Nutzers.
Dynamische Überschrift für Ergebnisse erstellen
Derzeit lautet die Überschrift der Seitenleiste immer „Ergebnisse“. Wir können die Informationen verbessern, indem wir sie an die aktuelle Suche anpassen. Beispiel: „Burger in der Nähe von New York“
Dazu verwenden wir die Geocoding API, um die Koordinaten des Kartenmittelpunkts in einen menschenlesbaren Standort wie einen Städtenamen umzuwandeln. Diese Logik wird von der neuen async
-Methode updateResultsHeader
verarbeitet. Sie wird jedes Mal aufgerufen, wenn eine Suche durchgeführt wird.
Suchanfrage des Nutzers formatieren
Damit die Benutzeroberfläche einheitlich aussieht, formatieren wir den Suchbegriff des Nutzers automatisch in „Title Case“ (z.B. „burger restaurant“ wird zu „Burger Restaurant“. Eine Hilfsfunktion, toTitleCase
, übernimmt diese Transformation. Die performSearch
-Methode wird aktualisiert, um diese Funktion für die Eingabe des Nutzers zu verwenden, bevor die Suche durchgeführt und die Kopfzeile aktualisiert wird.
JavaScript-Datei aktualisieren
Aktualisieren Sie die script.js
-Datei mit der endgültigen Version des Codes. Dazu gehören die neuen Methoden toTitleCase
und updateResultsHeader
sowie die aktualisierte Methode performSearch
, in die sie integriert sind.
// script.js
class PlaceFinderApp {
constructor() {
// Get all DOM element references
this.queryInput = document.getElementById('query-input');
this.priceFilter = document.getElementById('price-filter');
this.ratingFilter = document.getElementById('rating-filter');
this.openNowFilter = document.getElementById('open-now-filter');
this.searchButton = document.getElementById('search-button');
this.placeSearch = document.getElementById('place-search-list');
this.gMap = document.querySelector('gmp-map');
this.loadingSpinner = document.getElementById('loading-spinner');
this.resultsHeaderText = document.getElementById('results-header-text');
this.placeholderMessage = document.getElementById('placeholder-message');
this.placeDetailsWidget = document.querySelector('gmp-place-details-compact');
this.placeDetailsRequest = this.placeDetailsWidget.querySelector('gmp-place-details-place-request');
this.searchRequest = this.placeSearch.querySelector('gmp-place-text-search-request');
// Initialize instance variables
this.map = null;
this.geocoder = null;
this.markers = {};
this.detailsPopup = null;
this.PriceLevel = null;
this.isSearchInProgress = false;
// Start the application
this.init();
}
async init() {
// Import libraries
await google.maps.importLibrary("maps");
const { Place, PriceLevel } = await google.maps.importLibrary("places");
const { AdvancedMarkerElement } = await google.maps.importLibrary("marker");
const { Geocoder } = await google.maps.importLibrary("geocoding");
// Make classes available to the instance
this.PriceLevel = PriceLevel;
this.AdvancedMarkerElement = AdvancedMarkerElement;
this.map = this.gMap.innerMap;
this.geocoder = new Geocoder();
this.initDetailsPopup();
this.attachEventListeners();
}
attachEventListeners() {
this.queryInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
this.performSearch();
}
});
this.placeSearch.addEventListener('gmp-select', this.handleResultClick.bind(this));
this.placeSearch.addEventListener('gmp-load', this.addMarkers.bind(this));
this.searchButton.addEventListener('click', this.performSearch.bind(this));
this.priceFilter.addEventListener('change', this.performSearch.bind(this));
this.ratingFilter.addEventListener('change', this.performSearch.bind(this));
this.openNowFilter.addEventListener('change', this.performSearch.bind(this));
}
initDetailsPopup() {
this.detailsPopup = new this.AdvancedMarkerElement({
content: this.placeDetailsWidget,
map: null,
zIndex: 100
});
this.map.addListener('click', () => { this.detailsPopup.map = null; });
}
// NEW: Helper function to format text to Title Case
toTitleCase(str) {
if (!str) return '';
return str.toLowerCase().split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
}
showLoading(visible) {
this.loadingSpinner.classList.toggle('visible', visible);
}
clearMarkers() {
for (const marker of Object.values(this.markers)) { marker.map = null; }
this.markers = {};
}
addMarkers() {
this.isSearchInProgress = false;
this.showLoading(false);
const places = this.placeSearch.places;
if (!places || places.length === 0) return;
for (const place of places) {
if (!place.location || !place.id) continue;
const marker = new this.AdvancedMarkerElement({
map: this.map,
position: place.location,
title: place.displayName,
});
marker.addListener('click', (event) => {
event.stop();
this.map.panTo(place.location);
this.placeDetailsRequest.place = place;
this.placeDetailsWidget.style.display = 'block';
this.detailsPopup.position = place.location;
this.detailsPopup.map = this.map;
});
this.markers[place.id] = marker;
}
}
handleResultClick(event) {
const place = event.place;
if (!place || !place.id) return;
const marker = this.markers[place.id];
if (marker) {
marker.click();
}
}
// UPDATED: Now integrates formatting and the dynamic header
async performSearch() {
if (this.isSearchInProgress) return;
this.isSearchInProgress = true;
this.placeholderMessage.classList.add('hidden');
this.placeSearch.classList.remove('hidden');
this.showLoading(true);
this.clearMarkers();
if (this.detailsPopup) this.detailsPopup.map = null;
this.searchRequest.textQuery = null;
setTimeout(async () => {
const rawQuery = this.queryInput.value.trim();
if (!rawQuery) {
this.showLoading(false);
this.isSearchInProgress = false;
return;
};
// Format the query and update the input box value
const formattedQuery = this.toTitleCase(rawQuery);
this.queryInput.value = formattedQuery;
// Update the header with the new query and location
await this.updateResultsHeader(formattedQuery);
// Pass the formatted query to the search request
this.searchRequest.textQuery = formattedQuery;
this.searchRequest.locationRestriction = this.map.getBounds();
const selectedPrice = this.priceFilter.value;
let priceLevels = [];
switch (selectedPrice) {
case "1": priceLevels = [this.PriceLevel.INEXPENSIVE]; break;
case "2": priceLevels = [this.PriceLevel.MODERATE]; break;
case "3": priceLevels = [this.PriceLevel.EXPENSIVE]; break;
case "4": priceLevels = [this.PriceLevel.VERY_EXPENSIVE]; break;
default: priceLevels = null; break;
}
this.searchRequest.priceLevels = priceLevels;
const selectedRating = parseFloat(this.ratingFilter.value);
this.searchRequest.minRating = selectedRating > 0 ? selectedRating : null;
this.searchRequest.isOpenNow = this.openNowFilter.checked ? true : null;
}, 0);
}
// NEW: Method to update the sidebar header with geocoded location
async updateResultsHeader(query) {
try {
const response = await this.geocoder.geocode({ location: this.map.getCenter() });
if (response.results && response.results.length > 0) {
const cityResult = response.results.find(r => r.types.includes('locality')) || response.results[0];
const city = cityResult.address_components[0].long_name;
this.resultsHeaderText.textContent = `${query} near ${city}`;
} else {
this.resultsHeaderText.textContent = `${query} near current map area`;
}
} catch (error) {
console.error("Geocoding failed:", error);
this.resultsHeaderText.textContent = `Results for ${query}`;
}
}
}
window.addEventListener('DOMContentLoaded', () => {
new PlaceFinderApp();
});
Ergebnisse prüfen
Speichern Sie die Datei script.js
und aktualisieren Sie die Seite.
Funktionen prüfen:
- Geben Sie
pizza
(alles klein geschrieben) in das Suchfeld ein und klicken Sie auf „Suchen“. Der Text im Feld sollte sich in „Pizza“ ändern und die Überschrift in der Seitenleiste sollte zu „Pizza in der Nähe von New York“ aktualisiert werden. - Schwenken Sie die Karte zu einer anderen Stadt, z. B. Boston, und suchen Sie noch einmal. Die Überschrift sollte zu „Pizza in der Nähe von Boston“ aktualisiert werden.
9. Glückwunsch
Sie haben erfolgreich eine vollständige, interaktive lokale Suchanwendung erstellt, die die Einfachheit des Places UI Kit mit der Leistungsfähigkeit der wichtigsten JavaScript-APIs der Google Maps Platform kombiniert.
Das haben Sie gelernt
- So strukturieren Sie eine Mapping-Anwendung mit einer JavaScript-Klasse, um Status und Logik zu verwalten.
- So verwenden Sie das Places UI Kit mit der Google Maps JavaScript API für die schnelle Entwicklung von Benutzeroberflächen.
- Erweiterte Markierungen programmatisch hinzufügen und verwalten, um benutzerdefinierte POIs auf der Karte anzuzeigen.
- So verwenden Sie den Geocoding Service, um Koordinaten in visuell lesbare Adressen umzuwandeln und so die Nutzerfreundlichkeit zu verbessern.
- Hier erfahren Sie, wie Sie häufige Race Conditions in einer interaktiven Anwendung mithilfe von Status-Flags identifizieren und beheben und dafür sorgen, dass Komponentenattribute richtig aktualisiert werden.
Nächste Schritte
- Weitere Informationen zum Anpassen von Advanced Markers, z. B. durch Ändern der Farbe oder Skalierung oder durch Verwendung von benutzerdefiniertem HTML
- Mit cloudbasiertem Gestalten von Karteninhalten können Sie das Erscheinungsbild Ihrer Karte an Ihre Marke anpassen.
- Fügen Sie die Drawing-Bibliothek hinzu, damit Nutzer Formen auf der Karte zeichnen können, um Suchbereiche zu definieren.
- Helfen Sie uns, die Inhalte zu erstellen, die für Sie am nützlichsten sind, indem Sie die folgende Umfrage beantworten:
Welche anderen Codelabs würden Sie sich wünschen?
Sie können das Codelab, das Sie am meisten interessiert, nicht finden? Hier können Sie sie mit einem neuen Problem anfordern.