|
3 weeks ago | |
---|---|---|
.. | ||
README.md | 3 weeks ago | |
assignment.md | 4 weeks ago |
README.md
Bygg en bankapp del 3: Metoder for å hente og bruke data
Quiz før forelesning
Introduksjon
Kjernen i enhver webapplikasjon er data. Data kan ha mange former, men hovedformålet er alltid å vise informasjon til brukeren. Etter hvert som webapplikasjoner blir mer interaktive og komplekse, har måten brukeren får tilgang til og interagerer med informasjon blitt en viktig del av webutvikling.
I denne leksjonen skal vi se hvordan vi kan hente data asynkront fra en server og bruke disse dataene til å vise informasjon på en nettside uten å laste HTML på nytt.
Forutsetninger
Du må ha bygget innloggings- og registreringsskjemaet som en del av webapplikasjonen for denne leksjonen. Du må også installere Node.js og kjøre server-APIet lokalt for å få kontodata.
Du kan teste at serveren kjører riktig ved å utføre denne kommandoen i terminalen:
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
AJAX og datahenting
Tradisjonelle nettsteder oppdaterer innholdet som vises når brukeren velger en lenke eller sender inn data via et skjema, ved å laste hele HTML-siden på nytt. Hver gang nye data må lastes, returnerer webserveren en helt ny HTML-side som må behandles av nettleseren, noe som avbryter brukerens handling og begrenser interaksjoner under omlastingen. Denne arbeidsflyten kalles også en Multi-Page Application eller MPA.
Da webapplikasjoner begynte å bli mer komplekse og interaktive, dukket en ny teknikk opp kalt AJAX (Asynchronous JavaScript and XML). Denne teknikken lar webapplikasjoner sende og hente data fra en server asynkront ved hjelp av JavaScript, uten å laste HTML-siden på nytt, noe som resulterer i raskere oppdateringer og jevnere brukerinteraksjoner. Når nye data mottas fra serveren, kan den nåværende HTML-siden også oppdateres med JavaScript ved hjelp av DOM-APIet. Over tid har denne tilnærmingen utviklet seg til det som nå kalles en Single-Page Application eller SPA.
Da AJAX først ble introdusert, var det eneste APIet tilgjengelig for å hente data asynkront XMLHttpRequest
. Men moderne nettlesere implementerer nå det mer praktiske og kraftige Fetch
API, som bruker promises og er bedre egnet til å manipulere JSON-data.
Selv om alle moderne nettlesere støtter
Fetch API
, er det alltid lurt å sjekke kompatibilitetstabellen på caniuse.com først hvis du vil at webapplikasjonen din skal fungere på eldre nettlesere.
Oppgave
I forrige leksjon implementerte vi registreringsskjemaet for å opprette en konto. Nå skal vi legge til kode for å logge inn med en eksisterende konto og hente dataene til denne kontoen. Åpne app.js
-filen og legg til en ny login
-funksjon:
async function login() {
const loginForm = document.getElementById('loginForm')
const user = loginForm.user.value;
}
Her starter vi med å hente skjemaelementet med getElementById()
, og deretter henter vi brukernavnet fra input-feltet med loginForm.user.value
. Hvert skjemakontroll kan nås via navnet (satt i HTML med name
-attributtet) som en egenskap av skjemaet.
På samme måte som vi gjorde for registreringen, skal vi lage en annen funksjon for å utføre en serverforespørsel, men denne gangen for å hente kontodata:
async function getAccount(user) {
try {
const response = await fetch('//localhost:5000/api/accounts/' + encodeURIComponent(user));
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
Vi bruker fetch
-APIet for å hente data asynkront fra serveren, men denne gangen trenger vi ingen ekstra parametere annet enn URLen vi skal kalle, siden vi kun henter data. Som standard oppretter fetch
en GET
-HTTP-forespørsel, som er det vi ønsker her.
✅ encodeURIComponent()
er en funksjon som rømmer spesialtegn for URL. Hvilke problemer kan vi få hvis vi ikke kaller denne funksjonen og bruker user
-verdien direkte i URLen?
La oss nå oppdatere login
-funksjonen vår til å bruke getAccount
:
async function login() {
const loginForm = document.getElementById('loginForm')
const user = loginForm.user.value;
const data = await getAccount(user);
if (data.error) {
return console.log('loginError', data.error);
}
account = data;
navigate('/dashboard');
}
Først, siden getAccount
er en asynkron funksjon, må vi bruke await
-nøkkelordet for å vente på serverresultatet. Som med enhver serverforespørsel, må vi også håndtere feiltilfeller. Foreløpig legger vi bare til en loggmelding for å vise feilen, og kommer tilbake til dette senere.
Deretter må vi lagre dataene et sted slik at vi senere kan bruke dem til å vise informasjon på dashbordet. Siden account
-variabelen ikke eksisterer ennå, oppretter vi en global variabel for den øverst i filen vår:
let account = null;
Etter at brukerdataene er lagret i en variabel, kan vi navigere fra login-siden til dashboard-siden ved hjelp av navigate()
-funksjonen vi allerede har.
Til slutt må vi kalle login
-funksjonen vår når innloggingsskjemaet sendes inn, ved å endre HTML:
<form id="loginForm" action="javascript:login()">
Test at alt fungerer riktig ved å registrere en ny konto og prøve å logge inn med den samme kontoen.
Før vi går videre til neste del, kan vi også fullføre register
-funksjonen ved å legge til dette nederst i funksjonen:
account = result;
navigate('/dashboard');
✅ Visste du at som standard kan du bare kalle server-APIer fra samme domene og port som nettsiden du ser på? Dette er en sikkerhetsmekanisme som håndheves av nettlesere. Men vent, webappen vår kjører på localhost:3000
mens server-APIet kjører på localhost:5000
, hvorfor fungerer det? Ved å bruke en teknikk kalt Cross-Origin Resource Sharing (CORS), er det mulig å utføre cross-origin HTTP-forespørsler hvis serveren legger til spesielle headere i responsen, som tillater unntak for spesifikke domener.
Lær mer om APIer ved å ta denne leksjonen
Oppdater HTML for å vise data
Nå som vi har brukerdataene, må vi oppdatere den eksisterende HTMLen for å vise dem. Vi vet allerede hvordan vi henter et element fra DOM ved for eksempel å bruke document.getElementById()
. Etter at du har et baseelement, her er noen APIer du kan bruke for å endre det eller legge til barnelementer:
-
Ved å bruke
textContent
-egenskapen kan du endre teksten til et element. Merk at endring av denne verdien fjerner alle elementets barn (hvis det finnes noen) og erstatter det med den oppgitte teksten. Som sådan er det også en effektiv metode for å fjerne alle barn av et gitt element ved å tilordne en tom streng''
til det. -
Ved å bruke
document.createElement()
sammen medappend()
-metoden kan du opprette og legge til ett eller flere nye barnelementer.
✅ Ved å bruke innerHTML
-egenskapen til et element er det også mulig å endre HTML-innholdet, men denne bør unngås da den er sårbar for cross-site scripting (XSS)-angrep.
Oppgave
Før vi går videre til dashbordskjermen, er det én ting vi bør gjøre på login-siden. For øyeblikket, hvis du prøver å logge inn med et brukernavn som ikke eksisterer, vises en melding i konsollen, men for en vanlig bruker endres ingenting, og du vet ikke hva som skjer.
La oss legge til et plassholderelement i innloggingsskjemaet der vi kan vise en feilmelding hvis nødvendig. Et godt sted ville være rett før innloggings-<button>
:
...
<div id="loginError"></div>
<button>Login</button>
...
Dette <div>
-elementet er tomt, noe som betyr at ingenting vil vises på skjermen før vi legger til innhold i det. Vi gir det også en id
slik at vi enkelt kan hente det med JavaScript.
Gå tilbake til app.js
-filen og opprett en ny hjelpefunksjon updateElement
:
function updateElement(id, text) {
const element = document.getElementById(id);
element.textContent = text;
}
Denne er ganske enkel: gitt et element-id og tekst, vil den oppdatere tekstinnholdet til DOM-elementet med den matchende id
. La oss bruke denne metoden i stedet for den forrige feilmeldingen i login
-funksjonen:
if (data.error) {
return updateElement('loginError', data.error);
}
Nå, hvis du prøver å logge inn med en ugyldig konto, bør du se noe slikt:
Nå har vi feilmeldingstekst som vises visuelt, men hvis du prøver det med en skjermleser, vil du merke at ingenting blir annonsert. For at tekst som dynamisk legges til en side skal bli annonsert av skjermlesere, må den bruke noe som kalles en Live Region. Her skal vi bruke en spesifikk type live region kalt et varsel:
<div id="loginError" role="alert"></div>
Implementer samme oppførsel for feil i register
-funksjonen (ikke glem å oppdatere HTML).
Vis informasjon på dashbordet
Ved å bruke de samme teknikkene vi nettopp har sett, skal vi også ta oss av å vise kontoinformasjonen på dashbord-siden.
Slik ser et kontoobjekt mottatt fra serveren ut:
{
"user": "test",
"currency": "$",
"description": "Test account",
"balance": 75,
"transactions": [
{ "id": "1", "date": "2020-10-01", "object": "Pocket money", "amount": 50 },
{ "id": "2", "date": "2020-10-03", "object": "Book", "amount": -10 },
{ "id": "3", "date": "2020-10-04", "object": "Sandwich", "amount": -5 }
],
}
Merk: for å gjøre livet ditt enklere kan du bruke den forhåndsdefinerte
test
-kontoen som allerede er fylt med data.
Oppgave
La oss starte med å erstatte "Balance"-seksjonen i HTML for å legge til plassholderelementer:
<section>
Balance: <span id="balance"></span><span id="currency"></span>
</section>
Vi legger også til en ny seksjon rett under for å vise kontobeskrivelsen:
<h2 id="description"></h2>
✅ Siden kontobeskrivelsen fungerer som en tittel for innholdet under, er den semantisk markert som en overskrift. Lær mer om hvordan overskriftsstruktur er viktig for tilgjengelighet, og ta en kritisk titt på siden for å avgjøre hva annet som kan være en overskrift.
Deretter oppretter vi en ny funksjon i app.js
for å fylle inn plassholderen:
function updateDashboard() {
if (!account) {
return navigate('/login');
}
updateElement('description', account.description);
updateElement('balance', account.balance.toFixed(2));
updateElement('currency', account.currency);
}
Først sjekker vi at vi har kontodataene vi trenger før vi går videre. Deretter bruker vi updateElement()
-funksjonen vi opprettet tidligere for å oppdatere HTML.
For å gjøre saldoen mer lesbar bruker vi metoden
toFixed(2)
for å vise verdien med 2 desimaler.
Nå må vi kalle updateDashboard()
-funksjonen vår hver gang dashbordet lastes. Hvis du allerede har fullført leksjon 1-oppgaven, bør dette være enkelt, ellers kan du bruke følgende implementering.
Legg til denne koden på slutten av updateRoute()
-funksjonen:
if (typeof route.init === 'function') {
route.init();
}
Og oppdater rute-definisjonen med:
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard', init: updateDashboard }
};
Med denne endringen vil updateDashboard()
-funksjonen kalles hver gang dashbord-siden vises. Etter en innlogging bør du da kunne se kontosaldoen, valutaen og beskrivelsen.
Opprett tabellrader dynamisk med HTML-maler
I første leksjon brukte vi HTML-maler sammen med appendChild()
-metoden for å implementere navigasjonen i appen vår. Maler kan også være mindre og brukes til å dynamisk fylle repetitive deler av en side.
Vi skal bruke en lignende tilnærming for å vise listen over transaksjoner i HTML-tabellen.
Oppgave
Legg til en ny mal i HTML-<body>
:
<template id="transaction">
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</template>
Denne malen representerer en enkelt tabellrad med de 3 kolonnene vi ønsker å fylle: dato, objekt og beløp for en transaksjon.
Deretter legger vi til denne id
-egenskapen til <tbody>
-elementet i tabellen innenfor dashbordmalen for å gjøre det enklere å finne med JavaScript:
<tbody id="transactions"></tbody>
HTMLen vår er klar, la oss bytte til JavaScript-kode og opprette en ny funksjon createTransactionRow
:
function createTransactionRow(transaction) {
const template = document.getElementById('transaction');
const transactionRow = template.content.cloneNode(true);
const tr = transactionRow.querySelector('tr');
tr.children[0].textContent = transaction.date;
tr.children[1].textContent = transaction.object;
tr.children[2].textContent = transaction.amount.toFixed(2);
return transactionRow;
}
Denne funksjonen gjør akkurat det navnet antyder: ved å bruke malen vi opprettet tidligere, oppretter den en ny tabellrad og fyller inn innholdet ved hjelp av transaksjonsdata. Vi skal bruke denne i updateDashboard()
-funksjonen for å fylle tabellen:
const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
const transactionRow = createTransactionRow(transaction);
transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows);
Her bruker vi metoden document.createDocumentFragment()
som oppretter et nytt DOM-fragment som vi kan jobbe med, før vi til slutt legger det til HTML-tabellen vår.
Det er fortsatt én ting vi må gjøre før denne koden kan fungere, da updateElement()
-funksjonen vår for øyeblikket kun støtter tekstinnhold. La oss endre koden litt:
function updateElement(id, textOrNode) {
const element = document.getElementById(id);
element.textContent = ''; // Removes all children
element.append(textOrNode);
}
Vi bruker append()
-metoden, da den lar oss legge til enten tekst eller DOM Nodes til et overordnet element, noe som er perfekt for alle våre brukstilfeller.
Hvis du prøver å bruke test
-kontoen for å logge inn, bør du nå se en transaksjonsliste på dashbordet 🎉.
🚀 Utfordring
Jobb sammen for å få dashbord-siden til å se ut som en ekte bankapp. Hvis du allerede har stylet appen din, prøv å bruke media queries for å lage et responsivt design som fungerer godt både på desktop og mobile enheter.
Her er et eksempel på en stylet dashbord-side:
Quiz etter forelesning
Oppgave
Refaktorer og kommenter koden din
Ansvarsfraskrivelse:
Dette dokumentet er oversatt ved hjelp av AI-oversettelsestjenesten Co-op Translator. Selv om vi streber etter nøyaktighet, vær oppmerksom på at automatiserte oversettelser kan inneholde feil eller unøyaktigheter. Det originale dokumentet på sitt opprinnelige språk bør anses som den autoritative kilden. For kritisk informasjon anbefales profesjonell menneskelig oversettelse. Vi er ikke ansvarlige for misforståelser eller feiltolkninger som oppstår ved bruk av denne oversettelsen.