# Създаване на Банкова Приложение Част 2: Създаване на Формуляр за Вход и Регистрация ## Предварителен Тест [Предварителен тест](https://ff-quizzes.netlify.app/web/quiz/43) ### Въведение В почти всички съвременни уеб приложения можете да създадете акаунт, за да имате свое собствено лично пространство. Тъй като множество потребители могат да имат достъп до уеб приложението едновременно, е необходим механизъм за съхранение на личните данни на всеки потребител отделно и за избор на информацията, която да се показва. Няма да разглеждаме как да управляваме [потребителската идентичност сигурно](https://en.wikipedia.org/wiki/Authentication), тъй като това е обширна тема сама по себе си, но ще се уверим, че всеки потребител може да създаде един (или повече) банкови акаунти в нашето приложение. В тази част ще използваме HTML формуляри, за да добавим вход и регистрация към нашето уеб приложение. Ще видим как да изпращаме данни към сървърна API програмно и в крайна сметка как да дефинираме основни правила за валидиране на потребителски вход. ### Предварителни изисквания Трябва да сте завършили [HTML шаблони и маршрутизация](../1-template-route/README.md) на уеб приложението за този урок. Също така трябва да инсталирате [Node.js](https://nodejs.org) и [да стартирате сървърната API](../api/README.md) локално, за да можете да изпращате данни за създаване на акаунти. **Обърнете внимание** Ще имате два терминала, работещи едновременно, както е описано по-долу: 1. За основното банково приложение, което създадохме в урока [HTML шаблони и маршрутизация](../1-template-route/README.md) 2. За [сървърната API на Банковото Приложение](../api/README.md), която току-що настроихме. Трябва да стартирате и двата сървъра, за да продължите с останалата част от урока. Те слушат на различни портове (порт `3000` и порт `5000`), така че всичко трябва да работи без проблеми. Можете да тествате дали сървърът работи правилно, като изпълните тази команда в терминала: ```sh curl http://localhost:5000/api # -> should return "Bank API v1.0.0" as a result ``` --- ## Формуляр и контроли Елементът `
` обхваща секция от HTML документ, където потребителят може да въвежда и изпраща данни чрез интерактивни контроли. Съществуват всякакви потребителски интерфейсни (UI) контроли, които могат да се използват в рамките на формуляр, като най-често срещаните са елементите `` и `
``` С помощта на атрибута `value` можем да дефинираме стойност по подразбиране за дадено поле. Забележете също, че полето за `balance` има тип `number`. Изглежда ли различно от другите полета? Опитайте да взаимодействате с него. ✅ Можете ли да навигирате и взаимодействате с формулярите само с клавиатура? Как бихте направили това? ## Изпращане на данни към сървъра Сега, когато имаме функционален интерфейс, следващата стъпка е да изпратим данните към сървъра. Нека направим бърз тест с текущия код: какво се случва, ако кликнете върху бутона *Вход* или *Регистрация*? Забелязахте ли промяната в секцията на URL адреса на браузъра? ![Снимка на екрана, показваща промяна в URL адреса на браузъра след клик върху бутона Регистрация](../../../../translated_images/click-register.e89a30bf0d4bc9ca867dc537c4cea679a7c26368bd790969082f524fed2355bc.bg.png) По подразбиране действието на `
` е да изпрати формуляра към текущия URL адрес на сървъра, използвайки [GET метода](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.3), като добавя данните от формуляра директно към URL адреса. Този метод обаче има някои недостатъци: - Изпратените данни са много ограничени по размер (около 2000 символа) - Данните са директно видими в URL адреса (не е подходящо за пароли) - Не работи с качване на файлове Затова можете да го промените, за да използва [POST метода](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.5), който изпраща данните от формуляра към сървъра в тялото на HTTP заявката, без предишните ограничения. > Въпреки че POST е най-често използваният метод за изпращане на данни, [в някои специфични сценарии](https://www.w3.org/2001/tag/doc/whenToUseGet.html) е за предпочитане да се използва GET методът, например при имплементиране на поле за търсене. ### Задача Добавете свойства `action` и `method` към формуляра за регистрация: ```html ``` Сега опитайте да регистрирате нов акаунт с вашето име. След като кликнете върху бутона *Регистрация*, трябва да видите нещо подобно: ![Прозорец на браузъра на адрес localhost:5000/api/accounts, показващ JSON низ с потребителски данни](../../../../translated_images/form-post.61de4ca1b964d91a9e338416e19f218504dd0af5f762fbebabfe7ae80edf885f.bg.png) Ако всичко върви добре, сървърът трябва да отговори на вашата заявка с [JSON](https://www.json.org/json-en.html) отговор, съдържащ данните за създадения акаунт. ✅ Опитайте да се регистрирате отново със същото име. Какво се случва? ## Изпращане на данни без презареждане на страницата Както вероятно забелязахте, има малък проблем с подхода, който току-що използвахме: при изпращане на формуляра излизаме от нашето приложение и браузърът се пренасочва към URL адреса на сървъра. Опитваме се да избегнем всички презареждания на страницата с нашето уеб приложение, тъй като създаваме [Едностранично приложение (SPA)](https://en.wikipedia.org/wiki/Single-page_application). За да изпратим данните от формуляра към сървъра без да принуждаваме презареждане на страницата, трябва да използваме JavaScript код. Вместо да поставяме URL адрес в свойството `action` на елемента ``, можете да използвате произволен JavaScript код, предшестван от низа `javascript:`, за да изпълните персонализирано действие. Използването на това означава също, че ще трябва да имплементирате някои задачи, които преди това се извършваха автоматично от браузъра: - Извличане на данните от формуляра - Конвертиране и кодиране на данните от формуляра в подходящ формат - Създаване на HTTP заявка и изпращането й към сървъра ### Задача Заменете `action` на формуляра за регистрация със: ```html ``` Отворете `app.js` и добавете нова функция с име `register`: ```js function register() { const registerForm = document.getElementById('registerForm'); const formData = new FormData(registerForm); const data = Object.fromEntries(formData); const jsonData = JSON.stringify(data); } ``` Тук извличаме елемента на формуляра, използвайки `getElementById()` и използваме помощника [`FormData`](https://developer.mozilla.org/docs/Web/API/FormData), за да извлечем стойностите от контролите на формуляра като набор от ключ/стойност двойки. След това конвертираме данните в обикновен обект, използвайки [`Object.fromEntries()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/fromEntries), и накрая сериализираме данните в [JSON](https://www.json.org/json-en.html), формат, който често се използва за обмен на данни в уеб пространството. Данните вече са готови за изпращане към сървъра. Създайте нова функция с име `createAccount`: ```js 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` тук. Това означава, че функцията съдържа код, който ще се изпълнява [**асинхронно**](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/async_function). Когато се използва заедно с ключовата дума `await`, тя позволява изчакване за изпълнение на асинхронен код - като изчакване за отговор от сървъра тук - преди да продължи. Ето кратко видео за използването на `async/await`: [![Async и Await за управление на обещания](https://img.youtube.com/vi/YwmlRkrxvkk/0.jpg)](https://youtube.com/watch?v=YwmlRkrxvkk "Async и Await за управление на обещания") > 🎥 Кликнете върху изображението по-горе за видео за async/await. Използваме API-то `fetch()`, за да изпратим JSON данни към сървъра. Този метод приема 2 параметъра: - URL адреса на сървъра, затова тук поставяме `//localhost:5000/api/accounts`. - Настройките на заявката. Тук задаваме метода на `POST` и предоставяме `body` за заявката. Тъй като изпращаме JSON данни към сървъра, трябва също да зададем заглавката `Content-Type` на `application/json`, за да може сървърът да интерпретира съдържанието. Тъй като сървърът ще отговори на заявката с JSON, можем да използваме `await response.json()`, за да анализираме JSON съдържанието и да върнем получения обект. Забележете, че този метод е асинхронен, затова използваме ключовата дума `await` тук, преди да върнем, за да се уверим, че всички грешки по време на анализа също са уловени. Сега добавете малко код към функцията `register`, за да извикате `createAccount()`: ```js const result = await createAccount(jsonData); ``` Тъй като използваме ключовата дума `await` тук, трябва да добавим ключовата дума `async` преди функцията register: ```js async function register() { ``` Накрая, нека добавим някои логове, за да проверим резултата. Финалната функция трябва да изглежда така: ```js 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://developer.mozilla.org/docs/Learn/Common_questions/What_are_browser_developer_tools) и опитате да регистрирате нов акаунт, не трябва да виждате никаква промяна на уеб страницата, но съобщение ще се появи в конзолата, потвърждавайки, че всичко работи. ![Снимка на екрана, показваща съобщение в конзолата на браузъра](../../../../translated_images/browser-console.efaf0b51aaaf67782a29e1a0bb32cc063f189b18e894eb5926e02f1abe864ec2.bg.png) ✅ Смятате ли, че данните се изпращат към сървъра сигурно? Какво би станало, ако някой успее да прихване заявката? Можете да прочетете за [HTTPS](https://en.wikipedia.org/wiki/HTTPS), за да научите повече за сигурната комуникация на данни. ## Валидиране на данни Ако опитате да регистрирате нов акаунт, без да зададете потребителско име, можете да видите, че сървърът връща грешка със статус код [400 (Bad Request)](https://developer.mozilla.org/docs/Web/HTTP/Status/400#:~:text=The%20HyperText%20Transfer%20Protocol%20(HTTP,%2C%20or%20deceptive%20request%20routing).). Преди да изпращате данни към сървъра, е добра практика [да валидирате данните от формуляра](https://developer.mozilla.org/docs/Learn/Forms/Form_validation) предварително, когато е възможно, за да се уверите, че изпращате валидна заявка. HTML5 контролите на формуляра предоставят вградена валидиране, използвайки различни атрибути: - `required`: полето трябва да бъде попълнено, иначе формулярът не може да бъде изпратен. - `minlength` и `maxlength`: дефинират минималния и максималния брой символи в текстови полета. - `min` и `max`: дефинират минималната и максималната стойност на числово поле. - `type`: дефинира вида на очакваните данни, като `number`, `email`, `file` или [други вградени типове](https://developer.mozilla.org/docs/Web/HTML/Element/input). Този атрибут може също да промени визуалното представяне на контролата на формуляра. - `pattern`: позволява дефиниране на [регулярен израз](https > Съвет: можете да персонализирате външния вид на вашите контролни елементи във формуляра в зависимост от това дали са валидни или не, като използвате CSS псевдокласовете `:valid` и `:invalid`. ### Задача Има два задължителни полета за създаване на валиден нов акаунт: потребителско име и валута. Останалите полета са по избор. Актуализирайте HTML формуляра, като използвате както атрибута `required`, така и текст в етикета на полето, за да: ```html ... ``` Въпреки че тази конкретна сървърна имплементация не налага специфични ограничения за максималната дължина на полетата, винаги е добра практика да се задават разумни ограничения за всяко текстово поле, което потребителят попълва. Добавете атрибут `maxlength` към текстовите полета: ```html ... ... ``` Сега, ако натиснете бутона *Регистрация* и някое поле не отговаря на зададените правила за валидация, ще видите нещо подобно: ![Екранна снимка, показваща грешка при валидация при опит за изпращане на формуляра](../../../../translated_images/validation-error.8bd23e98d416c22f80076d04829a4bb718e0e550fd622862ef59008ccf0d5dce.bg.png) Валидацията, която се извършва *преди* изпращането на данни към сървъра, се нарича **клиентска валидация**. Но имайте предвид, че не винаги е възможно да се извършат всички проверки без изпращане на данни. Например, тук не можем да проверим дали вече съществува акаунт със същото потребителско име, без да изпратим заявка към сървъра. Допълнителната валидация, която се извършва на сървъра, се нарича **сървърна валидация**. Обикновено и двете трябва да бъдат имплементирани. Клиентската валидация подобрява потребителското изживяване, като предоставя незабавна обратна връзка, но сървърната валидация е от съществено значение, за да се гарантира, че данните, които обработвате, са коректни и безопасни. --- ## 🚀 Предизвикателство Покажете съобщение за грешка в HTML, ако потребителят вече съществува. Ето пример как може да изглежда финалната страница за вход след малко стилизиране: ![Екранна снимка на страницата за вход след добавяне на CSS стилове](../../../../translated_images/result.96ef01f607bf856aa9789078633e94a4f7664d912f235efce2657299becca483.bg.png) ## Тест след лекцията [Тест след лекцията](https://ff-quizzes.netlify.app/web/quiz/44) ## Преглед и самостоятелно обучение Разработчиците са станали много креативни в усилията си за създаване на формуляри, особено по отношение на стратегиите за валидация. Научете повече за различни подходи към формулярите, като разгледате [CodePen](https://codepen.com); можете ли да намерите интересни и вдъхновяващи примери? ## Задание [Стилизирайте вашето банково приложение](assignment.md) --- **Отказ от отговорност**: Този документ е преведен с помощта на AI услуга за превод [Co-op Translator](https://github.com/Azure/co-op-translator). Въпреки че се стремим към точност, моля, имайте предвид, че автоматизираните преводи може да съдържат грешки или неточности. Оригиналният документ на неговия роден език трябва да се счита за авторитетен източник. За критична информация се препоръчва професионален човешки превод. Ние не носим отговорност за недоразумения или погрешни интерпретации, произтичащи от използването на този превод.