|
|
10 months ago | |
|---|---|---|
| .. | ||
| README.md | 10 months ago | |
| assignment.md | 12 months 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 باستخدام واجهة برمجة التطبيقات DOM. مع مرور الوقت، تطورت هذه الطريقة إلى ما يُعرف الآن باسم تطبيق صفحة واحدة أو SPA.
عندما تم تقديم AJAX لأول مرة، كانت واجهة برمجة التطبيقات الوحيدة المتاحة لجلب البيانات بشكل غير متزامن هي 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' };
}
}
نستخدم واجهة برمجة التطبيقات 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;
بعد حفظ بيانات المستخدم في متغير، يمكننا التنقل من صفحة تسجيل الدخول إلى لوحة التحكم باستخدام وظيفة navigate() التي لدينا بالفعل.
أخيرًا، نحتاج إلى استدعاء وظيفة login عند إرسال نموذج تسجيل الدخول، عن طريق تعديل HTML:
<form id="loginForm" action="javascript:login()">
اختبر أن كل شيء يعمل بشكل صحيح عن طريق تسجيل حساب جديد ومحاولة تسجيل الدخول باستخدام نفس الحساب.
قبل الانتقال إلى الجزء التالي، يمكننا أيضًا إكمال وظيفة register بإضافة هذا في نهاية الوظيفة:
account = result;
navigate('/dashboard');
✅ هل تعلم أنه بشكل افتراضي، يمكنك فقط استدعاء واجهات برمجة التطبيقات للخادم من نفس النطاق والمنفذ الذي تشاهد منه صفحة الويب؟ هذا هو آلية الأمان التي تفرضها المتصفحات. ولكن انتظر، تطبيق الويب الخاص بنا يعمل على localhost:3000 بينما واجهة برمجة التطبيقات للخادم تعمل على localhost:5000، لماذا يعمل؟ باستخدام تقنية تسمى مشاركة الموارد عبر الأصل (CORS)، يمكن إجراء طلبات HTTP عبر الأصل إذا أضاف الخادم رؤوسًا خاصة إلى الاستجابة، مما يسمح باستثناءات لنطاقات محددة.
تعرف على المزيد حول واجهات برمجة التطبيقات من خلال أخذ هذا الدرس
تحديث HTML لعرض البيانات
الآن بعد أن أصبح لدينا بيانات المستخدم، علينا تحديث HTML الحالي لعرضها. نحن نعرف بالفعل كيفية استرجاع عنصر من DOM باستخدام على سبيل المثال document.getElementById(). بعد أن يكون لديك عنصر أساسي، إليك بعض واجهات برمجة التطبيقات التي يمكنك استخدامها لتعديله أو إضافة عناصر فرعية له:
-
باستخدام خاصية
textContentيمكنك تغيير نص العنصر. لاحظ أن تغيير هذه القيمة يزيل جميع العناصر الفرعية للعنصر (إذا كان هناك أي منها) ويستبدلها بالنص المقدم. وبالتالي، فهي أيضًا طريقة فعالة لإزالة جميع العناصر الفرعية لعنصر معين عن طريق تعيين سلسلة فارغة''له. -
باستخدام
document.createElement()مع طريقةappend()يمكنك إنشاء وإرفاق عنصر أو أكثر جديد كعنصر فرعي.
✅ باستخدام خاصية innerHTML للعنصر يمكن أيضًا تغيير محتويات HTML الخاصة به، ولكن يجب تجنب هذه الطريقة لأنها عرضة لهجمات البرمجة النصية عبر المواقع (XSS).
المهمة
قبل الانتقال إلى شاشة لوحة التحكم، هناك شيء آخر يجب القيام به في صفحة تسجيل الدخول. حاليًا، إذا حاولت تسجيل الدخول باستخدام اسم مستخدم غير موجود، يتم عرض رسالة في وحدة التحكم ولكن بالنسبة للمستخدم العادي لا يتغير شيء ولا تعرف ما الذي يحدث.
لنقم بإضافة عنصر عنصر نائب في نموذج تسجيل الدخول حيث يمكننا عرض رسالة خطأ إذا لزم الأمر. مكان جيد سيكون قبل زر تسجيل الدخول <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. لنستخدم هذه الطريقة بدلاً من رسالة الخطأ السابقة في وظيفة login:
if (data.error) {
return updateElement('loginError', data.error);
}
الآن إذا حاولت تسجيل الدخول باستخدام حساب غير صالح، يجب أن ترى شيئًا مثل هذا:
الآن لدينا نص خطأ يظهر بصريًا، ولكن إذا جربته باستخدام قارئ الشاشة ستلاحظ أنه لا يتم الإعلان عن أي شيء. لكي يتم الإعلان عن النص الذي يتم إضافته ديناميكيًا إلى الصفحة بواسطة قارئات الشاشة، سيحتاج إلى استخدام شيء يسمى منطقة حية. هنا سنستخدم نوعًا محددًا من المنطقة الحية يسمى تنبيه:
<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الموجود مسبقًا والذي يحتوي بالفعل على بيانات.
المهمة
لنبدأ باستبدال قسم "الرصيد" في 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() في كل مرة يتم فيها تحميل صفحة لوحة التحكم. إذا كنت قد أكملت بالفعل مهمة الدرس الأول يجب أن يكون هذا بسيطًا، وإلا يمكنك استخدام التنفيذ التالي.
أضف هذا الكود إلى نهاية وظيفة 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 لتسجيل الدخول، يجب أن ترى الآن قائمة المعاملات على لوحة التحكم 🎉.
تحدي GitHub Copilot Agent 🚀
استخدم وضع الوكيل لإكمال التحدي التالي:
الوصف: قم بتحسين تطبيق البنك من خلال تنفيذ ميزة البحث والتصفية للمعاملات التي تتيح للمستخدمين العثور على معاملات محددة حسب نطاق التاريخ، المبلغ، أو وصف المعاملة.
المهمة: قم بإنشاء وظيفة بحث لتطبيق البنك تتضمن: 1) نموذج بحث يحتوي على حقول إدخال لنطاق التاريخ (من/إلى)، الحد الأدنى/الأقصى للمبلغ، وكلمات مفتاحية لوصف المعاملة، 2) وظيفة filterTransactions() لتصفية مصفوفة account.transactions بناءً على معايير البحث، 3) تحديث وظيفة updateDashboard() لعرض النتائج المصفاة، و 4) إضافة زر "مسح الفلاتر" لإعادة العرض إلى حالته الأصلية. استخدم طرق حديثة لمصفوفات JavaScript مثل filter() وتعامل مع الحالات الخاصة بمعايير البحث الفارغة.
🚀 التحدي
تعاونوا لجعل صفحة لوحة التحكم تبدو مثل تطبيق بنك حقيقي. إذا كنت قد قمت بتنسيق تطبيقك بالفعل، حاول استخدام استعلامات الوسائط لإنشاء تصميم متجاوب يعمل بشكل جيد على كل من أجهزة الكمبيوتر المكتبية والأجهزة المحمولة.
إليك مثال لصفحة لوحة التحكم بعد التنسيق:
اختبار ما بعد المحاضرة
الواجب
إعادة هيكلة وتعليق الكود الخاص بك
إخلاء المسؤولية:
تم ترجمة هذا المستند باستخدام خدمة الترجمة بالذكاء الاصطناعي Co-op Translator. بينما نسعى لتحقيق الدقة، يرجى العلم أن الترجمات الآلية قد تحتوي على أخطاء أو عدم دقة. يجب اعتبار المستند الأصلي بلغته الأصلية المصدر الرسمي. للحصول على معلومات حاسمة، يُوصى بالترجمة البشرية الاحترافية. نحن غير مسؤولين عن أي سوء فهم أو تفسيرات خاطئة ناتجة عن استخدام هذه الترجمة.



