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/th/7-bank-project/3-data
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

สร้างแอปธนาคาร ตอนที่ 3: วิธีการดึงและใช้งานข้อมูล

แบบทดสอบก่อนเรียน

แบบทดสอบก่อนเรียน

บทนำ

หัวใจสำคัญของทุกเว็บแอปพลิเคชันคือ ข้อมูล ข้อมูลอาจมีหลายรูปแบบ แต่จุดประสงค์หลักคือการแสดงข้อมูลให้ผู้ใช้เห็น ด้วยความซับซ้อนและการโต้ตอบที่เพิ่มขึ้นของเว็บแอป วิธีที่ผู้ใช้เข้าถึงและโต้ตอบกับข้อมูลจึงกลายเป็นส่วนสำคัญของการพัฒนาเว็บ

ในบทเรียนนี้ เราจะเรียนรู้วิธีดึงข้อมูลจากเซิร์ฟเวอร์แบบอะซิงโครนัส และใช้ข้อมูลนี้เพื่อแสดงผลบนหน้าเว็บโดยไม่ต้องโหลด HTML ใหม่

ความรู้พื้นฐานที่ควรมี

คุณต้องสร้าง ฟอร์มเข้าสู่ระบบและลงทะเบียน ของเว็บแอปในบทเรียนก่อนหน้านี้ และติดตั้ง Node.js รวมถึง รัน API เซิร์ฟเวอร์ บนเครื่องของคุณเพื่อให้ได้ข้อมูลบัญชี

คุณสามารถทดสอบว่าเซิร์ฟเวอร์ทำงานได้ถูกต้องโดยรันคำสั่งนี้ในเทอร์มินัล:

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

AJAX และการดึงข้อมูล

เว็บไซต์แบบดั้งเดิมจะอัปเดตเนื้อหาที่แสดงเมื่อผู้ใช้คลิกลิงก์หรือส่งข้อมูลผ่านฟอร์ม โดยการโหลดหน้า HTML ใหม่ทุกครั้ง เมื่อใดก็ตามที่ต้องโหลดข้อมูลใหม่ เซิร์ฟเวอร์จะส่งหน้า HTML ใหม่ทั้งหมดกลับมา ซึ่งต้องประมวลผลโดยเบราว์เซอร์ กระบวนการนี้จะขัดจังหวะการกระทำของผู้ใช้และจำกัดการโต้ตอบระหว่างการโหลด กระบวนการนี้เรียกว่า Multi-Page Application หรือ MPA

กระบวนการอัปเดตในแอปพลิเคชันหลายหน้า

เมื่อเว็บแอปพลิเคชันเริ่มซับซ้อนและโต้ตอบได้มากขึ้น เทคนิคใหม่ที่เรียกว่า AJAX (Asynchronous JavaScript and XML) ได้เกิดขึ้น เทคนิคนี้ช่วยให้เว็บแอปสามารถส่งและดึงข้อมูลจากเซิร์ฟเวอร์แบบอะซิงโครนัสโดยใช้ JavaScript โดยไม่ต้องโหลดหน้า HTML ใหม่ ทำให้การอัปเดตเร็วขึ้นและการโต้ตอบราบรื่นขึ้น เมื่อได้รับข้อมูลใหม่จากเซิร์ฟเวอร์ หน้า HTML ปัจจุบันสามารถอัปเดตได้ด้วย JavaScript ผ่าน DOM API เทคนิคนี้พัฒนาไปสู่สิ่งที่เรียกว่า Single-Page Application หรือ SPA

กระบวนการอัปเดตในแอปพลิเคชันหน้าเดียว

เมื่อ AJAX ถูกนำมาใช้ครั้งแรก API เดียวที่ใช้ดึงข้อมูลแบบอะซิงโครนัสคือ XMLHttpRequest แต่เบราว์เซอร์สมัยใหม่ได้เพิ่ม Fetch API ซึ่งสะดวกและทรงพลังมากกว่า โดยใช้ promises และเหมาะสมกับการจัดการข้อมูล JSON

แม้ว่าเบราว์เซอร์สมัยใหม่ทั้งหมดจะรองรับ Fetch API แต่หากคุณต้องการให้เว็บแอปทำงานบนเบราว์เซอร์รุ่นเก่าหรือเบราว์เซอร์ที่ล้าสมัย ควรตรวจสอบ ตารางความเข้ากันได้บน caniuse.com ก่อนเสมอ

งานที่ต้องทำ

ใน บทเรียนก่อนหน้า เราได้สร้างฟอร์มลงทะเบียนเพื่อสร้างบัญชีแล้ว ตอนนี้เราจะเพิ่มโค้ดเพื่อเข้าสู่ระบบด้วยบัญชีที่มีอยู่ และดึงข้อมูลบัญชี เปิดไฟล์ app.js และเพิ่มฟังก์ชัน login ใหม่:

async function login() {
  const loginForm = document.getElementById('loginForm')
  const user = loginForm.user.value;
}

ในที่นี้ เราเริ่มต้นด้วยการดึงองค์ประกอบฟอร์มด้วย getElementById() และดึงชื่อผู้ใช้จากอินพุตด้วย loginForm.user.value ทุกฟอร์มคอนโทรลสามารถเข้าถึงได้ผ่านชื่อ (ที่กำหนดใน HTML ด้วยแอตทริบิวต์ name) เป็นพร็อพเพอร์ตีของฟอร์ม

ในลักษณะเดียวกับที่เราทำสำหรับการลงทะเบียน เราจะสร้างฟังก์ชันอีกตัวเพื่อทำการร้องขอข้อมูลจากเซิร์ฟเวอร์ แต่ครั้งนี้เพื่อดึงข้อมูลบัญชี:

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

เราใช้ fetch API เพื่อร้องขอข้อมูลจากเซิร์ฟเวอร์แบบอะซิงโครนัส แต่ครั้งนี้เราไม่ต้องการพารามิเตอร์เพิ่มเติมนอกจาก URL ที่จะเรียก เนื่องจากเราเพียงแค่ดึงข้อมูล โดยค่าเริ่มต้น fetch จะสร้างคำขอ HTTP GET ซึ่งเป็นสิ่งที่เราต้องการในที่นี้

encodeURIComponent() เป็นฟังก์ชันที่ใช้เข้ารหัสอักขระพิเศษสำหรับ URL หากเราไม่เรียกใช้ฟังก์ชันนี้และใช้ค่า user โดยตรงใน URL อาจเกิดปัญหาอะไรได้บ้าง?

ตอนนี้เราจะอัปเดตฟังก์ชัน login เพื่อใช้ 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');
}

เนื่องจาก getAccount เป็นฟังก์ชันแบบอะซิงโครนัส เราจึงต้องใช้คำสั่ง await เพื่อรอผลลัพธ์จากเซิร์ฟเวอร์ เช่นเดียวกับคำขอเซิร์ฟเวอร์ใดๆ เราต้องจัดการกรณีที่เกิดข้อผิดพลาดด้วย สำหรับตอนนี้เราจะเพิ่มข้อความใน log เพื่อแสดงข้อผิดพลาด และจะกลับมาปรับปรุงในภายหลัง

จากนั้นเราต้องเก็บข้อมูลไว้ที่ใดที่หนึ่งเพื่อใช้แสดงข้อมูลแดชบอร์ดในภายหลัง เนื่องจากตัวแปร account ยังไม่มีอยู่ เราจะสร้างตัวแปร global สำหรับมันที่ด้านบนของไฟล์:

let account = null;

หลังจากบันทึกข้อมูลผู้ใช้ลงในตัวแปรแล้ว เราสามารถนำทางจากหน้า login ไปยัง dashboard โดยใช้ฟังก์ชัน navigate() ที่เรามีอยู่แล้ว

สุดท้าย เราต้องเรียกใช้ฟังก์ชัน login เมื่อฟอร์มเข้าสู่ระบบถูกส่ง โดยแก้ไข HTML:

<form id="loginForm" action="javascript:login()">

ทดสอบว่าทุกอย่างทำงานได้ถูกต้องโดยการลงทะเบียนบัญชีใหม่และลองเข้าสู่ระบบด้วยบัญชีเดียวกัน

ก่อนที่จะไปยังส่วนถัดไป เราสามารถเติมเต็มฟังก์ชัน register โดยเพิ่มโค้ดนี้ที่ด้านล่างของฟังก์ชัน:

account = result;
navigate('/dashboard');

คุณรู้หรือไม่ว่าโดยค่าเริ่มต้น คุณสามารถเรียกใช้ API เซิร์ฟเวอร์ได้เฉพาะจาก โดเมนและพอร์ตเดียวกัน กับหน้าเว็บที่คุณกำลังดูอยู่? นี่เป็นกลไกความปลอดภัยที่เบราว์เซอร์บังคับใช้ แต่เดี๋ยวก่อน เว็บแอปของเราทำงานบน localhost:3000 ในขณะที่ API เซิร์ฟเวอร์ทำงานบน localhost:5000 ทำไมถึงใช้งานได้? ด้วยการใช้เทคนิคที่เรียกว่า Cross-Origin Resource Sharing (CORS) เราสามารถทำคำขอ HTTP ข้ามต้นทางได้หากเซิร์ฟเวอร์เพิ่มส่วนหัวพิเศษในคำตอบเพื่ออนุญาตข้อยกเว้นสำหรับโดเมนเฉพาะ

เรียนรู้เพิ่มเติมเกี่ยวกับ API โดยเข้าร่วม บทเรียนนี้

อัปเดต HTML เพื่อแสดงข้อมูล

ตอนนี้เรามีข้อมูลผู้ใช้แล้ว เราต้องอัปเดต HTML ที่มีอยู่เพื่อแสดงข้อมูลนั้น เรารู้อยู่แล้วว่าจะดึงองค์ประกอบจาก DOM ได้อย่างไร เช่น document.getElementById() หลังจากที่คุณมีองค์ประกอบพื้นฐานแล้ว นี่คือ API บางส่วนที่คุณสามารถใช้เพื่อแก้ไขหรือเพิ่มองค์ประกอบลูก:

  • ใช้พร็อพเพอร์ตี textContent เพื่อเปลี่ยนข้อความขององค์ประกอบ โปรดทราบว่าการเปลี่ยนค่านี้จะลบลูกทั้งหมดขององค์ประกอบ (ถ้ามี) และแทนที่ด้วยข้อความที่ให้มา ดังนั้นจึงเป็นวิธีที่มีประสิทธิภาพในการลบลูกทั้งหมดขององค์ประกอบที่กำหนดโดยกำหนดค่าเป็นสตริงว่าง ''

  • ใช้ document.createElement() ร่วมกับเมธอด append() เพื่อสร้างและแนบองค์ประกอบลูกใหม่หนึ่งหรือหลายองค์ประกอบ

การใช้พร็อพเพอร์ตี innerHTML ขององค์ประกอบสามารถเปลี่ยนเนื้อหา HTML ได้เช่นกัน แต่ควรหลีกเลี่ยงเนื่องจากมีความเสี่ยงต่อการโจมตี cross-site scripting (XSS)

งานที่ต้องทำ

ก่อนที่จะไปยังหน้าจอแดชบอร์ด มีสิ่งหนึ่งที่เราควรทำในหน้า login ปัจจุบัน หากคุณพยายามเข้าสู่ระบบด้วยชื่อผู้ใช้ที่ไม่มีอยู่ ข้อความจะแสดงในคอนโซล แต่สำหรับผู้ใช้ทั่วไปไม่มีอะไรเปลี่ยนแปลงและคุณไม่รู้ว่าเกิดอะไรขึ้น

เรามาเพิ่มองค์ประกอบ placeholder ในฟอร์มเข้าสู่ระบบเพื่อแสดงข้อความข้อผิดพลาดหากจำเป็น ตำแหน่งที่ดีคือก่อนปุ่ม <button>:

...
<div id="loginError"></div>
<button>Login</button>
...

องค์ประกอบ <div> นี้ว่างเปล่า หมายความว่าจะไม่มีอะไรแสดงบนหน้าจอจนกว่าเราจะเพิ่มเนื้อหาให้กับมัน เราให้ id เพื่อให้สามารถดึงได้ง่ายด้วย JavaScript

กลับไปที่ไฟล์ app.js และสร้างฟังก์ชันช่วยเหลือใหม่ updateElement:

function updateElement(id, text) {
  const element = document.getElementById(id);
  element.textContent = text;
}

ฟังก์ชันนี้ค่อนข้างตรงไปตรงมา: เมื่อได้รับ id ขององค์ประกอบและ ข้อความ มันจะอัปเดตข้อความขององค์ประกอบ DOM ที่มี id ตรงกัน เรามาใช้เมธอดนี้แทนข้อความข้อผิดพลาดในฟังก์ชัน login:

if (data.error) {
  return updateElement('loginError', data.error);
}

ตอนนี้หากคุณพยายามเข้าสู่ระบบด้วยบัญชีที่ไม่ถูกต้อง คุณควรเห็นบางอย่างเช่นนี้:

ภาพหน้าจอแสดงข้อความข้อผิดพลาดระหว่างการเข้าสู่ระบบ

ตอนนี้เรามีข้อความข้อผิดพลาดที่แสดงผลแบบมองเห็นได้ แต่ถ้าคุณลองใช้กับ screen reader คุณจะสังเกตเห็นว่าไม่มีอะไรถูกประกาศออกมา เพื่อให้ข้อความที่เพิ่มเข้ามาในหน้าแบบไดนามิกถูกประกาศโดย screen reader เราจำเป็นต้องใช้สิ่งที่เรียกว่า Live Region ในที่นี้เราจะใช้ live region ประเภทหนึ่งที่เรียกว่า alert:

<div id="loginError" role="alert"></div>

นำพฤติกรรมเดียวกันนี้ไปใช้กับข้อผิดพลาดในฟังก์ชัน register (อย่าลืมอัปเดต HTML)

แสดงข้อมูลบนแดชบอร์ด

โดยใช้เทคนิคเดียวกับที่เราเพิ่งเรียนรู้ เราจะจัดการแสดงข้อมูลบัญชีบนหน้าแดชบอร์ด

นี่คือลักษณะของออบเจ็กต์บัญชีที่ได้รับจากเซิร์ฟเวอร์:

{
  "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 }
  ],
}

หมายเหตุ: เพื่อให้ง่ายขึ้น คุณสามารถใช้บัญชี test ที่มีข้อมูลอยู่แล้ว

งานที่ต้องทำ

เริ่มต้นด้วยการแทนที่ส่วน "Balance" ใน HTML เพื่อเพิ่มองค์ประกอบ placeholder:

<section>
  Balance: <span id="balance"></span><span id="currency"></span>
</section>

เราจะเพิ่มส่วนใหม่ด้านล่างเพื่อแสดงคำอธิบายบัญชี:

<h2 id="description"></h2>

เนื่องจากคำอธิบายบัญชีทำหน้าที่เป็นหัวเรื่องสำหรับเนื้อหาด้านล่าง จึงถูกทำเครื่องหมายเชิงความหมายว่าเป็นหัวเรื่อง เรียนรู้เพิ่มเติมเกี่ยวกับ โครงสร้างหัวเรื่อง และพิจารณาโครงสร้างของหน้าเพื่อดูว่าส่วนใดควรเป็นหัวเรื่อง

ต่อไป เราจะสร้างฟังก์ชันใหม่ใน app.js เพื่อเติมข้อมูลใน placeholder:

function updateDashboard() {
  if (!account) {
    return navigate('/login');
  }

  updateElement('description', account.description);
  updateElement('balance', account.balance.toFixed(2));
  updateElement('currency', account.currency);
}

ก่อนอื่น เราตรวจสอบว่าเรามีข้อมูลบัญชีที่ต้องการก่อนดำเนินการต่อ จากนั้นเราใช้ฟังก์ชัน updateElement() ที่เราสร้างขึ้นก่อนหน้านี้เพื่ออัปเดต HTML

เพื่อให้การแสดงยอดเงินดูสวยงามขึ้น เราใช้เมธอด toFixed(2) เพื่อบังคับให้แสดงค่าด้วย 2 หลักหลังจุดทศนิยม

ตอนนี้เราต้องเรียกใช้ฟังก์ชัน updateDashboard() ทุกครั้งที่โหลดหน้าแดชบอร์ด หากคุณทำ งานในบทเรียนที่ 1 เสร็จแล้ว นี่ควรจะง่าย หากไม่ คุณสามารถใช้การดำเนินการต่อไปนี้

เพิ่มโค้ดนี้ที่ส่วนท้ายของฟังก์ชัน updateRoute():

if (typeof route.init === 'function') {
  route.init();
}

และอัปเดตการกำหนดเส้นทางด้วย:

const routes = {
  '/login': { templateId: 'login' },
  '/dashboard': { templateId: 'dashboard', init: updateDashboard }
};

ด้วยการเปลี่ยนแปลงนี้ ทุกครั้งที่แสดงหน้าแดชบอร์ด ฟังก์ชัน updateDashboard() จะถูกเรียกใช้ หลังจากเข้าสู่ระบบ คุณควรจะสามารถเห็นยอดเงิน สกุลเงิน และคำอธิบายบัญชีได้

สร้างแถวในตารางแบบไดนามิกด้วย HTML Templates

ใน บทเรียนแรก เราใช้ HTML templates ร่วมกับเมธอด appendChild() เพื่อใช้งานการนำทางในแอปของเรา Templates ยังสามารถมีขนาดเล็กและใช้เติมส่วนที่ซ้ำๆ ของหน้าแบบไดนามิกได้

เราจะใช้วิธีเดียวกันนี้เพื่อแสดงรายการธุรกรรมในตาราง HTML

งานที่ต้องทำ

เพิ่ม template ใหม่ใน <body> ของ HTML:

<template id="transaction">
  <tr>
    <td></td>
    <td></td>
    <td></td>
  </tr>
</template>

Template นี้แสดงถึงแถวในตารางเดียว โดยมี 3 คอลัมน์ที่เราต้องเติม: วันที่ วัตถุ และ จำนวนเงิน ของธุรกรรม

จากนั้น เพิ่มพร็อพเพอร์ตี id ให้กับ <tbody> ของตารางใน template แดชบอร์ดเพื่อให้ง่ายต่อการค้นหาโดยใช้ JavaScript:

<tbody id="transactions"></tbody>

HTML ของเราพร้อมแล้ว มาสลับไปที่โค้ด JavaScript และสร้างฟังก์ชันใหม่ 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;
}

ฟังก์ชันนี้ทำตามชื่อของมัน: โดยใช้ template ที่เราสร้างขึ้นก่อนหน้านี้ มันสร้างแถวใหม่ในตารางและเติมเนื้อหาด้วยข้อมูลธุรกรรม เราจะใช้สิ่งนี้ในฟังก์ชัน updateDashboard() เพื่อเติมข้อมูลในตาราง:

const transactionsRows = document.createDocumentFragment();
for (const transaction of account.transactions) {
  const transactionRow = createTransactionRow(transaction);
  transactionsRows.appendChild(transactionRow);
}
updateElement('transactions', transactionsRows);

ในที่นี้ เราใช้เมธอด document.createDocumentFragment() ซึ่งสร้าง fragment DOM ใหม่ที่เราสามารถทำงานได้ ก่อนที่จะเพิ่มลงในตาราง HTML ของเราในที่สุด

ยังมีสิ่งหนึ่งที่เราต้องทำก่อนที่โค้ดนี้จะทำงานได้ เนื่องจากฟังก์ชัน updateElement() ของเราปัจจุบันรองรับเฉพาะข้อความเท่านั้น เรามาเปลี่ยนโค้ดของมันเล็กน้อย:

function updateElement(id, textOrNode) {
  const element = document.getElementById(id);
  element.textContent = ''; // Removes all children
  element.append(textOrNode);
}

เราใช้เมธอด append() เนื่องจากมันอนุญาตให้แนบทั้งข้อความหรือ DOM Nodes กับองค์ประกอบแม่ ซึ่งเหมาะสำหรับทุกกรณีการใช้งานของเรา หากคุณลองใช้บัญชี test เพื่อเข้าสู่ระบบ ตอนนี้คุณควรจะเห็นรายการธุรกรรมบนแดชบอร์ดแล้ว 🎉


🚀 ความท้าทาย

ทำงานร่วมกันเพื่อทำให้หน้าแดชบอร์ดดูเหมือนแอปธนาคารจริง หากคุณได้ออกแบบแอปของคุณแล้ว ลองใช้ media queries เพื่อสร้าง การออกแบบที่ตอบสนอง ซึ่งทำงานได้ดีทั้งบนอุปกรณ์เดสก์ท็อปและมือถือ

นี่คือตัวอย่างของหน้าแดชบอร์ดที่ได้รับการออกแบบ:

ภาพหน้าจอของตัวอย่างผลลัพธ์ของแดชบอร์ดหลังจากการออกแบบ

แบบทดสอบหลังการบรรยาย

แบบทดสอบหลังการบรรยาย

งานที่ได้รับมอบหมาย

ปรับปรุงและใส่คอมเมนต์ในโค้ดของคุณ


ข้อจำกัดความรับผิดชอบ:
เอกสารนี้ได้รับการแปลโดยใช้บริการแปลภาษา AI Co-op Translator แม้ว่าเราจะพยายามให้การแปลมีความถูกต้องมากที่สุด แต่โปรดทราบว่าการแปลโดยอัตโนมัติอาจมีข้อผิดพลาดหรือความไม่ถูกต้อง เอกสารต้นฉบับในภาษาดั้งเดิมควรถือเป็นแหล่งข้อมูลที่เชื่อถือได้ สำหรับข้อมูลที่สำคัญ ขอแนะนำให้ใช้บริการแปลภาษามืออาชีพ เราจะไม่รับผิดชอบต่อความเข้าใจผิดหรือการตีความผิดที่เกิดจากการใช้การแปลนี้