يوضح هذا المقال كيفية تعريف صفحة لحذف كائن المؤلف وتحديث كائن الكتاب للتعرّف على المزيد حول الاستمارات Forms في إطار عمل Express.
استمارة حذف مؤلف
سنوضح كيفية تعريف صفحة لحذف كائنات المؤلف Author
، إذ ستكون استراتيجيتنا -كما ناقشنا في قسم تصميم الاستمارة- هي السماح فقط بحذف الكائنات التي لا تشير إليها كائنات أخرى، وهذا يعني أننا لن نسمح بحذف المؤلف Author
إذا أشار إليه كتاب Book
، ويعني ذلك من حيث التقديم أن الاستمارة يجب أن تؤكد عدم وجود كتب مرتبطة بالمؤلف قبل حذفه. tإذا كان هناك كتب مرتبطة به، فيجب أن تعرضها وتوضح أنه يجب حذفها قبل حذف كائن المؤلف Author
.
متحكم وجهة Get
افتح الملف /controllers/authorController.js، ثم ابحث عن تابع المتحكم author_delete_get()
المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
// عرض استمارة حذف مؤلف في طلب GET exports.author_delete_get = asyncHandler(async (req, res, next) => { // الحصول على تفاصيل المؤلف وجميع كتبه على التوازي const [author, allBooksByAuthor] = await Promise.all([ Author.findById(req.params.id).exec(), Book.find({ author: req.params.id }, "title summary").exec(), ]); if (author === null) { // لا توجد نتائج res.redirect("/catalog/authors"); } res.render("author_delete", { title: "Delete Author", author: author, author_books: allBooksByAuthor, }); });
يحصل المتحكم على معرّف نسخة المؤلف Author
لحذفه من معامل عنوان URL (وهو req.params.id
)، ويستخدم await
لانتظار الوعد الذي أعاده التابع Promise.all()
للانتظار بطريقة غير متزامنة لسجل المؤلف المُحدَّد وجميع الكتب المرتبطة به على التوازي، ويصيّر العرض author_delete.pug عند اكتمال كلتا العمليتين، مع تمرير متغيرات title
و author
و author_books
.
ملاحظة: إن لم يُعِد التابع findById()
أيّ نتائج، فلن يكون المؤلف موجودًا في قاعدة البيانات، وبالتالي لا يوجد شيء يمكن حذفه، لذا نعيد التوجيه مباشرةً إلى قائمة جميع المؤلفين.
if (author === null) { // لا توجد نتائج res.redirect("/catalog/authors"); }
متحكم طلب وجهة Post
ابحث عن تابع المتحكم author_delete_post()
المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
// معالجة حذف مؤلف في طلب POST exports.author_delete_post = asyncHandler(async (req, res, next) => { // الحصول على تفاصيل المؤلف وجميع كتبه على التوازي const [author, allBooksByAuthor] = await Promise.all([ Author.findById(req.params.id).exec(), Book.find({ author: req.params.id }, "title summary").exec(), ]); if (allBooksByAuthor.length > 0) { // المؤلف لديه كتب، لذا اعرض باستخدام طريقة وجهة GET نفسها res.render("author_delete", { title: "Delete Author", author: author, author_books: allBooksByAuthor, }); return; } else { // المؤلف ليس لديه كتب، لذا احذف الكائن وأعِد التوجيه إلى قائمة المؤلفين await Author.findByIdAndRemove(req.body.authorid); res.redirect("/catalog/authors"); } });
نتحقق الكود أولًا من تقديم المعرّف المُرسَل عبر معاملات متن الاستمارة بدلًا من استخدام النسخة الموجودة في عنوان URL، ثم يحصل على المؤلف والكتب المرتبطة به باستخدام طريقة وِجهة GET
نفسها. إن لم يكن هناك كتب، فإننا نحذف كائن المؤلف ونعيد التوجيه إلى قائمة جميع المؤلفين، وإذا كان هناك كتب، فسنعيد عرض الاستمارة مع تمرير المؤلف وقائمة الكتب لحذفها.
ملاحظة: يمكننا التحقق من أن استدعاء التابع findById()
يعيد أيّ نتائج، فإن لم يكن الأمر كذلك، نعرض قائمة جميع المؤلفين مباشرةً. لقد تركنا الشيفرة كما هي موضحة أعلاه للإيجاز، إذ ستظل تعيد قائمة المؤلفين إن لم يُعثَر على المعرّف، ولكن ذلك سيحدث بعد التابع findByIdAndRemove()
.
العرض View
أنشئ العرض /views/author_delete.pug وضع فيه النص التالي:
extends layout block content h1 #{title}: #{author.name} p= author.lifespan if author_books.length p #[strong Delete the following books before attempting to delete this author.] div(style='margin-left:20px;margin-top:20px') h4 Books dl each book in author_books dt a(href=book.url) #{book.title} dd #{book.summary} else p Do you really want to delete this Author? form(method='POST' action='') div.form-group input#authorid.form-control(type='hidden',name='authorid', required='true', value=author._id ) button.btn.btn-primary(type='submit') Delete
يوسّع هذا العرض قالب التخطيط Layout من خلال تعديل الكتلة content
، إذ يعرض تفاصيل المؤلف في أعلى الملف، ثم يتضمن تعليمة شرطية تعتمد على عدد كتب المؤلف author_books
(تعليمات if
و else
) كما يلي:
-
إذا كان هناك كتب مرتبطة بالمؤلف، فستسرد الصفحة الكتب وتشير إلى أنه يجب حذفها قبل حذف هذا المؤلف
Author
. - إن لم يكن هناك كتب، فستعرض الصفحة طلبًا لتأكيد الحذف.
-
إذا نُقِر على زر الحذف Delete، فسيُرسَل معرّف المؤلف إلى الخادم في طلب
POST
وسيُحذَف سجل المؤلف.
إضافة عنصر تحكم الحذف Delete Control
سنضيف الآن عنصر تحكم الحذف إلى عرض تفاصيل المؤلف، إذ تُعَد صفحة التفاصيل مكانًا جيدًا لحذف سجل منه.
ملاحظة: سيكون عنصر التحكم مرئيًا فقط للمستخدمين المُصرَّح لهم عند التقديم الكامل، ولكن ليس لدينا نظام تصريح حاليًا.
افتح العرض author_detail.pug
وأضِف الأسطر التالية إلى نهايته:
hr p a(href=author.url+'/delete') Delete author
يجب أن يظهر عنصر التحكم الآن بوصفه رابطًا في صفحة تفاصيل المؤلف كما يلي:
كيف تبدو استمارة حذف المؤلف؟
شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/"، ثم حدّد رابط جميع المؤلفين All authors، واختر مؤلفًا معينًا، ثم حدّد رابط حذف المؤلف Delete author.
إن لم يكن لدى المؤلف كتب، فستُعرَض صفحة مثل الصفحة التالية، إذ سيحذف الخادم المؤلف ويعيد التوجيه إلى قائمة المؤلفين بعد الضغط على زر الحذف:
إذا كان لدى المؤلف كتب، فسيُقدَّم عرض يشبه ما يلي، ثم يمكنك حذف الكتب من صفحات تفاصيلها (بعد تقديم الشيفرة البرمجية المتعلقة بذلك):
ملاحظة: يمكن تقديم الصفحات الأخرى الخاصة بحذف الكائنات باستخدام الطريقة نفسها، لذا تركناها كتحدٍ لك.
استمارة تحديث كتاب
سنوضح كيفية تعريف صفحة لتحديث كائنات الكتاب Book
، إذ تشبه معالجة استمارة تحديث كتاب إلى حدٍ كبير معالجة استمارة إنشاء كتاب، باستثناء أنه يجب عليك ملء الاستمارة في وِجهة GET
بقيمٍ من قاعدة البيانات.
متحكم وجهة Get
افتح الملف "/controllers/bookController.js"، ثم ابحث عن تابع المتحكم book_update_get()
المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
// عرض استمارة تحديث كتاب في طلب GET exports.book_update_get = asyncHandler(async (req, res, next) => { // الحصول على الكتاب والمؤلفين وأنواع الكتب للاستمارة const [book, allAuthors, allGenres] = await Promise.all([ Book.findById(req.params.id).populate("author").populate("genre").exec(), Author.find().sort({ family_name: 1 }).exec(), Genre.find().sort({ name: 1 }).exec(), ]); if (book === null) { // لا توجد نتائج const err = new Error("Book not found"); err.status = 404; return next(err); } // ميّز أنواع الكتب المختارة بوصفّها مُحدَّدة for (const genre of allGenres) { for (const book_g of book.genre) { if (genre._id.toString() === book_g._id.toString()) { genre.checked = "true"; } } } res.render("book_form", { title: "Update Book", authors: allAuthors, genres: allGenres, book: book, }); });
يحصل المتحكم على معرّف الكتاب Book
لتحديثه من معامل عنوان URL (هو req.params.id
)، وينتظر (باستخدام await
) الوعد الذي أعاده التابع Promise.all()
للحصول على سجل الكتاب Book
المحدد (مع ملء حقلي نوع الكتاب والمؤلف) وجميع سجلات المؤلف Author
ونوع الكتاب Genre
. تتحقق الدالة عند اكتمال العمليات من العثور على أيّ كتب، فإن لم يُعثَر على أيٍّ منها، فسترسل خطأ عدم العثور على الكتاب "Book not found" إلى البرمجية الوسيطة لمعالجة الخطأ.
ملاحظة: لا يُعَد عدم العثور على أيّ كتاب كنتيجة خطأً في البحث، ولكنه كذلك في هذا التطبيق لأننا نعلم أنه يجب أن يكون هناك سجل كتاب مطابق. تطبّق الشيفرة السابقة اختبار المقارنة (book===null
) في دالة رد النداء، ولكن يمكن أن تضيف التابع orFail()
إلى السلسلة التعاقبية الخاصة بالاستعلام.
نميّز بعد ذلك أنواع الكتب المختارة حاليًا بوصفها مُحدَّدة، ثم نقدّم العرض book_form.pug، ونمرر متغيرات title
والكتاب وجميع المؤلفين authors
وجميع أنواع الكتب genres
.
متحكم وجهة Post
ابحث عن تابع المتحكم book_update_post()
المُصدَّر وضع مكانه الشيفرة البرمجية التالية:
// معالجة تحديث كتاب في طلب POST exports.book_update_post = [ // تحويل نوع الكتاب إلى مصفوفة (req, res, next) => { if (!Array.isArray(req.body.genre)) { req.body.genre = typeof req.body.genre === "undefined" ? [] : [req.body.genre]; } next(); }, // التحقق من صحة الحقول وتطهيرها body("title", "Title must not be empty.") .trim() .isLength({ min: 1 }) .escape(), body("author", "Author must not be empty.") .trim() .isLength({ min: 1 }) .escape(), body("summary", "Summary must not be empty.") .trim() .isLength({ min: 1 }) .escape(), body("isbn", "ISBN must not be empty").trim().isLength({ min: 1 }).escape(), body("genre.*").escape(), // طلب العملية بعد التحقق من صحة البيانات وتطهيرها asyncHandler(async (req, res, next) => { // استخراج أخطاء التحقق من صحة البيانات من الطلب const errors = validationResult(req); // إنشاء كائن كتاب مع بيانات مُهرَّبة أو محذوف منها المسافات والمعرّف القديم const book = new Book({ title: req.body.title, author: req.body.author, summary: req.body.summary, isbn: req.body.isbn, genre: typeof req.body.genre === "undefined" ? [] : req.body.genre, _id: req.params.id, // هذا مطلوب، أو سيجري إسناد معرّف جديد }); if (!errors.isEmpty()) { // توجد أخطاء، لذا اعرض الاستمارة مرة أخرى مع قيم مُطهَّرة أو رسائل خطأ // الحصول على جميع المؤلفين وأنواع الكتب للاستمارة const [allAuthors, allGenres] = await Promise.all([ Author.find().exec(), Genre.find().exec(), ]); // ميّز أنواع الكتب المختارة بوصفها مُحدَّدة for (const genre of allGenres) { if (book.genre.indexOf(genre._id) > -1) { genre.checked = "true"; } } res.render("book_form", { title: "Update Book", authors: allAuthors, genres: allGenres, book: book, errors: errors.array(), }); return; } else { // البيانات الواردة من الاستمارة صالحة، لذا حدّث السجل const thebook = await Book.findByIdAndUpdate(req.params.id, book, {}); // أعِد التوجيه إلى صفحة تفاصيل الكتاب res.redirect(thebook.url); } }), ];
تُعَد هذه الشيفرة البرمجية مشابهة جدًا لشيفرة وِجهة Post التي استخدمناها لإنشاء كتاب Book، إذ نتحقق أولًا من صحة بيانات الكتاب القادمة من الاستمارة ونطهرها ونستخدمها لإنشاء كائن كتاب Book
جديد مع ضبط قيمة _id
الخاصة به على معرّف الكائن المُراد تحديثه. إذا كان هناك أخطاء عند التحقق من صحة البيانات، فسنعيد تصيير الاستمارة، بالإضافة إلى عرض البيانات التي أدخلها المستخدم والأخطاء وقوائم أنواع الكتب والمؤلفين. إذا لم يكن هناك أخطاء، فسنستدعي التابع Book.findByIdAndUpdate()
لتحديث مستند الكتاب Book
، ثم نعيد التوجيه إلى صفحة تفاصيله.
العرض View
ليست هناك حاجة لتغيير عرض الاستمارة (/views/book_form.pug)، إذ يناسب هذا القالب إنشاء الكتاب وتحديثه.
إضافة زر تحديث
افتح العرض book_detail.pug وتأكّد من وجود روابط لكل من حذف وتحديث الكتب أسفل الصفحة كما يلي:
hr p a(href=book.url+'/delete') Delete Book p a(href=book.url+'/update') Update Book
يجب أن تكون قادرًا الآن على تحديث الكتب من صفحة تفاصيل الكتاب.
كيف تبدو استمارة تحديث الكتاب؟
شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/"، ثم حدّد رابط جميع الكتب All books، واختر كتابًا معينًا، ثم حدّد رابط تحديث الكتاب Update Book. يجب أن تبدو الاستمارة تمامًا مثل صفحة إنشاء كتاب Create book، ولكن مع العنوان "Update book"، وتكون الاستمارة مملوءة مسبقًا بقيم السجل.
ملاحظة: يمكن تقديم الصفحات الأخرى الخاصة بتحديث الكائنات باستخدام الطريقة نفسها، لذا تركناها كتحدٍ لك.
ترجمة -وبتصرُّف- للمقالين Delete Author form و Update Book form.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.