# ساخت اپلیکیشن بانکی قسمت اول: قالبهای 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` داخل آن قرار دهید. ما از این [کد پایه](https://en.wikipedia.org/wiki/Boilerplate_code) HTML شروع خواهیم کرد:
```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);
}
```
سیستم ناوبری را با افزودن اتصالها به لینکهای *Login* و *Logout* در 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) ترجمه شده است. در حالی که ما برای دقت تلاش میکنیم، لطفاً توجه داشته باشید که ترجمههای خودکار ممکن است شامل خطاها یا نادرستیهایی باشند. سند اصلی به زبان اصلی آن باید به عنوان منبع معتبر در نظر گرفته شود. برای اطلاعات حساس، توصیه میشود از ترجمه انسانی حرفهای استفاده کنید. ما هیچ مسئولیتی در قبال سوءتفاهمها یا تفسیرهای نادرست ناشی از استفاده از این ترجمه نداریم.