1. Sebelum memulai
Codelab ini mengajarkan Anda cara membuat aplikasi penelusuran lokal yang sepenuhnya interaktif dengan menggunakan Google Maps Platform Places UI Kit.
Prasyarat
- Project Google Cloud dengan API dan kredensial yang diperlukan telah dikonfigurasi.
- Pengetahuan dasar tentang HTML dan CSS.
- Pemahaman tentang JavaScript modern.
- Browser web modern, seperti Chrome versi terbaru.
- Editor teks pilihan Anda.
Yang akan Anda lakukan
- Menyusun aplikasi pemetaan menggunakan class JavaScript.
- Menggunakan Komponen Web untuk menampilkan peta
- Gunakan Elemen Penelusuran Tempat untuk melakukan dan menampilkan hasil Penelusuran Teks.
- Membuat dan mengelola penanda peta
AdvancedMarkerElement
kustom secara terprogram. - Menampilkan Elemen Detail Tempat saat pengguna memilih lokasi.
- Gunakan Geocoding API untuk membuat antarmuka yang dinamis dan mudah digunakan.
Yang Anda butuhkan
- Project Google Cloud yang mengaktifkan penagihan
- Kunci API Google Maps Platform
- ID Peta
- API berikut diaktifkan:
- Maps JavaScript API
- Places UI Kit
- Geocoding API
2. Memulai persiapan
Untuk langkah pengaktifan berikut, Anda harus mengaktifkan Maps JavaScript API, Places UI Kit, dan Geocoding API.
Menyiapkan Google Maps Platform
Jika Anda belum memiliki akun Google Cloud Platform dan project dengan penagihan diaktifkan, lihat panduan Memulai Google Maps Platform untuk membuat akun penagihan dan project.
- Di Cloud Console, klik menu drop-down project lalu pilih project yang ingin Anda gunakan untuk codelab ini.
- Aktifkan API dan SDK Google Maps Platform yang diperlukan untuk codelab ini di Google Cloud Marketplace. Untuk melakukannya, ikuti langkah-langkah dalam video ini atau dokumentasi ini.
- Buat kunci API di halaman Kredensial di Cloud Console. Anda dapat mengikuti langkah-langkah dalam video ini atau dokumentasi ini. Semua permintaan ke Google Maps Platform memerlukan kunci API.
3. Application shell dan peta fungsional
Pada langkah pertama ini, kita akan membuat tata letak visual lengkap untuk aplikasi dan membuat struktur berbasis class yang bersih untuk JavaScript. Hal ini memberi kita fondasi yang kuat untuk membangunnya. Di akhir bagian ini, Anda akan memiliki halaman bergaya yang menampilkan peta interaktif.
Buat file HTML
Pertama, buat file bernama index.html
. File ini akan berisi struktur lengkap aplikasi kita, termasuk header, filter penelusuran, sidebar, penampung peta, dan komponen web yang diperlukan.
Salin kode berikut ke index.html
. Pastikan untuk mengganti YOUR_API_KEY_HERE
dengan kunci API Google Maps Platform Anda sendiri, dan DEMO_MAP_ID
dengan ID Peta Google Maps Platform Anda sendiri.
<!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>
Buat file CSS
Selanjutnya, buat file bernama style.css
. Sekarang kita akan menambahkan semua gaya yang diperlukan untuk menciptakan tampilan yang bersih dan modern sejak awal. CSS ini menangani tata letak, warna, font, dan tampilan keseluruhan semua elemen UI kita.
Salin kode berikut ke 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);
}
Membuat class aplikasi JavaScript
Terakhir, buat file bernama script.js
. Kita akan menyusun aplikasi di dalam class JavaScript bernama PlaceFinderApp
. Cara ini membuat kode kita tetap teratur dan mengelola status dengan bersih.
Kode awal ini akan menentukan class, menemukan semua elemen HTML kita di constructor
, dan membuat metode init()
untuk memuat library Google Maps Platform.
Salin kode berikut ke 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();
});
Pembatasan Kunci API
Anda mungkin perlu menambahkan batasan baru ke kunci API agar Codelab ini berfungsi. Lihat Membatasi kunci API Anda untuk mengetahui informasi dan panduan selengkapnya tentang cara melakukannya.
Periksa hasil kerja Anda
Buka file index.html
di browser web Anda. Anda akan melihat halaman dengan header yang berisi kotak penelusuran dan filter, sidebar dengan pesan "Hasil penelusuran Anda akan muncul di sini", dan peta besar yang berpusat di New York City. Pada tahap ini, kontrol penelusuran belum berfungsi.
4. Menerapkan fungsi penelusuran
Di bagian ini, kita akan mewujudkan aplikasi dengan menerapkan fungsi penelusuran inti. Kita akan menulis kode yang berjalan saat pengguna mengklik tombol "Telusuri". Kita akan membuat fungsi ini dengan praktik terbaik sejak awal untuk menangani interaksi pengguna dengan baik dan mencegah bug umum seperti kondisi persaingan.
Di akhir langkah ini, Anda akan dapat mengklik tombol penelusuran dan melihat spinner pemuatan muncul saat aplikasi mengambil data di latar belakang.
Membuat metode penelusuran
Pertama, tentukan metode performSearch
di dalam class PlaceFinderApp
. Fungsi ini akan menjadi inti dari logika penelusuran kita. Kita juga akan memperkenalkan variabel instance, isSearchInProgress
, untuk bertindak sebagai "penjaga gerbang". Tindakan ini mencegah pengguna memulai penelusuran baru saat penelusuran sedang berlangsung, yang dapat menyebabkan error.
Logika di dalam performSearch
mungkin tampak rumit, jadi kita akan menguraikannya:
- Pertama-tama, periksa apakah penelusuran sedang berlangsung. Jika demikian, tidak ada yang terjadi.
- Fungsi ini menyetel tanda
isSearchInProgress
ketrue
untuk "mengunci" fungsi. - Fungsi ini menampilkan spinner pemuatan dan menyiapkan UI untuk hasil baru.
- Tindakan ini menetapkan properti
textQuery
permintaan penelusuran kenull
. Ini adalah langkah penting yang memaksa komponen web untuk mengenali bahwa ada permintaan baru yang masuk. - Ini menggunakan
setTimeout
dengan penundaan0
. Teknik JavaScript standar ini menjadwalkan kode lainnya untuk dijalankan dalam tugas browser berikutnya, sehingga memastikan komponen telah memproses nilainull
terlebih dahulu. Meskipun pengguna menelusuri hal yang sama persis dua kali, penelusuran baru akan selalu dipicu.
Menambahkan pemroses peristiwa
Selanjutnya, kita perlu memanggil metode performSearch
saat pengguna berinteraksi dengan aplikasi. Kita akan membuat metode baru, attachEventListeners
, untuk menyimpan semua kode penanganan peristiwa di satu tempat. Untuk saat ini, kita hanya akan menambahkan pemroses untuk peristiwa click
tombol penelusuran. Kita juga akan menambahkan placeholder untuk peristiwa lain, gmp-load
, yang akan kita gunakan pada langkah berikutnya.
Perbarui file JavaScript
Perbarui file script.js
Anda dengan kode berikut. Bagian baru atau yang diubah adalah metode attachEventListeners
dan metode 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();
});
Periksa hasil kerja Anda
Simpan file script.js
dan muat ulang index.html
di browser Anda. Halaman akan terlihat sama seperti sebelumnya. Sekarang, klik tombol "Telusuri" di header.
Anda akan melihat dua hal terjadi:
- Pesan pengganti "Hasil penelusuran Anda akan muncul di sini" akan hilang.
- Indikator pemuatan muncul dan terus berputar.
Spinner akan berputar selamanya karena kita belum memberi tahu kapan harus berhenti. Kita akan melakukannya di bagian berikutnya saat menampilkan hasilnya. Hal ini mengonfirmasi bahwa fungsi penelusuran kami dipicu dengan benar.
5. Menampilkan hasil dan menambahkan penanda
Setelah pemicu penelusuran berfungsi, tugas berikutnya adalah menampilkan hasil di layar. Kode di bagian ini akan menghubungkan logika penelusuran ke UI. Setelah Elemen Penelusuran Tempat selesai memuat data, elemen ini akan melepaskan "kunci" penelusuran, menyembunyikan spinner pemuatan, dan menampilkan penanda di peta untuk setiap hasil.
Mendengarkan penyelesaian penelusuran
Elemen Penelusuran Tempat memicu peristiwa gmp-load
saat berhasil mengambil data. Ini adalah sinyal yang tepat bagi kami untuk memproses hasilnya.
Pertama, tambahkan pemroses peristiwa untuk peristiwa ini dalam metode attachEventListeners
.
Membuat metode penanganan penanda
Selanjutnya, kita akan membuat dua metode bantuan baru: clearMarkers
dan addMarkers
.
clearMarkers()
akan menghapus semua penanda dari penelusuran sebelumnya.addMarkers()
akan dipanggil oleh pemrosesgmp-load
kami. Fungsi ini akan melakukan loop melalui daftar tempat yang ditampilkan oleh penelusuran dan membuatAdvancedMarkerElement
baru untuk setiap tempat. Di sini juga kita akan menyembunyikan spinner pemuatan dan melepaskan kunciisSearchInProgress
, sehingga menyelesaikan siklus penelusuran.
Perhatikan bahwa kita menyimpan penanda dalam objek (this.markers
) menggunakan ID Tempat sebagai kunci. Ini adalah cara untuk mengelola penanda, dan akan memungkinkan kita menemukan penanda tertentu nanti.
Terakhir, kita perlu memanggil clearMarkers()
di awal setiap penelusuran baru. Tempat terbaik untuk melakukannya adalah di dalam performSearch
.
Perbarui file JavaScript
Perbarui file script.js
Anda dengan metode baru dan perubahan pada attachEventListeners
dan 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();
});
Periksa hasil kerja Anda
Simpan file Anda dan muat ulang halaman di browser Anda. Klik tombol "Telusuri".
Indikator lingkaran berputar pemuatan akan muncul sesaat, lalu menghilang. Sidebar akan diisi dengan daftar tempat yang relevan dengan istilah penelusuran, dan Anda akan melihat penanda yang sesuai muncul di peta. Penanda belum melakukan apa pun saat diklik; kita akan menambahkan interaktivitas tersebut di bagian berikutnya.
6. Mengaktifkan filter penelusuran dan interaktivitas daftar
Aplikasi kita kini dapat menampilkan hasil penelusuran, tetapi belum interaktif. Di bagian ini, kita akan mengaktifkan semua kontrol pengguna. Kita akan mengaktifkan filter, mengaktifkan penelusuran dengan tombol "Enter", dan menghubungkan item dalam daftar hasil ke lokasi yang sesuai di peta.
Di akhir langkah ini, aplikasi akan terasa sepenuhnya responsif terhadap input pengguna.
Mengaktifkan filter penelusuran
Pertama, metode performSearch
akan diperbarui untuk membaca nilai dari semua kontrol filter di header. Untuk setiap filter (harga, rating, dan "Buka Sekarang"), properti yang sesuai akan ditetapkan pada objek searchRequest
sebelum penelusuran dijalankan.
Menambahkan pemroses peristiwa untuk semua kontrol
Selanjutnya, kita akan memperluas metode attachEventListeners
. Kita akan menambahkan pemroses untuk peristiwa change
pada setiap kontrol filter, serta pemroses keydown
pada input penelusuran untuk mendeteksi saat pengguna menekan tombol "Enter". Semua pemroses baru ini akan memanggil metode performSearch
.
Menghubungkan daftar hasil ke peta
Untuk menciptakan pengalaman yang lancar, mengklik item dalam daftar hasil sidebar akan memfokuskan peta pada lokasi tersebut.
Metode baru, handleResultClick
, akan memproses peristiwa gmp-select
, yang dipicu oleh Elemen Penelusuran Tempat saat item diklik. Fungsi ini akan menemukan lokasi tempat terkait dan menggeser peta dengan lancar ke lokasi tersebut.
Agar ini berfungsi, pastikan atribut selectable
ada di komponen gmp-place-search
Anda di index.html
.
<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>
Perbarui file JavaScript
Perbarui file script.js
Anda dengan kode lengkap berikut. Versi ini mencakup metode handleResultClick
baru dan logika yang diperbarui di attachEventListeners
dan 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();
});
Periksa hasil kerja Anda
Simpan file script.js
Anda dan muat ulang halaman. Aplikasi sekarang harus sangat interaktif.
Pastikan hal berikut:
- Penelusuran dengan menekan "Enter" di kotak penelusuran akan berfungsi.
- Mengubah salah satu filter (Harga, Rating, Buka Sekarang) akan memicu penelusuran baru dan memperbarui hasilnya.
- Mengklik item dalam daftar hasil sidebar kini akan menggeser peta dengan lancar ke lokasi item tersebut.
Di bagian berikutnya, kita akan mengimplementasikan kartu detail yang muncul saat penanda diklik.
7. Menerapkan Elemen Detail Tempat
Aplikasi kita sekarang sepenuhnya interaktif, tetapi tidak memiliki fitur utama: kemampuan untuk melihat informasi selengkapnya tentang tempat yang dipilih. Di bagian ini, kita akan menerapkan Elemen Detail Tempat yang akan muncul saat pengguna mengklik penanda di peta, atau memilih item di Elemen Penelusuran Tempat.
Membuat penampung kartu detail yang dapat digunakan kembali
Cara paling efisien untuk menampilkan detail tempat di peta adalah dengan membuat satu penampung yang dapat digunakan kembali. Kita akan menggunakan AdvancedMarkerElement
sebagai penampung ini. Kontennya akan berupa widget gmp-place-details-compact
tersembunyi yang sudah ada di index.html
kita.
Metode baru, initDetailsPopup
, akan menangani pembuatan penanda yang dapat digunakan kembali ini. Objek ini akan dibuat satu kali saat aplikasi dimuat dan akan dimulai dalam keadaan tersembunyi. Kita juga akan menambahkan pemroses ke peta utama dalam metode ini, sehingga mengklik di mana saja pada peta akan menyembunyikan kartu detail.
Memperbarui perilaku klik penanda
Selanjutnya, kita perlu memperbarui apa yang terjadi saat pengguna mengklik penanda tempat. Listener 'click'
di dalam metode addMarkers
kini akan bertanggung jawab untuk menampilkan kartu detail.
Saat penanda diklik, pemroses akan:
- Geser peta ke lokasi penanda.
- Perbarui kartu detail dengan informasi untuk tempat tertentu tersebut.
- Posisikan kartu detail di lokasi penanda dan buat kartu tersebut terlihat.
Menghubungkan klik daftar ke klik penanda
Terakhir, kita akan memperbarui metode handleResultClick
. Daripada hanya menggeser peta, peta kini akan memicu peristiwa click
secara terprogram pada penanda yang sesuai. Ini adalah pola yang efektif yang memungkinkan kita menggunakan kembali logika yang sama persis untuk kedua interaksi, sehingga kode kita tetap bersih dan mudah dikelola.
Perbarui file JavaScript
Perbarui file script.js
Anda dengan kode berikut. Bagian baru atau yang diubah adalah metode initDetailsPopup
dan metode addMarkers
serta handleResultClick
yang diperbarui.
// 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();
});
Periksa hasil kerja Anda
Simpan file script.js
Anda dan muat ulang halaman. Aplikasi sekarang akan menampilkan detail sesuai permintaan.
Pastikan hal berikut:
- Mengklik penanda di peta kini akan memusatkan peta dan membuka kartu detail bergaya di atas penanda.
- Mengklik item dalam daftar hasil sidebar akan melakukan hal yang sama persis.
- Mengklik peta di luar kartu akan menutup kartu.
- Memulai penelusuran baru juga akan menutup kartu detail yang terbuka.
8. Menambahkan sentuhan akhir
Aplikasi kita kini berfungsi penuh, tetapi ada beberapa sentuhan akhir yang dapat kita tambahkan untuk membuat pengalaman pengguna menjadi lebih baik. Di bagian terakhir ini, kita akan menerapkan dua fitur utama: header dinamis yang memberikan konteks yang lebih baik untuk hasil penelusuran, dan pemformatan otomatis untuk kueri penelusuran pengguna.
Membuat header hasil dinamis
Saat ini, header sidebar selalu bertuliskan "Hasil". Kita dapat membuatnya lebih informatif dengan memperbaruinya agar mencerminkan penelusuran saat ini. Misalnya, "Burger di dekat New York".
Untuk melakukannya, kita akan menggunakan Geocoding API untuk mengonversi koordinat tengah peta menjadi lokasi yang dapat dibaca manusia, seperti nama kota. Metode async
baru, updateResultsHeader
, akan menangani logika ini. Fungsi ini akan dipanggil setiap kali penelusuran dilakukan.
Memformat kueri penelusuran pengguna
Untuk memastikan UI terlihat bersih dan konsisten, kami akan memformat istilah penelusuran pengguna secara otomatis menjadi "Title Case" (misalnya, "burger restaurant" menjadi "Burger Restaurant"). Fungsi bantuan, toTitleCase
, akan menangani transformasi ini. Metode performSearch
akan diupdate untuk menggunakan fungsi ini pada input pengguna sebelum melakukan penelusuran dan memperbarui header.
Perbarui file JavaScript
Perbarui file script.js
Anda dengan versi final kode. Hal ini mencakup metode toTitleCase
dan updateResultsHeader
baru, serta metode performSearch
yang diperbarui yang mengintegrasikannya.
// 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();
});
Periksa hasil kerja Anda
Simpan file script.js
Anda dan muat ulang halaman.
Verifikasi fitur:
- Ketik
pizza
(semua huruf kecil) di kotak penelusuran, lalu klik telusuri. Teks di dalam kotak akan berubah menjadi "Pizza", dan header di sidebar akan diperbarui menjadi "Pizza di dekat New York". - Geser peta ke kota lain, seperti Boston, lalu telusuri lagi. Header akan diperbarui menjadi "Pizza di dekat Boston".
9. Selamat
Anda telah berhasil membuat aplikasi penelusuran lokal yang lengkap dan interaktif yang menggabungkan kesederhanaan Places UI Kit dengan kecanggihan API JavaScript Google Maps Platform inti.
Yang telah Anda pelajari
- Cara menyusun aplikasi pemetaan menggunakan class JavaScript untuk mengelola status dan logika.
- Cara menggunakan Places UI Kit dengan Google Maps JavaScript API untuk pengembangan UI yang cepat.
- Cara menambahkan dan mengelola Penanda Lanjutan secara terprogram untuk menampilkan lokasi menarik kustom di peta.
- Cara menggunakan Geocoding Service untuk mengubah koordinat menjadi alamat yang dapat dibaca manusia demi pengalaman pengguna yang lebih baik.
- Cara mengidentifikasi dan memperbaiki kondisi persaingan umum dalam aplikasi interaktif dengan menggunakan tanda status dan memastikan properti komponen diperbarui dengan benar.
Apa langkah selanjutnya?
- Pelajari lebih lanjut Menyesuaikan Penanda Lanjutan dengan mengubah warna, skala, atau bahkan menggunakan HTML kustom.
- Jelajahi Gaya Visual Peta Berbasis Cloud untuk menyesuaikan tampilan dan nuansa peta agar cocok dengan merek Anda.
- Coba tambahkan Drawing Library untuk mengizinkan pengguna menggambar bentuk di peta guna menentukan area penelusuran.
- Bantu kami membuat konten yang menurut Anda paling berguna dengan menjawab survei berikut:
Apa saja codelab lain yang ingin Anda lihat?
Tidak dapat menemukan codelab yang paling Anda minati? Ajukan permintaan bersama masalah baru di sini.