# ساخت یک اپلیکیشن بانکی قسمت ۱: قالبهای HTML و مسیرها در یک اپلیکیشن وب
## آزمون پیش از درس
[آزمون پیش از درس](https://ff-quizzes.netlify.app/web/quiz/41)
### مقدمه
از زمان ظهور جاوااسکریپت در مرورگرها، وبسایتها تعاملیتر و پیچیدهتر از همیشه شدهاند. فناوریهای وب اکنون به طور گسترده برای ایجاد اپلیکیشنهای کاملاً کاربردی که مستقیماً در مرورگر اجرا میشوند، استفاده میشوند و به آنها [اپلیکیشنهای وب](https://en.wikipedia.org/wiki/Web_application) میگوییم. از آنجا که اپلیکیشنهای وب بسیار تعاملی هستند، کاربران نمیخواهند هر بار که عملی انجام میشود، منتظر بارگذاری مجدد کل صفحه باشند. به همین دلیل، جاوااسکریپت برای بهروزرسانی مستقیم 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 قابل استفاده مجدد است که توسط مرورگر نمایش داده نمیشود و باید در زمان اجرا با استفاده از جاوااسکریپت نمونهسازی شود.
### وظیفه
ما یک اپلیکیشن بانکی با دو صفحه ایجاد خواهیم کرد: صفحه ورود و داشبورد. ابتدا، در بدنه HTML یک عنصر جایگزین اضافه میکنیم که برای نمونهسازی صفحات مختلف اپلیکیشن خود از آن استفاده خواهیم کرد:
```html
Loading...
```
ما به آن یک `id` میدهیم تا بعداً با جاوااسکریپت راحتتر بتوانیم آن را پیدا کنیم.
> نکته: از آنجا که محتوای این عنصر جایگزین خواهد شد، میتوانیم یک پیام یا نشانگر بارگذاری در آن قرار دهیم که هنگام بارگذاری اپلیکیشن نمایش داده شود.
سپس، قالب HTML برای صفحه ورود را در زیر اضافه میکنیم. فعلاً فقط یک عنوان و یک بخش حاوی یک لینک که برای ناوبری استفاده میشود، در آن قرار میدهیم:
```html
Bank App
```
بعد، یک قالب HTML دیگر برای صفحه داشبورد اضافه میکنیم. این صفحه شامل بخشهای مختلفی خواهد بود:
- یک هدر با عنوان و لینک خروج
- موجودی فعلی حساب بانکی
- لیستی از تراکنشها که در یک جدول نمایش داده میشود
```html
```
> نکته: هنگام ایجاد قالبهای HTML، اگر میخواهید ببینید که چگونه به نظر میرسند، میتوانید خطوط `` و `` را با قرار دادن آنها درون `` کامنت کنید.
✅ به نظر شما چرا از ویژگیهای `id` در قالبها استفاده میکنیم؟ آیا میتوانستیم از چیز دیگری مانند کلاسها استفاده کنیم؟
## نمایش قالبها با جاوااسکریپت
اگر فایل HTML فعلی خود را در مرورگر امتحان کنید، خواهید دید که روی `Loading...` گیر میکند. این به این دلیل است که ما باید مقداری کد جاوااسکریپت اضافه کنیم تا قالبهای HTML را نمونهسازی و نمایش دهیم.
نمونهسازی یک قالب معمولاً در ۳ مرحله انجام میشود:
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);
}
```
کاری که اینجا انجام میدهیم دقیقاً همان ۳ مرحلهای است که در بالا توضیح داده شد. ما قالبی با `id` مشخص شده را نمونهسازی میکنیم و محتوای کلون شده آن را درون جایگزین اپلیکیشن خود قرار میدهیم. توجه داشته باشید که باید از `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 واحد حاوی تمام صفحات استفاده میکند، این رفتار پیشفرض کمکی به ما نمیکند. ما باید این نگاشت را به صورت دستی ایجاد کنیم و با استفاده از جاوااسکریپت قالب نمایش داده شده را بهروزرسانی کنیم.
### وظیفه
ما از یک شیء ساده برای پیادهسازی یک [نگاشت](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 فعلی را بهروزرسانی کنیم.
ما باید از جاوااسکریپت و به طور خاص از [`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 را دوباره بارگذاری کند. لازم است این رفتار هنگام مدیریت مسیریابی با جاوااسکریپت سفارشی، با استفاده از تابع `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');
}
...
```
اگر مسیری پیدا نشود، اکنون به صفحه `login` هدایت میشویم.
حالا یک تابع برای دریافت URL هنگام کلیک روی یک لینک و جلوگیری از رفتار پیشفرض لینک مرورگر ایجاد کنیم:
```js
function onLinkClick(event) {
event.preventDefault();
navigate(event.target.href);
}
```
سیستم ناوبری را با افزودن اتصال به لینکهای *ورود* و *خروج* در HTML کامل کنیم.
```html
Login
...
Logout
```
شیء `event` در بالا، رویداد `click` را ضبط کرده و آن را به تابع `onLinkClick` ما ارسال میکند.
با استفاده از ویژگی [`onclick`](https://developer.mozilla.org/docs/Web/API/GlobalEventHandlers/onclick) رویداد `click` را به کد جاوااسکریپت متصل کنید، اینجا فراخوانی تابع `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();
```
> توجه: ما از یک [تابع فلش](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions/Arrow_functions) برای تعریف هندلر رویداد `popstate` خود برای اختصار استفاده کردیم، اما یک تابع معمولی نیز به همان صورت کار میکند.
در اینجا یک ویدیوی یادآوری درباره توابع فلش آورده شده است:
[](https://youtube.com/watch?v=OP6eEbOj2sc "توابع فلش")
> 🎥 روی تصویر بالا کلیک کنید تا ویدیویی درباره توابع فلش مشاهده کنید.
حالا سعی کنید از دکمههای بازگشت و جلو مرورگر خود استفاده کنید و بررسی کنید که مسیر نمایش داده شده این بار به درستی بهروزرسانی میشود.
---
## 🚀 چالش
یک قالب و مسیر جدید برای یک صفحه سوم که اعتبارنامههای این اپلیکیشن را نشان میدهد، اضافه کنید.
## آزمون پس از درس
[آزمون پس از درس](https://ff-quizzes.netlify.app/web/quiz/42)
## مرور و مطالعه شخصی
مسیریابی یکی از بخشهای شگفتآوراً دشوار توسعه وب است، به ویژه با حرکت وب از رفتارهای بارگذاری صفحه به بارگذاری صفحات در اپلیکیشنهای تکصفحهای. کمی درباره [چگونگی مدیریت مسیریابی توسط سرویس 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) ترجمه شده است. در حالی که ما تلاش میکنیم دقت را حفظ کنیم، لطفاً توجه داشته باشید که ترجمههای خودکار ممکن است شامل خطاها یا نادرستیها باشند. سند اصلی به زبان اصلی آن باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حساس، توصیه میشود از ترجمه انسانی حرفهای استفاده کنید. ما هیچ مسئولیتی در قبال سوء تفاهمها یا تفسیرهای نادرست ناشی از استفاده از این ترجمه نداریم.