اذهب إلى المحتوى

إنشاء مكتبة محلية باستخدام Express: إنشاء استمارة Form لأنواع الكتب


Ola Abbas

يوضح هذا المقال كيفية تعريف صفحة لإنشاء كائنات النوع Genre، إذ يُعَد ذلك مكانًا جيدًا لتعلم التعامل مع الاستمارات Forms لأن  الاستمارة الخاصة بنوع الكتاب Genre تحتوي على حقل واحد فقط هو اسم النوع name وبدون اعتماديات، ويجب إعداد الوجهات Routes والمتحكمات Controllers والعروض Views لهذه الصفحة مثلها مثل أيّ صفحة أخرى.

استيراد توابع التحقق من صحة البيانات Validation وتطهيرها Sanitization

يمكن استخدام express-validator في المتحكمات من خلال طلب require الدوال التي نريد استخدامها للتحقق من صحة حقول النماذج من الوحدة 'express-validator'.

افتح الملف "‎/controllers/genreController.js"، وأضف السطر التالي في بداية الملف قبل أيٍّ من دوال معالج الوجهة:

const { body, validationResult } = require("express-validator");

ملاحظة: تسمح الصيغة السابقة باستخدام body و validationResult بوصفهما دوال وسيطة كما سترى في قسم طلب الوجهة من النوع Post، وهي تكافئ ما يلي:

const validator = require("express-validator");
const body = validator.body;
const validationResult = validator.validationResult;

متحكم وجهة Get

ابحث عن تابع المتحكم genre_create_get()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية التي تؤدي إلى تقديم التصيير genre_form.pug وتمرير متغير العنوان:

// عرض استمارة إنشاء نوع ك‫تاب في طلب GET 
exports.genre_create_get = (req, res, next) => {
  res.render("genre_form", { title: "Create Genre" });
};

لاحظ أن ذلك يؤدي إلى وضع دالة معالج وجهة Express "العادي" مكان المعالج غير المتزامن للعنصر البديل الذي أضفناه في مقال الوجهات والمتحكمات. لا حاجة للمغلّف asyncHandler()‎ لهذه الوجهة، لأنه لا يحتوي على أيّ شيفرة برمجية يمكنها رمي استثناء.

متحكم وجهة Post

ابحث عن تابع المتحكم genre_create_post()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية:

// معالجة إ‫نشاء نوع كتاب Genre في طلب POST
exports.genre_create_post = [
  // التحقق من صحة حقل الاسم وتطهيره
  body("name", "Genre name must contain at least 3 characters")
    .trim()
    .isLength({ min: 3 })
    .escape(),

  // طلب العملية بعد التحقق من صحة البيانات وتطهيرها
  asyncHandler(async (req, res, next) => {
    // استخراج أخطاء التحقق من صحة البيانات من الطلب
    const errors = validationResult(req);

    // إنشاء كائن نوع كتاب مع بيانات مُهرَّبة ومحذوف منها المسافات
    const genre = new Genre({ name: req.body.name });

    if (!errors.isEmpty()) {
      // توجد أخطاء، لذا اعرض الاستمارة مرة أخرى مع قيم مُطهَّرة أو رسائل خطأ
      res.render("genre_form", {
        title: "Create Genre",
        genre: genre,
        errors: errors.array(),
      });
      return;
    } else {
      // البيانات الواردة من الاستمارة صالحة
      // تحقق مما إذا كان نوع الكتاب الذي يحمل الاسم نفسه موجود مسبقًا
      const genreExists = await Genre.findOne({ name: req.body.name }).exec();
      if (genreExists) {
        // نوع الكتاب موجود مسبقًا، لذا أعِد التوجيه إلى صفحة تفاصيل هذا النوع
        res.redirect(genreExists.url);
      } else {
        await genre.save();
        // حُفِظ نوع الكتاب الجديد، لذا أعِد التوجيه إلى صفحة تفاصيل نوع الكتاب
        res.redirect(genre.url);
      }
    }
  }),
];

أول شيء يجب ملاحظته هو أن المتحكم يحدّد مصفوفة من الدوال الوسيطة بدلًا من وجود دالة وسيطة واحدة مع الوسائط (req, res, next)، وتُمرَّر المصفوفة إلى دالة الموجِّه ويُستدعَى كل تابع حسب ترتيبه.

ملاحظة: يُعَد هذا الأسلوب ضروريًا، لأن أدوات التحقق من صحة البيانات Validators هي دوال وسيطة.

يعرِّف التابع الأول في المصفوفة أداة التحقق من صحة الجسم (body()‎) التي تتحقق من صحة الحقل وتطهّره، وتستخدم الدالةtrim()‎ لإزالة أي مسافة بيضاء لاحقة أو بادئة، وتتحقق من أن حقل الاسم name غير فارغ، ثم تستخدم الدالة escape()‎ لإزالة أيّ محارف HTML خطيرة.

[
  // تحقق من أن حقل الاسم غير فارغ
  body("name", "Genre name must contain at least 3 characters")
    .trim()
    .isLength({ min: 3 })
    .escape(),
  // …
];

بعد تحديد أدوات التحقق من الصحة، ننشئ دالة وسيطة لاستخراج أيّ أخطاء في عملية التحقق من صحة البيانات، إذ نستخدم التابع isEmpty()‎ للتحقق ما إذا كان هناك أيّ أخطاء في نتيجة التحقق من صحة البيانات، وإذا كان هناك أخطاء، فسنصيّر الاستمارة مرةً أخرى، ونمرّر كائن نوع الكتاب المُطهَّر ومصفوفة رسائل الخطأ (errors.array()‎).

// طلب العملية بعد التحقق من صحة البيانات وتطهيرها
asyncHandler(async (req, res, next) => {
  // استخراج أخطاء التحقق من صحة البيانات من الطلب
  const errors = validationResult(req);

  // إنشاء كائن نوع كتاب مع بيانات مُهرَّبة ومحذوف منها المسافات
  const genre = new Genre({ name: req.body.name });

  if (!errors.isEmpty()) {
    // توجد أخطاء، لذا اعرض الاستمارة مرة أخرى مع قيم مُطهَّرة أو رسائل خطأ
    res.render("genre_form", {
      title: "Create Genre",
      genre: genre,
      errors: errors.array(),
    });
    return;
  } else {
    // البيانات الواردة من الاستمارة صالحة
    // …
  }
});

إذا كانت بيانات اسم نوع الكتاب صالحة، فسنتحقق من وجود نوع كتاب Genre يحمل الاسم نفسه، لأننا لا نريد إنشاء نسخ مكررة. إذا كان الأمر كذلك، فسنعيد التوجيه إلى صفحة تفاصيل نوع الكتاب الموجود مسبقًا، وإن لم يكن الأمر كذلك، فسنحفظ نوع الكتاب Genre الجديد ونعيد التوجيه إلى صفحة تفاصيله. لاحظ أننا ننتظر باستخدام await نتيجة استعلام قاعدة البيانات باتباع النمط نفسه لمعالجات الوِجهة الأخرى.

// تحقق مما إذا كان نوع الكتاب الذي يحمل الاسم نفسه موجودًا مسبقًا
const genreExists = await Genre.findOne({ name: req.body.name }).exec();
if (genreExists) {
  // نوع الكتاب موجود مسبقًا، لذا أعِد التوجيه إلى صفحة تفاصيله
  res.redirect(genreExists.url);
} else {
  await genre.save();
  // حُفِظ نوع الكتاب الجديد، لذا أعِد التوجيه إلى صفحة تفاصيل نوع الكتاب
  res.redirect(genre.url);
}

سنستخدم هذا النمط في جميع المتحكمات اللاحقة، إذ سنشغّل أدوات التحقق من صحة البيانات (مع أدوات التطهير)، ثم نتحقق من الأخطاء وإما نعيد عرض الاستمارة مع معلومات الخطأ أو نحفظ البيانات.

العرض View

يُصيّر العرض نفسه في كل من متحكمات أو وِجهات GET و POST عندما ننشئ نوع كتاب Genre جديد (وسيُستخدَم لاحقًا عند تحديث نوع كتاب Genre) ، ولكن في حالة GET تكون الاستمارة فارغة ونمرر متغير العنوان فقط، بينما في حالة POST قد يكون المستخدم قد أدخل بيانات غير صالحة سابقًا ، لذا نمرّر في المتغير genre نسخة مُطهَّرة من البيانات المُدخَلة ونمرّر مصفوفة من رسائل الخطأ في المتغير errors. توضح الشيفرة التالية شيفرة المتحكم لتصيير القالب في كلتا الحالتين:

// ‫تصيير وِجهة GET
res.render("genre_form", { title: "Create Genre" });

// تصيير‫ وِجهة POST
res.render("genre_form", {
  title: "Create Genre",
  genre,
  errors: errors.array(),
});

أنشئ العرض "‎/views/genre_form.pug" وضع فيه النص التالي:

extends layout

block content

  h1 #{title}

  form(method='POST' action='')
    div.form-group
      label(for='name') Genre:
      input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' required='true' value=(undefined===genre ? '' : genre.name) )
    button.btn.btn-primary(type='submit') Submit

  if errors
    ul
      for error in errors
        li!= error.msg
 

يُعَد أغلب هذا القالب مألوفًا من مقال سابق، إذ سنوسّع أولًا القالب الأساسي layout.pug ونعدّل الكتلة block التي اسمها 'content'، ثم لدينا عنوان مع المتغير title الذي مرّرناه من المتحكم عبر التابع render()‎. لدينا بعد ذلك شيفرة Pug الخاصة باستمارة HTML التي تستخدم method="POST"‎ لإرسال البيانات إلى الخادم، وستُرسَل البيانات إلى عنوان URL نفسه الخاص بالصفحة لأن الإجراء action هو سلسلة نصية فارغة.

تعرِّف الاستمارة حقلًا واحدًا مطلوبًا من النوع النصي "text" اسمه "name"يمثل اسم نوع الكتاب، وتعتمد القيمة الافتراضية للحقل على ما إذا كان المتغير genre مُعرَّفًا أم لا. إذا اُستدعِي هذا الحقل من وِجهة GET، فسيكون فارغًا لأنها استمارة جديدة، وإذا اُستدعِي من وِجهة POST، فسيحتوي على القيمة غير الصالحة التي أدخلها المستخدم في الأصل.

الجزء الأخير من الصفحة هو شيفرة الخطأ التي تؤدي إلى طباعة قائمة الأخطاء، إذا كان متغير الخطأ مُعرَّفًا، وبالتالي لن يظهر هذا القسم عند عرض القالب في وجهة GET.

ملاحظة: عرضنا طريقة واحدة فقط لتصيير الأخطاء، ويمكنك أيضًا الحصول على أسماء الحقول المتأثرة من متغير الخطأ، واستخدامها للتحكم في مكان عرض رسائل الخطأ، وما إذا أدرتَ تطبيق شيفرة CSS مخصصة وغير ذلك.

كيف تبدو استمارة نوع الكتاب؟

شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط إنشاء نوع كتاب جديد Create new genre. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي، ويجب حفظ القيمة بعد إدخالها وستُنقَل إلى صفحة تفاصيل نوع الكتاب:

01 locallibary express genre create empty

الخطأ الوحيد الذي نتحقق منه من طرف الخادم هو أن حقل نوع الكتاب يجب ألّا يكون فارغًا. توضح لقطة الشاشة التالية كيف ستبدو قائمة الأخطاء إن لم تقدّم نوع كتاب (المحدَّد باللون الأحمر):

02 locallibary express genre create error

ملاحظة: تستخدم عملية التحقق من صحة البيانات التابع trim() لضمان عدم قبول المسافة البيضاء بوصفها اسمًا لنوع الكتاب، ويمكننا أيضًا التحقق من أن الحقل ليس فارغًا من طرف العميل من خلال إضافة القيمة required='true'‎ إلى تعريف الحقل في الاستمارة.

input#name.form-control(type='text', placeholder='Fantasy, Poetry etc.' name='name' value=(undefined===genre ? '' : genre.name), required='true' )

ترجمة -وبتصرُّف- للمقال Create genre form.

اقرأ المزيد


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...