|
3 weeks ago | |
---|---|---|
.. | ||
README.md | 3 weeks ago | |
assignment.md | 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-страницу, которую браузер должен обработать, прерывая текущие действия пользователя и ограничивая взаимодействие во время перезагрузки. Этот рабочий процесс также называют многостраничным приложением или MPA.
С развитием более сложных и интерактивных веб-приложений появилась новая техника, называемая AJAX (асинхронный JavaScript и XML). Эта техника позволяет веб-приложениям отправлять и получать данные с сервера асинхронно с помощью JavaScript, без необходимости перезагрузки HTML-страницы, что обеспечивает более быстрые обновления и плавное взаимодействие с пользователем. Когда сервер возвращает новые данные, текущая HTML-страница может быть обновлена с помощью JavaScript через API DOM. Со временем этот подход эволюционировал в то, что сейчас называется одностраничным приложением или 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' };
}
}
Мы используем API fetch
для асинхронного запроса данных с сервера, но на этот раз нам не нужны дополнительные параметры, кроме 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
, чтобы дождаться результата от сервера. Как и в случае любого серверного запроса, нам также нужно обработать ошибки. Пока мы просто добавим сообщение в журнал для отображения ошибки и вернемся к этому позже.
Затем нам нужно сохранить данные где-то, чтобы позже использовать их для отображения информации на панели управления. Поскольку переменная account
еще не существует, мы создадим глобальную переменную для нее в начале нашего файла:
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-содержимое, но этот метод следует избегать, так как он уязвим для атак межсайтового скриптинга (XSS).
Задача
Перед тем как перейти к экрану панели управления, есть еще одна вещь, которую мы должны сделать на странице login. В настоящее время, если вы пытаетесь войти с именем пользователя, которое не существует, сообщение отображается в консоли, но для обычного пользователя ничего не меняется, и он не понимает, что происходит.
Давайте добавим элемент-заполнитель в форму входа, где мы можем отображать сообщение об ошибке, если это необходимо. Хорошее место — прямо перед кнопкой входа <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);
}
Теперь, если вы попытаетесь войти с недействительной учетной записью, вы должны увидеть что-то вроде этого:
Теперь у нас есть текст ошибки, который отображается визуально, но если вы попробуете использовать экранный диктор, вы заметите, что ничего не объявляется. Чтобы текст, который динамически добавляется на страницу, был объявлен экранными дикторами, он должен использовать так называемую живую область (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
, которая уже заполнена данными.
Задача
Начнем с замены раздела "Balance" в 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)
, чтобы принудительно отображать значение с двумя цифрами после десятичной точки.
Теперь нам нужно вызывать нашу функцию 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);
}
Мы используем метод append()
, так как он позволяет прикреплять как текст, так и узлы DOM к родительскому элементу, что идеально подходит для всех наших случаев использования.
Если вы попробуете войти, используя учетную запись test
, теперь на панели управления должен появиться список транзакций 🎉.
🚀 Задача
Работайте вместе, чтобы сделать страницу панели управления похожей на настоящее банковское приложение. Если вы уже стилизовали свое приложение, попробуйте использовать медиазапросы, чтобы создать адаптивный дизайн, который будет хорошо работать как на настольных компьютерах, так и на мобильных устройствах.
Вот пример стилизованной страницы панели управления:
Викторина после лекции
Задание
Рефакторинг и комментирование вашего кода
Отказ от ответственности:
Этот документ был переведен с использованием сервиса автоматического перевода Co-op Translator. Несмотря на наши усилия обеспечить точность, автоматические переводы могут содержать ошибки или неточности. Оригинальный документ на его родном языке следует считать авторитетным источником. Для получения критически важной информации рекомендуется профессиональный перевод человеком. Мы не несем ответственности за любые недоразумения или неправильные интерпретации, возникшие в результате использования данного перевода.