19 KiB
Bygg en bankapp del 2: Skapa ett inloggnings- och registreringsformulär
Förhandsquiz
Introduktion
I nästan alla moderna webbappar kan du skapa ett konto för att få din egen privata plats. Eftersom flera användare kan använda en webbapp samtidigt behöver du en mekanism för att lagra varje användares personliga data separat och välja vilken information som ska visas. Vi kommer inte att gå igenom hur man hanterar användaridentitet säkert eftersom det är ett omfattande ämne i sig, men vi kommer att se till att varje användare kan skapa ett (eller flera) bankkonton i vår app.
I den här delen kommer vi att använda HTML-formulär för att lägga till inloggning och registrering i vår webbapp. Vi kommer att se hur man skickar data till en server-API programmatiskt och slutligen hur man definierar grundläggande valideringsregler för användarinmatningar.
Förkunskaper
Du behöver ha slutfört HTML-mallar och routing för webbappen för denna lektion. Du behöver också installera Node.js och köra server-API:t lokalt så att du kan skicka data för att skapa konton.
Observera Du kommer att ha två terminaler igång samtidigt enligt listan nedan:
- För huvudbankappen vi byggde i lektionen HTML-mallar och routing
- För Bank APP server-API:t som vi just satte upp ovan.
Du behöver ha båda servrarna igång för att kunna följa resten av lektionen. De lyssnar på olika portar (port 3000
och port 5000
) så allt bör fungera utan problem.
Du kan testa att servern körs korrekt genom att köra detta kommando i en terminal:
curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result
Formulär och kontroller
Elementet <form>
kapslar in en sektion av ett HTML-dokument där användaren kan mata in och skicka data med interaktiva kontroller. Det finns alla möjliga användargränssnittskontroller (UI) som kan användas inom ett formulär, den vanligaste är <input>
och <button>
-elementen.
Det finns många olika typer av <input>
, till exempel för att skapa ett fält där användaren kan ange sitt användarnamn kan du använda:
<input id="username" name="username" type="text">
Attributet name
kommer att användas som egenskapsnamn när formulärdata skickas. Attributet id
används för att associera en <label>
med formulärkontrollen.
Ta en titt på hela listan över
<input>
-typer och andra formulärkontroller för att få en idé om alla inbyggda UI-element du kan använda när du bygger ditt gränssnitt.
✅ Observera att <input>
är ett tomt element som du inte ska lägga till en matchande sluttagg på. Du kan dock använda den självstängande <input/>
-notationen, men det är inte nödvändigt.
Elementet <button>
inom ett formulär är lite speciellt. Om du inte anger dess type
-attribut kommer det automatiskt att skicka formulärdata till servern när det trycks. Här är de möjliga värdena för type
:
submit
: Standard inom ett<form>
, knappen triggar formulärens skicka-åtgärd.reset
: Knappen återställer alla formulärkontroller till deras ursprungliga värden.button
: Tilldelar ingen standardbeteende när knappen trycks. Du kan sedan tilldela anpassade åtgärder med JavaScript.
Uppgift
Låt oss börja med att lägga till ett formulär till login
-mallen. Vi behöver ett fält för användarnamn och en Logga in-knapp.
<template id="login">
<h1>Bank App</h1>
<section>
<h2>Login</h2>
<form id="loginForm">
<label for="username">Username</label>
<input id="username" name="user" type="text">
<button>Login</button>
</form>
</section>
</template>
Om du tittar noga kan du märka att vi också har lagt till ett <label>
-element här. <label>
-element används för att lägga till ett namn till UI-kontroller, såsom vårt användarnamnsfält. Etiketter är viktiga för läsbarheten av dina formulär, men har också ytterligare fördelar:
- Genom att associera en etikett med en formulärkontroll hjälper det användare som använder hjälpmedelstekniker (som skärmläsare) att förstå vilken data de förväntas ange.
- Du kan klicka på etiketten för att direkt sätta fokus på den associerade inmatningen, vilket gör det enklare att nå på pekskärmsbaserade enheter.
Tillgänglighet på webben är ett mycket viktigt ämne som ofta förbises. Tack vare semantiska HTML-element är det inte svårt att skapa tillgängligt innehåll om du använder dem korrekt. Du kan läsa mer om tillgänglighet för att undvika vanliga misstag och bli en ansvarsfull utvecklare.
Nu ska vi lägga till ett andra formulär för registrering, precis under det föregående:
<hr/>
<h2>Register</h2>
<form id="registerForm">
<label for="user">Username</label>
<input id="user" name="user" type="text">
<label for="currency">Currency</label>
<input id="currency" name="currency" type="text" value="$">
<label for="description">Description</label>
<input id="description" name="description" type="text">
<label for="balance">Current balance</label>
<input id="balance" name="balance" type="number" value="0">
<button>Register</button>
</form>
Med attributet value
kan vi definiera ett standardvärde för en given inmatning.
Observera också att inmatningen för balance
har typen number
. Ser det annorlunda ut än de andra inmatningarna? Prova att interagera med det.
✅ Kan du navigera och interagera med formulären med bara ett tangentbord? Hur skulle du göra det?
Skicka data till servern
Nu när vi har ett fungerande gränssnitt är nästa steg att skicka data till vår server. Låt oss göra ett snabbt test med vår nuvarande kod: vad händer om du klickar på knappen Logga in eller Registrera?
Märkte du förändringen i webbläsarens URL-sektion?
Standardåtgärden för ett <form>
är att skicka formuläret till den aktuella server-URL:en med GET-metoden, och lägga till formulärdata direkt i URL:en. Denna metod har dock vissa begränsningar:
- Den skickade datan är mycket begränsad i storlek (cirka 2000 tecken)
- Datan är direkt synlig i URL:en (inte bra för lösenord)
- Det fungerar inte med filuppladdningar
Därför kan du ändra det till att använda POST-metoden som skickar formulärdata till servern i HTTP-förfrågans kropp, utan några av de tidigare begränsningarna.
Även om POST är den mest använda metoden för att skicka data, i vissa specifika scenarier är det att föredra att använda GET-metoden, till exempel när man implementerar ett sökfält.
Uppgift
Lägg till action
och method
-egenskaper till registreringsformuläret:
<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">
Försök nu att registrera ett nytt konto med ditt namn. Efter att ha klickat på knappen Registrera bör du se något som detta:
Om allt går bra bör servern svara på din begäran med ett JSON-svar som innehåller kontodata som skapades.
✅ Försök att registrera igen med samma namn. Vad händer?
Skicka data utan att ladda om sidan
Som du förmodligen märkte finns det ett litet problem med det tillvägagångssätt vi just använde: när vi skickar formuläret lämnar vi vår app och webbläsaren omdirigerar till serverns URL. Vi försöker undvika alla sidomladdningar med vår webbapp, eftersom vi skapar en Single-page application (SPA).
För att skicka formulärdata till servern utan att tvinga en sidomladdning måste vi använda JavaScript-kod. Istället för att sätta en URL i action
-egenskapen för ett <form>
-element kan du använda valfri JavaScript-kod som föregås av strängen javascript:
för att utföra en anpassad åtgärd. Genom att använda detta innebär det också att du måste implementera vissa uppgifter som tidigare gjordes automatiskt av webbläsaren:
- Hämta formulärdata
- Konvertera och koda formulärdata till ett lämpligt format
- Skapa HTTP-förfrågan och skicka den till servern
Uppgift
Byt ut registreringsformulärets action
med:
<form id="registerForm" action="javascript:register()">
Öppna app.js
och lägg till en ny funktion som heter register
:
function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const data = Object.fromEntries(formData);
const jsonData = JSON.stringify(data);
}
Här hämtar vi formulärelementet med getElementById()
och använder hjälpen FormData
för att extrahera värdena från formulärkontroller som en uppsättning nyckel/värde-par. Sedan konverterar vi datan till ett vanligt objekt med Object.fromEntries()
och slutligen serialiserar datan till JSON, ett format som ofta används för att utbyta data på webben.
Datan är nu redo att skickas till servern. Skapa en ny funktion som heter createAccount
:
async function createAccount(account) {
try {
const response = await fetch('//localhost:5000/api/accounts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: account
});
return await response.json();
} catch (error) {
return { error: error.message || 'Unknown error' };
}
}
Vad gör denna funktion? Först, observera nyckelordet async
här. Detta innebär att funktionen innehåller kod som kommer att köras asynkront. När det används tillsammans med nyckelordet await
tillåter det att vänta på att asynkron kod ska köras - som att vänta på serverns svar här - innan det fortsätter.
Här är en snabb video om användning av async/await
:
🎥 Klicka på bilden ovan för en video om async/await.
Vi använder fetch()
-API:t för att skicka JSON-data till servern. Denna metod tar 2 parametrar:
- Serverns URL, så vi sätter tillbaka
//localhost:5000/api/accounts
här. - Inställningarna för begäran. Det är där vi ställer in metoden till
POST
och tillhandahållerbody
för begäran. Eftersom vi skickar JSON-data till servern måste vi också ställa inContent-Type
-headern tillapplication/json
så att servern vet hur man tolkar innehållet.
Eftersom servern kommer att svara på begäran med JSON kan vi använda await response.json()
för att analysera JSON-innehållet och returnera det resulterande objektet. Observera att denna metod är asynkron, så vi använder nyckelordet await
här innan vi returnerar för att säkerställa att eventuella fel under analysen också fångas.
Lägg nu till lite kod i funktionen register
för att kalla createAccount()
:
const result = await createAccount(jsonData);
Eftersom vi använder nyckelordet await
här måste vi lägga till nyckelordet async
före registerfunktionen:
async function register() {
Slutligen, låt oss lägga till några loggar för att kontrollera resultatet. Den slutliga funktionen bör se ut så här:
async function register() {
const registerForm = document.getElementById('registerForm');
const formData = new FormData(registerForm);
const jsonData = JSON.stringify(Object.fromEntries(formData));
const result = await createAccount(jsonData);
if (result.error) {
return console.log('An error occurred:', result.error);
}
console.log('Account created!', result);
}
Det var lite långt men vi kom dit! Om du öppnar dina webbläsarutvecklingsverktyg och försöker registrera ett nytt konto bör du inte se någon förändring på webbsidan, men ett meddelande kommer att visas i konsolen som bekräftar att allt fungerar.
✅ Tror du att datan skickas till servern säkert? Vad händer om någon lyckas avlyssna begäran? Du kan läsa om HTTPS för att lära dig mer om säker datakommunikation.
Datavalidering
Om du försöker registrera ett nytt konto utan att ange ett användarnamn först kan du se att servern returnerar ett fel med statuskod 400 (Bad Request).
Innan du skickar data till en server är det en bra praxis att validera formulärdata i förväg när det är möjligt, för att säkerställa att du skickar en giltig begäran. HTML5-formulärkontroller tillhandahåller inbyggd validering med olika attribut:
required
: fältet måste fyllas i, annars kan formuläret inte skickas.minlength
ochmaxlength
: definierar det minsta och största antalet tecken i textfält.min
ochmax
: definierar det minsta och största värdet för ett numeriskt fält.type
: definierar vilken typ av data som förväntas, somnumber
,email
,file
eller andra inbyggda typer. Detta attribut kan också ändra den visuella renderingen av formulärkontrollen.pattern
: tillåter att definiera ett reguljärt uttryck-mönster för att testa om den angivna datan är giltig eller inte.
Tips: du kan anpassa utseendet på dina formulärkontroller beroende på om de är giltiga eller ogiltiga genom att använda CSS-pseudoklasserna
:valid
och:invalid
.
Uppgift
Det finns två obligatoriska fält för att skapa ett giltigt nytt konto: användarnamn och valuta. De andra fälten är valfria. Uppdatera formulärets HTML genom att använda både attributet required
och text i fältets etikett så att:
<label for="user">Username (required)</label>
<input id="user" name="user" type="text" required>
...
<label for="currency">Currency (required)</label>
<input id="currency" name="currency" type="text" value="$" required>
Även om denna specifika serverimplementation inte ställer några specifika gränser för fältens maximala längd, är det alltid en bra praxis att definiera rimliga gränser för all användarinmatning.
Lägg till attributet maxlength
till textfälten:
<input id="user" name="user" type="text" maxlength="20" required>
...
<input id="currency" name="currency" type="text" value="$" maxlength="5" required>
...
<input id="description" name="description" type="text" maxlength="100">
Om du nu trycker på knappen Registrera och ett fält inte följer en valideringsregel som vi har definierat, bör du se något liknande detta:
Validering som denna, som utförs innan någon data skickas till servern, kallas klientbaserad validering. Men notera att det inte alltid är möjligt att utföra alla kontroller utan att skicka data. Till exempel kan vi inte här kontrollera om ett konto redan existerar med samma användarnamn utan att skicka en begäran till servern. Ytterligare validering som utförs på servern kallas serverbaserad validering.
Vanligtvis behöver båda implementeras, och även om klientbaserad validering förbättrar användarupplevelsen genom att ge omedelbar feedback till användaren, är serverbaserad validering avgörande för att säkerställa att användardata som hanteras är korrekt och säker.
🚀 Utmaning
Visa ett felmeddelande i HTML om användaren redan existerar.
Här är ett exempel på hur den slutliga inloggningssidan kan se ut efter lite styling:
Quiz efter föreläsningen
Granskning & Självstudier
Utvecklare har blivit mycket kreativa när det gäller att bygga formulär, särskilt när det handlar om valideringsstrategier. Lär dig om olika formulärflöden genom att titta igenom CodePen; kan du hitta några intressanta och inspirerande formulär?
Uppgift
Ansvarsfriskrivning:
Detta dokument har översatts med hjälp av AI-översättningstjänsten Co-op Translator. Även om vi strävar efter noggrannhet, vänligen notera att automatiska översättningar kan innehålla fel eller felaktigheter. Det ursprungliga dokumentet på sitt ursprungliga språk bör betraktas som den auktoritativa källan. För kritisk information rekommenderas professionell mänsklig översättning. Vi ansvarar inte för eventuella missförstånd eller feltolkningar som uppstår vid användning av denna översättning.