You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Web-Dev-For-Beginners/translations/mo/7-bank-project/2-forms
Lee Stott 2daab5271b
Update Quiz Link
3 weeks ago
..
README.md Update Quiz Link 3 weeks ago
assignment.md 🌐 Update translations via Co-op Translator 4 weeks ago

README.md

建立銀行應用程式第二部分:建立登入與註冊表單

課前測驗

課前測驗

簡介

在幾乎所有現代的網頁應用程式中,您都可以創建一個帳戶來擁有自己的私人空間。由於多個使用者可以同時訪問網頁應用程式,因此需要一種機制來分別儲存每位使用者的個人資料,並選擇要顯示的資訊。我們不會深入探討如何安全地管理使用者身份,因為這是一個非常廣泛的主題,但我們會確保每位使用者能夠在我們的應用程式中創建一個(或多個)銀行帳戶。

在這部分,我們將使用 HTML 表單為我們的網頁應用程式新增登入與註冊功能。我們將學習如何以程式化方式將資料發送到伺服器 API並最終定義基本的使用者輸入驗證規則。

先決條件

您需要完成本課程中HTML 模板與路由的部分。此外,您還需要安裝 Node.js在本地運行伺服器 API,以便能夠發送資料來創建帳戶。

請注意 您需要同時運行以下兩個終端:

  1. 用於我們在HTML 模板與路由課程中建立的主要銀行應用程式。
  2. 用於我們剛剛設置的銀行應用程式伺服器 API

您需要這兩個伺服器都在運行,才能完成本課程的其餘部分。它們分別監聽不同的埠(埠 3000 和埠 5000),因此應該可以正常運作。

您可以在終端中執行以下命令來測試伺服器是否正常運行:

curl http://localhost:5000/api
# -> should return "Bank API v1.0.0" as a result

表單與控制項

<form> 元素封裝了 HTML 文件的一個區域使用者可以在其中使用互動式控制項輸入並提交資料。在表單中可以使用各種使用者介面UI控制項其中最常見的是 <input><button> 元素。

<input> 有許多不同的類型,例如,您可以使用以下代碼創建一個讓使用者輸入用戶名的欄位:

<input id="username" name="username" type="text">

name 屬性將在表單資料發送時作為屬性名稱使用。而 id 屬性則用於將 <label> 與表單控制項關聯。

查看 <input> 類型其他表單控制項的完整列表,了解在構建 UI 時可以使用的所有原生 UI 元素。

請注意,<input> 是一個空元素,您不應為其添加匹配的閉合標籤。不過,您可以使用自閉合的 <input/> 表示法,但這不是必須的。

在表單中,<button> 元素有些特殊。如果您未指定其 type 屬性,按下按鈕時會自動提交表單資料到伺服器。以下是可能的 type 值:

  • submit:表單中的預設值,按鈕觸發表單提交操作。
  • reset:按鈕將所有表單控制項重置為初始值。
  • button:按鈕按下時不分配預設行為。您可以使用 JavaScript 為其分配自定義操作。

任務

讓我們從在 login 模板中新增一個表單開始。我們需要一個用戶名欄位和一個登入按鈕。

<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>

如果您仔細觀察,會發現我們還新增了一個 <label> 元素。<label> 元素用於為 UI 控制項(例如我們的用戶名欄位)添加名稱。標籤對於表單的可讀性非常重要,並且還帶來以下額外好處:

  • 通過將標籤與表單控制項關聯,可以幫助使用輔助技術(如螢幕閱讀器)的使用者理解需要提供哪些資料。
  • 您可以點擊標籤直接將焦點放在相關的輸入欄位上,這對於觸控螢幕設備來說更加方便。

網頁無障礙性是一個經常被忽視但非常重要的主題。得益於語義化 HTML 元素,如果正確使用它們,創建無障礙內容並不困難。您可以閱讀更多關於無障礙性的內容,以避免常見錯誤並成為一名負責任的開發者。

接下來,我們將在前一個表單的下方新增第二個註冊表單:

<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>

使用 value 屬性,我們可以為給定的輸入欄位定義預設值。 另外,請注意 balance 的輸入欄位使用了 number 類型。它看起來與其他輸入欄位不同嗎?試著與它互動看看。

您能僅使用鍵盤導航並與表單互動嗎?您會怎麼做?

將資料提交到伺服器

現在我們有了一個功能性的 UI下一步是將資料發送到伺服器。讓我們使用當前的代碼進行快速測試如果您點擊登入註冊按鈕會發生什麼?

您是否注意到瀏覽器的 URL 區域發生了變化?

點擊註冊按鈕後瀏覽器 URL 的變化截圖

<form> 的預設行為是使用 GET 方法將表單提交到當前伺服器 URL並將表單資料直接附加到 URL 中。然而,這種方法有一些缺點:

  • 發送的資料大小非常有限(約 2000 個字符)。
  • 資料直接顯示在 URL 中(對於密碼來說並不理想)。
  • 不支持文件上傳。

因此,您可以將其更改為使用 POST 方法,該方法將表單資料作為 HTTP 請求的主體發送到伺服器,避免了上述限制。

雖然 POST 是最常用於發送資料的方法,但在某些特定情況下,例如實現搜索欄時,使用 GET 方法更為合適。

任務

為註冊表單新增 actionmethod 屬性:

<form id="registerForm" action="//localhost:5000/api/accounts" method="POST">

現在嘗試使用您的名字註冊一個新帳戶。點擊註冊按鈕後,您應該會看到如下內容:

瀏覽器窗口顯示 localhost:5000/api/accounts 的地址,並顯示包含使用者資料的 JSON 字符串

如果一切順利,伺服器應該會以包含已創建帳戶資料的 JSON 響應回覆您的請求。

再次使用相同的名字註冊會發生什麼?

在不重新加載頁面的情況下提交資料

您可能已經注意到,我們剛剛使用的方法有一個小問題:提交表單時,我們離開了應用程式,瀏覽器重定向到伺服器 URL。我們正在嘗試避免所有頁面重新加載因為我們正在構建一個單頁應用程式SPA

為了在不強制重新加載頁面的情況下將表單資料發送到伺服器,我們需要使用 JavaScript 代碼。與其在 <form> 元素的 action 屬性中放置 URL您可以使用任何以 javascript: 字符串開頭的 JavaScript 代碼來執行自定義操作。這樣做也意味著您需要實現一些瀏覽器之前自動完成的任務:

  • 獲取表單資料
  • 將表單資料轉換並編碼為合適的格式
  • 創建 HTTP 請求並將其發送到伺服器

任務

將註冊表單的 action 替換為:

<form id="registerForm" action="javascript:register()">

打開 app.js,新增一個名為 register 的函數:

function register() {
  const registerForm = document.getElementById('registerForm');
  const formData = new FormData(registerForm);
  const data = Object.fromEntries(formData);
  const jsonData = JSON.stringify(data);
}

在這裡,我們使用 getElementById() 獲取表單元素,並使用 FormData 幫助程序將表單控制項中的值提取為一組鍵/值對。接著,我們使用 Object.fromEntries() 將資料轉換為普通物件,最後將資料序列化為 JSON,這是一種網頁上常用的資料交換格式。

資料現在已準備好發送到伺服器。創建一個名為 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' };
  }
}

這個函數在做什麼?首先,注意這裡的 async 關鍵字。這表示該函數包含將異步執行的代碼。當與 await 關鍵字一起使用時,它允許等待異步代碼執行(例如等待伺服器響應)後再繼續。

這裡有一段關於 async/await 使用的快速影片:

管理 Promise 的 Async 和 Await

🎥 點擊上方圖片觀看關於 async/await 的影片。

我們使用 fetch() API 將 JSON 資料發送到伺服器。此方法接受兩個參數:

  • 伺服器的 URL因此我們在這裡放回 //localhost:5000/api/accounts
  • 請求的設置。在這裡,我們將方法設置為 POST,並提供請求的 body。由於我們將 JSON 資料發送到伺服器,因此還需要將 Content-Type 標頭設置為 application/json,以便伺服器知道如何解釋內容。

由於伺服器會以 JSON 響應請求,我們可以使用 await response.json() 解析 JSON 內容並返回結果物件。請注意,此方法是異步的,因此我們在返回之前使用 await 關鍵字以確保在解析期間的任何錯誤也能被捕獲。

現在在 register 函數中新增一些代碼來調用 createAccount()

const result = await createAccount(jsonData);

由於我們在這裡使用了 await 關鍵字,因此需要在 register 函數之前新增 async 關鍵字:

async function register() {

最後,讓我們新增一些日誌來檢查結果。最終的函數應如下所示:

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);
}

這部分有點長,但我們完成了!如果您打開瀏覽器開發者工具,並嘗試註冊一個新帳戶,您應該不會看到網頁發生任何變化,但控制台中會出現一條消息,確認一切正常運作。

瀏覽器控制台中顯示日誌消息的截圖

您認為資料是安全地發送到伺服器的嗎?如果有人能攔截請求會怎麼樣?您可以閱讀HTTPS來了解更多關於安全資料通信的內容。

資料驗證

如果您嘗試在未先設置用戶名的情況下註冊新帳戶,您會看到伺服器返回了一個狀態碼為 400錯誤請求 的錯誤。

在將資料發送到伺服器之前,最好先驗證表單資料以確保您發送的是有效請求。HTML5 表單控制項提供了內建的驗證功能,通過使用各種屬性:

  • required:該欄位必須填寫,否則表單無法提交。
  • minlengthmaxlength:定義文本欄位的最小和最大字符數。
  • minmax:定義數字欄位的最小和最大值。
  • type:定義預期的資料類型,例如 numberemailfile其他內建類型。此屬性還可能改變表單控制項的視覺呈現。
  • pattern:允許定義一個正則表達式模式,用於測試輸入的資料是否有效。 提示:您可以使用 :valid:invalid CSS 偽類來根據表單控件是否有效自訂其外觀。

任務

要建立一個有效的新帳戶,需要填寫兩個必填欄位:使用者名稱和貨幣,其餘欄位為選填。更新表單的 HTML使用 required 屬性以及欄位標籤中的文字來實現:

<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>

雖然這個伺服器的實作並未對欄位的最大長度設置特定限制,但為使用者輸入的文字定義合理的限制始終是個好習慣。

為文字欄位新增 maxlength 屬性:

<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">

現在,如果你按下 註冊 按鈕,而某個欄位未遵守我們定義的驗證規則,你應該會看到類似以下的畫面:

嘗試提交表單時顯示的驗證錯誤截圖

像這樣在將任何資料發送到伺服器之前進行的驗證稱為 客戶端驗證。但請注意,並非所有檢查都能在不發送資料的情況下完成。例如,我們無法在這裡檢查是否已經存在相同使用者名稱的帳戶,除非向伺服器發送請求。在伺服器上執行的額外驗證稱為 伺服器端驗證

通常需要同時實現這兩種驗證。雖然使用客戶端驗證可以通過即時反饋提升使用者體驗,但伺服器端驗證對於確保你處理的使用者資料是可靠且安全的至關重要。


🚀 挑戰

如果使用者已存在,請在 HTML 中顯示一條錯誤訊息。

以下是一個經過一些樣式設計後的最終登入頁面的範例:

添加 CSS 樣式後的登入頁面截圖

課後測驗

課後測驗

複習與自學

開發者在表單構建方面變得非常有創意,特別是在驗證策略方面。透過瀏覽 CodePen 來了解不同的表單流程;你能找到一些有趣且具有啟發性的表單嗎?

作業

為你的銀行應用程式設計樣式

免責聲明
本文檔使用 AI 翻譯服務 Co-op Translator 進行翻譯。儘管我們努力確保準確性,但請注意,自動翻譯可能包含錯誤或不準確之處。原始語言的文件應被視為權威來源。對於關鍵信息,建議使用專業人工翻譯。我們對因使用此翻譯而引起的任何誤解或錯誤解釋不承擔責任。