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/bg/7-bank-project/3-data/README.md

27 KiB

Създаване на банково приложение, част 3: Методи за извличане и използване на данни

Предварителен тест

Предварителен тест

Въведение

В основата на всяко уеб приложение стои данните. Данните могат да приемат различни форми, но основната им цел винаги е да предоставят информация на потребителя. С развитието на уеб приложенията, които стават все по-интерактивни и сложни, начинът, по който потребителят достъпва и взаимодейства с информацията, се превръща в ключова част от уеб разработката.

В този урок ще разгледаме как да извличаме данни от сървър асинхронно и да ги използваме за показване на информация на уеб страница, без да презареждаме HTML.

Предпоставки

Трябва да сте създали Формата за вход и регистрация като част от уеб приложението за този урок. Също така трябва да инсталирате Node.js и да стартирате API сървъра локално, за да получите данни за акаунта.

Можете да тествате дали сървърът работи правилно, като изпълните тази команда в терминала:

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

AJAX и извличане на данни

Традиционните уеб сайтове обновяват съдържанието, когато потребителят избере линк или изпрати данни чрез форма, като презареждат цялата HTML страница. Всеки път, когато трябва да се заредят нови данни, уеб сървърът връща изцяло нова HTML страница, която трябва да бъде обработена от браузъра, прекъсвайки текущото действие на потребителя и ограничавайки взаимодействията по време на презареждането. Този работен процес се нарича многостранично приложение или MPA.

Работен процес на обновяване в многостранично приложение

С развитието на сложни и интерактивни уеб приложения се появи нова техника, наречена AJAX (Асинхронен JavaScript и XML). Тази техника позволява на уеб приложенията да изпращат и получават данни от сървър асинхронно, използвайки JavaScript, без да се налага презареждане на HTML страницата, което води до по-бързи обновявания и по-гладки взаимодействия с потребителя. Когато нови данни се получат от сървъра, текущата HTML страница може да бъде обновена с JavaScript, използвайки DOM API. С времето този подход еволюира в това, което сега наричаме Едностранично приложение или SPA.

Работен процес на обновяване в едностранично приложение

Когато AJAX беше въведен, единственият наличен API за асинхронно извличане на данни беше XMLHttpRequest. Но съвременните браузъри вече поддържат по-удобния и мощен Fetch API, който използва обещания и е по-подходящ за работа с 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 създава GET HTTP заявка, което е точно това, което ни трябва тук.

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, за да изчакаме резултата от сървъра. Както при всяка заявка към сървъра, трябва да се справим и с грешки. Засега ще добавим само съобщение за грешка в логовете и ще се върнем към това по-късно.

След това трябва да съхраним данните някъде, за да можем по-късно да ги използваме за показване на информацията на таблото. Тъй като променливата account все още не съществува, ще създадем глобална променлива за нея в началото на файла:

let account = null;

След като данните за потребителя са запазени в променлива, можем да преминем от страницата за вход към таблото, използвайки функцията navigate(), която вече имаме.

Накрая, трябва да извикаме нашата функция login, когато формата за вход бъде изпратена, като модифицираме HTML:

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

Тествайте дали всичко работи правилно, като регистрирате нов акаунт и опитате да влезете с него.

Преди да преминем към следващата част, можем също да завършим функцията register, като добавим това в края на функцията:

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

Знаете ли, че по подразбиране можете да извиквате API сървъри само от същия домейн и порт, от който разглеждате уеб страницата? Това е механизъм за сигурност, наложен от браузърите. Но чакайте, нашето уеб приложение работи на localhost:3000, докато API сървърът работи на localhost:5000. Защо работи? Чрез използване на техника, наречена Споделяне на ресурси между различни източници (CORS), е възможно да се изпълняват HTTP заявки между различни източници, ако сървърът добави специални заглавия към отговора, позволяващи изключения за конкретни домейни.

Научете повече за API, като вземете този урок

Обновяване на HTML за показване на данни

Сега, когато имаме данните за потребителя, трябва да обновим съществуващия HTML, за да ги покажем. Вече знаем как да извличаме елемент от DOM, използвайки например document.getElementById(). След като имате основен елемент, ето някои API, които можете да използвате, за да го модифицирате или добавите дъщерни елементи към него:

  • С помощта на свойството textContent можете да промените текста на елемент. Имайте предвид, че промяната на тази стойност премахва всички дъщерни елементи на елемента (ако има такива) и ги заменя с предоставения текст. По този начин това е и ефективен метод за премахване на всички дъщерни елементи на даден елемент, като му зададете празен низ ''.

  • С помощта на document.createElement() заедно с метода append() можете да създавате и прикачвате един или повече нови дъщерни елементи.

С помощта на свойството innerHTML на елемент също е възможно да промените неговото HTML съдържание, но това трябва да се избягва, тъй като е уязвимо към атаки чрез скриптове между сайтове (XSS).

Задача

Преди да преминем към екрана на таблото, има още нещо, което трябва да направим на страницата за вход. В момента, ако се опитате да влезете с потребителско име, което не съществува, съобщение се показва в конзолата, но за обикновения потребител нищо не се променя и не знаете какво се случва.

Нека добавим елемент за запазване на място във формата за вход, където можем да показваме съобщение за грешка, ако е необходимо. Добро място би било точно преди бутона за вход:

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

Сега, ако се опитате да влезете с невалиден акаунт, трябва да видите нещо подобно:

Екранна снимка, показваща съобщението за грешка при вход

Сега имаме текст за грешка, който се показва визуално, но ако го опитате със скрийн рийдър, ще забележите, че нищо не се обявява. За да може текст, който се добавя динамично към страница, да бъде обявен от скрийн рийдъри, той трябва да използва нещо, наречено Жива област (Live Region). Тук ще използваме специфичен тип жива област, наречена предупреждение:

<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, който вече е попълнен с данни.

Задача

Нека започнем с подмяна на секцията "Баланс" в HTML, за да добавим елементи за запазване на място:

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

Също така ще добавим нова секция точно под нея, за да покажем описанието на акаунта:

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

Тъй като описанието на акаунта функционира като заглавие за съдържанието под него, то е маркирано семантично като заглавие. Научете повече за това как структурата на заглавията е важна за достъпността и направете критичен преглед на страницата, за да определите какво друго може да бъде заглавие.

След това ще създадем нова функция в app.js, за да попълним елементите за запазване на място:

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 шаблони

В първия урок използвахме HTML шаблони заедно с метода appendChild(), за да реализираме навигацията в нашето приложение. Шаблоните могат да бъдат и по-малки и да се използват за динамично попълване на повтарящи се части от страница.

Ще използваме подобен подход, за да покажем списъка с транзакции в HTML таблицата.

Задача

Добавете нов шаблон в <body> на HTML:

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

Този шаблон представлява един ред в таблицата с трите колони, които искаме да попълним: дата, обект и сума на транзакцията.

След това добавете този id атрибут към <tbody> елемента на таблицата в шаблона на таблото, за да го направите по-лесен за намиране с 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;
}

Тази функция прави точно това, което предполага името ѝ: използвайки шаблона, който създадохме по-рано, тя създава нов ред в таблицата и попълва съдържанието му с данни за транзакцията. Ще я използваме във функцията updateDashboard(), за да попълним таблицата:

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

Тук използваме метода document.createDocumentFragment(), който създава нов DOM фрагмент, върху който можем да работим, преди накрая да го прикачим към нашата HTML таблица.

Има още едно нещо, което трябва да направим, преди този код да заработи, тъй като нашата функция updateElement() в момента поддържа само текстово съдържание. Нека променим нейния код малко:

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

Използваме Ако опитате да използвате акаунта test за вход, вече трябва да виждате списък с транзакции на таблото 🎉.


🚀 Предизвикателство

Работете заедно, за да направите страницата на таблото да изглежда като истинско приложение за банкиране. Ако вече сте стилизирали приложението си, опитайте да използвате медийни заявки, за да създадете адаптивен дизайн, който работи добре както на настолни, така и на мобилни устройства.

Ето пример за стилизирана страница на таблото:

Екранна снимка на примерен резултат от таблото след стилизиране

Тест след лекцията

Тест след лекцията

Задание

Рефакторирайте и коментирайте кода си


Отказ от отговорност:
Този документ е преведен с помощта на AI услуга за превод Co-op Translator. Въпреки че се стремим към точност, моля, имайте предвид, че автоматичните преводи може да съдържат грешки или неточности. Оригиналният документ на неговия изходен език трябва да се счита за авторитетен източник. За критична информация се препоръчва професионален превод от човек. Ние не носим отговорност за каквито и да е недоразумения или погрешни интерпретации, произтичащи от използването на този превод.