# Изградња апликације за банкарство, део 1: HTML шаблони и руте у веб апликацији
## Квиз пре предавања
[Квиз пре предавања](https://ff-quizzes.netlify.app/web/quiz/41)
### Увод
Од појаве JavaScript-а у претраживачима, веб-сајтови постају интерактивнији и сложенији него икада. Веб технологије се сада често користе за креирање потпуно функционалних апликација које раде директно у претраживачу, а које називамо [веб апликације](https://en.wikipedia.org/wiki/Web_application). Како су веб апликације веома интерактивне, корисници не желе да чекају да се цела страница поново учита сваки пут када се изврши нека акција. Зато се JavaScript користи за директно ажурирање HTML-а преко DOM-а, како би се обезбедило глатко корисничко искуство.
У овом предавању поставићемо основе за креирање веб апликације за банкарство, користећи HTML шаблоне за креирање више екрана који могу бити приказани и ажурирани без потребе за поновним учитавањем целе HTML странице.
### Предуслови
Потребан вам је локални веб сервер да бисте тестирали веб апликацију коју ћемо направити у овом предавању. Ако га немате, можете инсталирати [Node.js](https://nodejs.org) и користити команду `npx lite-server` из вашег пројектног директоријума. Ово ће креирати локални веб сервер и отворити вашу апликацију у претраживачу.
### Припрема
На вашем рачунару, направите фасциклу под називом `bank` са фајлом `index.html` унутар ње. Почећемо од овог HTML [основног кода](https://en.wikipedia.org/wiki/Boilerplate_code):
```html
Bank App
```
---
## HTML шаблони
Ако желите да креирате више екрана за веб страницу, једно решење би било да направите један HTML фајл за сваки екран који желите да прикажете. Међутим, ово решење има неке недостатке:
- Морате поново учитати цео HTML приликом преласка на други екран, што може бити споро.
- Тешко је делити податке између различитих екрана.
Другачији приступ је да имате само један HTML фајл и дефинишете више [HTML шаблона](https://developer.mozilla.org/docs/Web/HTML/Element/template) користећи `` елемент. Шаблон је поновљив HTML блок који претраживач не приказује, а који треба да се инстанцира у време извршавања помоћу JavaScript-а.
### Задатак
Направићемо апликацију за банкарство са два екрана: страницу за пријаву и контролну таблу. Прво, додајмо у HTML тело елемент који ће служити као место за приказивање различитих екрана наше апликације:
```html
Loading...
```
Доделили смо му `id` како би га касније лакше пронашли помоћу JavaScript-а.
> Савет: пошто ће садржај овог елемента бити замењен, можемо ставити поруку о учитавању или индикатор који ће се приказивати док се апликација учитава.
Затим додајмо HTML шаблон за страницу за пријаву. За сада ћемо у њега ставити само наслов и секцију која садржи линк који ћемо користити за навигацију.
```html
Bank App
```
Затим додајмо други HTML шаблон за страницу контролне табле. Ова страница ће садржати различите секције:
- Заглавље са насловом и линком за одјаву
- Тренутни баланс банковног рачуна
- Листу трансакција, приказану у табели
```html
```
> Савет: када креирате HTML шаблоне, ако желите да видите како ће изгледати, можете коментарисати `` и `` линије тако што ћете их обухватити са ``.
✅ Зашто мислите да користимо `id` атрибуте на шаблонима? Да ли бисмо могли да користимо нешто друго, као што су класе?
## Приказивање шаблона помоћу JavaScript-а
Ако пробате ваш тренутни HTML фајл у претраживачу, видећете да се заглавио на приказивању `Loading...`. То је зато што треба да додамо JavaScript код за инстанцирање и приказивање HTML шаблона.
Инстанцирање шаблона обично се ради у 3 корака:
1. Проналажење елемента шаблона у DOM-у, на пример помоћу [`document.getElementById`](https://developer.mozilla.org/docs/Web/API/Document/getElementById).
2. Клонирање елемента шаблона, користећи [`cloneNode`](https://developer.mozilla.org/docs/Web/API/Node/cloneNode).
3. Додавање у DOM испод видљивог елемента, на пример помоћу [`appendChild`](https://developer.mozilla.org/docs/Web/API/Node/appendChild).
✅ Зашто морамо да клонирамо шаблон пре него што га додамо у DOM? Шта мислите да би се десило ако прескочимо овај корак?
### Задатак
Направите нови фајл под називом `app.js` у вашем пројектном директоријуму и увезите тај фајл у `` секцију вашег HTML-а:
```html
```
Сада у `app.js`, направићемо нову функцију `updateRoute`:
```js
function updateRoute(templateId) {
const template = document.getElementById(templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
Овде радимо тачно 3 корака описана изнад. Инстанцирамо шаблон са `id`-ом `templateId` и стављамо његов клонирани садржај унутар нашег места за апликацију. Напомињемо да морамо користити `cloneNode(true)` да бисмо копирали цело стабло шаблона.
Сада позовите ову функцију са једним од шаблона и погледајте резултат.
```js
updateRoute('login');
```
✅ Која је сврха овог кода `app.innerHTML = '';`? Шта се дешава без њега?
## Креирање рута
Када говоримо о веб апликацији, *рутирање* подразумева мапирање **URL-ова** на одређене екране који треба да буду приказани. На веб-сајту са више HTML фајлова, ово се ради аутоматски јер се путање фајлова одражавају на URL. На пример, са овим фајловима у вашем пројектном директоријуму:
```
mywebsite/index.html
mywebsite/login.html
mywebsite/admin/index.html
```
Ако креирате веб сервер са `mywebsite` као кореном, мапирање URL-а ће бити:
```
https://site.com --> mywebsite/index.html
https://site.com/login.html --> mywebsite/login.html
https://site.com/admin/ --> mywebsite/admin/index.html
```
Међутим, за нашу веб апликацију користимо један HTML фајл који садржи све екране, тако да нам овај подразумевани начин неће помоћи. Морамо ручно креирати ову мапу и ажурирати приказани шаблон помоћу JavaScript-а.
### Задатак
Користићемо једноставан објекат за имплементацију [мапе](https://en.wikipedia.org/wiki/Associative_array) између URL путања и наших шаблона. Додајте овај објекат на врх вашег `app.js` фајла.
```js
const routes = {
'/login': { templateId: 'login' },
'/dashboard': { templateId: 'dashboard' },
};
```
Сада мало модификујмо функцију `updateRoute`. Уместо да директно прослеђујемо `templateId` као аргумент, желимо да га пронађемо тако што ћемо прво погледати тренутни URL, а затим користити нашу мапу да добијемо одговарајућу вредност `templateId`. Можемо користити [`window.location.pathname`](https://developer.mozilla.org/docs/Web/API/Location/pathname) да добијемо само део путање из URL-а.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
const template = document.getElementById(route.templateId);
const view = template.content.cloneNode(true);
const app = document.getElementById('app');
app.innerHTML = '';
app.appendChild(view);
}
```
Овде смо мапирали руте које смо декларисали на одговарајући шаблон. Можете пробати да ли ради исправно тако што ћете ручно променити URL у вашем претраживачу.
✅ Шта се дешава ако унесете непознату путању у URL? Како бисмо могли да решимо ово?
## Додавање навигације
Следећи корак за нашу апликацију је додавање могућности навигације између страница без потребе за ручним мењањем URL-а. Ово подразумева две ствари:
1. Ажурирање тренутног URL-а
2. Ажурирање приказаног шаблона на основу новог URL-а
Други део смо већ решили функцијом `updateRoute`, тако да морамо да смислимо како да ажурирамо тренутни URL.
Мораћемо да користимо JavaScript, а конкретно [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState) који омогућава ажурирање URL-а и креирање новог уноса у историји прегледања, без поновног учитавања HTML-а.
> Напомена: Док HTML елемент за сидро [``](https://developer.mozilla.org/docs/Web/HTML/Element/a) може самостално да креира хиперлинкове ка различитим URL-овима, он подразумевано чини да претраживач поново учита HTML. Неопходно је спречити ово понашање када се рутирање обрађује прилагођеним JavaScript-ом, користећи функцију `preventDefault()` на догађају клика.
### Задатак
Направимо нову функцију коју можемо користити за навигацију у нашој апликацији:
```js
function navigate(path) {
window.history.pushState({}, path, path);
updateRoute();
}
```
Ова метода прво ажурира тренутни URL на основу дате путање, а затим ажурира шаблон. Својство `window.location.origin` враћа корен URL-а, омогућавајући нам да реконструишемо комплетан URL из дате путање.
Сада када имамо ову функцију, можемо решити проблем који имамо ако путања не одговара ниједној дефинисаној рути. Модификоваћемо функцију `updateRoute` додавањем резервне опције за једну од постојећих рута ако не можемо пронаћи подударност.
```js
function updateRoute() {
const path = window.location.pathname;
const route = routes[path];
if (!route) {
return navigate('/login');
}
...
```
Ако рута не може бити пронађена, сада ћемо бити преусмерени на страницу за пријаву.
Сада направимо функцију за добијање URL-а када се кликне на линк и за спречавање подразумеваног понашања линка у претраживачу:
```js
function onLinkClick(event) {
event.preventDefault();
navigate(event.target.href);
}
```
Завршимо систем навигације додавањем веза за наше линкове *Login* и *Logout* у HTML-у.
```html
Login
...
Logout
```
Објекат `event` изнад хвата догађај `click` и прослеђује га нашој функцији `onLinkClick`.
Користећи атрибут [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick), повежите догађај клика са JavaScript кодом, овде позивом функције `navigate()`.
Пробајте да кликнете на ове линкове, сада би требало да можете да се крећете између различитих екрана ваше апликације.
✅ Метода `history.pushState` је део HTML5 стандарда и имплементирана је у [свим модерним претраживачима](https://caniuse.com/?search=pushState). Ако правите веб апликацију за старије претраживаче, постоји трик који можете користити уместо овог API-ја: коришћењем [хеша (`#`)](https://en.wikipedia.org/wiki/URI_fragment) пре путање можете имплементирати рутирање које ради са регуларном навигацијом сидра и не учитава поново страницу, јер је његова сврха била креирање унутрашњих линкова унутар странице.
## Обрада дугмади за назад и напред у претраживачу
Коришћење `history.pushState` креира нове уносе у историји навигације претраживача. Можете то проверити држећи *дугме за назад* вашег претраживача, требало би да прикаже нешто овако:

Ако пробате да кликнете на дугме за назад неколико пута, видећете да се тренутни URL мења и историја се ажурира, али исти шаблон се и даље приказује.
То је зато што апликација не зна да треба да позове `updateRoute()` сваки пут када се историја промени. Ако погледате документацију за [`history.pushState`](https://developer.mozilla.org/docs/Web/API/History/pushState), можете видети да ако се стање промени - што значи да смо прешли на други URL - догађај [`popstate`](https://developer.mozilla.org/docs/Web/API/Window/popstate_event) се активира. Користићемо то да решимо овај проблем.
### Задатак
Да бисмо били сигурни да се приказани шаблон ажурира када се историја претраживача промени, додаћемо нову функцију која позива `updateRoute()`. Урадићемо то на дну нашег `app.js` фајла:
```js
window.onpopstate = () => updateRoute();
updateRoute();
```
> Напомена: користили смо [arrow функцију](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) овде за декларацију нашег `popstate` обрађивача догађаја ради концизности, али би обична функција радила исто.
Ево освежавајућег видеа о arrow функцијама:
[](https://youtube.com/watch?v=OP6eEbOj2sc "Arrow Functions")
> 🎥 Кликните на слику изнад за видео о arrow функцијама.
Сада пробајте да користите дугмад за назад и напред у вашем претраживачу и проверите да ли се приказана рута исправно ажурира овог пута.
---
## 🚀 Изазов
Додајте нови шаблон и руту за трећу страницу која приказује кредите за ову апликацију.
## Квиз после предавања
[Квиз после предавања](https://ff-quizzes.netlify.app/web/quiz/42)
## Преглед и самостално учење
Рутирање је један од изненађујуће сложених делова веб развоја, посебно како веб прелази са понашања освежавања странице на освежавање странице у апликацијама са једном страницом (Single Page Application). Прочитајте мало о [начину на који Azure Static Web App сервис](https://docs.microsoft.com/azure/static-web-apps/routes/?WT.mc_id=academic-77807-sagibbon) обрађује рутирање. Можете ли објаснити зашто су неке од одлука описаних у том документу неопходне?
## Задатак
[Унапредите рутирање](assignment.md)
---
**Одрицање од одговорности**:
Овај документ је преведен коришћењем услуге за превођење помоћу вештачке интелигенције [Co-op Translator](https://github.com/Azure/co-op-translator). Иако настојимо да обезбедимо тачност, молимо вас да имате у виду да аутоматски преводи могу садржати грешке или нетачности. Оригинални документ на изворном језику треба сматрати ауторитативним извором. За критичне информације препоручује се професионални превод од стране људи. Не сносимо одговорност за било каква погрешна тумачења или неспоразуме који могу произаћи из коришћења овог превода.