Teil 4 - Suchseite und Parametrisierung
Dieses Lab ist Teil der Amenity Finder Serie, einer umfassenden Anleitung, die Schritt für Schritt zeigt, wie man eine Web-Applikation mithilfe von Web-Components entwickelt. In jedem Teil der Serie werden spezifische Aspekte der Entwicklung behandelt und es werden bewährte Methoden und Techniken vermittelt, um eine moderne und skalierbare Webanwendung aufzubauen.
Vorschau
Im vierten Teil der Serie entwickeln wir die Suchseite und implementieren die Parametrisierung, um Suchanfragen abzusetzen. Wir gestalten das Suchformular und integrieren die Funktionalität zur Übermittlung der Suchkriterien.
Parametrisierung zum Absetzen der Suche. Wenn du dieses Lab bereits absolviert hast, wirst du im fünften Teil der Amenity Finder Serie lernen, wie wir die Abfrage durchführen und die Suchergebnisse anzeigen.
Vorbereitung
Ziele
In diesem Lab verfolgen wir folgende Ziele:
- Implementierung der Eingabe der Suchparameter für die Umkreissuche: Wir werden eine Benutzeroberfläche entwickeln, über die Benutzer die Suchparameter für die Umkreissuche eingeben können. Dies umfasst die Auswahl eines Standorts, die Angabe des gewünschten Umkreises und möglicherweise zusätzliche Filteroptionen.
- Anbindung einer API zur Suche von Restaurants inklusive Geo-Lokalisierung: Wir werden eine externe API integrieren, die uns Zugriff auf Restaurant-Daten und Geo-Lokalisierungsdienste bietet. Mithilfe dieser API können wir die Suchanfragen der Benutzer verarbeiten und relevante Restaurants basierend auf den eingegebenen Suchparametern und der geografischen Position finden.
- Rudimentäre Anzeige der Suchergebnisse in einer separaten Resultate-View: Nachdem wir die Suchanfrage an die API gesendet haben und die Ergebnisse erhalten haben, werden wir eine separate Ansicht erstellen, um die Suchergebnisse anzuzeigen. Dies kann eine Liste von Restaurants mit grundlegenden Informationen oder eine Kartenansicht mit Markierungen für die gefundenen Restaurants beinhalten.
Branch
Der vierte Teil des Amenity Finder Labs ist auf GitHub verfügbar.
Wichtige Links
Weiterführende Informationen zu diesem Thema sind unter folgenden Links nachzulesen:
Aufgabenstellung
Erstelle drei Eingabefelder mit dem
<mwc-textfield>
-Element, um die folgenden Daten einzugeben:Latitude
Longitude
Radius
⚠️ hint ⚠️
- Du kannst folgende Beispielwerte verwenden:
- Latitude:
47.3902
- Longitude:
8.5158
- Latitude:
- Du kannst folgende Beispielwerte verwenden:
Füge einen <mwc-button>
-Button mit der Beschriftung “Search” hinzu.
Integriere eine Karte mit dem <leaflet-map>
-Element.
Implementiere ein Binding der Daten zwischen dem Zustand und der Benutzeroberfläche (UI) sowie umgekehrt. Das bedeutet, dass Änderungen im Zustand die UI aktualisieren und Eingaben des Benutzers in der UI den Zustand aktualisieren sollen.
Löse die Suche aus, ohne das Routing zu verwenden. Verwende hierfür einen Custom Event mit dem Namen execute-search
, der durch einen Klick auf den “Search”-Button ausgelöst wird. Fange dieses Ereignis im AmenityFinder
auf und wechsle zur Ergebnisansicht, indem du das currentView
entsprechend setzt.
Optional
Optional kannst du die folgenden zusätzlichen Aufgaben in Betracht ziehen.
- Baue eine “Locate me” Funktionalität ein:
- Füge einen
<mwc-button>
-Button mit der Beschriftung “Locate me” hinzu. - Implementiere die Aktion für den “Locate me” Button. Dies kann bedeuten, dass du die Latitude und Longitude basierend auf den Lokalisierungsdaten des Geräts anpasst.
- Füge einen
- Ermögliche das Setzen der Latitude und Longitude durch einen Klick auf die Karte:
- Konfiguriere die Karte über das
updateCenterOnClick
Property (oder das entsprechende Attribut). - Verbinde das
center-updated
Event, um auf Änderungen des Kartenmittelpunkts zu reagieren.
- Konfiguriere die Karte über das
- Setze die Suche ab und nutze das Routing:
- Richte die entsprechende Routing-Logik ein, um auf das
execute-search
Event zu reagieren. - Routen zur Seite
/results/:lat/:lon/:radius
, wenn das execute-search Event ausgelöst wird. Die Werte für Latitude, Longitude und Radius sollten entsprechend in der URL übergeben werden.
- Richte die entsprechende Routing-Logik ein, um auf das
Du hast jetzt die Möglichkeit, die Aufgabenstellung eigenständig zu lösen. Wenn du deine Lösung abgeschlossen hast oder wenn du sofort weiterlesen möchtest, stellen wir im nächsten Abschnitt unseren Lösungsvorschlag für den dritten Teil der Amenity Finder Serie vor.
Unser Lösungsvorschlag
Schritte
Die Suchseite inkl. Parametrisierung zum Absetzen der Suche wird in sieben Schritten umgesetzt:
- Anforderungen an Suche definieren
- Suchformular einbauen
- Karte einbauen
- Suche absetzen und Wechsel auf ResultView
- Wechsel mittels Routing (optional)
- Geolocation API einbauen (optional)
- Suchparameter durch Karteninteraktion anpassen (optional)
Anforderungen an Suche definieren
Bevor wir mit der Implementierung der Suche anfangen, definieren wir die folgenden funktionalen Anforderungen:
- Der Benutzer soll einen Breite- und Längengrad sowie einen Umkreis für die Suche manuell angeben können
- Optional sollen mithilfe von Geolocation API der Breite- und Längengrad automatisch festgestellt werden können
- Weiter soll der Benutzer auch mit Klicks auf der Karte seinen Suchmittelpunkt manuell setzen können
- Die Suchparameter sollen visuell auf einer Karte dargestellt werden, damit sich der Benutzer einfacher orientieren kann
- Beim Absetzen der Suche sollen Resultate zu den Suchparametern über eine Schnittstelle abgefragt werden
- Die Resultate sollten rudimentär in einer separaten View angezeigt werden
Suchformular einbauen
In der vorherigen Lektion haben wir eine SearchView
Komponente vorbereitet. Sie sollte unter http://localhost:8000/search erreichbar sein und etwa wie folgt aussehen:
Diese erweitern wir nun um die folgenden Elemente:
- Ein Formular mit Eingabefeldern für den Breite- und Längengrad sowie einen Umkreis
- Properties für diese Informationen, damit wir die Änderungen im UI in unserem Datenmodell tracken können
- Eine interaktive Karte für das Anzeigen des Breite- und Längengrad sowie des Umkreises
Komponenten für das Suchformular einbinden
Für die Eingabefelder im Suchformular können wir zwei weitere MWC Komponente verwenden:
<mwc-textfield>
für die Eingabefelder<mwc-button>
für die Buttons zum Absetzen der Suche und für das Feststellen des Breite- und Längengrades mittels der Geolocation API.
Wir installieren die beiden Komponenten
$ npm install @material/mwc-button@^0.22.1 @material/mwc-textfield@^0.22.1
importieren sie im SearchView.js
import '@material/mwc-button';
import '@material/mwc-textfield';
und passen die render()
Methode im SearchView.js
wie folgt an
--- src/views/SearchView.js
+++ src/views/SearchView.js
export class SearchView extends LitElement {
render() {
- return html`Search…`;
+ return html`
+ <h1>Search</h1>
+
+ <mwc-textfield label="Latitude"></mwc-textfield>
+ <mwc-textfield label="Longitude"></mwc-textfield>
+ <mwc-textfield label="Radius (m)"></mwc-textfield>
+
+ <mwc-button outlined label="Locate Me" icon="my_location"></mwc-button>
+ <mwc-button raised label="Search"></mwc-button>
+ `;
}
}
Unsere Suchseite sieht nun etwa wie folgt aus:
Formularwerte (UI) und Daten (Properties) synchronisieren
Das Formular hat jetzt noch keine Funktionalität. Damit wir die Werte im UI (Formular) mit dem Zustand der Applikation (Properties im SearchView
) synchron halten können, definieren wir die folgenden Properties:
static;
get;
properties();
{
return {
latitude: { type: String },
longitude: { type: String },
radius: { type: Number },
};
}
Die natürliche Lösung beim Umgang mit Formularen ist es, ein Formular-Element <form>
zu verwenden und jeweils die Daten aller «abgeschickten» Input-Elemente zu verarbeiten. In unserem Fall verwenden wir statt nativer Formular-Elemente wie <input>
oder <textarea>
Wrapper-Komponenten von MWC. Die Input-Elemente befinden sich dabei im Shadow DOM und werden so leider nicht in den Daten eines übergeordneten <form>
Elements inkludiert.
Mit dem Form Associated Custom Elements Standard gibt es ein Bestreben, dieses Problem zu lösen. Dieser Standard wird aber im Moment noch nicht breit unterstützt und ist in den MWC Komponenten auch erst kürzlich implementiert worden.
Für unseren Anwendungsfall binden wir uns also direkt auf den nativen Events der Input-Felder resp. der MWC Komponenten, welche diese Events propagieren. Möchten wir, dass sich der Wert unserer Properties jeweils erst dann ändert, wenn der Benutzer das Feld verlässt, binden wir uns auf den change
Event. Wollen wir, dass die Werte bei jedem Keystroke ändert, können wir uns z. B. auf den keyup
Event binden:
--- src/views/SearchView.js
+++ src/views/SearchView.js
return html`
<h1>Search</h1>
- <mwc-textfield label="Latitude"></mwc-textfield>
- <mwc-textfield label="Longitude"></mwc-textfield>
- <mwc-textfield label="Radius (m)"></mwc-textfield>
+ <mwc-textfield label="Latitude" @keyup="${e => (this.latitude = e.target.value)}"></mwc-textfield>
+ <mwc-textfield label="Longitude" @keyup="${e => (this.longitude = e.target.value)}"></mwc-textfield>
+ <mwc-textfield label="Radius (m)" @keyup="${e => (this.radius = e.target.value)}"></mwc-textfield>
<mwc-button outlined label="Locate Me" icon="my_location"></mwc-button>
<mwc-button raised label="Search"></mwc-button>
Damit wir im Browser den Zustand der Komponente sehen, bauen wir noch temporär den folgenden Code ein, der die drei Properties in der render()
Methode ausgibt:
--- src/views/SearchView.js
+++ src/views/SearchView.js
<mwc-button outlined label="Locate Me" icon="my_location"></mwc-button>
<mwc-button raised label="Search"></mwc-button>
+
+ <p>
+ Latitude: ${this.latitude}<br />
+ Longitude: ${this.longitude}<br />
+ Radius: ${this.radius}
+ </p>
`;
}
}
Im UI sehen wir nun, dass die Werte der Properties sich jeweils bei der Eingabe in den Formularfeldern anpassen:
ESLint meldet noch folgenden Fehler:
Arrow function should not return assignment no-return-assign
Er ist also mit unserer Schreibweise beim Event-Handler e => (this.latitude = e.target.value)
unzufrieden. Wir definieren die folgende Exception für die no-return-assign
Regel:
--- package.json
+++ package.json
"extends": [
"@open-wc",
"prettier"
- ]
+ ],
+ "rules": {
+ "no-return-assign": "off"
+ }
},
"prettier": {
"singleQuote": true,
Karte einbauen
Unterhalb des Suchformulars soll eine interaktive Karte eingebaut werden. Wir werden auf dieser die Suchparameter visualisieren. Damit wir etwas Zeit bei der Implementierung sparen, haben wir für die Karte eine Webkomponente vorbereitet: <leaflet-map>
. Sie verwendet die Open-Source-Bibliothek Leaflet. Damit können interaktive Karten mit unterschiedlichstem Kartenmaterial («Tile Layers») dargestellt werden. Die <leaflet-map>
verwendet als Tile Layer das Kartenmaterial von OpenStreetMap®.
Zuerst installieren wir die Komponente:
$ npm i @inventage/leaflet-map@^0.8.0
und importieren sie in unserer SearchView
:
import '@inventage/leaflet-map';
Die <leaflet-map>
kennt verschiedene Properties, darunter auch latitude
, longitude
und radius
. Wir können also beim Einbau der Map unsere SearchView
Properties direkt an die Komponente weitergeben (binden):
--- src/views/SearchView.js
+++ src/views/SearchView.js
render() {
return html`
<h1>Search</h1>
<mwc-button outlined label="Locate Me" icon="my_location"></mwc-button>
<mwc-button raised label="Search"></mwc-button>
- <p>
- Latitude: ${this.latitude}<br />
- Longitude: ${this.longitude}<br />
- Radius: ${this.radius}
- </p>
+ <leaflet-map .latitude="${this.latitude}" .longitude="${this.longitude}" .radius="${this.radius}"></leaflet-map>
`;
}
}
Wir sehen nun die Karte, allerdings noch ohne Inhalt. Unsere Properties in der SearchView
haben initial noch den Wert undefined
. Damit unsere Suchseite auch initial und ohne Benutzerinteraktion richtig funktioniert, definieren wir die folgenden Default-Werte für unsere Daten (Properties):
--- src/views/SearchView.js
+++ src/views/SearchView.js
`;
}
+ constructor() {
+ super();
+
+ this.latitude = '47.3902';
+ this.longitude = '8.5158';
+ this.radius = 1000;
+ }
+
render() {
return html`
<h1>Search</h1>
Wir schauen auf die laufende Applikation und sehen ungefähr das folgende Bild:
Das sieht schonmal sehr gut aus. Einzig die Formularfelder bilden den Zustand der Komponente noch nicht ab. Wir ergänzen dies, indem wir das value
Property der Input-Felder auf die Werte unserer Properties der SearchView
Komponente mit .value="${this.ourProperty}"
binden:
--- src/views/SearchView.js
+++ src/views/SearchView.js
return html`
<h1>Search</h1>
- <mwc-textfield label="Latitude" @keyup="${e => (this.latitude = e.target.value)}"></mwc-textfield>
- <mwc-textfield label="Longitude" @keyup="${e => (this.longitude = e.target.value)}"></mwc-textfield>
- <mwc-textfield label="Radius (m)" @keyup="${e => (this.radius = e.target.value)}"></mwc-textfield>
+ <mwc-textfield label="Latitude" .value="${this.latitude}" @keyup="${e => (this.latitude = e.target.value)}"></mwc-textfield>
+ <mwc-textfield label="Longitude" .value="${this.longitude}" @keyup="${e => (this.longitude = e.target.value)}"></mwc-textfield>
+ <mwc-textfield label="Radius (m)" .value="${this.radius}" @keyup="${e => (this.radius = e.target.value)}"></mwc-textfield>
<mwc-button outlined label="Locate Me" icon="my_location"></mwc-button>
<mwc-button raised label="Search"></mwc-button>
Jetzt sieht es besser aus. Die Default-Werte für unsere Properties werden sowohl im Formular als auch auf der Karte dargestellt. Während wir die Werte im Formular anpassen, sollte die Kartenansicht in der <leaflet-map>
Komponente dies abbilden.
Suche absetzen
Wir haben bis jetzt eine funktionierende SearchView
gebaut. Der nächste Schritt ist nun das Absetzen einer Suche und das Anzeigen der Suchresultate. Dabei sollen über eine Schnittstelle mögliche Restaurants abgefragt werden. Eine wichtige Frage ist, wer (welche Komponente oder Orchestrator) diese Abfrage machen soll.
Wir haben uns dafür entschieden, dass die SearchView
lediglich für das Sammeln der Suchparameter verantwortlich sein soll. Die eigentliche Abfrage der Schnittstelle übernimmt bei uns die ResultsView
, welche wir in der nächsten Lektion anschauen werden. Die SearchView
teilt über einen Event der Aussenwelt mit, welche Suchparameter ausgewählt worden sind. Der AmenityFinder
orchestriert diesen Event und kümmert sich um das Routing und Instanziierung der ResultsView
. Die eigentliche Abfrage und das Anzeigen der Resultate wird in der ResultsView
gemacht.
Implementierung execute-search
Event
Die Suche soll durch den Klick auf den «Search» Button abgesetzt werden. Wir binden uns auf den click
Event beim entsprechenden button:
--- src/views/SearchView.js
+++ src/views/SearchView.js
<mwc-textfield label="Radius (m)" .value="${this.radius}" @keyup="${e => (this.radius = e.target.value)}"></mwc-textfield>
<mwc-button outlined label="Locate Me" icon="my_location" @click="${this._handleLocateMeClick}" .disabled="${!canGeolocate()}"></mwc-button>
- <mwc-button raised label="Search"></mwc-button>
+ <mwc-button raised label="Search" @click="${this._triggerSearch}"></mwc-button>
<leaflet-map
.latitude="${this.latitude}"
und definieren den entsprechenden Event-Handler als Methode wie folgt:
_triggerSearch();
{
this.dispatchEvent(
new CustomEvent('execute-search', {
detail: {
latitude: this.latitude,
longitude: this.longitude,
radius: this.radius,
},
})
);
}
Wir setzen also bei der Suche einfach einen Custom execute-search
Event ab. Damit wir das Absetzen einer Suche mit falschen Parametern schon früh verhindern, bauen wir eine rudimentäre Validierung ein. Wir wollen, dass eine Suche nur dann abgesetzt werden kann, wenn alle drei Parameter latitude
, longitude
und radius
gesetzt und nicht leer sind:
--- src/views/SearchView.js
+++ src/views/SearchView.js
<mwc-textfield label="Radius (m)" .value="${this.radius}" @keyup="${e => (this.radius = e.target.value)}"></mwc-textfield>
<mwc-button outlined label="Locate Me" icon="my_location" @click="${this._handleLocateMeClick}" .disabled="${!canGeolocate()}"></mwc-button>
- <mwc-button raised label="Search" @click="${this._triggerSearch}"></mwc-button>
+ <mwc-button raised label="Search" @click="${this._triggerSearch}" .disabled="${!this._canSearch()}"></mwc-button>
<leaflet-map
.latitude="${this.latitude}"
--- src/views/SearchView.js
+++ src/views/SearchView.js
})
);
}
+
+ _canSearch() {
+ return this.latitude && this.longitude && this.radius;
+ }
}
customElements.define('search-view', SearchView);
Wechsel zu ResultView
Den Wechsel zur ResultView zeigen wir zunächst ohne die Verwendung von Page.js, sondern rein mithilfe des currentView
Properties.
Im AmenityFinder
können wir nun einerseits die Properties und andererseits den execute-search
Event binden und einen entsprechenden, datengetriebenen Wechsel zur Suchresultate-Seite einbauen:
--- src/AmenityFinder.js
+++ src/AmenityFinder.js
case 'home':
return html`<home-view></home-view>`;
case 'search':
- return html`<search-view></search-view>`;
+ return html`<search-view
+ .latitude="${this.latitude}"
+ .longitude="${this.longitude}"
+ .radius="${this.radius}"
+ @execute-search="${(e) => this._onExecuteSearch(e)}"
+ ></search-view>`;
case 'results':
return html`<results-view></results-view>`;
Als Event Handler für den execute-search
Event schreiben wir eine neue Methode _onExecuteSearch(e)
. Sie bekommt unseren Custom Event als Argument. In dieser Methode lesen wir die Details aus dem Event aus, speichern sie in den entsprechenden Properties und setzen den Wert des currentView
Properties auf results
. Durch die Veränderung der Properties löst Lit ein Rendering aus und zeigt so die ResultView dar.
Zunächst definieren wir dafür die drei Parameter als Properties im AmenityFinder
:
--- src/AmenityFinder.js
+++ src/AmenityFinder.js
return {
showSidebar: { type: Boolean },
currentView: { type: String },
+ latitude: { type: String },
+ longitude: { type: String },
+ radius: { type: Number },
};
}
Sodass wir nun die Implementation von _onExecuteSearch
vornehmen können:
--- src/AmenityFinder.js
+++ src/AmenityFinder.js
+ _onExecuteSearch(e) {
+ this.latitude = e.detail.latitude;
+ this.longitude = e.detail.longitude;
+ this.radius = e.detail.radius;
+ this.currentView = "results";
+ }
Die default Werte für Latitude, Longitude und Radius verschieben wir von der SearchView in den Konstruktor der AmenityFinder Klasse:
--- src/AmenityFinder.js
+++ src/AmenityFinder.js
this.showSidebar = false;
this.currentView = 'home';
+ this.latitude = '47.3902';
+ this.longitude = '8.5158';
+ this.radius = 1000;
}
Wechsel mittels Routing (optional)
Wenn wir in (Lektion 3) den optionalen Teil mit dem Einbau von Page.js durchgeführt haben, so können wir nun darauf aufbauen.
Die Navigation zur Suchresultate-Seite funktioniert nun. Uns fehlen allerdings noch die entsprechenden latitude
, longitude
und radius
Parameter. Wir möchten, dass die Parameter über die URL definiert werden. Mithilfe von Page.js können wir unsere Route-Definition entsprechend um diese Parameter erweitern:
--- src/AmenityFinder.js
+++ src/AmenityFinder.js
- page('/results', () => {
+ page('/results/:lat/:lon/:radius', ctx => {
+ this._setSearchParametersFromRouteContext(ctx);
this.currentView = 'results';
});
page('/search', () => {
Die Logik für das Parsen der URL Parameter übernimmt Page.js. Wir müssen noch die Methode implementieren, um die Parameter aus dem Kontext-Objekt in Daten (Properties) zu speichern und entsprechend weiter an die ResultsView
zu geben. Der AmenityFinder
wird damit zum Orchestrator dieser drei Suchparameter. Die neue _setSearchParametersFromRouteContext
Methode zum Abgleichen der URL mit unseren Daten definieren wir wie folgt:
_setSearchParametersFromRouteContext(ctx);
{
const {
params: { radius, lat, lon },
} = ctx;
if (!radius || !lat || !lon) {
return;
}
this.radius = radius;
this.latitude = lat;
this.longitude = lon;
}
Den execute-search
Event-Handler passen wir noch an, indem wir die Daten aus dem Event an das Routing über die neu definierte /results/:lat/:lon/:radius
URL übergeben:
// eslint-disable-next-line class-methods-use-this
_onExecuteSearch(e);
{
page(`/results/${e.detail.latitude}/${e.detail.longitude}/${e.detail.radius}`);
}
Es werden keine Properties mehr in der _onExecuteSearch
Methode gesetzt. Dies hat alles der bei Page registrierte Handler übernommen.
Klicken wir nun auf den «Search» Button, sollten wir im Browser die ResultsView
sehen. Als URL sollten die Suchparameter, wie z. B. /results/47.3902/8.5158/1000
, sichtbar sein. Wir haben damit die Grundlage geschaffen, den Zustand der Suche über die URL zu steuern. In der nächsten Lektion werden wir die Abfrage der Suchresultate und die ResultsView
bauen.
Geolocation API einbauen (optional)
Damit der Benutzer die Längen- und Breitengrade für seine eigene Position nicht manuell eingeben muss, wollen wir ihm die Möglichkeit bieten, seine Position automatisch feststellen zu lassen. Der Browser bietet dafür die Geolocation API. Beim Klick auf den «Locate me» Button soll die Position des Benutzers erkannt werden und, sofern erfolgreich, automatisch auf die Daten der Komponente abgebildet werden.
Wir bauen uns dafür eine Helfer-Funktion und definieren sie in einem eigenen File, damit wir die SearchView
Klasse nicht überladen. Wir legen ein neues Verzeichnis utils
und darin eine neue Datei mit dem Namen geolocation.js
an. Darin definieren wir die folgenden zwei Funktionen:
export const canGeolocate = () => {
return 'geolocation' in navigator;
};
und
export const detectUserLocation = () => {
return new Promise((resolve, reject) => {
if (!canGeolocate()) {
reject(new Error('Geolocation not possible'));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => resolve(position),
(error) => reject(error)
);
});
};
Die erste Funktion ist selbsterklärend. Sie gibt nur dann true
zurück, wenn der Browser die Geolocation API unterstützt. Wir können diese Funktion einerseits in der zweiten detectUserLocation
Funktion verwenden, gleichzeitig aber auch in unserer SearchView
Komponente um den «Locate me» z. B. auszublenden oder zu disablen, im Fall, dass er die Funktion ohnehin nicht verwenden kann.
Die zweite Funktion detectUserLocation
ist eigentlich nur ein Promise-Wrapper um die native Browser-Funktion Geolocation.getCurrentPosition()
. Diese ist mithilfe eines Callbacks implementiert. Der Promise-Wrapper erlaubt es uns, zusätzliche Logik einzubauen und ist von der Syntax her schöner in der Anwendung und ermöglicht dank Promises die Verwendung von async
und await
.
Sie resolved das Promise mit einem GeolocationPosition-Objekt im Fall, dass die Location festgestellt wurde. Kann die API nicht verwendet oder konnte die Position nicht ermittelt werden, wird das Promise mit einem entsprechendem Fehler rejected.
src/utils/geolocation.js
/**
* Returns true if the current agent supports geolocation.
*
* @returns {boolean}
*/
const canGeolocate = () => {
return 'geolocation' in navigator;
};
/**
* Function to detect a user's location, promise based.
*
* @link https://developer.mozilla.org/en-US/docs/Web/API/Geolocation_API/Using_the_Geolocation_API
*
* @returns {Promise<unknown>}
*/
const detectUserLocation = () => {
return new Promise((resolve, reject) => {
if (!canGeolocate()) {
reject(new Error('Geolocation not possible'));
return;
}
navigator.geolocation.getCurrentPosition(
(position) => resolve(position),
(error) => reject(error)
);
});
};
export { canGeolocate, detectUserLocation };
Wir importieren nun die beiden Funktionen in unserer SearchView
:
import { canGeolocate, detectUserLocation } from '../utils/geolocation.js';
und implementieren die dazugehörige die Funktionalität auf unserem «Locate me» Button:
--- src/views/SearchView.js
+++ src/views/SearchView.js
<mwc-textfield label="Longitude" .value="${this.longitude}" @keyup="${e => (this.longitude = e.target.value)}"></mwc-textfield>
<mwc-textfield label="Radius (m)" .value="${this.radius}" @keyup="${e => (this.radius = e.target.value)}"></mwc-textfield>
- <mwc-button outlined label="Locate Me" icon="my_location"></mwc-button>
+ <mwc-button outlined label="Locate Me" icon="my_location" @click="${this._handleLocateMeClick}" .disabled="${!canGeolocate()}"></mwc-button>
<mwc-button raised label="Search"></mwc-button>
<leaflet-map .latitude="${this.latitude}" .longitude="${this.longitude}" .radius="${this.radius}"></leaflet-map>
Damit Klick-Event-Handler funktioniert, definieren wit noch die _handleLocateMeClick()
Methode wie folgt:
async;
_handleLocateMeClick();
{
try {
const {
coords: { latitude, longitude },
} = await detectUserLocation();
this.latitude = latitude;
this.longitude = longitude;
} catch (err) {
console.error(err);
}
}
Die ESLint Konfiguration von open-wc lässt keine console.*
Statements zu. Auch dies können wir noch kurz anpassen und übersteuern, sodass console.error
und console.info
erlaubt sind:
--- package.json
+++ package.json
"eslint-config-prettier"
],
"rules": {
- "no-return-assign": "off"
+ "no-return-assign": "off",
+ "no-console": [
+ "error",
+ {
+ "allow": [
+ "info",
+ "error"
+ ]
+ }
+ ]
}
},
"prettier": {
Etwaige Lint-Fehler sollten nun weg sein und unsere SearchView
sollte wie folgt aussehen:
Etwas unschön ist jetzt noch, dass der Button im Fokus bleibt beim Klick-Event. Dies können wir mit einer einfachen Anpassung unseres Handlers korrigieren:
--- src/views/SearchView.js
+++ src/views/SearchView.js
`;
}
- async _handleLocateMeClick() {
+ async _handleLocateMeClick(e) {
+ e.target.blur();
+
try {
const {
coords: { latitude, longitude },
Suchparameter durch Karteninteraktion anpassen (optional)
Durch die Weitergabe der latitude
, longitude
und radius
Properties an <leaflet-map>
passt sich die Karte den Suchparametern an («Properties down ↓»). Die Webkomponente kennt aber auch einen center-updated
Event und ein updateCenterOnClick
Property. Durch die Kombination dieser beiden können wir die Suchparameter anhand der Interaktion mit der Karte anpassen («Events up ↑»).
Wir setzen das updatecenteronclick
(Attribut) auf der <leaflet-map>
Komponente und binden eine Methode als Event-Handler beim center-updated
Event wie folgt:
--- src/views/SearchView.js
+++ src/views/SearchView.js
<mwc-button outlined label="Locate Me" icon="my_location" @click="${this._handleLocateMeClick}" .disabled="${!canGeolocate()}"></mwc-button>
<mwc-button raised label="Search"></mwc-button>
- <leaflet-map .latitude="${this.latitude}" .longitude="${this.longitude}" .radius="${this.radius}"></leaflet-map>
+ <leaflet-map
+ .latitude="${this.latitude}"
+ .longitude="${this.longitude}"
+ .radius="${this.radius}"
+ @center-updated="${this._updateLatitudeLongitudeFromMap}"
+ updatecenteronclick
+ ></leaflet-map>
`;
}
Die Methode für den Event-Handler definieren wir weiter unten:
_updateLatitudeLongitudeFromMap(e);
{
const {
detail: { latitude, longitude },
} = e;
if (!latitude || !longitude) {
return;
}
this.latitude = latitude;
this.longitude = longitude;
}
Unsere interaktive Karte sieht nun etwa so aus:
Grossartig!
Du hast den vierten Teil der Amenity Finder Serie erfolgreich gemeistert. In diesem Teil haben wir die Suchseite der Applikation implementiert und die Möglichkeit geschaffen, Suchparameter für die Umkreissuche festzulegen. Du hast möglicherweise Eingabefelder und Schaltflächen verwendet, um die Parameter einzugeben und die Suche abzusetzen. Dies eröffnet den Benutzern die Möglichkeit, nach Amenities in ihrer Umgebung zu suchen. Im fünften Teil werden wir die Abfrage und Anzeige der Suchergebnisse implementieren.