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

البحث في الموقع

المحتوى عن 'تعلم تطوير الويب'.

  • ابحث بالكلمات المفتاحية

    أضف وسومًا وافصل بينها بفواصل ","
  • ابحث باسم الكاتب

نوع المحتوى


التصنيفات

  • الإدارة والقيادة
  • التخطيط وسير العمل
  • التمويل
  • فريق العمل
  • دراسة حالات
  • التعامل مع العملاء
  • التعهيد الخارجي
  • السلوك التنظيمي في المؤسسات
  • عالم الأعمال
  • التجارة والتجارة الإلكترونية
  • نصائح وإرشادات
  • مقالات ريادة أعمال عامة

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • PHP
    • Laravel
    • ووردبريس
  • جافاسكربت
    • لغة TypeScript
    • Node.js
    • React
    • Vue.js
    • Angular
    • jQuery
    • Cordova
  • HTML
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • SQL
  • لغة C#‎
    • ‎.NET
    • منصة Xamarin
  • لغة C++‎
  • لغة C
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • لغة Kotlin
  • لغة Rust
  • برمجة أندرويد
  • لغة R
  • الذكاء الاصطناعي
  • صناعة الألعاب
  • سير العمل
    • Git
  • الأنظمة والأنظمة المدمجة

التصنيفات

  • تصميم تجربة المستخدم UX
  • تصميم واجهة المستخدم UI
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب GIMP
    • كريتا Krita
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
    • Blender
  • نصائح وإرشادات
  • مقالات تصميم عامة

التصنيفات

  • مقالات DevOps عامة
  • خوادم
    • الويب HTTP
    • البريد الإلكتروني
    • قواعد البيانات
    • DNS
    • Samba
  • الحوسبة السحابية
    • Docker
  • إدارة الإعدادات والنشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
    • ريدهات (Red Hat)
  • خواديم ويندوز
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH
  • شبكات
    • سيسكو (Cisco)

التصنيفات

  • التسويق بالأداء
    • أدوات تحليل الزوار
  • تهيئة محركات البحث SEO
  • الشبكات الاجتماعية
  • التسويق بالبريد الالكتروني
  • التسويق الضمني
  • استسراع النمو
  • المبيعات
  • تجارب ونصائح
  • مبادئ علم التسويق

التصنيفات

  • مقالات عمل حر عامة
  • إدارة مالية
  • الإنتاجية
  • تجارب
  • مشاريع جانبية
  • التعامل مع العملاء
  • الحفاظ على الصحة
  • التسويق الذاتي
  • العمل الحر المهني
    • العمل بالترجمة
    • العمل كمساعد افتراضي
    • العمل بكتابة المحتوى

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
    • ماجنتو
    • بريستاشوب
    • أوبن كارت
    • دروبال
  • الترجمة بمساعدة الحاسوب
    • omegaT
    • memoQ
    • Trados
    • Memsource
  • برامج تخطيط موارد المؤسسات ERP
    • تطبيقات أودو odoo
  • أنظمة تشغيل الحواسيب والهواتف
    • ويندوز
    • لينكس
  • مقالات عامة

التصنيفات

  • آخر التحديثات

أسئلة وأجوبة

  • الأقسام
    • أسئلة البرمجة
    • أسئلة ريادة الأعمال
    • أسئلة العمل الحر
    • أسئلة التسويق والمبيعات
    • أسئلة التصميم
    • أسئلة DevOps
    • أسئلة البرامج والتطبيقات

التصنيفات

  • كتب ريادة الأعمال
  • كتب العمل الحر
  • كتب تسويق ومبيعات
  • كتب برمجة
  • كتب تصميم
  • كتب DevOps

ابحث في

ابحث عن


تاريخ الإنشاء

  • بداية

    نهاية


آخر تحديث

  • بداية

    نهاية


رشح النتائج حسب

تاريخ الانضمام

  • بداية

    نهاية


المجموعة


النبذة الشخصية

  1. تخطيط الشبكة Grid هو أسلوب تخطيط ثنائي الاتجاه يستخدم لترتيب عناصر صفحة الويب، إذ يسمح بتوضع المحتوى ضمن أسطر وأعمدة ويقدم ميزات عدة تسمح بتنفيذ التخطيطات المعقدة بأسلوب مباشر. وسنقدم لك في هذا المقال كل ما تحتاجه لتبدأ العمل على تخطيط الصفحات باستخدامه. قبل البدء في قراءة هذا المقال يتوجب عليك أن: تطلع على أساسيات HTML كما شرحناها في سلسلة مقالات مدخل إلى HTML. تتفهم أساسيات عمل CSS. ما هو تخطيط الشبكة Grid Layout؟ الشبكة هي مجموعة من الخطوط الأفقية والعمودية التي تشكل نموذجًا لترتيب العناصر ضمنه. يمكّننا هذا التخطيط من إنشاء تخطيطات لا تقفز فيها العناصر أو تغير اتساعها عند الانتقال من صفحة إلى أخرى مما يمنح موقع الويب تناسقًا أفضل. وتتكون الشبكة تقليديًا من أعمدة وصفوف، وفراغات بين كل سطر وكل عمود، وتُعرف هذه الفراغات بالأقنية gutters كما في الصورة التالية: إنشاء شبكة باستخدام CSS إن قررت أن تخطيط الشبكة هو ما يحتاجه تصميم صفحة الويب الخاصة بك، يمكنك استخدام لغة CSS لإنجاز الأمر. سنلقي نظرة على الميزات الأساسية لتخطيط الشبكة أولًا، ثم نستكشف كيفية إنشاء تخطيط شبكة بسيط لمشروعك. تحديد الشبكة بداية حمًل وافتح هذا الملف الذي سيكون نقطة الانطلاق للعمل ضمن محرر الكود وضمن المتصفح. يعرض هذا المثال حاوية تضم عدة عناصر أبناء لها تخطيط الانسياب الاعتيادي افتراضيًا، إذ تظهر تحت بعضها. سنتعامل مع هذا الملف في القسم الأول من مقالنا، ونطبق بعض التغييرات لاستيعاب سلوك تخطيط الشبكة. ولتعيين شبكة نستخدم القيمة grid للخاصية display. ستفعّل هذه القيمة تخطيط الشبكة، وستتحول جميع العناصر ضمن هذه الحاوية إلى عناصر شبكة grid item. لهذا ضع التصريح التالي ضمن الملف: .container { display: grid; } وعلى خلاف الصندوق المرن، لن تجد اختلافًا مباشرًا في توضع العناصر عند تطبيق القاعدة display: grid، لأن هذا التصريح سيضع العناصر ضمن شبكة من عمود واحد وستبقى فوق بعضها البعض كما هو الحال في الانسياب الاعتيادي. ولترى شيئًا أقرب إلى الشبكة، لا بد من إضافة أعمدة جديدة إليها. لهذا سنضع ثلاث أعمدة لكل منها اتسع مقداره 300 بكسل. يمكنك اختيار أي واحدة طول أو نسبة مئوية لضبط اتساع هذه الأعمدة. .container { display: grid; grid-template-columns: 200px 200px 200px; } أضف التصريح الثاني إلى قاعدة CSS ثم أعد تحميل الصفحة وسترى كيف رتبت العناصر نفسها ليحتل كل منها مكانًا في الشبكة. See the Pen grid1 by Hsoub Academy (@HsoubAcademy) on CodePen. الشبكات المرنة واستخدام الواحدة fr يمكنك تعيين الشبكات بوحدات fr إضافة إلى وحدات الطول والنسب المئوية. وتمثل هذه الوحدة جزءًا من المساحة المتاحة ضمن حاوية الشبكة وتتيح مرونة في تحجيم الأسطر والأعمدة. غيّر القاعدة السابقة كي ننشئ ثلاث أعمدة اتساع كل منها 1fr أي جزء من كل: .container { display: grid; grid-template-columns: 1fr 1fr 1fr; } تتميز الشبكة الآن بوجود مسارات مرنة، إذ توزع الوحدة fr المساحة المتوفرة بشكل تناسبي. وبإمكانك طبعًا استخدام أية قيم موجبة للمسارات في شبكتك كالتالي: .container { display: grid; grid-template-columns: 2fr 1fr 1fr; } إذ يأخذ المسار (عمود هنا) جزئين 2fr من أصل أربعة أجزاء كلية من المساحة المتوفرة بينما يأخذ كل من المسارين التاليين جزءًا واحدًا 1fr وبالتالي سيكون العمود الأول أكثر اتساعًا. وبإمكانك المزج بين الوحدة fr ووحدات الأطوال الثابتة، وفي هذه الحالة تحجز المساحة اللازمة للمسارات ثابتة الاتساع ثم يجري توزيع المساحة الباقية على المسارات التي تحمل قيمًا نسبية. See the Pen grid2 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: تتوزع المساحات المتاحة فقط بين المسارات عند استخدام الوحدة fr وليس كامل المساحة، بمعنى أنه إذا شغل مسار مساحة أكبر لأن محتواه أكبر سيقل الفراغ الذي تتقاسمه المسارات. الأقنية بين المسارات لإنشاء فراغات بين المسارات، نستخدم الخاصيات التالية: column-gap: للفراغات (الأقنية) بين الأعمدة. row-gap: الفراغات بين الأسطر. gap: خاصية تختصر الخاصيتين السابقتين. .container { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 20px; } يمكن أن تُقدّر أبعاد الأقنية بأي وحدة قياس ثابتة أو نسبة مئوية ما عدا الوحدة التناسبية fr. See the Pen grid3 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: سبُقت الخاصيات السابقة في المواصفات الأقدم بالبادئة -grid لكن المواصفات الجديدة ألغتها. مع ذلك يبقى استخدامها صحيحًا كاسم بديل. لهذا كي تبقى في مأمن من المشاكل وتُبقي شيفرتك منيعة استخدم كلتا النسختين من الخاصية: .container { display: grid; grid-template-columns: 2fr 1fr 1fr; grid-gap: 20px; gap: 20px; } تكرار المسارات الموجودة بإمكانك تكرار جميع مسارات الشبكة أو جزء منها باستخدام الدالة ()repeat التي تقدمها CSS. لترى ذلك، غير طريقة جدولة المسارات في التصريح السابق إلى الشكل التالي: .container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; } ستكون النتيجة ظهور ثلاثة أعمدة متناسبة 1fr من حيث الاتساع كما سبق. إذ يشير المعامل الأول من الدالة ()repeat إلى عدد مرات التكرار بينما يشير المعامل الثاني إلى عدد المسارات، فقد ترغب بتكرارها أكثر من مسار. الشبكات الصريحة والمضمنة لقد خصصنا في الشبكة أعمدةً فقط حتى اللحظة، بينما ظهرت الأسطر تلقائيًا لتناسب المحتوى وهذا مثال عن الشبكات الصريحة مقابل الشبكات الضمنية. وإليك الفرق: الشبكات الصريحة: تُنشأ باستخدام الخاصيتين grid-template-columns أو grid-template-rows. الشبكات الضمنية: توسّع الشبكة الصريحة عندما لا تستطيع الشبكة احتواء المحتوى كأن يظهر ضمن أسطر جديدة. تنشأ المسارات في الشبكات الضمنية بأبعاد تلقائية auto افتراضيًا، وهذا يعني عمومًا بأنه واسعة كفاية لتضم المحتوى. ولمنح الشبكات الضمنية مسارات بأبعاد مخصصة، بإمكانك استخدام الخاصيتين grid-auto-rows و grid-auto-columns. فإن خصصت القيمة 100px للخاصية grid-auto-rows سترى بأن ارتفاع الصفوف التي تنشأ هو 100 بكسل. .container { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: 100px; gap: 20px; } See the Pen grid4 by Hsoub Academy (@HsoubAcademy) on CodePen. الدالة ()minmax لن يكون ارتفاع 100 بكسل كافيًا في حالتنا السابقة إن أردنا وضع محتوى أطول من 100 بكسل، وبالتالي يحدث عندها الطفحان overflow. لهذا من الأفضل أن يكون ارتفاع المسارات 100 بكسل على الأقل وتكون قادرة على التوسع أكثر عند إضافة محتوى أكبر. ومن المعروف عمومًا في تصميم الويب أنه من الصعب توقع ارتفاع أي عنصر وخاصة عند إضافة محتوى أو عند تغيير حجم الخط مما قد يسبب مشاكل في التصميمات التي تحاول أن تجعل التصميم مثاليًا من كل النواحي. تتيح لنا الدالة ()minmax تحديد الحجم الأدنى والأقصى للمسار، فالصيغة minmax(100px, auto) للدالة تحدد حجمًا أدنى للمسار مقداره 100 بكسل، بينما حددت قيمة الحجم الأقصى ليكون تلقائيًا auto وتعني أن العنصر سيتوسع حتى يستوعب المحتوى. جرّب ضبط قيمة الخاصية grid-auto-rows باستخدام هذه الدالة: .container { display: grid; grid-template-columns: repeat(3, 1fr); grid-auto-rows: minmax(100px, auto); gap: 20px; } فإن أضفت محتوى أكبر سترى كيف يتوسع المسار حتى يستوعب المحتوى الجديد. لاحظ كيف يحدث التوسع أفقيًا مع السطر. أعمدة بقدر ما تتسع له المساحة بإمكاننا تطبيق الأفكار التي تعلمناها سابقًا حول ترتيب المسارات والتكرار والدالة ()minmax لإنشاء نماذج مفيدة. فمن الجيد أحيانًا أن تكون الشبكة قادرة على إنشاء أعمدة بقدر ما تتسع لها الحاوية. ولإنجاز ذلك، نضبط قيمة الخاصية grid-template-columns باستخدام الدالة ()repeat لكن بدل أن نمرر لها المعامل الأول عددًا، نمرر لها القيمة auto-fill. كما نستخدم الدالة ()minmax كمعامل ثاني ونحدد فيها قيمة أصغر قياس نريده للأعمدة، ونجعل قيمة أكبر قياس هو 1fr. جرّب ذلك بكتابة الشيفرة التالية: .container { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); grid-auto-rows: minmax(100px, auto); gap: 20px; } See the Pen Untitled by Hsoub Academy (@HsoubAcademy) on CodePen. نجح الأمر لأن الشبكة ستنشئ أكبر عدد ممكن من الأعمدة التي اتساعها 200 بكسل يمكن للحاوية استيعابها ومن ثم تقسم المساحات الفارغة الباقية بين جميع الأعمدة من خلال القيمة 1fr التي رأينا أنها توزّع المساحة بالتساوي بين المسارات. توزيع المحتوى وفقًا للأسطر ننتقل الآن من إنشاء الشبكات إلى وضع المحتوى ضمنها، وقد رأينا أن للشبكة أسطر، وهذه الأسطر مرقمة ابتداءً من 1 وتتعلق بنمط الكتابة في الصفحة. إذ يُكتب السطر الأول من العمود في الإنكليزية (التي تُكتب من اليسار إلى اليمين) انطلاقًا من الناحية اليسرى للشبكة ويكون هذا السطر في الأعلى، أما في العربية التي تُكتب من اليمين إلى اليسار فستبدأ كتابة المحتوى في العمود من يمين الشبكة. بإمكاننا ترتيب الأشياء وفقًا لهذه لأسطر الكتابة بتحديد بداية ونهاية السطر من خلال الخاصيات التالية: grid-column-start grid-column-end grid-row-start grid-row-end يمكن لجميع الخاصيات أن تأخذ رقم السطر كقيمة لها، كما يمكن استخدام الخاصيات المختصرة التالية: grid-column grid-row التي تتيح لك تحديد بداية ونهاية السطر مباشرة وتفصل بين القيمتين الشرطة المائلة /. لتجريب الأمر، نزّل الملف الذي يضم شيفرة المثال التالي (يمكنك أيضًا الاطلاع على كيفية عملها مباشرة على جيت-هاب). ويضم الملف شبكة ومقال بسيط. لاحظ كيف توضِّع القيمة كل عنصر ضمن خلية مخصصة من الشبكة، ولترتيب جميع العناصر في الشبكة باستخدام أسطر الشبكة، أضف القواعد التالية غلى نهاية شيفرة CSS: header { grid-column: 1 / 3; grid-row: 1; } article { grid-column: 2; grid-row: 2; } aside { grid-column: 1; grid-row: 2; } footer { grid-column: 1 / 3; grid-row: 3; } See the Pen grid6 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: بإمكانك أيضًا استخدام القيمة 1- كي تستهدف نهاية العمود أو السطر ثم اعدد ابتداءً من النهاية إلى البداية باستخدام القيم السالبة. وتذكر أن الأسطر تُعدّ دائمًا من حافة الشبكة الصريحة وليست الضمنية. ترتيب العناصر باستخدام الخاصية grid-template-areas توجد طريقة بديلة لترتيب العناصر في الشبكة باستخدام الخاصية grid-template-areas وإعطاء أسماء للعناصر المختلفة في التصميم. لتقف على الأمر، أزل شيفرة ترتيب العناصر وفق الأسطر في المثال السابق ثم أضف الشيفرة التالية بدلًا عنها: .container { display: grid; grid-template-areas: "header header" "sidebar content" "footer footer"; grid-template-columns: 1fr 3fr; gap: 20px; } header { grid-area: header; } article { grid-area: content; } aside { grid-area: sidebar; } footer { grid-area: footer; } أعد تحميل الصفحة وسترى أن العناصر قد رُتبّت بالطريقة السابقة نفسها دون أن تستخدم أية أرقام للأسطر. See the Pen grid7 by Hsoub Academy (@HsoubAcademy) on CodePen. إليك قواعد الخاصية grid-template-areas: عليك ملئ كل خلية من خلايا الشبكة. كرر الاسم لتضم خليتين في خلية واحدة. استخدم النقطة . لتترك الخلية فارغة. لا بد أن تكون مساحة الشبكة مربّعة. فلا يمكنك مثلًا تصميم شبكة على شكل حرف L. لا يمكن تكرار المساحات في أماكن مختلفة. بإمكانك تجريب عدة خيارات في تخطيط الشبكة بهذه الطريقة، حاول مثلًا وضع التذييل تحت المقالة فقط وأن تجعل الشريط الجانبي يمتد للأسفل. هذا الطريقة في تخطيط الشبكة جميلة جدًا لأنها واضحة، وبمجرد النظر إلى شيفرة CSS ستعرف تمامًا ما الذي سيحدث. إطارات العمل مع الشبكة في شبكات CSS تميل إطارات العمل مع الشبكات grid frameworks لتكون شبكات مكونة من 12-16 عمود. ولا حاجة بالطبع إلى أدوات أخرى سوى شبكة CSS لتنفيذ إطار العمل هذا، فهي موجودة فعلًا في المواصفات. نزّل الملف الذي يضم شيفرة المثال التالي وهي عبارة عن حاوية مكونة من شبكة تتكون من 12 عمودًا، وتبقى شيفرة HTML نفسها التي استخدمناها في المثال السابق. بإمكاننا الآن استخدام التوضع المبني على الأسطر لتوزيع المحتوى ضمن شبكتنا ذات 12 عمودًا: header { grid-column: 1 / 13; grid-row: 1; } article { grid-column: 4 / 13; grid-row: 2; } aside { grid-column: 1 / 4; grid-row: 2; } footer { grid-column: 1 / 13; grid-row: 3; } See the Pen grid8 by Hsoub Academy (@HsoubAcademy) on CodePen. إن استخدمت الأداة Firefox Grid Inspector لتوضح خطوط الشبكة في تصميمك، سترى كيف تعمل تمامًا شبكتنا ذات 12 عمودًا: الخلاصة تجولنا في مقالنا على الميزات التي يقدمها تخطيط الشبكة Grid Layout في لغة CSS، واستعرضنا أمثلة مختلفة على استخدامه في ترتيب عناصر صفحة الويب وتنفيذ التخطيطات المعقدة بسهولة كبيرة، ومن المفترض أن تصبح قادرًا الآن على استخدام الشبكات بفعالية ضمن تصميماتك. ترجمة -وبتصرف- للمقال: Grids اقرأ أيضًا المقال السابق: تخطيط الصندوق المرن Flexbox في صفحات الويب مدخل إلى تخطيط صفحات الويب باستخدام CSS تعرف على أساسيات CSS تقنيات كتابة شيفرات CSS احترافية وسهلة الصيانة التحكم في تموضع العناصر في CSS
  2. لربما صادفتك بعض المشاكل عندما بنيت في مقال سابق لعبة تخمين الرقم الصحيح، أو وجدت أن هذه اللعبة لا تعمل بالشكل المطلوب! لهذا سنفرد هذا المقال لمساعدتك في البحث عن المشكلات البرمجية التي قد تصادفك وإيجاد حل لها، من خلال تزويدك ببعض التلميحات والنصائح عن كيفية إيجاد وإصلاح اﻷخطاء. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسوب أساسيات HTML أساسيات عمل CSS أساسيات جافا سكريبت أنواع اﻷخطاء البرمجية عندما ترتكب خطأ عند كتابة الشيفرة ستواجه عمومًا نوعين من اﻷخطاء: أخطاء في الصياغة (قواعدية) syntax error: وتنتج عن الخطأ في كتابة التعليمات مما يسبب توقف عمل البرنامج تمامًا أو توقف جزء منه، وستظهر عادة بعض رسائل الخطا أيضًا. وهي عادة قابلة للإصلاح إن كنت تألف العمل على اﻷدوات المناسبة للتنقيح وتعرف ما تعنيه رسالة الخطأ. أخطاء منطقية logic errors: في هذه الحالة تكون الصياغة صحيحة، لكن الشيفرة لا تعمل كما ينبغي، أي أن البرنامج يُنفّذ بنجاح لكنه يعطي نتائج غير صحيحة. وهي أخطاء أصعب إصلاحًا لعدم توفر رسائل أخطاء توجهك إلى مصدر الخطأ. فالعملية إذًا ليست بهذه السهولة. وما ستراه عندما تتعمق في البرمجة وجود تفاصيل أخرى لأنواع اﻷخطاء، لكن التصنيف السابق هو كل ما تحتاجه في هذا المستوى المبكر من مسيرتك، وسنعمل على هذين النوعين في هذا المقال. مثال عن خطأ حتى نبدأ العمل، عليك أن تعود إلى لعبة خمّن الرقم الصحيح، لكن ما سنفعله أننا سنعود إلى نسخة أخرى من اللعبة ارتُكبت فيها أخطاء عمدًا. لهذا حمّل نسخة منها. افتح الملف ضمن المحرر النصي وضمن المتصفح. جرّب أن تلعب، وسترى أن اللعبة لا تعمل عند النقر على زر "Submit guess". ملاحظة: ربما قد حصلت بالفعل على نسخة لا تعمل عند تجريب كتابة الشيفرة بنفسك، لكننا نريد منك أن تعمل على نسختنا في هذا المقال كي تتعلم التقنيات التي نقدمها لحل المشاكل ثم يمكنك العودة بعدها لتصحيح أخطاء نسختك. ما سنفعله حاليًا هو الانتقال إلى "طرفية المطوّر" لنرى إن كانت تعرض أية أخطاء صياغة كي نحاول إصلاحها، وهذا ما ستتعلمه تاليًا. إصلاح أخطاء الصياغة قد تكون كتبت بعض أوامر جافا سكريبت في طرفية جافا سكريبت ضمن أدوات مطوري ويب DevTools، لكن الفائدة الأكبر من كتابة الشيفرة في أدوات المطور هي رسائل أخطاء الصياغة التي تعرضها لك الطرفية عند وقوع الخطأ. لنبدأ باصطياد اﻷخطاء إذًا! 1.انتقل إلى النافذة التي ظهرت number-game-errors.html ثم افتح طرفية جافا سكريبت وسترى رسالة خطأ التالية: 2. السطر اﻷول من الرسالة هو التالي Uncaught TypeError: guessSubmit.addeventListener is not a function number-game-errors.html:86:15` يخبرنا الجزء الأول عن سبب الخطأ ويخبرنا الجزء الثاني عن موقع الخطأ في الشيفرة وهو السطر 86، المحرف 15 في الملف "number-game-errors.html". 3. إن ألقينا نظرة على موقع الخطأ في السطر 86 سنجد السطر التالي: guessSubmit.addeventListener("click", checkGuess); تحذير: قد لا يكون الخطأ موجودًا لديك في السطر 86 في حال كنت تستخدم موسّعًا يُشغّل خادمًا على جهازك، فقد يسبب ذلك إقحام شيفرة إضافية إلى شيفرتك، لهذا قد تشير أدوات مطوري ويب إلى مكان للخطأ مختلف عن السطر 86. 4. ينص الخطأ على أن الشيفرة لا تمثل دالة، وهذا يعني أن الدالة التي نستدعيها لم يميزها مفسّر جافا سكريبت. وغالبًا ما يكون السبب خطأ في كتابة شيء ما. فإن لم تكن متأكدًا من الكتابة الصحيحة لتعليمة أو صياغة معينة فمن الأفضل مراجعة توثيق جافا سكريبت على موسوعة حسوب والبحث عن "addeventListener". 5. بالعودة إلى التوثيق السابق نرى أن الخطأ كان في تهجئة اسم الدالة التي تُكتب بالشكل addEventListener وليس بالشكل addeventListener ومن مميزات لغة جافا سكريبت أنها حساسة لحالة الأحرف، وبالتالي تصحيح كتابة اسم الدالة سيحل المشكلة. نفّذ ذلك وتأكد من حل المشكلة. جولة ثانية على أخطاء الصياغة 1. احفظ التغييرات على صفحتك وحدّث المتصفح وسترى أن الخطأ قد زال. 2. لو حاولت مجددًا تخمين رقم ثم النقر على زر اﻹرسال سترى خطأً آخر: 3. الخطأ هذه المرة هو التالي: Uncaught TypeError: can't access property "textContent", lowOrHi is null قد تجد رسالة مختلفة هنا وفقًا للمتصفح الذي تستخدمه. فالرسالة السابقة هي ما يعرضه متصفح فايرفوكس، أما رسالة خطأ متصفح كروم مثلًا هي: Uncaught TypeError: Cannot set properties of null (setting 'textContent') الخطأ ذاته لكن كل متصفح يصفه بطريقة مختلفة عن اﻵخر. ملاحظة: لا يُعرض هذا الخطأ فور تحميل الصفحة لأنه يحدث ضمن الدالة كتلة ()checkGuess. وكما سنرى في مقالات قادمة أن الشيفرة داخل الدوال تنفذ في سياق منفصل عن بقية الشيفرة، وبهذه الحالة لن تعمل الشيفرة ضمن الدالة ولن يقع الخطأ حتى تّنفّذ الدالة ()checkGuess في السطر 86. 4. ألق نظرة على الخطأ في السطر 80 وسترى الشيفرة التالية: lowOrHi.textContent = "Last guess was too high!"; 5. تحاول الشيفرة في هذا السطر إسناد سلسلة نصية إلى الخاصية textContent للمتغير lowOrHi لكن اﻷمر لا ينجح لأن المتغيّر لا يتضمن ما يُفترض أنه يتضمنه. لهذا حاول أن تبحث عن ورود آخر لهذا المتغّير وستجده في السطر 49: const lowOrHi = document.querySelector("lowOrHi"); نحاول في هذا السطر أن نسند إلى lowOrHi مرجعًا إلى عنصر من عناصر صفحة HTML، لنلقي إذًا نظرة على ما يحتويه هذا المتغير بعد هذه العملية من خلال كتابة الأمر التالي في السطر 50: console.log(lowOrHi); تطبع هذه التعليمة قيمة المتغير lowOrHi على الطرفية. 6. احفظ التغييرات وحدّث المتصفح وسترى نتيجة التعليمة ()console.log على النحو التالي: إن قيمة هذا المتغير هي null لهذا عرض متصفح رسالة الخطأ مشيرًا إلى أن lowOrHi is null. لهذا هنالك خطأ بالتأكيد في السطر 49. إذ تعني القيمة null لاشيء أو لا قيمة، وأخفقت في النهاية عملية إسناد مرجع إلى التغيّر. 7. لنفكر بسبب حدوث مشكلة السطر 49، إذ يحتوي هذا السطر التابع ()document.querySelector للحصول على مرجع إلى عنصر من خلال البحث عن محدد CSS المطابق. ولو بحثا عن العنصر المطلوب لوجدنا أنه الفقرة النصية <p class="lowOrHi"></p> 8. إذا ما نريده هو محدد صنف class selector يبدأ سمه بنقطة .، لكن المحدد الذي مُرر إلى التابع ()querySelector في السطر 49 كان بلا نقطة، وقد يكون ذلك سبب المشكلة لهذا جرّب إصلاح اﻷمر بتحويل lowOrHi إلى lowOrHi. 9. جرّب حفظ التغييرات وتحديث الصفحة، ومن المفترض حينها أن تكون نتيجة التعليمة ()console.log هي العنصر <p>، وهكذا نكون قد أصلحنا خطأ آخر. يمكنك اﻵن حذف السطر ()console.log أو إبقاءه كمرجع -اﻷمر يعود إليك-. جولة ثالثة على أخطاء الصياغة 1. إن حاولت تجريب اللعبة مجددًا ستجد أن إصلاحاتك ناجحة وستعمل بشكل رائع حتى اللحظة التي تنهي فيها اللعبة بأن تخمّن الرقم الصحيح أوتنتهي محاولاتك. 2. ستخفق اللعبة مجددًا في هذه المرحلة وسيظهر من جديد الخطأ الأول "TypeError: resetButton.addeventListener is not a function"، لكن مصدره هذه المرة السطر 94. 3. بالعودة إلى هذا السطر والتدقيق فيه نجد أن الخطأ المرتكب هو نفسه الخطأ المرتكب في المرة اﻷولى، لهذا غيّر addeventListener إلى addEventListener. الخطأ المنطقي يجب أن تعمل اللعبة جيدًا بعد تطبيق اﻹصلاحات السابقة جميعها، لكن بعد أن تجرب اللعبة عدة مرات ستلاحظ بلا شك أن الرقم العشوائي الذي تختاره اللعبة وعليك تخمينه هو دائما (1). وبالتأكيد لا نريد أن تكون اللعبة بهذا الشكل! لا شك إذًا أن هناك خطأ ما في منطق اللعبة لأنها لا تعيد أية أخطاء لكنها في المقابل لا تعطي النتيجة المتوقعة منها. 1. ابحث عن المتغيّر randomNumber والأسطر التي ضُبط فيها بداية وستجد في السطر 45 تقريبًا التصريح عن المتغّير وإسناد قيمة له: let randomNumber = Math.floor(Math.random()) + 1; 2. ستجد في السطر 113 الأمر الذي يوّلد الرقم العشوائي قبل كل بداية لعبة randomNumber = Math.floor(Math.random()) + 1; 3. للتحقق أن هذه اﻷسطر هي مصدر المشكلة، سنحاول استخدام التعليمة ()console.logأسفل كل من السطرين السابقين كالتالي: console.log(randomNumber); 4. احفظ التغييرات وحدّث المتصفح ثم جرّب أن تلعب بعض الجولات وسترى أن قيمة المتغيّر randomNumber تُسجّل (1) دائمًا في الطرفية. العمل على إصلاح اﻷخطاء المنطقية لنتأمل كيفية عمل التابع ()Math.random في محاولة فهم الخلل، إذ يوّلد هذا التابع رقمًا عشريًا عشوائيًا بين 0 و 1 مثل 0.5463723462. ثم نمرر العدد العشوائي الناتج إلى تابع آخر هو ()Math.floor والذي يقرّب قيمة العدد العشري إلى العدد الصحيح اﻷقل منه مباشرة، ثم نضيف إلى الناتج العدد 1. Math.floor(Math.random()) + 1; إن تقريب عدد عشري بين 0 و 1 إلى أقرب عدد صحيح أصغر منه مباشرة يعطي الرقم 0 دائمًا وعند إضافة الرقم 1 سيكون الناتج 1 دائمًا! لهذا من الواضح أن علينا ضرب العدد العشوائي بالعدد 100 للحصول على عدد عشري بين 0 و 100 ثم نقرّبه إلى أقرب عدد صحيح أصغر منه فستكون النتيجة عدد عشوائي صحيح بين 0 و 99: Math.floor(Math.random() * 100); نضيف بعد ذلك الرقم 1 لنحصل على عدد بين 1 و100 Math.floor(Math.random() * 100) + 1; جرّب أن تعدّل السطرين اللذان يضمان المنطق السابق ثم احفظ التغييرات وحدّث المتصفح وسترى أن اللعبة تعمل اﻵن كما نريد. أخطاء شائعة أخرى ستقع في العديد من الأخطاء الشائعة عند كتابة شيفرتك، لهذا سنلقي نظرة على أهمها في الأقسام التالية. الخطأ SyntaxError: missing ; before statement يشير هذا الخطأ عمومًا إلى أنك أغفلت الفاصلة المنقوطة ; في نهاية أحد أسطر الشيفرة، وقد يكون الخطأ أحيانًا أكثر غموضًا. فلو بدلنا السطر التالي ضمن الدالة ()checkGuess const userGuess = Number(guessField.value); إلى السطر التالي const userGuess === Number(guessField.value); سيعرض المتصفح الخطأ السابق لأنه يعتقد بأن تحاول عمل شيء مختلف. لهذا عليك الانتباه إلى عدم الخلط بين عامل الإسناد = الذي يُسند قيمة إلى متغير ما، وبين عامل المساواة المنطقي === الذي يختبر تساوي قيمة مع قيمة أخرى ثم يعيد قيمة منطقية قيمتها صحيح true أو خطأ false. تخبرك اللعبة أنك فزت دائمًا سواء كان تخمينك صحيحًا أو خاطئًا قد يكون هذا الخطأ نتيجة للخلط بين عاملي اﻹسناد والمساواة. فلو غيرنا السطر التالي ضمن الدالة ()chsckGuess if (userGuess === randomNumber) { إلى السطر التالي: if (userGuess = randomNumber) { سيعيد الاختبار في هذه الحالة القيمة true دائمًا وستخبرك اللعبة عندها أنك فائز دائمًا. انتبه لذلك! الخطأ SyntaxError: missing ) after argument list هذا الخطأ بسيط نوعًا ما، ويعني عمومًا أنك أغفلت كتابة قوس الإغلاق في نهاية دالة أو تابع عند استدعائه. الخطأ: SyntaxError: missing : after property id يرتبط هذا الخطأ عادة بإعداد كائن جافا سكريبت بطريقة غير صحيحة، لكننا حاولنا إظهاره هنا بتبديل هذا السطر: function checkGuess() { بالسطر التالي function checkGuess( { إذ سيعتقد المتصفح انك تحاول تمرير محتوى دالة كوسيط إلى دالة أخرى. عليك الانتباه إلى اﻷقواس جيدًا! الخطأ: SyntaxError: missing } after function body وهو خطأ بسيط يشير إلى إغفال أحد القوسين المعقوصين في بنية دالة أو كتلة شرطية، يمكنك توليد هذا الخطأ بحذف القوس المعقوص من نهاية الدالة ()checkGuess الخطأ: '*SyntaxError: expected expression, got '*string أو SyntaxError: unterminated string literal تعني هذه اﻷخطاء عمومًا أنك أغفلت أحد علامات التنصيص التي تغلق سلسلة نصية أو تبدؤها. ففي الخطأ اﻷول يستبدل المتصفح القيمة string بالقيمة غير المتوقعة التي وجدها بدلًا من إشارة التنصيص في بداية السلسلة النصية. أما الخطأ الثاني فيعني أنك لم تنهي السلسلة النصية بإشارة تنصيص. وفي جميع اﻷخطاء التي قد تواجهك، فكّر بالطريقة التي اتبعناها في أمثلتنا لحل اﻷخطاء. فعندما يقع الخطأ، انتقل إلى السطر الذي وقع فيه والذي تشير إليه طرفية أدوات مطوري ويب وحاول أن تتفحص ما يمكن أن يكون خاطئًا. وتذكر دائمًا أنه ليس من الضرورة أن تكون اﻷخطاء في نفس السطر، وأن الخطأ قد لا ينتج بالضرورة عن نفس اﻷسباب التي تحدثنا عنها في مقالنا. الخلاصة هكذا نكون قد تعرفنا على أساسيات تتبع اﻷخطاء البسيطة في جافا سكريبت. وتذكر أنه ليس من السهل دائمًا اكتشاف الخطأ في الشيفرة البرمجية، لكن ما قدمناه في هذه المقالة قد يوفر عليك ساعات من العناء ويسرّع وتيرة اكتشافك اﻷسباب المحتملة للأخطاء وخاصة في بداية رحلتك في تعلم البرمجة. ترجمة -وبتصرف لمقال What went wrong troubleshooting JavaScript اقرأ أيضًا المقال السابق: تجربتك اﻷولى مع جافا سكريبت مبادئ كتابة جافا سكريبت متسقة ومفهومة كيفية التعامل مع الأخطاء البرمجية الزلات البرمجية والأخطاء في جافاسكريبت
  3. تظهر أهمية لغات البرمجة في إنجاز مهام مكررة وبسرعة مثل الحسابات المتعددة، أو في الحالات التي ينبغي فيها إنجاز الكثير من اﻷعمال المتشابهة. لهذا سنخصص هذا المقال للحديث عن الحلقات في جافا سكريبت التي تعالج تلك اﻷمور. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب أساسيات HTML أساسيات عمل CSS أساسيات لغة جافا سكريبت ما هي فائدة الحلقات؟ تتمحور فكرة الحلقة على تكرار الشيء ذاته باستمرار. قد تختلف الشيفرة قليلًا في كل جولة أو قد تبقى الشيفرة كما هي وتتغير قيمة المتغيرات فيها. مثال عن شيفرة تستخدم الحلقات لنفرض أنك تريد رسم 100 دائرة عشوائية ضمن العنصر <canvas> انقر الزر "Update" لتنفيذ الشيفرة مرة تلو اﻷخرى لترى مجموعات مختلفة من الدوائر): See the Pen Looping code1 by Hsoub Academy (@HsoubAcademy) on CodePen. إليك شيفرة جافا سكريبت التي تنفّذ المطلوب: const btn = document.querySelector("button"); const canvas = document.querySelector("canvas"); const ctx = canvas.getContext("2d"); document.addEventListener("DOMContentLoaded", () => { canvas.width = document.documentElement.clientWidth; canvas.height = document.documentElement.clientHeight; }); function random(number) { return Math.floor(Math.random() * number); } function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); for (let i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc( random(canvas.width), random(canvas.height), random(50), 0, 2 * Math.PI, ); ctx.fill(); } } btn.addEventListener("click", draw); مع أو بدون حلقة ليس عليك فهم الشيفرة بأكملها اﻵن، لكن ألق نظرة على الجزء الذي يرسم 100 دائرة في الشيفرة: for (let i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc( random(canvas.width), random(canvas.height), random(50), 0, 2 * Math.PI, ); ctx.fill(); } تُعيد الدالة random المعرّفة سابقًا رقمًا صحيحًا محصورًا بين الصفر و 1-x. أما الفكرة العامة فهي أننا نستخدم حلقة لتكرار الشيفرة السابقة 100 مرة، وكل مرة ترسم الشيفرة دائرة في مكان عشوائي من الصفحة. وتبقى كمية الشيفرة اللازمة لرسم الدائرة نفسها سواءً رسمنا 10 أو 100 أو 1000 دائرة، وما يتغير فقط هو عدد مرات التكرار، ولو لم نستخدم الحلقة كان لا بد من كتابة الشيفرة التالية في كل مرة نريد رسم دائرة: ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc( random(canvas.width), random(canvas.height), random(50), 0, 2 * Math.PI, ); ctx.fill(); وسيكون الأمر مملًا وصعب التنقيح والصيانة. استخدام الحلقات للتنقل ضمن مجموعات غالبًا ما تستخدم الحلقة للتنقل بين مجموعة من العناصر لتنفيذ أمر ما على كل عنصر. ومن اﻷمثلة على المجموعات نجد المصفوفات، إضافة إلى بُنى أخرى في جافا سكريبت مثل المجموعات Sets و الخرائط Maps. الحلقة for...of تعد هذه الحلقة أساسية في النقل ضمن عناصر مجموعة: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; for (const cat of cats) { console.log(cat); } في مثالنا هذا، تنص العبارة البرمجية for (const cat of cats) على ما يلي: إذا كانت cats مجموعة من العناصر استخرج العنصر اﻷول منها. إسناد قيمة هذا العنصر إلى المتغير cat ومن ثم نفّذ الشيفرة الموجودة ضمن القوسين المعقوصين {}. استخراج العنصر التالي وكرر الخطوة 2 حتى تصل إلى نهاية المجموعة. التابعين filter و map تقدّم جافا سكريبت حلقات مخصصة أيضًا للتنقل ضمن المجموعات، وسنشير إلى اثنتين منهما. الحلقة اﻷولى يمثلها التابع ()map الذي ينفّذ شيئًا محددًا على كل عناصر المجموعة ويعيد مجموعة جديدة تضم العناصر المتغيرة: function toUpper(string) { return string.toUpperCase(); } const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; const upperCats = cats.map(toUpper); console.log(upperCats); // [ "LEOPARD", "SERVAL", "JAGUAR", "TIGER", "CARACAL", "LION" ] مررنا في مثالنا السابق دالة إلى التابع ()cats.map الذي يستدعيها مرة لكل عنصر من عناصر المصفوفة، ثم يمرر هذا العنصر إلى الدالة ويجمّع القيم العائدة عنها بعد كل استدعاء ضمن مصفوفة جديدة تحتوى عناصر المصفوفة القديمة بعد تغيير حالة أحرف هذه العناصر. [ "LEOPARD", "SERVAL", "JAGUAR", "TIGER", "CARACAL", "LION" ] أما الحلقة الثانية فيقدمها التابع ()filter الذي يختبر كل عنصر من عناصر المجموعة ويعيد مصفوفة جديدة تضم العناصر التي تحقق شرطًا معينًا: function lCat(cat) { return cat.startsWith("L"); } const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; const filtered = cats.filter(lCat); console.log(filtered); // [ "Leopard", "Lion" ] يبدو الأمر مشابهًا للتابع ()map، ما عدا أن القيمة التي تعيدها الدالة التي نمررها هي قيمة منطقية. فإذا كانت true يُضمّن العنصر في المصفوفة الجديدة وإلا لن يُضمَّن. أما وظيفة الدالة في هذا المثال فهي اختبار العناصر التي تبدأ بالحرف "L"، لذا ستكون النتيجة كالتالي: [ "Leopard", "Lion" ] ولاحظ كيف يُستخدم التابعان ()map و ()filter في بعض اﻷحيان مع دوال وهذا ما سندرسه لاحقًا. وباستخدام الدوال يمكن إعادة كتابة الشيفرة السابقة كالتالي: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; const filtered = cats.filter((cat) => cat.startsWith("L")); console.log(filtered); // [ "Leopard", "Lion" ] حلقة forالمعيارية في مثالنا السابق الذي يدور حول رسم دوائر، لم يكن عليك التنقل بين عناصر مجموعة بل تكرار نفس الشيفرة 100 مرة. عليك في حالات كهذه استخدام الحلقة for التي لها الصياغة التالية: for (initializer; condition; final-expression) { // code to run } لدينا هنا: الكلمة المحجوزة for. ضمن القوسين التاليين ثلاثة عناصر تفصل بينها فاصلة: مهييء initializer: وهو متغيّر له قيمة عددية محددة سوف تزداد لتعدد المرات التي تثنفَّذ فيها الحلقة. ويُشار إليها أحيانًا بالعداد. شرط condition: ويحدد متى ستنتهي الحلقة. وهو عادة عبارة برمجية تستخدم عامل موازنة وتختبر إذا ما تحقق الشرط أم لا. عبارة إنهاء final-expression:وتُنفّذ أو تُقيَّّم في كل مرة تنهي الحلقة أحد التكرارات. وتُستخدم عادة لتزيد (أو تنقص) العداد لتقربه من النقطة التي لا يتحقق بعدها الشرط (العنصر الثاني). أقواس معقوصة تضم كتلة من الشيفرة التي تُنفَّذ في كل تكرار للحلقة. مثال عن حلقة for: حساب مربعات أعداد لنلق نظرة على مثال حقيقي لنتصوّر اﻷمر بوضوح أكثر: const results = document.querySelector("#results"); function calculate() { for (let i = 1; i < 10; i++) { const newResult = `${i} x ${i} = ${i * i}`; results.textContent += `${newResult}\n`; } results.textContent += "\nFinished!"; } const calculateBtn = document.querySelector("#calculate"); const clearBtn = document.querySelector("#clear"); calculateBtn.addEventListener("click", calculate); clearBtn.addEventListener("click", () => (results.textContent = "")); تُعطي الشيفرة السابقة الخرج التالي: See the Pen Looping code2 by Hsoub Academy (@HsoubAcademy) on CodePen. تحسب الشيفرة السابقة مربعات الأعداد من 1 إلى 9 وتطبع النتيجة، وتُستخدم الحلقة for كأساس لإجراء الحسابات. لنحلل بشيء من التفصيل العبارة for (let i = 1; i < 10; i++): المهيء (let i = 1) يبدأ العداد i من القيمة 1. ولاحظ ضرورة استخدام التعليمة let لأننا نعيد إسناد قيمة له عند كل تكرار. الشرط (i < 10) ويعني أن تستمر الحلقة في تكرار نفسها طالما أن i أصغر من 10. عبارة اﻹنهاء (++i) وتضيف واحد إلى العداد عند إتمام كل تكرار. نحسب داخل الحلقة مربع القيمة الحالية للمتغير i بالشكل :i*i، وننشئ عبارة نصية تضم العملية التي أجريناها ونتيجتها. كما نضيف السلسلة n\ إلى آخر السلسلة النصية كي تُطبع نتيجة التكرار التالي على سطر جديد. وما سيحدث اﻵن هو: خلال التكرار الأول ستكون i=1، وستكون النتيجة 1x1=1. خلال التكرار الثاني ستكون i=2، وستكون النتيجة 2x2=4. تستمر الحلقة بهذا الشكل. عندما تصبح i=10 تتوقف الحلقة عند تنفيذ الشيفرة ما داخل القوسين المعقوصين وتنتقل إلى الشفرة التي تلي الحلقة وتطبع الرسالة !finished على سطر جديد. التنقل بين عناصر مجموعة باستخدام الحلقة for بإمكانك استخدام الحلقة for للتنقل بين عناصر مجموعة بدلًا من for...of. لنعد إلى مثال for...of السابق: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; for (const cat of cats) { console.log(cat); } سنكتب الشيفرة كالتالي: const cats = ["Leopard", "Serval", "Jaguar", "Tiger", "Caracal", "Lion"]; for (let i = 0; i < cats.length; i++) { console.log(cats[i]); } نبدأ هذه الحلقة عند قيمة i=0 وننهيها عندما تصل قيمة i إلى طول المصفوفة. ونستخدم i ضمن الحلقة للوصول إلى كل عنصر من عناصرها. ستعمل الحلقة جيدًا وقد استخدمت قبل ظهور وكانت الطريقة المعيارية للتنقل بين عناصر مصفوفة، لكن قد تزداد الفرصة عند استخدامها إلى ظهور ثغرات في الشيفرة مثل: أن تبدأ i من القيمة 1 وتنسى أن تعداد المصفوفات يبدأ من 0. قد تنهي الحلقة عند i<=cats.length وتنسى أن نآخر دليل في المصفوفة هو length-1. لأسباب كهذه، يُفضّل استخدام for...of إن استطعت ذلك، لكنك ستحتاج في بعض اﻷحيان إلى for عند النقل بين عناصر مصفوفة. فإن أردت مثلًا طباعة رسالة تضم قائمة بأسماء القطط باستخدام الشيفرة التالية: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; for (const cat of cats) { myFavoriteCats += `${cat}, `; } console.log(myFavoriteCats); // "My cats are called Pete, Biggles, Jasmine, " لن تكون النتيجة (من ناحية تكوين الجملة في اﻹنكليزية) مصاغة بالكل الصحيح: My cats are called Pete, Biggles, Jasmine, لهذا يجب أن تتعامل الحلقة مع آخر اسم بشكل مختلف حتى تكون النتيجة بالشكل: My cats are called Pete, Biggles, and Jasmine. ولفعل ذلك، ينبغي أن نعرف دليل آخر عنصر ونحتاج عندها إلى الحلقة for لتفحّص قيمة i: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; for (let i = 0; i < cats.length; i++) { if (i === cats.length - 1) { // We are at the end of the array myFavoriteCats += `and ${cats[i]}.`; } else { myFavoriteCats += `${cats[i]}, `; } } console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine." الخروج من الحلقة باستخدام التعليمة break إن أردت الخروج من حلقة باكرًا قبل انتهاء جميع التكرارات، استخدم التعليمة break التي رأيناها في مقال العبارات الشرطية عند التعامل مع البنية switch، إذ تنهي التعليمة break البنية switch في الحالة case التي تحقق تطابقًا مع الشرط. يتكرر اﻷمر ذاته مع الحلقات، إذ يمكن إنهاء الحلقة مباشرة باستخدام break وجعل المتصفح يقفز إلى الشيفرة التي تلي الحلقة. فلو أردنا مثلًا تطبيقًا للبحث عبر مصفوفة تضم جهات اتصال وأرقام هواتف ونريد فقط رقمًا محددًا، سنحتاج إلى شيفرة HTML تضم عنصر إدخال نصي <input> لإدخال الرقم الذي نبحث عنه وزر <button> لتنفيذ العملية وفقرة نصية <p> لعرض النتيجة: <label for="search">Search by contact name: </label> <input id="search" type="text" /> <button>Search</button> <p></p> ستكون شيفرة جافا سكريبت كالتالي: const contacts = [ "Chris:2232322", "Sarah:3453456", "Bill:7654322", "Mary:9998769", "Dianne:9384975", ]; const para = document.querySelector("p"); const input = document.querySelector("input"); const btn = document.querySelector("button"); btn.addEventListener("click", () => { const searchName = input.value.toLowerCase(); input.value = ""; input.focus(); para.textContent = ""; for (const contact of contacts) { const splitContact = contact.split(":"); if (splitContact[0].toLowerCase() === searchName) { para.textContent = `${splitContact[0]}'s number is ${splitContact[1]}.`; break; } } if (para.textContent === "") { para.textContent = "Contact not found."; } }); See the Pen Looping code3 by Hsoub Academy (@HsoubAcademy) on CodePen. نعرّف أولًا بعض المتغيّرات منها مصفوفة جهات الاتصال المؤلفة من عناصر نصيّة تحتوي على اسم ورقم هاتف تفصل بينهما نقطتان فوق بعضهما : نربط بعدها مترصّد أحداث إلى الزر btn لتشغيل الشيفرة التي تنفّذ البحث وتعيد النتيجة عند النقر عليه. نخزّن القيم المدخلة إلى مربع اﻹدخال في المتغيّر searchName قبل أن نفرّغه ونعيد إليه تركيز الدخل من جديد. ولاحظ استخدام التابع ()toLowerCase كي يحول النص المدخل إلى حروف صغيرة وبالتالي لن يكون البحث حساسًا لحالة الحروف. نأتي إلى الجزء الذي يضم الحلقة for...of: نفصل بداية جهة الاتصال عند النقطتين المتعامدتين ونخزّن القيمتين الناتجتين عن عملية الفصل في مصفوفة تُدعى . نستخدم بعد ذلك عبارة شرطية لاختبار إن كانت قيمة splitContact[0] (وتمثّل اسم المستخدم المحوّل إلى حروف صغيرة) متطابقة مع القيمة المخزّنة في المتغيرsearchName فإن وجد التطابق نعرض في الفقرة النصية الرقم الموافق ثم نستخدم break ﻹنهاء الحلقة. إن لم نجد جهة اتصال مطابقة للبحث لن تظهر أي قيمة في الفقرة النصية، لهذا نضع ضمنها النص "Contact not found." بمعني أننا لم نجد جهة الاتصال المطلوبة. ملاحظة: بإمكانك الاطلاع على الشيفرة المصدرية الكاملة لهذا المثال على جيت-هب، وتستطيع تجربته مباشرة كذلك. تخطي أحد التكرارات باستخدام continue تعمل التعليمة continue بشكل مشابه للتعليمة break لكنها تتخطى التكرار الحالي إلى التكرار التالي بدلًا من الخروج من الحلقة نهائيًا. لنلق نظرة على مثال آخر يأخذ عدة مدخلات ويعيد فقط اﻷعداد التي تمثل مرّبعات ﻷعداد صحيحة. إليك أولًا شيفرة HTML: <label for="number">Enter number: </label> <input id="number" type="number" /> <button>Generate integer squares</button> <p>Output:</p> وهذه شيفرة جافا سكريبت اللازمة: const para = document.querySelector("p"); const input = document.querySelector("input"); const btn = document.querySelector("button"); btn.addEventListener("click", () => { para.textContent = "Output: "; const num = input.value; input.value = ""; input.focus(); for (let i = 1; i <= num; i++) { let sqRoot = Math.sqrt(i); if (Math.floor(sqRoot) !== sqRoot) { continue; } para.textContent += `${i} `; } }); وهذا هو خرج البرنامج: See the Pen Looping Code4 by Hsoub Academy (@HsoubAcademy) on CodePen. يجب أن يكون الدخل في هذه الحالة عددًا (المتغير num)، وتبدأ الحلقة من 1 (لأن 0 لا يهمنا في هذا التطبيق). هنالك أيضًا شرط للخروج من الحلقة وهو أن يكون العداد أكبر من العدد المدخل num، ويزداد العداد بمقدار 1 في كل تكرار. نحسب الجذر التربيعي لكل عدد داخل الحلقة ياستخدام التابع (i)Math.sqrtومن ثم نتحقق أنه عدد صحيح بمقارنته مع نفسه عند تقريبه إلى أصغر عدد صحيح باستخدام التابع ()Math.floor. إن لم يتساوى الجذر التربيعي والقيمة المقرّبة له (==!) فهذا يعني أن الجذر التربيعي ليس عددًا صحيحًا ولن يكون مهمًا بالنسبة لنا لهذا نستخدم continue في هذه الحالة للانتقال إلى العدد التالي دون أن نسجّل هذا العدد. إن كان الجذر التربيعي صحيحًا نتخطى الكتلة if كليًا ولن تُنفَّذ التعليمة continue بل نضيف قيمة i وبعده مسافة فارغة إلى محتوى الفقرة النصية. ملاحظة: بإمكانك الاطلاع على الشيفرة المصدرية الكاملة لهذا المثال على جيت-هب، وتستطيع تجربته مباشرة كذلك. الحلقتين while و do...while إن الحلقة for ليست الوحيدة في جافا سكريبت، وعلى الرغم أنك لن تحتاج إلى معرفة كل أنواع الحلقات حاليًا، من الجيد الاطلاع على بعضها كي تميّز وجه الشبه والاختلاف بينها. لنلق نظرة بداية على الحلقة while التي لها الصياغة التالية: initializer while (condition) { // code to run final-expression } تعمل الحلقة بشكل مشابه للحلقة for، إلا أن المهيّء يُضبط قبل الحلقة وعبارة الإنهاء موجودة ضمن الحلقة بعد الشيفرة التي تُنفَّذ عند تحقق الشرط بدلًا من وجودهما بين القوسين التاليين للتعليمة while إلى جانب عبارة الشرط. وكما نرى، فإن العناصر الثلاث السابقة الموجودة في حلقة for موجودة أيضًا في الحلقة while وبنفس الترتيب لأنك تحتاج إلى المهيئ قبل أن تختبر صحة الشرط ثم تُنفَّذ عبارة اﻹنهاء عند انتهاء تنفيذ شيفرة الحلقة التي تستمر طالما أن الشرط محقق. نحاول تاليًا إعادة كتابة شيفرة مثالنا السابق عن القطط لكن باستخدام الحلقة while: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; let i = 0; while (i < cats.length) { if (i === cats.length - 1) { myFavoriteCats += `and ${cats[i]}.`; } else { myFavoriteCats += `${cats[i]}, `; } i++; } console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine." وبالنسبة للحلقة do...while، فهي مشابهة جدًا للحلقة while مع بعض التغييرات في الصياغة: initializer do { // code to run final-expression } while (condition) نلاحظ في هذه الحالة أن المهيئ يأتي أولًا قبل بداية الحلقة ثم الكلمة المحجوزة do يتبعها قوسين معقوصين يضمان الشيفرة التي يجب تنفيذها ثم عبارة اﻹنهاء ثم يأتي بعد القوسين المعقوصين مباشرة التعليمة while يليها الشرط. إن الفرق الرئيسي بين الحلقتين السابقتين هو أن الشيفرة ضمن الحلقة do...while ستُنفَّذ على اﻷقل مرة واحدة قبل اختبار صحة الشرط. إي تُنفَّذ الشيفرة وبعدها يُختبر الشرط لتحديد الحاجة إلى تنفيذ الشيفرة مجددًا. أما في whileو forيُختبر الشرط أولًا. لنحاول تاليًا إعادة كتابة شيفرة مثالنا السابق عن القطط باستخدام الحلقة do...while: const cats = ["Pete", "Biggles", "Jasmine"]; let myFavoriteCats = "My cats are called "; let i = 0; do { if (i === cats.length - 1) { myFavoriteCats += `and ${cats[i]}.`; } else { myFavoriteCats += `${cats[i]}, `; } i++; } while (i < cats.length); console.log(myFavoriteCats); // "My cats are called Pete, Biggles, and Jasmine." تحذير: يجب التأكد في حلقات while و do...while وجميع الحلقات اﻷخرى من زيادة أو إنقاص المهيئ تبعًا للحالة حتى يصبح الشرط في مرحلة ما غير محقق وتنتهي الحلقة وإلا ستستمر الحلقة إلى ما لا نهاية أو سيوقفها المتصفح أو ستنهار الشيفرة. تُدعى هذه الحالة -حلقة لانهائية infinite loop-. تطبيق عملي: إبدأ العد التنازلي! نطلب إليك في هذا التمرين طباعة عد تنازلي من 10 إلى 0، ونريد تحديدًا: حلقة من 10 إلى 0، وقد زوّدناك بالمهيئ ;let i = 10. إنشاء فقرة نصية جديدة في كل تكرار للحلقة وإلحاقها بعنصر الخرج <div> الذي نختاره باستخدام اﻷمر : const output = document.querySelector('.output'); وقد زوّدناك في التعليقات بثلاث أسطر من الشيفرة يجب استخدامها ضمن الحلقة: السطر;const para = document.createElement('p') الذي يُنشىء فقرة نصية جديدة. السطر ;output.appendChild(para) الذي يُلحق الفقرة النصية بالعنصر <div>. السطر =para.textContentالذي يجعل محتوى الفقرة ما تضعه من الناحية اليمنى للمساواة. يتطلب كل تكرار نصًا مختلفًا يوضع ضمن الفقرة النصية وفقًا للعدد الذي وصلنا إليه، لذا تحتاج عبارة شرطية وعدة عبارات =para.textContent: إن كان العدد هو 10 يجب طباعة "Countdown 10". إن كان العدد هو 0 يجب طباعة "!Blast off". يُطبع أي عدد آخر كما هو. تذكّر أن تضيف عدادًا وانتبه إلى أننا نعد تنازليًا في تمريننا وليس تصاعديًا (لا تستخدم ++i!). ملاحظة: إن بدأت كتابة الحلقة على الشكل فقد يتوقف المتصفح عن التنفيذ إن لم تضع الشرط النهائي، انتبه إلى ذلك. يمكنك لتفادي اﻷمر كتابة الشيفرة ضمن تعليقات ومن ثم تزيل إشارات التعليق عندما تنتهي من كتابة الشيفرة كاملةً. إن ارتكبت خطأً تستطيع أن تنقر على الزر "Reset" لتعود إلى الوضع الأساسي، كما يمكنك النقر على الزر "Show solution" لترى الحل. See the Pen Looping Code5 by Hsoub Academy (@HsoubAcademy) on CodePen. تطبيق عملي: ملء قائمة المدعوين نطلب منك في هذا التمرين أخذ قائمة أسماء من مصفوفة ووضعها ضمن قائمة المدعوين، لكن لا نريد دعوة "Phil" و "Lola" لأسباب خاصة.لهذا نحتاج إلى قائمة للمدعوين وأخرى لغير المدعوين. لهذا نطلب منك تحديدًا: كتابة حلقة تتنقل بين عناصر المصفوفة people. تتحقق في كل مرور من أن اسم الشخص ليس "Phil"أو "Lola" مستخدمًا عبارة شرطية: إن كان كذلك، ألحق الاسم بنهاية المحتوى النصي textContent للفقرة النصية refused يليه فاصلة وفراغ. إن لم يكن كذلك، ألحق الاسم بنهاية المحتوى النصي textContent للفقرة النصية admitted يليه فاصلة وفراغ. زوّدناك مسبقًا بأسطر الشيرة التالية: السطر =+refused.textContent الذي يمثل بداية أمر ضم شيء ما إلى نهاية refused.textContent. السطر =+admitted.textContent الذي يمثل بداية أمر ضم شيء ما إلى نهاية admitted.textContent. وإليك بطلب آخر يمنحك نقاطًا إضافية: بعد إكمالك المهمتين السابقتين بنجاح سنتركك مع قائمتين من الأسماء تفصل بينها فواصل لكنها غير مرتبة. هل تستطيع اقتطاع الفواصل ووضع نقطة آخر القائمة؟ عد إلى مقال توابع جافا سكريبت للتعامل مع النصوص إن احتجت أي مساعدة. إن ارتكبت خطأً تستطيع أن تنقر على الزر "Reset" لتعود إلى الوضع الأساسي، كما يمكنك النقر على الزر "Show solution" لترى الحل. See the Pen Looping Code6 by Hsoub Academy (@HsoubAcademy) on CodePen. ما هو نوع الحلقة التي علي استخدامها؟ إن كنت ستتنقل ضمن عناصر مصفوفة أو أي كائن يدعم ذلك ولن تحتاج إلى دليل العنصر (موقعه) في كل مرة، استخدم for...of فهي أسهل قراءة وأقل توليدًا للأخطاء. واستخدم for و while و do...while لبقية الحالات ، فجميعها تخدمك لحل نفس المسائل. أما أيها ستختار فهذا أمر شخصي وذلك وفقًا لسهولة تطبيقها أو وضوحها بالنسبة إليك. لكننا ننصحك باستخدام for كبداية لأن ترتيب وموقع عناصرها أسهل تذكرًا (جميعها ضمن القوسين التاليين للكلمة المحجوزة for) فلن تنسى أيًا منها. لنلق نظرة عليها جميعًا: الحلقة for...of: for (const item of array) { // code to run } الحلقةfor: for (initializer; condition; final-expression) { // code to run } الحلقة while: initializer while (condition) { // code to run final-expression } الحلقة do...while: initializer do { // code to run final-expression } while (condition) ملاحظة: هناك حلقات أخرى لها ميزات مختلفة وفوائد في حالات محددة وخاصة لكننا لن نمر عليها في هذا المقال. خلاصة قدمنا لك في هذا المقال المفاهيم الأساسية والخيارات المختلفة عند تكرار تنفيذ شيفرة جافا سكريبت عبر الحلقات. وعرضنا أهمية هذا النوع من البنى البرمجية في التعامل مع الحالات التي تتكرر فيها الشيفرة نفسها وأوضحنا طريقة عملها من خلال أمثلة عدة. إن استعصى عليك فهم أية نقطة، عد إليها مجددًا أو اطرح سؤالك عبر نقاش الصفحة. ترجمة -وبتصرف- لمقال Looping code اقرأ أيضًا المقال السابق: العبارات الشرطية واتخاذ القرار في جافا سكريبت تعلم جافا سكريبت من الصفر حتى الاحتراف هيكل البرنامج في جافاسكريبت ترميز النصوص والتعامل مع كائنات الملفات في جافاسكربت
  4. الصندوق المرن Flexbox هو أسلوب تخطيط وحيد الاتجاه لترتيب العناصر في صفحة الويب سواء على سطر واحد أو ضمن عمود واحد. إذ تتمتد العناصر لتشغل مساحة أوسع، أو تتقلص لتتلائم مع مساحة أقل. سنتطرق في هذا المقال إلى جميع الأساسيات التي تغطي هذا المفهوم. عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML كما شرحناها في سلسلة مقالات مدخل إلى HTML. تفهم أساسيات عمل CSS. لماذا نستخدم الصندوق المرن؟ بقيت التخطيطات المعتمدة على خاصيات CSS المتعلقة بتعويم العناصر floats وبتوضعها positioning ولفترة طويلة أكثر التخطيطات موثوقية عبر مختلف المتصفحات، وقد نجح هذا الأمر لكن مع وجود شيء من المحدودية في التنفيذ وشيء من الإحباط أحيانًا. فتخطيطات بسيطة كالتي سنذكرها ستكون إما صعبة التنفيذ أو مستحيلة باستخدام تلك الأدوات وبأسلوب مرن وملائم: توسيط كتلة من المحتوى عموديًا ضمن العنصر الأب. التوزيع المتساوي لاتساع العنصر الأب بين مختلف العناصر الأبناء ضمن حاوية بغض النظر عن مقدار المساحة المتوفرة. ضبط جميع الأعمدة في التخطيط متعدد الأعمدة على نفس الارتفاع إن ضمت كميات مختلفة من المحتوى. وكما سنرى، يساعدنا تخطيط الصندوق المرن في تنفيذ الكثير من المهام بسهولة أكبر، وإليك التفاصيل. مثال تمهيدي بسيط سنمر في هذا المقال بسلسلة من التمارين كي نفهم آلية عمل الصندوق المرن. عليك بداية أن تنسخ ملف التمرين من جيت-هاب إلى جهازك، ثم حمّله ضمن متصفح حديث (كروم أو فايرفوكس) والق نظرة على الشيفرة باستخدام محرر الشيفرة الذي تستخدمه. بإمكان أيضًا الاطلاع على طريقة عمله مباشرة على جيت-هاب. ستلاحظ وجود ترويسة <header> تضم عنوانًا من المستوى الأول، وكذلك العنصر <section> الذي يضم ثلاث عناصر <article>. نستخدم هذه الشيفرة في بناء تخطيط معياري مكون من ثلاث أعمدة. تحديد العناصر التي ينبغي ترتيبها كصناديق مرنة علينا كبداية اختيار العناصر التي سترتب كصناديق مرنة، ولفعل ذلك ضبطنا الخاصية display للعنصر الأب للعناصر التي تريدها أن تتحول لصناديق مرنة (وهي في حالتنا العناصر <article>) على القيمة flex. وبالطبع فإن العنصر الأب هو <section>. section { display: flex; } هكذا يصبح العنصر حاوية مرنة وتصبح العناصر الأبناء عناصر مرنة. وستكون النتيجة شيئًا من هذا القبيل: إذًا، منحنا هذا التصريح بمفرده ما نريد، وهذا أمر رائع! إذ عُرض المحتوى ضمن ثلاث أعمدة لها الاتساع ذاته والارتفاع ذاته. والسبب في ذلك أن أن القيمة الافتراضية للعناصر المرنة (العناصر الأبناء ضمن الحاوية المرنة) تحل هذا النوع من المسائل مباشرة. دعونا نعيد ما حدث حتى يكون كل شيء واضحًا. يعمل العنصر الذي ضبطنا قيمة الخاصية display له على flex كعنصر كتلي من ناحية الطريقة التي يتفاعل فيها مع بقية أجزاء الصفحة، لكن أبناءه قد رُتِّبت كعناصر مرنة. وسنشرح في القسم التالي بتفاصيل أكبر ما يعنيه هذا الكلام. لا حظ أيضًا إمكانية استخدام القاعدة ;display: inline-flex إن أردت توضيع أبناء عنصر معين كعناصر مرنة لكن سيسلك هذا العنصر سلوك العناصر السطرية. نموذج الصندوق المرن عندما تتوضع العناصر بصفتها عناصر مرنة فإنها تتوضع وفق محورين: المحور الأساسي main axis: وله الاتجاه الذي تتوضع وفقه العناصر المرنة (أي على شكل سطر عبر الصفحة أو عمود يتجه لأسفل الصفحة) وتُدعى بداية هذا المحور البداية الأساسية main start وتُدعى نهايته بالنهاية الأساسية main end. المحور القاطع cross axis: هو المحور العمودي على اتجاه توضع العناصر المرنة وله بداية القاطع cross start ونهاية القاطع cross end. يُدعى العنصر الذي تُطبق عليه القاعدة display: flex (العنصر <section> في مثالنا) بالحاوية المرنة flex container. تُدعى العناصر الموجودة داخل الحاوية المرنة بالعناصر المرنة (العناصر <article> في مثالنا). تذكر هذه المصطلحات دائمًا، وعد إليها إن اختلط عليك الأمر في الأقسام القادمة. أعمدة وأسطر يقدم مخطط الصندوق المرن خاصية تُدعى flex-direction تحدد اتجاه المحور الأساسي (الاتجاه التي تُترتب وفقه عناصر الصندوق المرن). تأخذ هذه الخاصية القيمة row افتراضيًا والتي ترتب العناصر في صف باتجاه انسياب اللغة الافتراضية للمتصفح (من اليمين إلى اليسار في العربية و العكس في الإنجليزية). حاول أن تضيف التصريح التالي لقاعدة التنسيق الخاصة بالعنصر <section>: flex-direction: column; سترى أن هذه القاعة ستعيد العناصر إلى تخطيط العمود الواحد كما كان تقريبًا قبل إضافة أية تنسيقات. احذف هذا التصريح من شيفرة مثالنا قبل المتابعة. ملاحظة: بإمكانك إيضًا ترتيب العناصر المرنة عكسيًا باستخدام القيمتين row-reverse و column-reverse. التفاف العناصر من أحد المشاكل التي تظهر عند استخدام تخطيط ثابت الاتساع أو الإرتفاع هو طفحان العناصر في مرحلة ما خارج الحاوية مما يدمر التخطيط. لنلق نظرة على المثال wrap0.html الموجود على جيت-هاب (انسخ هذا المثال على جهازك إن كنت ترغب في تطبيق الأكواد الواردة في هذا المقال) لاحظ هنا كيف خرجت الأعمدة الأبناء خارج الحاوية، ولحل هذه المشكلة يمكن أن نستخدم التصريح التالي ضمن قاعدة تنسيق الكتلة الحاوية <section>: flex-wrap: wrap; أضف أيضًا التصريح التالي إلى قاعدة تنسيق العناصر <article>: flex: 200px; حاول استعراض الشيفرة الآن في المتصفح وسترى أن التخطيط قد تحسّن كثيرًا: لدينا الآن عدة أسطر يضم كلًا منها عدة عناصر أبناء مرنة تشغل تمامًا المساحة المتاحة للسطر، وعند حدوث طفحان يُنقل العنصر إلى السطر التالي. واستخدم التصريح flex: 200px في تنسيق العناصر <article> للدلالة إلى أن اتساع كل عنصر يجب أن يكون على الأقل 200 بكسل، وسنناقش هذه الخاصية بالتفصيل لاحقًا. يمكن أن نلاحظ أيضًا أن آخر العناصر الأبناء في السطر الأخير أكثر اتساعًا كي تشغل مساحة السطر بأكمله. هناك الكثير لنفعله أيضًا! حاول أن تغيّر قيمة الخاصية flex-direction إلى row-reverse وسترى أن تخطيط الأسطر المتعددة بقي كما هو لكن العناصر قد رتّبت بشكل معكوس ابتداءًا من الزاوية الأخرى للمتصفح. الخاصية المختصرة flex-flow تجدر الملاحظة هنا أن الخاصيات المختصرة موجودة للخاصيات flex-direction و flex-wrap و flex-flow. إذ يمكنك مثلًا استبدال التصريح: flex-direction: row; flex-wrap: wrap; بالتصريح التالي: flex-flow: row wrap; التحديد المرن لأبعاد العناصر المرنة لنعد الآن إلى مثالنا الأول لنرى كيف يمكننا التحكم بحجم المساحة الفارغة التي يمكن أن يشغلها العنصر المرن بالنسبة إلى بقية العناصر المرنة. شغّل الملف flexbox0.html أو انسخ الملف flexbox1.html إلى جهازك ليكون نقطة الانطلاق إذا قررت تطبيق ما سنفعله. أضف بداية القاعدة التالية أسفل تصريحات CSS: article { flex: 1; } إن القيمة في التصريح السابق هي قيمة نسبية لا وحدة لها تشير إلى حجم الفراغ المتاح لكل عنصر مرن على امتداد المحور الأساسي موازنة مع غيره من العناصر المرنة. في حالتنا منحنا كل عنصر <article> نفس القيمة وهي 1 كي تأخذ جميع العناصر نفس الكمية من الفراغ الإضافي الذي قد يبقى (الذي لا يتسع لعنصر بأكمله) بعد أن تُحتسب قيم بعض الخاصيات مثل الهوامش والحشوات. تشترك جميع العناصر المرنة بهذه النسبة وحتى لو وضعنا القيمة 40000 بدل القيمة 1 فسيكون لها الأثر ذاته. أضف الآن القاعدة التالية تحت القاعدة السابقة: article:nth-of-type(3) { flex: 2; } عند النقر على زر التحديث في المتصفح، سترى أن العنصر <article> الثالث يشغل ضعفي مساحة العنصرين الباقيين. في هذه الحالة سيكون هناك أربع وحدات تناسبية إجمالًا موزعة على الشكل (4=1+1+2). سيشغل أو عنصرين مرنين ما مقداره وحدة من أصل أربعة أي 1/4 من المساحة المتاحة، وسيشغل العنصر الثالث الربعين الآخرين. بإمكانك أيضًا أن تخصص حدًا أدنى من الحجم لكل عنصر مرن. لترى ذلك، حاول تعديل قواعد تنسيق العنصر <article> كما يلي: article { flex: 1 200px; } article:nth-of-type(3) { flex: 2 200px; } تنص هذه القواعد على أن كل عنصر مرن سيعطى اتساعًا قدره 200 بكسل بداية من المساحة المتوفرة، وبعدها تتقاسم العناصر المساحة الباقية بنفس النسبة. جرّب أن تنقر على زر التحديث في المتصفح وسترى اختلافًا في طريقة تقاسم المساحة الفارغة. تتجلى القيمة الحقيقية لتخطيط الصندوق المرن من خلال مرونته وتجاوبه، فإن غيرّت أبعاد المتصفح أو أضفت عنصر <article> جديد سيبقى التخطيط جميلًا. الخاصية flex المختصرة موازنة بالخاصية المطوّلة يمكن استخدام الخاصية المختصرة flex لتحديد ثلاث قيم على الأكثر: القيمة النسبية التي ليس لها واحدة وقد ناقشناها في الأعلى. بالإمكان ضبط هذه القيمة بمفردها أيضًا من خلال الخاصية المطولة flex-grow. قيمة نسبية أخرى ليس لها واحدة تعبر عنها الخاصية المطوّلة flex-shrink وتظهر أهميتها عندما يحدث الطفحان في الحاوية المرنة. إذ تحدد هذه القيمة مقدار ما يتقلصه العنصر ليمنع الطفحان. لا تُعد هذه الميزة للصندوق المرن متقدمة ولن نغطيها لاحقًا. القيمة التي تحدد الحد الأدنى لاتساع العنصر المرن والتي ناقشناها سابقًا. بالإمكان أن نضبط هذه القيمة أيضًا باستخدام الخاصية المطوّلة flex-basis. لا ننصحك باستخدام الصيغ المطوّلة لخاصيات الصندوق المرن إلا في الحالة التي لا مفر من استخدامها (كأن تلغي قاعدة ما حُدِّدت قيمتها سابقًا). إذ سيزيد استخدام هذه الخاصيات من حجم الشيفرة المكتوبة، وقد تغدو مربكة أحيانًا. المحاذاة الأفقية والعمودية نستطيع أيضًا استخدام خاصيات الصندوق المرن لمحاذاة العناصر المرنة عبر المحور الأساسي أو القاطع. ولتوضيح ذلك، سنلقي نظرة على المثال flex-align0.html الموجود على جت-هاب (بإمكانك متابعته مباشرة) أيضًا)، وسنحاول تحويل محتواه إلى قائمة من الأزرار المرنة الأنيقة. لاحظ بداية وجود شريط قائمة أفقي يضم عدة أزرار محشورة بجوار بعضها ابتداءً من الزاوية اليمينية العليا. جهز نسخة عن هذا الملف على جهازك، ثم أضف الشيفرة التالية إلى أسفل شيفرة CSS: div { display: flex; align-items: center; justify-content: space-around; } انقر على زر "تحديث" في المتصفح وسترى أن الأزرار قد ضبطت لتبدو متمركزة في منتصف القائمة عموديًا وأفقيًا، وقد أنجزنا ذلك باستخدام الخاصيتين التاليتين: الخاصية align-items: وتتحكم بموضع العناصر المرنة على امتداد المحور القاطع: تأخذ هذه الخاصية القيمة stretch افتراضيًا وتوسّع العناصر المرنة لتشغل المساحة المتاحة للعنصر الأب وفق اتجاه المحور القاطع. فإن لم يكن للعنصر الأب ارتفاع محدد وفق اتجاه المحور القاطع، ستأخذ جميع العناصر الأبناء ارتفاع أطول العناصر المرنة. لهذا كان للأعمدة في مثالنا الأول نفس الارتفاع تلقائيًا. أما القيمة التي استخدمناها في الشيفرة السابقة، ستُبقي الأبعاد الأصلية للعناصر كما هي لكن ستجعلها تتمركز وفق اتجاه المحور القاطع. لهذا تجد الأزرار متمركزة عموديًا في مثالنا الحالي. يمكن للخاصية أن تأخذ قيمًا مثل flex-start و flex-end والتي تحاذي العناصر المرنة إلى بداية ونهاية المحور القاطع. بإمكانك إلغاء سلوك الخاصية align-items لعنصر مرن محدد بتطبيق الخاصية align-self عليه. إليك كيف يكون الأمر: button:first-child { align-self: flex-end; } جرب تلك القيم لضبط محاذاة العناصر لترى تأثيرها ثم احذف ما فعلته عندما تنتهي. الخاصية justify-content: وتتحكم بموضع العناصر المرنة على امتداد المحور الأساسي: تأخذ الخاصية القيمة flex-start افتراضيًا مما يجعل جميع العناصر مرتبة ابتداءً من بداية المحور الأساسي. يمكن أن تأخذ الخاصية القيمة flex-end فترتب حينها العناصر من ناحية النهاية. وترتب العناصر حول مركز الحاوية المرنة إن كانت قيمة هذه الخاصية center. استخدمنا في الشيفرة السابقة القيمة space-around والتي توزّع جميع العناصر المرنة بالتساوي على طول المحور الأساسي مع وجود بعض المساحة الفارغة في كلا الطرفين. هنالك قيمة أخرى للخاصية وهيspace-between وهي مشابهة جدًا للقيمة space-around إلا أنها لا تترك أية مسافات فارغة إضافية في البداية والنهاية. أهملت الخاصية justify-items في تخطيط الصندوق المرن ولن نشير إليها، لكننا ننصحك بتجريب القيم السابقة للخاصيتين المتعلقتين بمحاذاة العناصر المرنة قبل متابعة القراءة. ترتيب العناصر المرنة يمتلك تخطيط الصندوق المرن ميزة تغيير ترتيب العناصر المرنة ضمن التخطيط دون التأثير على الترتيب المصدري. وهذا أمر يستحيل إنجازه باستخدام أساليب التخطيط التقليدي. إن الشيفرة الموافقة لهذه الميزة بسيطة، حاول أن تضيف الشيفرة التالية إلى شيفرة مثال شريط الأزرار: button:first-child { order: 1; } انقر على زر التحديث في المتصفح وسترى أن الزر "Smile" قد انتقل إلى نهاية المحور الأساسي، وسنشرح قليلًا كيف حدث هذا: تأخذ الخاصية order القيمة 0 لجميع العناصر المرنة افتراضيًا. ستظهر جميع العناصر ذات القيمة الأعلى لهذه الخاصية في النهاية موازنة بالعناصر التي تحمل قيمة أقل. تحافظ العناصر التي تحمل نفسة القيمة للخاصية order على ترتيب الشيفرة المصدرية. فإن كان لدينا مثلًا أربعة عناصر تحمل القيم 0،1،1،2 على الترتيب سيكون ترتيب عرضها على الشاشة الرابع ثم الثاني والثالث ثم الأول. سيظهر العنصر الثالث بعد الثاني على الرغم من امتلاكهما نفس قيمة الخاصية order إلا أن الثاني جاء قبل الثالث في ترتيب الشيفرة المصدرية. يمكن للخاصية أن تأخذ قيمًا سالبة كي تعرض العناصر التي تحمل هذه القيم قبل العنصر الذي يحمل القيمة 0. يمكننا مثلًا عرض الزر "Blush" في مثالنا في بداية المحور الأساسي باستخدام قاعدة التنسيق التالية: button:last-child { order: -1; } صناديق مرنة متداخلة بإمكانك إنشاء بعض التخطيطات الجميلة والمعقدة باستخدام الصناديق المرنة. فمن الممكن جدًا أن أجعل عنصرًا مرنًا حاويةً مرنةً أيضًا وأن يكون بعض أبناءه أيضًا حاويات مرنة وهكذا. ألق نظرة الآن على المثال complex-flexbox.html على جت-هاب (بإمكانك متابعة التنفيذ المباشر له أيضًا). إن شيفرة HTML لهذا المثال بسيطة، إذ تحتوي الشيفرة على العنصر <section> وضمنه ثلاثة عناصر <article> يحوي آخرها ثلاث عناصر <div> ويضم أول العناصر <div> خمسة أزرار. section - article article article - div - button div button div button button button لنلق نظرة على الشيفرة المستخدمة لتخطيط الصفحة، وسنرى أن أول ما فعلناه هو ضبط العناصر الأبناء للعنصر <section> لتكون صناديق مرنة. section { display: flex; } ثم ضبطنا بعض القيم المرنة للعناصر. وانتبه جيدًا إلى القاعدة الثانية في شيفرة التنسيق والتي جعلت العناصر الأبناء للعنصر <article> الثالث عناصر مرنة أيضًا لكننا رتبناها على هيئة عمود. article { flex: 1 200px; } article:nth-of-type(3) { flex: 3 200px; display: flex; flex-flow: column; } نختار تاليًا أول العناصر <div> ونطبق عليه التنسيق ;flex: 1 100px كي نعطيه ارتفاعًا مقداره 100 بكسل كحد أدنى، ثم نرتب العناصر الأبناء له على شكل عناصر مرنة. سنختار أن تتوضع الأزرار ضمن سطر وتكون قابلة للالتفاف والانتقال إلى سطر آخر في حال الطفحان كما اخترنا أن نعطيها محاذاة إلى مركز العنصر الأب كما فعلنا مع مثال قائمة الأزرار سابقًا: article:nth-of-type(3) div:first-child { flex: 1 100px; display: flex; flex-flow: row wrap; align-items: center; justify-content: space-around; } حاولنا في الخطوة الأخيرة تحديد حجم الأزرار باستخدام القاعدة ;flex: 1 auto. ولهذه القاعدة تأثير مهم، فإن حاولت أن تغير حجم نافذة المتصفح، ستشغل الأزرار أوسع مساحة ممكنة من الحيز المتاح وتبقى في السطر ذاته ما لم يحدث طفحان لتنتقل عندها إلى سطر آخر. button { flex: 1 auto; margin: 5px; font-size: 18px; line-height: 1.5; } توافقية المتصفحات مع تخطيط الصندوق المرن تدعم معظم المتصفحات الحديثة تخطيط الصندوق المرن مثل فايرفوكس وكروم وأوبرا ومايكروسوفت إيدج وإكسبلورر 11 والنسخ الجديدة من نظامي التشغيل أندرويد وأي أو إس. لكن عليك أن تدرك أن البعض يستخدم متصفحات قديمة لا تدعم هذا التخطيط (أو تفعل لكن بأسلوب قديم حقًا). وطالما أنك لا تزال في مرحلة التعلم والتجريب، لا تكترث للأمر كثيرًا. فإن قررت أن تستخدم تخطيط الصندوق المرن في تصميم موقعك، لا بأس إذًا من اختبار شيفرتك وتجريب بعض الحلول حتى تبقى تجربة مستخدمي موقعك مقبولة بأكبر عدد ممكن من المتصفحات. ويبقى تخطيط الصندوق المرن مربكًا موازنة من بعض ميزات CSS. فإن لم يدعم المتصفح مثلًا الظل حول العنصر فلن تتأثر تجربة المستخدمين كثيرًا لكن إن لم يدعم تخطيط الصنوق المرن فسيكسر المتصفح التخطيط بأكمله ولن يستطيع الزائر استخدام الصفحة أصلًا. الخلاصة حاولنا في هذا المقال الإشارة إلى أساسيات الصندوق المرن، ونتمنى أن تكون المعلومات التي وصلتك مفيدة وتساعدك في خطواتك التالية في رحلة التعلم. ترجمة -وبتصرف- للمقال: Flexbox اقرأ أيضًا المقال السابق: الانسياب الاعتيادي Normal Flow لعناصر صفحة الويب مدخل إلى تخطيط صفحات الويب باستخدام CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS تحجيم الأشياء في CSS تعرف على أساسيات لغة CSS
  5. بعد أن اطلعنا في مقال سابق على مفهوم جافا سكريبت وما يمكنها فعله، سنتابع في مقالنا هذا فكرة إنشاء تطبيق بسيط باستخدام جافا سكربت من خلال جولة إرشادية عملية نبني من خلالها تطبيق "خمّن الرقم Guess the number" خطوةً بخطوة. عليك قبل البدء بقراءة هذا المقال أن تٌلّم بأساسيات العمل على الحاسوب وأساسيات HTML و CSS وكذلك فهم طبيعة جافا سكريبت. ولا بد أيضًا من اﻹشارة إلى ما يجب أن تتوقعه بعد قراءة هذا المقال، فلا تتوقع أن تتعلم جافا سكريبت أو حتى أن تفهم كل الشيفرة التي نطلب منك كتابتها، بل كل ما نريده هو إعطاؤك فكرة عن طريقة عمل ميزات جافا سكريبت مع بعضها، وكيف تبدو كتابة شيفرة جافا سكريبت. سنعود في مقالات تالية إلى كل الميزات التي نستعرضها في هذا المقال بتفاصيل أكثر. لذا لا تقلق إن لم تفهم كل شيء مباشرةً. ملاحظة: إن الكثير من ميزات شيفرة جافا سكريبت هي نفسها تقريبًا في لغات البرمجة اﻷخرى مثل الدوال functions والحلقات loops وغيرها. فقد تبدو صياغة الشيفرة مختلفة لكن المفهوم يبقى ذاته. فكّر مثل مبرمج من أصعب اﻷمور التي تصادفك عند تعلم البرمجة هو طريقة تطبيق قواعد اللغة لحل مشاكل فعلية وليس تعلّم هذه القواعد. فعليك إذًا أن تتعلم كيف تفكّر مثل المبرمجين، ويتضمن ذلك الاطلاع على وصف ما يتطلّبه برنامجك ثم تقدير ميزات الشيفرة التي تستخدمها ﻹنجاز المطلوب وكيف ستربط بين هذه الميزات. يتطلب اﻷمر مزيجًا من العمل الجاد والخبرة بصياغة اللغة والتدريب، إضافة إلى لمسة من الإبداع. وكلًما كتبت شيفرة أكثر ستتمكن منها أكثر. لن نعدك بالطبع أن تكوّن دماغ مبرمج في خمس دقائق، لكن سنتيح لك فرص عديدة كي تمارس تفكير المبرمج لنلق نظرة إذًا على المثال الذي نبنيه في مقالنا ونراجع الغاية العامة من تقسيمه إلى مهام. تطبيق -خمّن الرقم Guess the number- نعرض في هذا التطبيق كيفية بناء لعبة بسيطة كما في المثال الحي التالي: حاول أن تجرّب اللعبة وتعتاد عليها قبل أن نتابع، ثم لنتخيل أن مديرك قد أعطاك الوصف الموجز التالي للعبة كي تبنيها: بعد الاطلاع على الموجز السابق، لا بد أولًا من تقسيم العملية إلى مهام أصغر قابلة للتنفيذ إستنادًا إلى عقلية المبرمج قدر اﻹمكان: توليد رقم عشوائي بين 1 و 100. تسجيل رقم المحاولة التي وصلها اللاعب ابتداءً من 1. إعطاء اللاعب طريقة ليسجًل العدد الذي يخمّنه. تسجيل العدد الذي خمّنه اللاعب وعرضه في مكان ما كي يرى محاولاته السابقة. التحقق من صحة التخمين. إن كان التخمين صحيحًا: عرض رسالة تهنئة. منع اللاعب من إدخال أية تخمينات أخرى (لأن ذلك يسيء إلى اللعبة). عرض آلية تتيح للاعب اللعب مجددًا. إن كان التخمين خاطئًا: إخبار اللاعب أن تخمينه خاطئ وإن كان التخمين قريبًا جدًا أو بعيدًا جدًا. السماح للاعب بإدخال تخمين جديد. زيادة عدّاد المحاولات بمقدار 1. إن كان التخمين خاطئًا وتجاوز اللاعب عدد المحاولات المتاحة: إبلاغ اللاعب بانتهاء اللعبة. منع اللاعب من إدخال أية تخمينات أخرى (لأن ذلك يسيء إلى اللعبة). عرض آلية تتيح للاعب اللعب مجددًا. التأكد عند إعادة اللعبة أن منطق اللعبة وواجهة المستخدم قد عادا إلى الوضع اﻷصلي، ثم العودة إلى الخطوة 1. سنحاول اﻵن التفكير بآلية لتحويل هذه الخطوات إلى شيفرة صحيحة ثم بناء اللعبة واكتشاف ميزات جافا سكريبت أثناء تقدم العمل. اﻹعدادات اﻷساسية لنبدأ العمل على كتابة الشيفرة البرمجية للعبة تخمين الأرقام والتي ستعمل كما موضح في الصورة التالية. تحتاج في البداية لتحضير نسخة محلية خاصة بك من الملف number-guessing-game-start.html كما تستيطع أن تختبره مباشرة ضمن المستودع المخصص له على جيت-هب. افتح الملف ضمن المتصفح وضمن المحرر النصي. سترى للوهلة الأولى ترويسة بسيطة وفقرة نصية توضح التعليمات ونموذج لإدخال التخمين، لكن التطبيق لن يعمل حاليًا. سنضع كل شيفرة اللعبة ضمن العنصر <script> في نهاية شيفرة HTML: <script> // Your JavaScript goes here </script> إضافة متغيرات لتخزين البيانات أضف بداية الأسطر التالية ضمن العنصر <script>: let randomNumber = Math.floor(Math.random() * 100) + 1; const guesses = document.querySelector(".guesses"); const lastResult = document.querySelector(".lastResult"); const lowOrHi = document.querySelector(".lowOrHi"); const guessSubmit = document.querySelector(".guessSubmit"); const guessField = document.querySelector(".guessField"); let guessCount = 1; let resetButton; يهيئ هذا القسم من الشيفرة المتغيرات والثوابت التي نحتاجها لتخزين البيانات اللازمة لبرنامجنا. تُعرّف المتغيرات variables مبدئيًا بأنها أسماء لقيم مثل اﻷعداد أو النصوص. ويمكنك إنشاء متغير باستخدام التعليمة let يليها اسم المتغير. وتُعرّف الثوابت constant بأنها أسماء لقيم أيضًا لكن لا يمكن تغيير هذه القيم بمجرد أن نهيئها. نستخدم في حالتنا الثوابت لتخزين مراجع إلى عناصر من واجهة المستخدم. وقد يتغير النص ضمن تلك العناصر لكن الثوابت تشير دائمًا إلى العنصر نفسه الذي ضُبطت عليه في البداية. يمكن إنشاء الثابت باستخدام التعليمة const يليها اسم الثابت. يمكن إسناد قيمة إلى الثابت أو المتغير باستخدام الإشارة (=) تليها القيمة التي تريد. في مثالنا: يُسند إلى المتغير اﻷول randomNumber عدد عشوائي بين 1 و 100 يُحسب من خلال خوارزمية رياضية. أنشئت الثوابت الثلاث اﻷولى لتخزين مراجع إلى المقاطع النصية الخاصة بالنتائج في شيفرة HTML وتستخدم ﻹدراج نصوص ضمن هذه الفقرات برمجيًا، ولاحظ أنها ضمن عنصر <div>والذي يُستخدم لاختيار المقاطع الثلاث لتصفيرها (إعادتها إلى وضعها اﻷصلي) لاحقًا عن إعادة اللعبة إلى وضعها اﻷصلي. <div class="resultParas"> <p class="guesses"></p> <p class="lastResult"></p> <p class="lowOrHi"></p> </div> يُخّزن الثابتان التاليان مرجع إلى عنصر اﻹدخال النصي في النموذج وإلى زر اﻹرسال وتُستخدمان للتحكم بإرسال العدد الذي يُخمنه اللاعب. <label for="guessField">Enter a guess: </label> <input type="number" id="guessField" class="guessField" /> <input type="submit" value="Submit guess" class="guessSubmit" /> يٌخزّن المتغيران اﻷخيران القيمة 1 لعدد محاولات اللعب (ويُستخدم لتتبع عدد المحاولات التي قام بها اللاعب) ومرجعًا إلى زر إعادة الضبط الذي لا يظهر حاليًا (لكنه سيظهر لاحقًا). ملاحظة: سنتعلم أكثر عن المتغيرات والثوابت لاحقًا في سلسلة المقالات هذه عن جافا سكريبت. الدوال أضف تاليًا الشيفرة التالية تحت الشيفرة السابقة: function checkGuess() { alert("I am a placeholder"); } الدوال هي كتل من الشيفرة يمكن استخدامها مجددًا، إذ يمكن كتابتها مرة واحدة وتنفيذها مرارًا وتكرارًا موفرين عناء كتابة الشيفرة من جديد، وهذا أمر غاية في اﻷهمية.وهنالك عدد من الطرق في تعريف الدوال لكننا سنركز الآن على نوع بسيط. نستخدم في مثالنا الكلمة المفتاحية function متبوعةً باسم الدالة وبعدها قوسين معقوصين{}. نضع الشيفرة التي تنفذها الدالة عند استدعائها داخل القوسين المعقوصين. وعندما نريد تنفيذ الشيفرة، تكتب اسم الدالة يليها قوسين () لنجرب ذلك اﻵن، احفظ التغييرات التي أجريتها على الملف وحدّث الصفحة ضمن المتصفح. انتقل بعد ذلك إلى (طرفية جافا سكريبت ضمن أدوات مطوري ويب ثم أدخل السطر التالي: checkGuess(); من المفترض أن ترى بعد الضغط على المفتاح Enter/Return رسالة تنبيه نصها I am a placeholder، ﻷننا عرفنا دالة في شيفرتنا تعيد هذه الرسالة كلما استدعيناها. ملاحظة: سنتعلم أكثر عن الدوال لاحقًا في سلسلة المقالات هذه. العوامل تسمح العوامل Operators في لغة جافا سكريبت بإجراء اختبارات وتنفيذ العمليات الرياضية وضم السلاسل النصية إلى بعضها وغيرها من اﻷشياء. فإن لم تكن قد فعلت ذلك مسبقًا، احفظ التغييرات على ملفك ثم أعد تحميل الصفحة في المتصفح وانتقل إلى طرفية جافا سكريبت في أدوات مطوري ويب. حاول أن تنفذ اﻷمثلة المذكورة في الجدول التالي، منتبهًا إلى نقل المثال حرفيًا كما هو ثم انقر Enter بعد كل عملية لترى نتيجتها: العامل اسمه مثال + الجمع 6 + 9 - الطرح 20 - 15 * الضرب 3 * 7 / القسمة 10 / 5 هنالك أيضًا بعض العوامل المختصرة تُدعى عوامل اﻹسناد المركّبة compound assignment operators، فإن أردت مثلًا إضافة عدد جديد إلى عدد موجود وإعادة النتيجة، إليك الطريقة: let number1 = 1; number1 += 2; وهذا مشابه لعمل الشيفرة: let number2 = 1; number2 = number2 + 2; وعند تنفيذ اختبارات نتيجتها (صحيح أو خاطئ) وذلك ضمن العبارات الشرطية (سنراها لاحقًا) نستخدم عوامل الموازنة comparison operators مثل: العامل الاسم مثال === مساواة تامة (هل يساويه تمامًا؟) 5===2+4 // خاطئ، chris===pop// خاطئ، 5===2+3 // صحيح، 2==='2' // خاطئ موازنة رقم بنص ==! لا مساواة تامة (هل هما غير متساويين؟) 5==!2+4 // صحيح، chris!==pop// صحيح، 5==! 2+3 // خاطئ، 2==!'2' // صحيح موازنة رقم بنص < أقل من 10>6 // صحيح، 10>20 //خاطئ > أكبر من 10<6 // خاطئ، 10<20 //صحيح السلاسل النصية تستخدم السلاسل النصية لتمثيل النصوص، وقد رأينا سابقًا متغير نصي في الشيفرة "I am a placeholder": function checkGuess() { alert("I am a placeholder"); } يمكن التصريح عن سلسلة نصية باستخدام إشارتي التنصيص المزدوجتين (" ") أو المفردتين (' ')، لكن ينبغي عليك استخدام أحد اﻷسلوبين من بداية السلسلة إلى نهايتها فلا يمكن أن تكتب مثلًا" "I am a placeholder'. باﻹمكان أيضًا تعريف السلسلة النصية بين علامتي اقتباس مائلتين (``) وتُدعى السلاسل المعرّفة بهذا الشكل بالقوالب المفسّرة template literals التي تحمل بعض الخاصيات المميزة وتحديدًا إمكانية وضع متغيرات أخرى أو تعابير برمجية ضمن السلسلة النصية لتتحول قيمها إلى جزء من النص: const name = "Mahalia"; const greeting = `Hello ${name}`; يمنحك هذا اﻷمر أسلوبًا لضم السلاسل النصية معًا. العبارات الشرطية بالعودة إلى الدالة ()ckeckGuess نجد أنه من المنطقي ألا تكون وظيفتها طباعة رسالة فقط، بل من المفترض أن تتحقق من صحة العدد الذي يخمّنه اللاعب وتعيد الجواب الملائم. لنستبدل اﻵن بالشيفرة التالية ما كان موجودًا ضمن الدالة ()ckeckGuess: function checkGuess() { const userGuess = Number(guessField.value); if (guessCount === 1) { guesses.textContent = "Previous guesses:"; } guesses.textContent = `${guesses.textContent} ${userGuess}`; if (userGuess === randomNumber) { lastResult.textContent = "Congratulations! You got it right!"; lastResult.style.backgroundColor = "green"; lowOrHi.textContent = ""; setGameOver(); } else if (guessCount === 10) { lastResult.textContent = "!!!GAME OVER!!!"; lowOrHi.textContent = ""; setGameOver(); } else { lastResult.textContent = "Wrong!"; lastResult.style.backgroundColor = "red"; if (userGuess < randomNumber) { lowOrHi.textContent = "Last guess was too low!"; } else if (userGuess > randomNumber) { lowOrHi.textContent = "Last guess was too high!"; } } guessCount++; guessField.value = ""; guessField.focus(); } ما وضعناه كمية كبيرة من الشيفرة بالفعل، لكن سنمر على كل قسم منها ونشرحه: يعرّف السطر اﻷول متغيرًا يُدعى userGuess وتُسند إليه القيمة التي أُدخلت ضمن المربع النصي في النموذج. نمرر بعد ذلك هذه القيمة إلى الدالة ()Number المدمجة في جافا سكريبت وذلك للتأكد أن هذه القيمة عدد بالفعل. وطالما أننا لن نغيّر هذا العدد فقد قررنا أن يكون ثابتًا const. نواجه بعد ذلك أول كتلة شرطية، وتُستخدم الكتل الشرطية لتنفيذ الشيفرة انتقائيًا إن تحقق شرط معين أو لم يتحقق. قد تبدو العبارة الشرطية شبيهة بالدالة لكنه ليست كذلك. تبدأ أبسط أشكال الكتل الشرطية بالكلمة المحجوزة if يليها بعض اﻷقواس ثم بعض اﻷقواس المعقوصة. يوضع الشرط أو الاختبار بين اﻷقواس العادية، فإن أعاد تنفيذ الشرط النتيجة true تُنفَّذ الشيفرة الموجودة ضمن اﻷقواس المعقوصة، وإن لم يكن اﻷمر كذلك، فلن تنفَّذ تلك الشيفرة وينتقل التنفيذ إلى القسم اﻵخر من الشيفرة. يختبر الشرط فيما لو كانت قيمة المتغيّر guessCount مساويًا للقيمة 1 (أي إذا كانت هذه المحاولة اﻷولى للاعب أم لا): guessCount === 1; إن تحقق ذلك، نجعل النص المحتوى ضمن الفقرة النصية guesses مساويًا Previous guesses، وإن لم يتحقق لا نفعل ذلك. نستخدم بعد ذلك قالبًا مفسّرًا لضم قيمة المتغّير userGuess إلى نهاية النص في الفقرة النصية guesses مع وجود مسافة فارغة بينهما. تُنفِّذ الشيفرة التالية عدة اختبارات: تتحق العبارة الشرطية اﻷولى {}()if إن كان التخمين الذي أدخله اللاعب مساويًا قيمة المتغيّر randomNumber الذي ضُبطت قيمته في بداية الشيفرة. فإن كان تخمين اللاعب صحيحًا، ربح اللعبة، لهذا نعرض رسالة تهنئة بلون أخضر ونمسح محتويات صندوق المعلومات الذي يشير إلى بعد أوقرب التخمين الخاطئ عن القيمة الصحيحة، ثم تُنفَّذ الدالة ()setGameOver التي سنناقشها لاحقًا. نربط بعد ذلك اختبارًا آخر في نهاية اﻷول من خلال البنية {}()else if والتي تتحقق إن كان الدور الذي لعبه اللاعب هو آخر دور له. فإن كان كذلك، يكرر ما فعلناه في الكتلة السابقة لكن مع رسالة تشير إلى نهاية اللعبة بدلًا من رسالة التهنئة. أما الكتلة اﻷخيرة التي نربطها بسابقتها فهي الكتلة {}else التي تضم شيفرة تُنفَّذ فقط إن فشل الاختباران في الكتلتين السابقتين (أي اللاعب لم يخمن العدد، ,ولم ينهي جميع محاولاته). نخبره في هذه الحالة أن تخمينه خاطئ ثم ننفذ اختبارًا آخر يتحقق إن كان تخمينه أكبر بكثير أو أقل بكثير من العدد الصحيح ثم نعرض رسالة أخرى بطريقة مناسبة ﻹخبار اللاعب بذلك. تحضر اﻷسطر الثلاث اﻷخيرة من الدالة (من 26 إلى 28) إرسال لتخمين التالي. إذ تضيف 1 إلى المتغير guessCount كي يستنفذ اللاعب دورًا جديدًا (العامل ++ هو عامل يزيد قيمة المتغير بمقدار 1). وتفرّغ قيمة المربع النصي في النموذج وتعطيه تركيز الدخل مجددًا، وبهذا يصبح اللاعب جاهزًا ﻹدخال التخمين التالي. اﻷحداث ما فعلنا حتى اﻵن هو إنجاز الدالة لكنها لن تفعل شيئًا لأننا لم نستدعها بعد. ومن المنطقي أن نستدعي هذه الدالة عند النقر على زر "Submit guess"، ولإنجاز ذلك، نحتاج إلى ما يُدعى حدثًا event. فاﻷحداث هي أشياء تحدث في المتصفح مثل نقر زر أو تحميل صفحة أو تشغيل فيديو وغيرها. ولكي ينفّذ المتصفح كتلة من الشيفرة كاستجابة لحدث ما نحتاج إلى مترصّد أحداث event listener يراقب أحداث معينة ويستدعي معالج أحداث event handler مناسب، والمعالج هو كتل من الشيفرة تعمل استجابةً لوقوع حدث في المتصفح. أضف السطر التالي تحت الدالة ()checkGuess: guessSubmit.addEventListener("click", checkGuess); أضفنا هنا مترصّد أحداث إلى الزر، وهو تابع يأخذ قيمتين (تُدعيان وسيطين arguments)، اﻷول هو نوع الحدث الذي نترصده فهو حدث (النقر click) وله قيمة نصية، والثاني هو الشيفرة التي تُنفَّذ عند وقوع الحدث وهي في حالتنا الدالة ()checkGuess. ولاحظ أنه لا حاجة لوضع القوسين عند كتابة الدالة كوسيط لمترصد الحدث ()addEventListener جرّب حفظ التغييرات على الملف ثم إعادة تحميل الصفحة في متصفحك وسيعمل التطبيق إلى حد معين. وما سيحدث أنه لو خمّنت العدد الصحيح أو أنهيت كل محاولاتك ستنهار اللعبة لأننا لم نعرّف بعد الدالة ()setGameOver التي من المفترض أن تُستدعى عند نهاية اللعبة. لهذا سنضيف الشيفرة الناقصة ﻹكمال اللعبة إنهاء جميع وظائف اللعبة لنضف اﻵن الدالة ()setGameOver إلى أسفل الشيفرة التي كتبتها حتى اﻵن ثم نشرح ما تفعله: function setGameOver() { guessField.disabled = true; guessSubmit.disabled = true; resetButton = document.createElement("button"); resetButton.textContent = "Start new game"; document.body.append(resetButton); resetButton.addEventListener("click", resetGame); } يعطّل أول سطرين عمل عنصر اﻹدخال النصي في النموذج وعمل الزر بضبط الخاصية disabled على القيمة trueلكل منهما. وهذا اﻷمر ضروري، لأن اللاعب سيتمكن من إرسال قيم تخمين أخرى رغم إنتهاء اللعبة، وسيجعل ذلك اللعبة فوضوية. توّلد اﻷسطر الثلاث التالية زرًا جديدًا <button>بعنوان "Start new game" ومن ثم تضيفه إلى شيفرة HTML الموجودة. يضبط السطر اﻷخير مترصّد أحداث للزر الجديد كي يترصد حدث النقر عليه ويستدعي الدالة ()resetGame. علينا اﻵن كتابة الشيفرة الخاصة بالدالة اﻷخيرة، لهذا ضع الشيفرة التالية في آخر ما كتبته: function resetGame() { guessCount = 1; const resetParas = document.querySelectorAll(".resultParas p"); for (const resetPara of resetParas) { resetPara.textContent = ""; } resetButton.parentNode.removeChild(resetButton); guessField.disabled = false; guessSubmit.disabled = false; guessField.value = ""; guessField.focus(); lastResult.style.backgroundColor = "white"; randomNumber = Math.floor(Math.random() * 100) + 1; } هذه الكتلة من الشيفرة طويلة نوعًا ما وتعيد كل شيء إلى وضعه اﻷصلي عند بداية اللعبة، وبالتالي يصبح اللاعب قادرًا على المتابعة من جديد، فهي: تعيد قيمة المتغير guessCount إلى 1. تفرّغ كل ما هو موجود في الفقرات النصية. فقد اخترنا جميع الفقرات النصية ضمن العنصر <div class="resultParas"></div> ثم تفقدنا من خلال حلقة كل فقرة وضبطنا الخاصية textContent لكل منها على' ' (سلسلة فارغة). تزيل زر إعادة الضبط من الشيفرة. تفعّل عناصر النموذج وتفرّغ الفقرات النصية وتعطي تركيز الدخل إلى مربع اﻹدخال النصي في النموذج كي يصبح جاهزًا لتلقي تخمين جديد من قبل اللاعب. تزيل لون خلفية الفقرة النصية lastResult. توّلد رقم عشوائي جديد كي لا تخمّن نفس الرقم العشوائي السابق. لقد اكتملت اللعبة اﻵن، تهانينا! وكل ما بقي لنا في هذا المقال هو الحديث عن بعض الميزات الهامة التي رأيناها في شيفرة اللعبة والتي ربما لم تدركها. الحلقات من أهم أجزاء الشيفرة التي كتبناها، والتي لا بد من الحديث عنها هي حلقة (for…of). فالحلقات هي مفاهيم برمجية غاية في اﻷهمية تتيح لك تنفيذ جزء من الشيفرة مرارًا وتكرارًا حتى يتحقق شرط معين. وكي نبدأ تتبع عمل الحلقات انتقل إلى طرفية جافا سكريبت في أدوات مطوري ويب، ثم أدخل الشيفرة التالية: const fruits = ["apples", "bananas", "cherries"]; for (const fruit of fruits) { console.log(fruit); } ما الذي حدث؟ لقد طبعت الكلمات 'apples', 'bananas', 'cherries' على الشاشة كنتيجة لتنفيذ الحلقة. إذ يُنشئ السطر ;['const fruits = ['apples', 'bananas', 'cherries مصفوفة (سنتعرف عليها لاحقًا في مقال آخر) وهي مبدئيًا مجموعة من العناصر (سلاسل نصية في حالتنا). وتزوّدك الحلقة for...of بطريقة للحصول على كل عنصر من المصفوفة وتطبيق شيفرة جافا سكريبت عليه، فما يفعله السطر (for (const fruit of fruits هو التالي: الحصول على أول عناصر المصفوفة fruits. ضبط قيمة المتغيّر fruit لتكون قيمة العنصر اﻷول، ثم تنفيذ شيفرة جافا سكريبت الموجودة داخل القوسين {}. الحصول على العنصر التالي من المصفوفة fruits. في هذه الحالة، تطبع الشيفرة قيمة المتغيّر fruit على الشاشة. لنلق نظرة اﻵن على الحلقة الموجودة في لعبة "خمّن الرقم" وتحديدًا ضمن الدالة ()resetGame: const resetParas = document.querySelectorAll(".resultParas p"); for (const resetPara of resetParas) { resetPara.textContent = ""; } توّلد هذه الشيفرة متغيّرًا يضم قائمة بجميع الفقرات النصية الموجودة ضمن العنصر <div class="resultParas"> باستخدام التابع ()querySelectorAll ومن ثم يتفقد كل فقرة باستخدام حلقة ويزيل المحتوى النصي لها. وتجدر الملاحظة أن ثوابت مثل resetParas يمكن إزالة المحتوى النصي لها. مناقشة سريع لمفهوم الكائن Object في جافا سكريبت سنضيف تحسينًا أخيرًا إلى اللعبة قبل الدخول في هذا النقاش وهو سطر يجب وضعه تحت السطر ;let resetButton بالقرب من بداية شيفرة جافا سكريبت ثم نحفظ التغيرات: guessField.focus(); يستخدم هذا السطر التابع ()focus الذي يجعل مؤشر الدخل ضمن عنصر اﻹدخال النصي <input> حالما تُحمّّل الصفحة، وهكذا يصبح اللاعب جاهزًا لكتابة تخمينه مباشرة دون الحاجة إلى النقر داخل مربع اﻹدخال النصي. صحيح أنها إضافة صغيرة لكنها تحسن استخدام اللعبة وتمنح اللاعب فكرة عما سيفعله تاليًا حتى يبدأ اللعب. لنحلل ما يجري هنا بمزيد من التفصيل. إذ نتعامل غالبًا في جافا سكريبت مع الكائنات objects، والكائن هو مجموعة مترابطة من الوظائف المخزّنة في شيء واحد. بإمكانك بالطبع إنشاء كائنات خاصة بك، لكن اﻷمر متقدم قليلًا ولن نغطيه في مقالنا. أما ما نناقشه اﻵن باختصار فهي الكائنات المدمجة مع اللغة والتي يتضمنها متصفحك وتسمح لك بتنفيذ العديد من الأمور المفيدة. في هذه الحالة الخاصة، أنشأنا ثابتًا يخزّن مرجعًا إلى عنصر اﻹدخال النصي الموجود في نموذج HTML، وستجد السطر التالي ضمن التصريحات في بداية الشيفرة: const guessField = document.querySelector(".guessField"); وللحصول على هذا المرجع، استخدمنا التابع ()querySelector العائد إلى الكائن document. ويأخذ هذا التابع معلومة واحدة وهي محدد CSS الذي يُستخدم ﻹنتقاء العنصر الذي تريد إنشاء مرجع إليه. ولأن الثابت guessField يحتوي مرجعًا إلى العنصر <input>، فلديه القدرة اﻵن على الوصول إلى خصائصه (تُخزَّن المتغيرات أساسًا ضمن كائنات، ولا يمكن تغيير قيم بعضها) وتوابعه (وهي أساسًا دوال مخزّنة ضمنه). ومن أحد توابع عنصر اﻹدخال النصي نجد ()focus لهذا يمكننا استخدام السطر التالي لنقل تركيز الدخل إلى هذا العنصر: guessField.focus(); لا يمكن استخدام التابع ()focus على المتغيرات التي لا تضم مراجعًا إلى عناصر، فالثابت guesses مثلًا يضم مرجعًا إلى العنصر <p> والمتغير guessCount يضم عددًا. التعامل مع كائنات المتصفح لنعمل قليلًا مع بعض كائنات المتصفح: افتح ملف اللعبة أولًا ضمن المتصفح. افتح أدوات مطوري ويب في متصفحك (من زر القائمة>أدوات إضافية>أدوات مطوري ويب "في متصفح فايرفكس"). وتأكد من وصولك إلى طرفية جافا سكريبت. اكتب في الطرفية guessField وستعرض لك حينها أن هذا المتغيّر يضم العنصر <input>، وستلاحظ أيضًا أن الطرفية تعرض لك تلقائيًا أسماء جميع الكائنات الموجودة ضمن بيئة التنفيذ بما في ذلك متغيّراتك التي صرحت عنها. اكتب اﻵن مايلي: guessField.value = 2; تمثل الخاصية value القيمة الحالية المُدخلة إلى مربع النص، وسترى أن تنفيذ اﻷمر السابق يغيّر النص الموجود في مربع النص. جرّب أن تكتب guesses في الطرفية ثم اضغط المفتاح "Enter" وسترى أن هذا المتغير يتضمن العنصر <p>. جرّب كتابة اﻷمر التالي: guesses.value وسيعيد المتصفح القيمة undefined لأن الفقرة النصية لا تمتلك الخاصية value لتغيير النص داخل الفقرة النصية جرّب الأمر التالي: "?guesses.textContent="Where is my paragraph ولكي تجرب أشياء أخرى، اكتب اﻷسطر التالية: guesses.style.backgroundColor = "yellow"; guesses.style.fontSize = "200%"; guesses.style.padding = "10px"; guesses.style.boxShadow = "3px 3px 6px black"; لكل عنصر في الصفحة خاصية تُدعى style تضم بحد ذاتها كائنًا له خاصيات تتضمن جميع تنسيقات CSS السطرية المطبقة على العنصر. ويسمح لنا ذلك ضبط تنسيقات جديدة للعنصر ديناميكيًا من خلال جافا سكريبت. الخلاصة هكذا نكون قد بنينا تطبيقًا بسيط باستخدام جافا سكريبت، استمتع بتجريب ما فعلته أو جرّب النسخة الجاهزة من اللعبة على جيت-هاب كما يمكنك تحميل شيفرتها المصدرية أيضًا. ترجمة -وبتصرف- للمقال Afirst splash into JavaScript اقرأ أيضًا المقال السابق: تعرّف على لغة جافا سكريبت من منظور عام تعلم لغة جافا سكريبت من الصفر حتى الاحتراف أساسيات لغة جافاسكربت العبارات الشرطية واتخاذ القرار في جافا سكريبت مدخل إلى جافاسكريبت كائنية التوجه (Object-Oriented JavaScript)
  6. نتابع في سلسلة المقالات هذه تغطية أساسيات جافاسكريبت وميزاتها، وتحديدًا أنواع البنى البرمجية التي نصادفها بكثرة في الشيفرة مثل العبارات الشرطية والحلقات والدوال والأحداث. وقد اطلعنا على هذه النقاط بشكل مبسط في سلسلة المقالات السابقة لكننا سنناقشها هنا بشيء من التفصيل. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافاسكربت كما شرحناها في سلسلة المقالات السابقة. ملاحظة: إن كنت تستخدم حاسوب أو جهاز لوحي أو غيره من الأجهزة التي لا تمكّنك من إنشاء ملفاتك الخاصة، جرِّب الشيفرة التي ستجدها في الأمثلة من خلال برامج كتابة شيفرة على الإنترنت مثل Glitch أو JSBin. نتحدث في هذا المقال عن مفهوم العبارات الشرطية في جافاسكريبت إذ لا بد من اتخاذ قرارات عند كتابة الشيفرة في أية لغة برمجة ومن ثم تنفيذ بعض اﻹجراءات وفقًا لمعطيات الدخل المختلفة. فلو أخذنا لعبة ما كمثال، سنجد أن اللعبة تنتهي عندما يصبح عدد المحاولات المسموحة 0، ونأخذ كمثال آخر تطبيقًا للطقس يعرض صورة شروق الشمس في الصباح ويعرض صورة قمر ونجوم في ساعات الليل. هذا اﻷفعال تقرر من خلال استخدام البنى الشرطية وستكون موضوع هذا المقال. لماذا نستخدم عبارات شرطية في جافا سكريبت؟ يتخذ البشر (وكائنات حية أخرى) باستمرار قرارات تؤثر على حياتهم منها البسيطة (كان تقرر تناول قطعة أو قطعتي بسكويت) أو قرارات مصيرية (هل سأبقى في بلدي والعمل في مزرعة أبي أو أن أسافر لأكمل دراستي). وهذا ما تمثله العبارات الشرطية في جافا سكريبت تمامًا، سواء لاتخاذ قرار (أكل قطعة بسكويت واحدة مثلًا) أو معالجة نتائج هذه القرارات (إن اكل قطعة واحد فقد لا أشعر بالشبع، وإن أكلت أكثر فقد أشعر بالتخمة). العبارة الشرطية if...else نلقي نظرة في هذا القسم على العبارة الشرطية if...else، وهي أكثر العبارات الشرطية شيوعًا في جافا سكريبت. الصياغة القواعدية اﻷساسية للعبارة if...else تبدو هذه العبارة بالشكل التالي: if (condition) { /* code to run if condition is true */ } else { /* run some other code instead */ } لدينا هنا: الكلمة المحجوزة if ويليها قوسان عاديان(). شرط يجري اختباره يوضع ضمن القوسين ( مثل هل قيمة ما أكبر من قيمة أخرى أو هل لهذه القيمة وجود؟). وتستخدم الشرط في صياغته عادة عوامل مقارنة تعيد إحدى النتيجتين صحيح true أو خاطئ false. مجموعة من اﻷقواس المعقوصة {} وضمنها بعض الشيفرة، وقد تكون هذه الشيفرة أي شيء نريده وتُنفَّذ إن تحقق الشرط. الكلمة المحجوزة else. مجموعة أخرى من اﻷقواس المعقوصة، وضمنها بعض الشيفرة التي تُنفَّذ فقط إن لم يتحقق الشرط (كانت نتيجته false). يمكن بسهولة قراءة الشيفرة السابقة التي تقول "إذا أعاد الشرط النتيجة true نفّذ الشيفرة A وإلا نفّذ الشيفرة B. وتجدر الملاحظة أنه ليس من الضروري تضمين القسم else، فالشيفرة التالية صحيحة تمامًا: if (condition) { /* code to run if condition is true */ } /* run some other code */ لكن لا بد من الانتباه في هذه الحالة إلى أن العبارة الشرطية في هذه الحالة لن تتحكم بكتلة الشيفرة التي تليها والتي ستُنفَّذ أيًا كانت نتيجة الشرط. ولا نعني بذلك أن هذا أمر سيء لكن قد لا يتحقق ما تريده بالطريقة المتوقعة، لأنك غالبًا ما تريد تنفيذ أحد كتلتين من الشيفرة في الحالات الشرطية وليس كلاهما. وأخيرًا قد تجد العبارة الشرطية دون أقواس معقوصة مع أنه أمر غير مرغوب إطلاقًا: if (condition) /* code to run if condition is true */ else /* run some other code instead */ لا يوجد بالطبع خطأ في الصياغة السابقة لكن وجود اﻷقواس يجعل الشيفرة أكثر وضوحًا، فهي توضح حدود الشيفرة التي تتحكم بها العبارة الشرطية، كما أن استخدام أسطر متعددة مع إزاحة مناسبة يسهّل قراءة الشيفرة أكثر. مثال واقعي لفهم صياغة العبارة الشرطية بصورة أفضل، لنتخيل في هذا المثال الواقعي طفلًا يطلب مساعدة أمه أو أبيه في مهمة ما. فقد يقول الوالد أو الوالدة "إن ساعدتني في التسوق سأعطيك مصروفًا إضافيًا كي تشتري اللعبة التي تريد". يمكن أن نمثل اﻷمر في جافا سكريبت كالتالي: let shoppingDone = false; let childsAllowance; if (shoppingDone === true) { childsAllowance = 10; } else { childsAllowance = 5; } تجعل الشيفرة السابقة قيمة المتغير shoppingDone دائمًا false وهذا يعني أن ظن الطفل سيخيب. لكن بالطبع يمكننا اقتراح آلية على الوالدين لتصبح قيمة المتغير shoppingDone هي true إن ساعد الطفل الوالد في التسوق. التعليمة else if يزودنا المثال السابق بخيارين أو نتيجتين، لكن ماذا لو أردنا خيارات أكثر؟ توجد طريقة لربط عدة خيارات أو نتائج إلى العبارة if...else باستخدام التعليمة else if التي تضم كتلة إضافية من الشيفرة بين الكتلتين {}()if و {} else. لنلق نظرة على المثال التالي الذي يصلح أن يكون جزءًا من تطبيق للأحوال الجوية: <label for="weather">Select the weather type today: </label> <select id="weather"> <option value="">--Make a choice--</option> <option value="sunny">Sunny</option> <option value="rainy">Rainy</option> <option value="snowing">Snowing</option> <option value="overcast">Overcast</option> </select> <p></p> const select = document.querySelector("select"); const para = document.querySelector("p"); select.addEventListener("change", setWeather); function setWeather() { const choice = select.value; if (choice === "sunny") { para.textContent = "It is nice and sunny outside today. Wear shorts! Go to the beach, or the park, and get an ice cream."; } else if (choice === "rainy") { para.textContent = "Rain is falling outside; take a rain coat and an umbrella, and don't stay out for too long."; } else if (choice === "snowing") { para.textContent = "The snow is coming down — it is freezing! Best to stay in with a cup of hot chocolate, or go build a snowman."; } else if (choice === "overcast") { para.textContent = "It isn't raining, but the sky is grey and gloomy; it could turn any minute, so take a rain coat just in case."; } else { para.textContent = ""; } } See the Pen js-making decisions1 by Hsoub Academy (@HsoubAcademy) on CodePen. لدينا في الشيفرة السابقة العنصر <select> الذي يسمح لنا باختيار أحد التوقعات الجوية إضافة إلى فقرة نصية بسيطة. نخزّن في شيفرة جافا سكريبت مرجعًا إلى العنصر <select> وآخر للفقرة النصية <p> ونضيف مترصد أحداث إلى <select> كي يستدعي الدالة ()setWeather عندما تتغير قيمته (يتغير الخيار). عندما تُنفَّذ هذه الدالة، تُسند إلى المتغير choice قيمة العنصر <select> ثم نستخدم عبارة شرطية لعرض نصوص مختلفة ضمن الفقرة النصية وفقًا لقيمة المتغير choice. لاحظ كيف اختُبرت كل الشروط باستخدام الكتلة {}()else if ما عدا الشرط اﻷول الذي استخدمنا في اختباره {}()if. أما آخر الخيارات فقد وضع ضمن الكتلة {} else ليكون الملاذ اﻷخير الذي تُنفّذ الشيفرة التي يضمها إن فشلت جميع الاختبارات السابقة. إذ تفرغ شيفرة هذه الكتلة النص الموجود في الفقرة النصية إن لم يختار المستخدم أيًا من خيارات العنصر <select> أي أعاد استخدام الخيار "--Make a choice--" الذي يُعرض افتراضيًا. ملاحظة حول عوامل المقارنة تُستخدم عوامل المقارنة للتحقق من صحة شرط العبارة الشرطية. ولو عدنا إلى المقال الذي يشرح [العوامل الرياضية]() سنجد الخيارات التالية: === و !== لاختبار تطابق قيمة أو عدم تطابقها مع قيمة أخرى. < و > لاختبار ما إذا كانت قيمة ما أكبر تمامًا أو أصغر تمامًا من أخرى. <= و >= لاختبار ما إذا كانت قيمة ما أكبر أو تساوي أو أصغر أو تساوي قيمة أخرى. نريد هنا اﻹشارة بشكل خاص إلى اختبار القيم المنطقية (true و false) ونموذج شائع الاستخدام في ذلك. تُعيد أية قيمة ليست إحدى القيم التالية: false أو undefined أو null أو 0 أو NaN أو ('') القيمة true عندما تستخدم كشرط في عبارة شرطية، وبالتالي بإمكانك استخدام اسم لمتغير لاختبار وجوده أو إن كانت قيمته true. إليك مثالًا: let cheese = "Cheddar"; if (cheese) { console.log("Yay! Cheese available for making cheese on toast."); } else { console.log("No cheese on toast for you today."); } وبالعودة إلى مثالنا السابق حول الطفل الذي يؤدي عملًا لوالديه، يمكن إعادة كتابة المثال كالتالي: let shoppingDone = false; let childsAllowance; //'shoppingDone === true' لا نريد أن نقول صراحة أن if (shoppingDone) { childsAllowance = 10; } else { childsAllowance = 5; } كتل if..else متداخلة باﻹمكان وضع الكتلة if...else ضمن كتلة if...else أخرى. إذ يمكننا مثلًا كتابة تمرين التوقعات الجوية ليعرض مجموعة أخرى من الخيارات وفقًا لدرجة الحرارة أيضًا: if (choice === "sunny") { if (temperature < 86) { para.textContent = `It is ${temperature} degrees outside — nice and sunny. Let's go out to the beach, or the park, and get an ice cream.`; } else if (temperature >= 86) { para.textContent = `It is ${temperature} degrees outside — REALLY HOT! If you want to go outside, make sure to put some sunscreen on.`; } } ستعمل الشيفرة ككل بتناسق مع بعضها، لكن كل عبارة if...else تعمل بشكل منفصل تمامًا عن اﻷخرى. العوامل المنطقية: AND و OR و NOT إن أردت اختبار عدة شروط دون أن تكتب عبارات متداخلة، يمكنك استخدام العوامل المنطقية وهي: العامل AND &&: يسمح لك بربط عبارتين أو أكثر وينبغي أن تكون نتيجة جميع هذه العبارات true حتى تعيد العبارة الشرطية النتيجة true. العامل OR ||: يسمح لك بربط عبارتين أو أكثر وينبغي أن تكون نتيجة إحدى هذه العبارات true حتى تعيد العبارة الشرطية النتيجة true. وللتعرف على عمل AND، سنعيد كتابة المثال السابق باستخدامها: if (choice === "sunny" && temperature < 86) { para.textContent = `It is ${temperature} degrees outside — nice and sunny. Let's go out to the beach, or the park, and get an ice cream.`; } else if (choice === "sunny" && temperature >= 86) { para.textContent = `It is ${temperature} degrees outside — REALLY HOT! If you want to go outside, make sure to put some sunscreen on.`; } تُنفَّذ الكتلة البرمجية اﻷولى إن أعاد الاختبار (choice === 'sunny' and temperature < 86) النتيجة true. لنلق نظرة اﻵن على مثال سريع عن استخدام OR: if (iceCreamVanOutside || houseStatus === "on fire") { console.log("You should leave the house quickly."); } else { console.log("Probably should just stay in then."); } أما النوع اﻷخير من العوامل المنطقة NOT والذي يعبّر عنه بالمحرف !، فيُستخدم لنفي عبارة، إليك المثال السابق بوجود عامل النفي: if (!(iceCreamVanOutside || houseStatus === "on fire")) { console.log("Probably should just stay in then."); } else { console.log("You should leave the house quickly."); } فإن أعادت العبارة OR القيمة true سينفيها العامل NOT وستعيد العبارة الكاملة القيمة false. بإمكانك دمج العدد الذي تساء من العوامل المنطقية مع بعضها إن أردت ووفقًا للبنية البرمجية التي تريدها. إليك مثالًا تُنفَّذ فيه شيفرة الكتلة إذا أعادت كلتا عبارتي OR القيمة true أي عندما تعيد العبارة الكلية AND القيمة true. if ((x === 5 || y > 3 || z <= 10) && (loggedIn || userName === "Steve")) { // run the code } من اﻷخطاء الشائعة المرتكبة عند استخدام العامل OR في العبارات الشرطية هو كتابة المتغير الذي تريد التحقق من حالته، ثم كتابة قائمة من القيم التي تريد موازنتها بالمتغير يفصل بينها العامل OR (||) كما في المثال التالي: // OR استخدام خاطئ للعامل if (x === 5 || 7 || 10 || 20) { // run my code } في هذه الحالة ستكون نتيجة الشرط داخل ()if صحيحة دائمًا لأن الرقم 7 أو أي عدد غير معدوم سيعطي النتيجة true ويُفهم اﻷمر على النحو " إذا كان x مساويًا 5 أو 7 ستكون النتيجة true" وهذا أمر محقق دائمًا (كون x متغير غير محدد القيمة). لكنك لا تريد هذه النتيجة منطقيًا، وعليك تصحيح الكود السابق على النحو التالي: if (x === 5 || x === 7 || x === 10 || x === 20) { // run my code } عبارة الاختيار المتعدد switch تؤدي عبارة if واجبها تمامًا في اختبار الشروط، لكن لا يخلو اﻷمر من بعض السلبيات. فهي أسلوب جيد عندما تكون أمام خيارات محدودة يتضمن كل منها كمية معقولة من الشيفرة، أو عندما يكون الشرط مركبًا ويضم عدة عوامل موازنة منطقية. لكن في الحالات التي تريد فيا إسناد قيمة إلى متغير أو طباعة عبارة محددة عند تحقق شرط معين، ستكون الشيفرة طويلة وخاصة إن كان أمامك عدد كبير من الخيارات. تظهر في حالات كهذه فائدة البنية switch التي تأخذ عبارة أو قيمة واحدة ثم تبحث عن هذه القيمة أو الشرط بين الخيارات المتعددة المتاحة حتى تجد المطلوب وتنفذ عندها الشيفرة التي يضمها الخيار المطابق. إليك قالب استخدام switch: switch (expression) { case choice1: // run this code break; case choice2: // run this code instead break; // include as many cases as you like default: // actually, just run this code break; } لدينا في الكود السابق: الكلمة المحجوزة switch يليها قوسان. عبارة أو قيمة ضمن القوسين. الكلمة المحجوزة case يتبعها خيار يمثل ما يمكن أن تكونه العبارة أو القيمة التي تشير إليها. شيفرة تُنفَّذ إن طابق الخيار العبارة أو القيمة. الكلمة المحجوزة break تليها فاصلة منقوطة كي يتوقف المتصفح عن التحقق من بقية الخيارات عندما يجد الخيار المطابق ويتابع تنفيذ الشيفرة ما بعد الكتلة switch. تكرار للبنية case وفقًا لعدد الخيارات الموجودة. الكلمة المحجوزة default يليها نمط الشيفرة الموجود في أي حالة case أخرى ما عدا أن default لا يتبعها خيار محدد ولا تحتاج إلى التعليمة break لأنها آخر ما يُنفّذ في عبارة switch ويمثّل الخيار الافتراضي إن أخفق التطابق مع جميع الخيارات المتاحة. ملاحظة: لا حاجة لوجود الجزء default وبإمكانك حذفه إن كنت متأكدًا تمامًا من وجود حالة تطابق. لكن إن أمكن وجود حالة عدم تطابق لا بد من تضمينها لمعالجة الحالات المجهولة. مثال عن كتلة switch لنلق نظرة اﻵن على مثال واقعي، يُظهر استخدام الكتلة switch في تطبيق الأحوال الجوية: <label for="weather">Select the weather type today: </label> <select id="weather"> <option value="">--Make a choice--</option> <option value="sunny">Sunny</option> <option value="rainy">Rainy</option> <option value="snowing">Snowing</option> <option value="overcast">Overcast</option> </select> <p></p> const select = document.querySelector("select"); const para = document.querySelector("p"); select.addEventListener("change", setWeather); function setWeather() { const choice = select.value; switch (choice) { case "sunny": para.textContent = "It is nice and sunny outside today. Wear shorts! Go to the beach, or the park, and get an ice cream."; break; case "rainy": para.textContent = "Rain is falling outside; take a rain coat and an umbrella, and don't stay out for too long."; break; case "snowing": para.textContent = "The snow is coming down — it is freezing! Best to stay in with a cup of hot chocolate, or go build a snowman."; break; case "overcast": para.textContent = "It isn't raining, but the sky is grey and gloomy; it could turn any minute, so take a rain coat just in case."; break; default: para.textContent = ""; } } العامل الثلاثي Ternary operator نريد أخيرًا المرور على صيغة أخيرة للعبارات الشرطية قبل الانتقال إلى التمارين وهي العامل الثلاثي الذي يختبر شرطًا واحدًا ويعيد قيمة أو عبارة إن كان الشرط محققًا وقيمة أو عبارة أخرى إن لم يكن محققًا. لهذا العامل فائدته في بعض الحالات ويحتاج إلى شيفرة أقل من كتلة if...else في حال كان لديك خيارين اثنين لانتقاء أحدهما وفقًا لنتيجة من الشكل true/false. condition ? run this code : run this code instead إليك مثالًا بسيطًا: const greeting = isBirthday ? "Happy birthday Mrs. Smith — we hope you have a great day!" : "Good morning Mrs. Smith."; تعرض الشيفرة متغيرًا يُدعى isBirthday فإن كانت قيمته true يُحييا الضيف برسالة معايدة بعيد ميلاده، أما إن كانت قيمته false فيُحييا بالتحية الاعتيادية. مثال عن العامل الثلاثي لا تُستخدم المتغيرات فقط مع العامل الثلاثي، بل يستطيع أن ينفذ دوال أو أسطر متعددة من الشيفرة. ولتوضيح الأمر، يعرض المثال التالي برنامجًا لاختيار سمة لتنسيق موقع ويب باستخدام العامل الثلاثي: <label for="theme">Select theme: </label> <select id="theme"> <option value="white">White</option> <option value="black">Black</option> </select> <h1>This is my website</h1> const select = document.querySelector("select"); const html = document.querySelector("html"); document.body.style.padding = "10px"; function update(bgColor, textColor) { html.style.backgroundColor = bgColor; html.style.color = textColor; } select.addEventListener("change", () => select.value === "black" ? update("black", "white") : update("white", "black"), ); See the Pen js-making-decisions2 by Hsoub Academy (@HsoubAcademy) on CodePen. لدينا في الشيفرة السابقة العنصر <select> الذي يُستخدم في اختيار سمة (أحد اللونين اﻷبيض أو اﻷسود)، إضافة إلى عنصر عنوان من المستوى اﻷول <h1> لعرض عنوان صفحة الويب. كما لدينا الدالة ()update التي تأخذ لونين كمعاملين لها. تضبط الدالة لون خلفية الصفحة لتصبح اللون الأول ولون النص هو اللون الثاني. نلاحظ أخيرًا استخدام مترصّد اﻷحداث onChange الذي ينُفِّذ الدالة مع معاملين هما اللونين اﻷسود واﻷبيض. يبدأ البرنامج بالعبارة الشرطية select.value === 'black' وبعدها تُنفّذ الدالة ()update ومعاملاها اللونان اﻷسود واﻷبيض، إذا كانت النتيجة true وبالتالي تصبح الخلفية سوداء والنص أبيض. أما إن أعاد الشرط السابق القيمة false، تُنفَّذ الدالة ()update ومعاملاها اللونان الأبيض واﻷسود فتكون الخلفية بيضاء والنص أسود اللون. مثال عل إنشاء تقويم زمني بسيط سننشئ تطبيق تطبيق تقويم زمني بسيط يتيح للمستخدم الاختيار بين اﻷشهر المختلفة المستخدم ليعرض تقويم مناسب للشهر. إذا كان لدينا تصميم الصفحة التالية: <h2>Live output</h2> <iframe id="output" width="100%" height="600px"></iframe> <h2>Editable code</h2> <p class="a11y-label"> Press Esc to move focus away from the code area (Tab inserts a tab character). </p> <textarea id="code" class="playable-code" style="height: 400px;width: 95%"> const select = document.querySelector('select'); const list = document.querySelector('ul'); const h1 = document.querySelector('h1'); select.addEventListener('change', () => { const choice = select.value; // ADD CONDITIONAL HERE createCalendar(days, choice); }); function createCalendar(days, choice) { list.innerHTML = ''; h1.textContent = choice; for (let i = 1; i <= days; i++) { const listItem = document.createElement('li'); listItem.textContent = i; list.appendChild(listItem); } } createCalendar(31, 'January'); </textarea> <div class="playable-buttons"> <input id="reset" type="button" value="Reset" /> <input id="solution" type="button" value="Show solution" /> </div> لنضف بعض التنسيقات كما يلي: html { font-family: sans-serif; } h2 { font-size: 16px; } .a11y-label { margin: 0; text-align: right; font-size: 0.7rem; width: 98%; } body { margin: 10px; background: #f5f9fa; } هذا كود جافا سكريبت المطلوب لعمل التقويم الزمني بالشكل المناسب: const select = document.querySelector("select"); const list = document.querySelector("ul"); const h1 = document.querySelector("h1"); select.addEventListener("change", () => { const choice = select.value; let days = 31; if (choice === "February") { days = 28; } else if ( choice === "April" || choice === "June" || choice === "September" || choice === "November" ) { days = 30; } createCalendar(days, choice); }); function createCalendar(days, choice) { list.innerHTML = ""; h1.textContent = choice; for (let i = 1; i <= days; i++) { const listItem = document.createElement("li"); listItem.textContent = i; list.appendChild(listItem); } } createCalendar(31, "January"); كما تلاحظ في الكود أعلاه فقد استخدمنا معالج أحداث onchange يرصد تغيّر قيمة العنصر الذي يختاره المستخدم من العنصر <select>، وكتبنا عبارة شرطية ضمن دالة معالج الأحداث onchange مهمتها ما يلي: النظر إلى الشهر المختار المُخزَّن في المتغير choice وهو قيمة العنصر <select>. ضبط المتغيّر days كي يكون مساويًا لعدد أيام الشهر المختار. وهذه النتيجة التي ستحصل عليها: تطبيق عملي: خيارات لونية أكثر ستحوّل في هذا التمرين العامل الثلاثي الذي تعرفنا عليه سابقًا إلى عبارة switch كي نتمكن من تطبيق خيارات أكثر على موقع الويب البسيط الذي نعمل عليه. لاحظ أن العنصر <select> لا يضم خيارين فقط بل خمسة، لهذا لا بد من استخدام switch هذه المرة تحت التعليق ADD SWITCH STATEMENT//: يجب أن تقبل العبارة المتغير choice ليكون عبارة الدخل. يجب أن تُوازن قيمة المتغير choice في كل حالة مع أحد الخيارات <option> وهي white أو black أو purple أو yellow أو psychedelic. يجب تنفيذ الدالة ()update في كل حالة وذلك بتمرير قيمتين لونيتين لها تمثّل اﻷولى لون الخلفية والثانية لون النص، وتذكر أن هذه القيم نصيّة ولا بد من وضعها ضمن إشارتي تنصيص. إذا ارتكبت خطأ، يمكنك دائمًا إعادة تعيين المثال باستخدام زر "Reset" الموجود في نهاية الإطار التالي، وإذا واجهتك مشكلة في حل السؤال بنفسك، فاضغط على زر "Show solution" لرؤية الحل الصحيح. See the Pen js-making-decisions4 by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة قدمنا في هذا المقال كل ما تحتاج معرفته حاليًا عن البنى الشرطية في جافا سكريبت، راجع المقال مرارًا لترسّخ المعلومات التي حصلت عليها في ذهنك أو بإمكانك طرح اﻷسئلة التي تشاء في نقاش صفحة المقال ضمن أكاديمية حسوب. ترجمة -وبتصرف- لمقال: Making decisions in your code-conditionals اقرأ أيضًا تعرّف على لغة جافا سكريبت من منظور عام تعلم لغة جافا سكريبت من الصفر حتى الاحتراف المعاملات المنطقية في جافاسكربت الجمل الشرطية if/else في جافاسكريبت
  7. يشرح هذا المقال الانسياب الاعتيادي للعناصر أو الطريقة التي تُرتّب فيها تلك العناصر ضمن صفحة الويب أي المواضع التي ستأخذها بشكل تلقائي إن لم يتغير تخطيطها. عليك قبل البدء في قراءة هذا المقال أن تكون على اطلاع بالمفاهيم التالية: أساسيات HTML. أساسيات عمل CSS. تُرتب العناصر في صفحة الويب كما شرحنا في المقال السابق مدخل إلى تخطيط صفحات ويب ضمن تخطيط تلقائي يُعرف بالانسياب الاعتيادي Normal Flow إن لم تُطبق أي تنسيقات CSS لتغيير هذا السلوك. وكما رأينا، يمكننا تغيير سلوك العناصر بتغيير موقعها في الانسياب الاعتيادي أو إزالتها من هذا الانسياب كليًا. وبالطبع فإن البدء بإنشاء مستند HTML متماسك ومهيكل بطريقة متينة وله انسياب اعتيادي واضح هي الطريقة الأنسب لتبدأ العمل على صفحة ويب. ستضمن بهذه الطريقة أن الصفحة مقروءة وواضحة بالنسبة لأي متصفح أو جهاز محدود الإمكانيات مثل قارئات الشاشة التي تقرأ للزائر محتويات الصفحة. إضافة إلى ذلك، فإن العمل مع تصميم الانسياب الاعتيادي الموجّة لإنشاء مستندات سهلة القراءة أفضل بكثير من العمل ضده عندما تبدأ بتعديل التخطيط بالشكل الذي تريده. لهذا السبب سنستعرض سريعًا في مقال اليوم بعض الأشياء التي مرّت معنا سابقًا والمتعلقة بالانسياب الاعتيادي قبل أن نتعمق في تفاصيل التخطيطات الأخرى. كيف تُرتب العناصر افتراضيًا في صفحة ويب؟ تبدأ عملية ترتيب العناصر التي تضيفها لصفحة الويب بشكل صناديق، وهذا ما يُدعى نموذج الصندوق Box model وهو نموذج يستخدم لتحديد وتصميم ترتيب وتخطيط العناصر في صفحة الويب. ويتكون النموذج من مربع يحيط بكل عنصر في الصفحة بحيث نحدد لكل مربع أربعة مكونات أساسية هي: الهامش Margin، والحاشية Padding، والإطار Border، والمحتوى Content كما تلاحظ في الصورة التوضيحية التالية: ولفهم المزيد عن ترتيب عناصر صفحة الويب عليك أن تفهم بشكل جيد ما هي المساحة التي سيشغلها كل عنصر تضيفه على الصفحة، فهناك نوعان مختلفان من عناصر HTML هما العناصر السطرية Inline-level والعناصر الكتلية Block-level، ويشغل كل نوع مساحة محددة من صفحة الويب، ويكمن الفرق الأساسي بين العناصر الكتلية والعناصر السطرية بأن العناصر الكتلية تعرض على أسطر منفصلة وتأخذ مساحة كاملة أفقيًا ورأسيًا، بينما تعرض العناصر السطرية على نفس السطر مع العناصر الأخرى وتأخذ مساحة تبعًا لحجم محتواها. حيث يملأ محتوى العناصر الكتلية block-level element افتراضيًا الفراغ الموافق لكامل السطر في العنصر الأب، وتنمو أو تتوسع هذه العناصر على طول البعد الرأسي أو ارتفاع الكتلة لاستيعاب محتواها، أما أبعاد العناصر السطرية inline-level elements فتتطابق مع أبعاد المحتوى. بالإمكان ضبط ارتفاع height أو اتساع width العناصر التي تأخذ فيها الخاصية display القيمة inline افتراضيًا مثل <img>، لكن تبقى قيمة display كما هي. وإن أردت التحكم بقيم الخاصية display للعناصر السطرية بهذه الطريقة، يمكن أن تستخدم CSS لجعلها تسلك سلوك العناصر الكتلية (مثل ;display: block أو ;display: inline-block التي تمزج ميزات كلا النوعين). ويفسر ذلك كيفية هيكلة العناصر كلًا على حدى، لكن ماذا عن هيكلتها عندما تكون معًا؟ هنا يظهر الانسياب الاعتيادي الذي أشرنا إليه سابقًا الذي يوضّع العناصر ضمن نافذة العرض الخاصة بالمتصفح. إذ ترتب العناصر الكتلية باتجاه الكتلة الذي يرتبط بدوره بنمط الكتابة (عادة أفقي horizontal-tb). يظهر كل عنصر في سطر مستقل تحت العنصر الذي يسبقه وتفصل بينهما الهوامش المخصصة لكل منها. ففي اللغة العربية أو الإنكليزية (أو أية لغة نمط الكتابة فيها أفقي)، سترتب العناصر الكتلية عموديًا. من ناحية أخرى، تسلك العناصر السطرية inline elements سلوكًا مختلفًا، فلا تظهر في أسطر مستقلة، بل تشغل السطر نفسه مع أي محتوى نصي مجاور أو ضمن عنصر حاوٍ طالما أن هناك فراغًا كافيًا ضمن المساحة الذي يشغلها العنصر الكتلي الأب. وإن لم يكن هناك فراغًا كافيًا فستنتقل العناصر التي تطفح عن السطر إلى سطر جديد. إن كان لأي عنصرين متجاورين عموديًا قيمة معينة للهامش وتلامس هامشيهما، سيبقى الهامش الأكبر فاصلًا بينهما ويختفي الهامش الأصغر، وهذا ما يُعرف بانكماش الهوامش margin collapsing، ويحدث فقط في الاتجاه العمودي. لنلق نظرة على المثال البسيط التالي الذي يشرح كل ما ذكرناه: <h1>Basic document flow</h1> <p> I am a basic block level element. My adjacent block level elements sit on new lines below me. </p> <p> By default we span 100% of the width of our parent element, and we are as tall as our child content. Our total width and height is our content + padding + border width/height. </p> <p> We are separated by our margins. Because of margin collapsing, we are separated by the width of one of our margins, not both. </p> <p> Inline elements <span>like this one</span> and <span>this one</span> sit on the same line along with adjacent text nodes, if there is space on the same line. Overflowing inline elements will <span>wrap onto a new line if possible (like this one containing text)</span>, or just go on to a new line if not, much like this image will do: <img src="long.jpg" alt="snippet of cloth" /> </p> body { width: 500px; margin: 0 auto; } p { background: rgba(255, 84, 104, 0.3); border: 2px solid rgb(255, 84, 104); padding: 10px; margin: 10px; } span { background: white; border: 1px solid black; } يوضح الكود السابق الطريقة الأساسية التي تتدفق بها العناصر والمحتوى في صفحة HTML تتضمن عنصر عنوان رئيسي h1، وأربع فقرات نصية p . لاحظ تأثير تطبيق نموذج الصندوق لتوضع هذه العناصر والقيم التي منحناها لتعديل الهامش والحدود والحشو للفقرات الأربعة p باستخدام تنسيقات CSS، سيكون الخرج على النحو التالي: See the Pen normal-flow-css by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة تعلمنا في هذا المقال أساسيات الانسياب الاعتيادي أو الطبيعي Normal Flow للعناصر على صفحة الويب، وهو تخطيط CSS الافتراضي لترتيب العناصر. وبفهم السلوك الافتراضي للعناصر الكتلية والسطرية والهوامش، سيكون من السهل تعديلها في المستقبل، وسنعرفك في مقالات لاحقة على طرق وتقنيات أخرى لتنظيم العناصر على صفحات الويب مثل طريقة العناصر العائمة Floats والتخطيط الشبكي Grids والصندوق المرن Flexbox التي توفر لك أساليب مختلفة تساعدك على تحقيق تخطيطات منوعة لعناصر صفحات الويب واختيار ما يناسب احتياجات تصميم واجهات موقعك من بينها. ترجمة -وبتصرف- للمقال: Normal flow اقرأ أيضًا المقال السابق: مدخل إلى تخطيط صفحات الويب باستخدام CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS بعض العناصر والمفاهيم المهمة في لغة HTML عرض محتوى صفحات الويب بتجاوب على الأجهزة المتعددة تعرف على أساسيات لغة CSS
  8. سنلقي نظرة في هذا المقال على جافا سكريبت JavaScript من منظور عام ونجيب على أسئلة مثل "ما هي جافا سكريبت؟" و "ماذا تفعل هذه اللغة؟" لنتأكد أنك تملك الفهم الجيد لهذه اللغة بالعموم قبل الغوص في التفاصيل الأكثر تعقيدًا. لن تحتاج أية معرفة مسبقة بلغة جافا سكربت لتتابع معنا، لكن عليك قبل البدء بقراءة هذا المقال أن تمتلك بعض المعرفة بالأمور التالية: معرفة ببعض أساسيات HTML و CSS، لهذا ننصحك بالاطلاع على بعض المقالات السابقة مثل: أساسيات HTML. أساسيات عمل CSS. عالم الويب ومعاييره. ملاحظة: إن كنت تعمل على حاسوب أو جهاز لوحي أو أجهزة أخرى لا تسمح لك بإنشاء ملفات خاصة بك، يمكن تجريب معظم الأمثلة والشيفرات ضمن محرر برمجي عبر الإنترنت مثل JSBin أو Glitch. ما هي لغة جافا سكريبت لغة جافا سكريبت هي لغة برمجة تسمح لك بتنفيذ ميزات عديدة في صفحات الويب. ترى ذلك في الصفحات التي لا تكتفي بعرض معلومات ثابتة أو تلك التي تحدّث محتواها تلقائيًا مع الوقت أو تعرض خرائط تفاعلية أو رسوم ثنائية وثلاثية البعد. تُعد جافا سكريبت الطبقة الثالثة من طبقات الكعكة التي تمثّل تقنيات الويب المعيارية أما الطبقتان الباقيتين فهما HTML و CSS، وقد غطينا المفاهيم المتعلقة بهما في مقالات سابقة من سلسلة تعلم تطوير الويب. HTML: لغة توصيف تُستخدم لهيكلة صفحات الويب وإعطاء معنى لمحتواها. فهي تعرّف مثلًا المقاطع النصية والعناوين وجداول البيانات والصور والفيديو. CSS: هي لغة تنسيق تُطبق قواعد تنسيق محددة على محتوى HTML مثل ضبط ألوان الخلفية وخطوط الكتابة وترتيب المحتوى ضمن عدة أعمدة جافا سكريبت: لغة برمجة تمكّنك من إنشاء محتوى يُحدّّث ديناميكيًا على صفحات الويب، والتحكم بالوسائط المتعددة وتحريك الصور وكل ما يجعل صفحة الويب تفاعلية (ليس كل شيء تمامًا، لكنها ستذهلك بما يمكن أن تفعله شيفرة مكوّنة من أسطر قليلة) تُبنى هذه الطبقات فوق بعضها بأناقة، ولنأخذ عنوان نصي بسيط كمثال. سنوصّف ذلك من خلال لغة HTML التي تعطيه الشكل وتصف الغاية منه: <p>Player 1: Chris</p> يمكننا بعد ذلك إضافة بعض تنسيقات CSS لكي نحسّن المظهر: p { font-family: "helvetica neue", helvetica, sans-serif; letter-spacing: 1px; text-transform: uppercase; text-align: center; border: 2px solid rgb(0 0 200 / 0.6); background: rgb(0 0 200 / 0.6); color: rgb(255 255 255 / 1); box-shadow: 1px 1px 2px rgb(0 0 200 / 0.4); border-radius: 10px; padding: 3px 10px; display: inline-block; cursor: pointer; } وأخيرًا يمكننا إعطاء ديناميكية للصفحة بإضافة شيفرة جافا سكريبت بسيطة على النحو التالي: const para = document.querySelector("p"); para.addEventListener("click", updateName); function updateName() { const name = prompt("Enter a new name"); para.textContent = `Player 1: ${name}`; } See the Pen javascript1 by Hsoub Academy (@HsoubAcademy) on CodePen. جرّب أن تنقر على العنوان النصي بنسخته الأخيرة وراقب ما سيحدث. ما تفعله جافا سكريبت حقيقة أكثر من ذلك بكثير، سنستعرض ذلك بتفاصيل أوفى في الفقرات التالية. ما الذي تستطيعه جافا سكريبت حقيقةً؟ تتكون لغة جافا سكريبت التي تعمل من ناحية العميل من بعض الميزات البرمجية البنيوية التي تسمح لك بتنفيذ الكثير من الأشياء مثل: تخزين قيم مهمة ضمن المتغيرات. فما فعلناه في المثال السابق مثلًا أننا طلبنا إدخال اسم جديد ثم خزّنا القيمة المُدخلة في متغيّر سميناه name. العمل على جزء من نص وهو ما يعرف برمجيًا بالسلسلة النصية String فإذ أخذنا في المثال السابق السلسلة النصية " :Player1" وأضفنا إليها قيمة المتغيّر name يمكننا إنشاء عنوان نصي كامل مثل "Player 1: Chris". تنفيذ شيفرة برمجية استجابةً لحدث معين يقع في صفحة الويب، فقد استخدمنا في المثال السابق الحدث click لالتقاط عملية النقر على العنوان النصي ومن ثم نفذنا الشيفرة التي تُحدّث العنوان بعد إضافة الاسم عليه. وهناك الكثير من الأشياء التي تقوم بها لغة جافا سكريبت أيضًا! ولعل أكثر الأمور إثارة، هي القدرة الوظيفية الكبيرة لهذه اللغة من طرف العميل والتي تُعرف بواجهة التطبيق البرمجية Application programming Interface واختصارًا API والتي تعطي قوة كبيرة في استخدام الشيفرة. فواجهات API هي مجموعة معدة سلفًا من الشيفرة التي تسمح للمطور بتنفيذ برامج من الصعب أو المستحيل إنجازها. وما تقدمه هذه الواجهات للشيفرة يشابه ما يمنحه الأثاث والتجهيزات المنزلية للمنزل. فمن السهل أن تأخذ قطع خشبية مقصوصة وجاهزة ثم تجمّعها بالبراغي لتحصل على رف كتب مثلًا بدلًا من أن تصمم كل شيء بنفسك، إذ عليك حينها أن تجد نوع الخشب المناسب ثم تقطع الأخشاب وفق القياس الصحيح والشكل المطلوب وأن تجد البراغي المناسبة ومن ثم تجميعها لتحصل على رف الكتب المطلوب. وتصنّف واجهات API عمومًا ضمن فئتين أساسيتين: واجهات API الخاصة بالمتصفح واجهات API يقدمها طرف آخر Third party API وسنشرح تاليًا آلية عمل كل منهما بمزيد من التفصيل. واجهات API الخاصة بالمتصفح وهي برمجيات مدمجة في بنية المتصفح، قادرة على التعامل مع البيانات في البيئة الحاسوبية أو أن تقوم ببعض العمليات المعقدة المفيدة مثل: واجهة DOM (أو DOM API) وتسمح بالتعامل مع ملفات HTML و CSS كإنشاء وحذف وتغيير شيفرات HTML ديناميكيًا وتطبيق تنسيقات جديدة، فكل مرة ترى فيها نافذة منبثقة تُعرض على المتصفح أو ظهور محتوى جديد في الصفحة هي من فعل واجهة DOM. واجهة الموقع الجغرافي (Geolocation API) تستخلص برمجيات الواجهة معلومات جغرافية عن موقعك، لهذا يستطيع تطبيق خرائط جوجل العثور على موقعك وإظهاره على الخريطة. واجهتا canvas و webGL: اللتان تساعدانك على إنشاء رسومات متحركة ثنائية وثلاثية الأبعاد، وتستطيع تنفيذ أشياء رائعة باستخدامهما. واجهات الفيديو والصوتيات مثل HTMLMediaElement و WebRTC: التي تسمح لك بتنفيذ أشياء مميزة مع الوسائط المتعددة مثل تشغيل مقاطع الصوت والفيديو في صفحة الويب مباشرة أو التقاط بث كاميرا الويب وعرضها على حاسوب آخر. ملاحظة: يُفضّل عند تجريب استخدام الواجهات البرمجية استخدام متصفحات حديثة مثل فايرفوكس أو كروم أو إيدج أو أوبيرا. كما عليك الأخذ بعين الاعتبار فكرة اختبار الشيفرة على اكثر من متصفح وخاصة عند الوصول إلى مرحلة تسليم شيفرة الإنتاج بعد إنهاء الشيفرة التجريبية. واجهات API يقدمها طرف ثالث لا تُبنى هذه الواجهات ضمن المتصفح افتراضيًا، وعادة ما تحصل على شيفرتها من الويب. ومن الأمثلة عليها واجهة تويتر Twitter API: وتسمح لك بعرض آخر تغريداتك ضمن موقع الويب الخاص بك مثلًا وغيرها من الأشياء. واجهة خرائط جوجل وواجهة OpenStreetMap API وتسمحان لك بإدراج خرائط ضمن موقعك الإلكتروني وغيرها من الوظائف. ملاحظة: إن الواجهات البرمجية موضوع متقدم، ولن نغطي أيًا منها في حديثنا عن جافا سكريبت، لكن هناك الكثير أيضًا لتتعلمه حول لغة جافا سكريبت، فلا تأخذك الحماسة المفرطة. فلن تكون قادرًا على بناء فيسبوك أو خرائط جوجل أو إنستغرام بعد دراستك جافا سكريبت مدة 24 ساعة! فهنالك الكثير من الأساسيات التي عليك معرفتها أولًا، وهذا ما ستوفره لك سلسلة المقالات التالية حول جافا سكريبت. ما الذي تفعله جافا سكريبت في صفحة الويب سنلقي هنا نظرة على بعض الشيفرة، ونستعرض ما يحدث حقيقةً عندما تنفّذ شيفرة جافا سكريبت في صفحتك. لنستذكر سريعًا ما يحدث عند تحميل صفحة ويب في متصفح . عندما تحمّل صفحة ويب في متصفحك فإنك تنفّذ شيفرتك (HTML و CSS وجافا سكريبت) ضمن بيئة تنفيذية (نافذة المتصفح)، وهذا أمر مماثل لمعمل يستقبل موادًا أولية (الشيفرة) ويخرج منتجًا (صفحة الويب). إن أكثر الاستخدامات شيوعًا لجافا سكريبت هو تعديل شيفرة HTML و CSS ديناميكيًا لتغيير الواجهة من خلال الواجهة البرمجية DOM API. ولاحظ أن شيفرة صفحات الويب تُحمّل عمومًا وتنفذ وفق ترتيب ظهورها في الصفحة. وقد تحدث أخطاء إن حُمّلت شيفرة جافا سكريبت ونُفِّذت قبل شيفرة HTML و CSS التي ستُعدّل. سنتعلم لاحقًا في مقالنا كيف نلتف على الموضوع. أمان المتصفح لكل نافذة من نوافذ المتصفح جيب خاص لتنفيذ الشيفرة ويُعرف هذا الجيب ببيئة التنفيذ. ويعني ذلك عمومًا أن الشيفرة في كل نافذة تعمل بشكل منفصل تمامًا عن شيفرة النوافذ الأخرى ولا يمكن أن تؤثر الشيفرة في النافذة مباشرة على الشيفرة التي تجري في النافذة الأخرى أو على موقع ويب آخر. يُعد هذا الأمر مقياسًا جيدًا للأمان، فلو لم يكن الأمر كذلك سيكتب القراصنة شيفرات لسرقة معلومات من مواقع أخرى وغيرها من الأفعال الضارة. ملاحظة: هنالك طريقة لإرسال الشيفرة والبيانات من مختلف المواقع أو النوافذ بطريقة آمنة لكنها تقنيات متقدمة لن نغطيها في هذا المقال لكن يمكنك الاطلاع على مقال التخاطب بين نوافذ المتصفح عبر جافا سكريبت في أكاديمية حسوب. ترتيب تنفيذ شيفرة جافا سكريبت عندما يصادف المتصفح كتلة من شيفرات جافا سكريبت، سينفّذها عادة بالترتيب من الأعلى إلى الأسفل. ويعني هذا ضرورة الانتباه إلى الترتيب الذي تضع وفقه الأشياء. لنعد على سبيل المثال إلى شيفرة جافا سكريبت في المثال الأول: const para = document.querySelector("p"); para.addEventListener("click", updateName); function updateName() { const name = prompt("Enter a new name"); para.textContent = `Player 1: ${name}`; } نختار في السطر الأول مقطعًا نصيًا ثم نضيف مترصّد للحدث (مستمع للحدث) في السطر الثالث كي تُنفّذ شيفرة الكتلة البرمجية ()updteName (الأسطر من 5-8). تسأل الكتلة البرمجية ()updateName (يُدعى هذا النوع القابل للاستخدام المتكرر من الكتل البرمجية دوال functions) أن يُدخل المستخدم اسمًا جديدًا ومن ثم تضع الاسم ضمن الفقرة النصية وتحدّث ما يُعرض على الشاشة. فإن بدلت ترتيب أول سطرين، لن تعمل الشيفرة، بل ستحصل على خطأ تعرضه طرفية المطوّر في المتصفح وهي typeError: para is undefined. ويعني هذا أن الكائن para غير موجود بعد، ولا يمكن إضافة مترصّد أحداث إليه. ملاحظة: هذا الخطأ شائع الحدوث، لهذا انتبه إلى وجود الكائن في الشيفرة قبل أن تحاول العمل معه. الشيفرة المفسّرة والمصرّفة ربما سمعت بالمصطلحين "مفسّر interpreted" و "مصرّف compiled" في سياق تعلمك للبرمجة. ففي اللغات المفسّرة تنفّذ الشيفرة من الأعلى إلى الأسفل وتُعاد نتيجة تنفيذ الشيفرة مباشرة، ولا حاجة لنقل الشيفرة إلى شكل آخر قبل أن يُنفّذها المتصفح. إذ يستقبل الشيفرة بشكلها النصي المفهوم من قبل المبرمج ثم يعالجها مباشرة. بينما تحوّل الشيفرة في اللغات المصرّفة إلى شكل آخر قبل أن يُنفّذها الحاسوب. إذ تحوّل مثلًا شيفرة لغتي C أو ++C إلى لغة الآلة التي ينفذها الحاسوب بعد ذلك. وينفّذ البرنامج انطلاقًا من صيغته الثنائية التي تنتج عن تصريف شيفرته المصدرية. جافا سكريبت هي لغة برمجة مفسّرة خفيفة الحجم، يتلقى المتصفح شيفرتها بشكلها النصي وينفّذه. وإذا أردنا الحديث تقنيًا يمكننا القول أن مفسرّات جافا سكريبت الحديثة تستخدم تقنية تّدعى التصريف عند التنفيذ Just-in-time compiling لتحسين الأداء. إذ تُصرّف شيفرة جافا سكريبت المصدرية إلى شكل ثنائي أسرع في نفس الوقت الذي تُنفّذ فيه الشيفرة وهذا ما يجعل التنفيذ أسرع ما يمكن. مع ذلك لا تزال جافا سكريبت في عداد اللغات المفسّرة لأن التصريف يجري أثناء التنفيذ بدلًا من تصريف الشيفرة مسبقًا. ولكلا نوعي اللغات ميزاته، لكن لن نناقش هذا الموضوع الآن. شيفرة طرف العميل موازنة مع شيفرة طرف الخادم لربما سمعت أيضًا بمصطلحي "طرف الخادم server-side" و "طرف العميل client-side" وخاصة في سياق تعلم تطوير الويب. تُنفّذ شيفرة طرف العميل على حاسوب العميل أو المستخدم، فما يحدث عند استعراض صفحة الويب هو أن المتصفح سينزّل شيفرة طرف العميل ثم ينفذها ويعرضها. وما سنتحدث عنه في سلسلة مقالات جافا سكريبت هو استخدام جافا سكريبت من طرف العميل فقط. تُنفّذ شيفرة طرف الخادم بالمقابل على الخوادم ثم تُنزّل نتيجة التنفيذ وتُعرض في المتصفح. ومن لغات ويب التي تعمل من طرف الخادم نذكر PHP و Python و Ruby و ASP.NET وكذلك لغة جافا سكريبت. إذ يمكن استخدام جافا سكريبت كلغة برمجة من طرف الخادم في بيئة Node.js الشهيرة. الشيفرة الديناميكية موازنة مع الشيفرة الساكنة تُستخدم كلمة ديناميكي لوصف شيفرة جافا سكريبت من طرف العميل ولغات طرف الخادم، وتشير إلى إمكانية تحديث صفحة الويب أو التطبيق ليعرض أشياء مختلفة في ظروف مختلفة، وتوليد محتوى جديد حسب الحاجة. إذ توُلّد لغات طرف الخادم محتوى جديدًا على الخادم عن طريق سحب البيانات من قواعد البيانات مثلًا، بينما تولّد شيفرة جافا سكريبت المحتوى ديناميكيًا ضمن متصفّح العميل، كأن تنشأ جداول HTML وتملأها بالبيانات التي تطلبها من الخادم ومن ثم تعرض هذه الجداول ضمن صفحة الويب التي يراها المستخدم. قد يكون هناك اختلاف بسيط بين سياقي العمل لكنهما متعلقان ببعضهما، وكلا النهجين (طرف العميل وطرف الخادم) يعملان معًا عادة. عندما لا تُحدَّث صفحة الويب ديناميكيًا بمحتوى جديد ندعوها ساكنة static، فهي تعرض نفس المحتوى دائمًا. كيف تضيف شيفرة جافا سكريبت إلى صفحتك؟ تُضاف شيفرة جافا سكريبت إلى صفحة HTML بنفس الأسلوب الذي تُضاف به شيفرة CSS. إذ تستخدم CSS العنصر <link> لتطبيق ورقة تنسيق خارجية و العنصر <style> لتطبيق ورقة تنسيق داخلية على شيفرة HTML، بينما لا تحتاج جافا سكريبت سوى العنصر <script>. لنلق نظرة على عمله. شيفرة جافا سكريبت داخلية قبل كل شيء، أنشئ نسخة من الملف apply-javascript.html على جهازك وخزّنها في مكان مناسب. افتح الملف في متصفحك وضمن المحرر النصي في نفس الوقت. سترى أن شيفرة HTML قد أنشأت صفحة ويب بسيطة تضم زرًا يمكن النقر عليه. اضف الشيفرة التالية ضمن الترويسة في المحرر النصي وقبل وسم النهاية <head/>: <script> // JavaScript goes here </script> سنضيف الآن بعض شيفرة جافا سكريبت ضمن الوسم <script> وتحت عبارة "JavaScript goes here//" لنجعل الصفحة أكثر حيوية: document.addEventListener("DOMContentLoaded", () => { function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } }); احفظ الملف وحدّث المتصفّح، وستلاحظ ظهور فقرة نصية تحت الزر عند النقر عليه. ملاحظة: إن رأيت أن المثال لا يعمل كما هو مطلوب، راجع الخطوات السابقة بتأنٍ وتحقق أنك فعلت كل شيء بالشكل الصحيح. هل خزنت الملف بلاحقة html.؟ هل وضعت الوسم <script> قبل وسم النهاية <head/>؟ هل أدخلت شيفرة جافا سكريبت كما هي تمامًا؟ وانتبه إلى أن جافا سكريبت حساسة لحالة الأحرف وعليك إضافة الشيفرات كما هي تمامًا وإلا لن تعمل. شيفرة جافا سكريبت خارجية تعمل الشيفرة السابقة جيدًا، لكن ماذا لو أردت أن تضع تلك الشيفرة في ملف خارجي منفصل؟ أنشئ ملفًا جديدًا في نفس المكان الذي خزّنت فيه ملف HTML ثم سمّه script.js. تأكد أن لاحقة الملف هي js. لأنها الطريقة التي يُعرف بها ملف جافا سكريبت. استبدل الوسم <script> بالوسم التالي: <script src="script.js" defer></script> ضع الشيفرة التالية في ملف script.js: function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } احفظ الملف وحدّث المتصفّح وسترى الشيء ذاته، لكن شيفرة جافا سكريبت في هذه الحالة موجودة في ملف خارجي. وهذا عمومًا أمر جيد من ناحية تنظيم الشيفرة وجعلها قابلة للاستخدام ضمن جميع صفحات الموقع، إضافة إلى أن ملفات HTML أسهل قراءة في هذه الحالة دون وجود قطع من الشيفرة مزروعةً ضمنها. معالجات جافا سكريبت السطرية قد تصادف في بعض الحالات شيفرة جافا سكريبت ضمن شيفرة HTML، وسيبدو الأمر مشابهًا لما يلي: function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } <button onclick="createParagraph()">Click me!</button> جرب هذه النسخة في المحرر التفاعلي: See the Pen javascript2 by Hsoub Academy (@HsoubAcademy) on CodePen. لهذه النسخة العمل ذاته كما في النسختين السابقتين، إلا أن العنصر <button> يضم معالجًا سطريًا هو onclick يفعّل عمل الدالة عند النقر على الزر. ننصحك بأن لا تفعل ذلك، فمن السيء أن تلوّث شيفرة HTML يشيفرة جافا سكريبت، إضافة إلى أنها طريقة غير فعّالة أن تضيف السمة "()onclick="createParagraph في كل زر تريده أن ينفّذ الوظيفة ذاتها. استخدام الدالة addEvenetListerer بدلًا من وضع شيفرة جافا سكريبت ضمن وسوم HTML، من الأفضل استخدام بناء جافا سكريبت صرف. إذ تسمح الدالة ()querySelectorAll بانتقاء كل أزرار الصفحة، ومن ثم إسناد معالج أحداث لكل زر من خلال الدالة ()addEventListener. إليك الشيفرة اللازمة: const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } قد تبدو الشيفرة أطول بقليل موازنة باستخدام السمة onclick، لكنها ستعمل مع جميع أزرار الصفحة مهما كان عددها أو مهما أضفت أو أزلت أزرارًا، دون الحاجة إلى تغيير شيفرة جافا سكريبت. ملاحظة: جرّب أن تعدّل في شيفرة الملف apply-javascript.html بإضافة بضعة أزرار إضافية، سترى عندها وبعد تحديث الصفحة أن النقر على أي زر منها سينشئ فقرة نصية. استراتيجيات تحميل السكربتات في صفحات الويب تصادفنا بعض المشاكل تتعلق بتوقيت تحميل السكربتات في صفحة الويب. فلا شيء بالبساطة التي يبدو عليها. ومن المشاكل الشائعة هي تحميل شيفرة HTML كاملة وبالترتيب الذي تظهر عليه ضمن الملف. فإن استخدمت جافا سكريبت للتعامل مع عناصر الصفحة (أو شجرة DOM بدقة)، لن تعمل الشيفرة إن حُّملت وفُسِّرت قبل أن يُحمّل عنصر HTML الذي تستهدفه. وما حدث في الأمثلة الماضية سواء باستخدام جافا سكربت داخليًا أو خارجيًا هو تحميل الشيفرة وتنفيذها ضمن ترويسة الملف وقبل تفسير أي عنصر من عناصر جسم الملف. قد ينتج عن هذا الأمر خطأ لهذا استخدمنا أسلوبًا للالتفاف حول المشكلة. لاحظ الأسلوب المستخدم في مثال الشيفرة الداخلية: document.addEventListener("DOMContentLoaded", () => { // … }); إن مترّصد الأحداث والذي يصغي إلى الحدث DOMContentLoaded في المتصفح، سينتظر اكتمال تحميل وتفسير جسم ملف HTML، ولن تعمل شيفرة جافا سكريبت ضمن هذه الكتلة قبل ذلك. وبهذه الطريقة نتفادى الخطأ. وفي مثال الشيفرة الخارجية، نستخدم ميزة أكثر حداثة لجافا سكريبت لحل المشكلة من خلال السمة defer التي تخبر المتصفح أن عليه إكمال تحميل شيفرة HTML عندما يصل إلى السمة <script>: <script src="script.js" defer></script> ستُحمّل شيفرة HTML و جافا سكريبت بالتوازي في هذه الحالة وستعمل الشيفرة جيدًا. ملاحظة: لا حاجة لاستخدام الحدث DOMContentLoaded في الشيفرة الخارجية لأن السمة defer تحل المشكلة. ولم نستخدم السمة defer في الشيفرة الداخلية لأنها تعمل فقط مع السكربت الخارجي. ومن الحلول القديمة التي استخدمت لحل لهذه المشكلة هو وضع شيفرة جافا سكريبت في نهاية الملف وقبل الموسم <body/> وبالتالي تُحمّل الشيفرة بعد إنتهاء تحميل وتفسير عناصر HTML. أما مشكلة هذا الحل هو إيقاف عمل شيفرة جافا سكريبت حتى يكتمل تحميل شجرة DOM الخاصة بملف HTML، وهذا ما يسبب مشكلة أداء كبيرة في مواقع الويب الأكبر، وسيبطئ الموقع. السمة async والسمة defer توجد في الواقع طريقتان عصريتان لتفادي مشكلة حجب الشيفرة باستخدام السمتين async و defer، وسنلقي نظرة عليهما. ستُحمّل السكربتات باستخدام async دون إيقاف تحميل الصفحة أثناء تقدم تحميل السكربت. لكن بمجرد اكتمال تحميل السكربت سيُنفَّذ هذا السكربت مما يمنع تصيير الصفحة، ولن تضمن بأي شكل تنفيذ السكربتات بترتيب محدد. ومن الأفضل استخدام هذه السمة عندما لا تتعلق السكربتات في الصفحة ببعضها البعض ولا بأي سكربت آخر في الصفحة. وبالنسبة للسكربتات التي تُحمّل باستخدام defer ستُحمّل بالترتيب الذي تظهر فيه ضمن الصفحة، ولن تعمل حتى يكتمل تحميل الصفحة، وهذا مفيد في السكربتات التي تتطلب اكتمال تكوين شجرة DOM الخاصة بالملف (وكمثال عليها السكربتات التي تعدّل عنصر أو أكثر في الصفحة). إليك تمثيلًا بصريًا لأساليب تحميل السكربتات وما الذي تعنيه لصفحتك: فلو كان لديك مثلًا عناصر <script> التالية: <script async src="js/vendor/jquery.js"></script> <script async src="js/script2.js"></script> <script async src="js/script3.js"></script> لا يمكنك الاعتماد على ترتيب تحميل العناصر السابقة، فقد يُحمّل jquery.js قبل أو بعد script2.js و script3.js، وإن حدث ذلك، ستوّلد أية دالة ضمن السكربتين الأخيرين خطأً إن اعتمدت على السكربت jquery لأنه قد لا يكون معرّفًا عند تنفيذها. لهذا استخدم السمة async إن كان لديك مجموعة من السكربتات التي تعمل في الخلفية وتريد فقط أن تضعها في مكانها المناسب بالسرعة الممكنة. فقد يكون لديك مثلًا ملفات بيانات خاصة بلعبة وتريدها أن تكون جاهزة عندما تبدأ اللعبة فعلًا، إذ لا بد من عرض مقدمة اللعبة وعناوينها والمساهمين فيها دون أن تُحجب حتى يُحمّل السكربت وأثناء ذلك يُحمّل السكربت بهدوء في الخلفية. وإن أردت تنفيذ السكربتات وفق تسلسل ظهورها (كما في الأسفل)، استخدم السمة defer، وستُنفَّذ حالما يُحمّل السكربت ومحتوى الصفحة: <script defer src="js/vendor/jquery.js"></script> <script defer src="js/script2.js"></script> <script defer src="js/script3.js"></script> نضمن في المثال الثاني أن jquery.js سيُحمّل قبل script2.js الذي يُحمّل قبل script3.js. ولن تُنفَّذ السكربتات حتى يكتمل تحميل الصفحة، وهذا أمر مفيد إن اعتمدت السكربتات على وجود شجرة DOM جاهزة وفي مكانها. باختصار: تخبر السمتان async و defer المتصفح أن يحمّل السكربتات في خيط منفصل بينما تحمّل بقية محتويات الصفحة في خيط مختلف في نفس الوقت وبالتالي لن يُحجب عرض محتوى الصفحة أثناء عملية إحضاره. تُنفّذ السكربتات التي تستخدم السمة async حالما ينتهي تحميلها، وسيحجب هذا عرض محتوى الصفحة أثناء التنفيذ ولا يمكن أن تضمن ترتيب السكربتات التي ستُنفَّذ. تُحمّل السكربتات التي تستخدم السمة defer بالترتيب الذي تظهر فيه وتُنفَّذ بمجرد انتهاء تحميل كل شيء. إن كان لابد من تنفيذ السكربت مباشرة بعد تحميله ولا يتعلق تنفيذه بسكربتات أو عناصر أخرى يفضّل استخدام async. إن كان لا بد من الانتظار حتى ينتهي تفسير الملف واعتمد السكربت على سكربتات أخرى أو على تكوين شجرة DOM الخاصة بالملف، استخدم defer وضع السكربتات (العناصر <script>) بالترتيب الذي تريده حتى يُنفذها المتصفح بنفس الترتيب. التعليقات في جافا سكريبت من الممكن كتابة تعليقات ضمن شيفرة جافا سكريبت كما هو الحال في HTML و CSS، وتُكتب هذه التعليقات لتزويد المطوّرين الذين يقرؤون الشيفرة (أو لك شخصيًا إن عدت إليها بعد فترة) بإرشادات عن طريقة عمل الشيفرة، وبالطبع يتجاهل المتصفح هذه التعليقات ولا يحللها. يُنصح باستخدام التعليقات ما أمكن فهي مفيدة وخاصة في التطبيقات الضخمة، وهنالك طريقتين لإدراج التعليقات: على شكل سطر وحيد بعد إشارتي شرطة أمامية //. // I am a comment أسطر متعددة مكتوبة بين النصين */ و /*. /* I am also a comment */ فمثلًا، يمكن إضافة التعليقات التالية إلى آخر مثال شرحناه: // Function: creates a new paragraph and appends it to the bottom of the HTML body. function createParagraph() { const para = document.createElement("p"); para.textContent = "You clicked the button!"; document.body.appendChild(para); } /* 1. Get references to all the buttons on the page in an array format. 2. Loop through all the buttons and add a click event listener to each one. When any button is pressed, the createParagraph() function will be run. */ const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", createParagraph); } ملاحظة: كثرة التعليقات أفضل عادة من التعليقات القليلة، لكن انتبه إن رأيت نفسك تكتب تعليقات كثيرة لتشرح المتغيرات مثلًا (والتي يجب أن تكون واضحة وبديهية) أو لتشرح عمليات بسيطة (عندها قد تكون شيفرتك معقدة). خلاصة هكذا نكون قد خطونا أولى خطواتنا في جافا سكريبت. فقد بدأنا بتمهيد نظري لنمنحك فكرة عن استخدامات جافا سكريبت واﻷشياء التي يمكن أن تفعلها بها. كما رأينا خلال تقدم المقال بعض اﻷمثلة عن كتابة الشيفرة وتعلمنا كيف نضع الشيفرة في مكانها المناسب ضمن بقية شيفرة موقع الويب، إضافة إلى أشياء عديدة أخرى. قد تبدو جافا سكريبت صعبة قليلًا للوهلة الأولى، لكن لا تقلق فما نتعلمه في سلسلة المقالات التالية، سيأخذك خطوة خطوة وبأسلوب واضح حتى تصل إلى المرحلة التي تبني فيها أمثلتك الخاصة باستخدام جافا سكريبت ترجمة -وبتصرف- لمقال What's JavaScript اقرأ أيضًا تعلم لغة جافا سكريبت أساسيات لغة جافاسكربت تعلم جافا سكريبت من الصفر للاحتراف الدليل السريع إلى لغة البرمجة جافاسكريبت JavaScript
  9. نراجع في هذا المقال بعض ميزات تخطيط الصفحات في CSS مثل القيم المختلفة للخاصية display وبعدها سنتعرف على بعض المفاهيم التي نغطيها تباعًا في سلسلة المقالات هذه، ونستعرض بإيجاز الخطوط العامة لتقنيات تخطيط الصفحات والتي نتوسع فيها مقالاتنا اللاحقة. عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات أساسيات HTML. تتفهم أساسيات CSS كما شرحناها في سلسلة المقالات خطواتك الأولى في CSS. تتيح لك تقنيات تخطيط الصفحات المعتمدة على CSS احتواء عناصر الصفحة والتحكم في موضعها بالنسبة إلى موقعها الافتراضي في الانسياب الاعتيادي أو التقليدي للعناصر أو بالنسبة إلى موضع بقية العناصر المحيطة بها أو بالنسبة إلى الحاوية الأم أو بالنسبة إلى نافذة العرض الأساسية أو نافذة المتصفح. وسنغطي في هذا المقال التقنيات التالية بشيء من التفصيل: الانسياب الاعتيادي للعناصر Normal flow الخاصية Display الصندوق المرن Flexbox تخطيط الشبكة Grid تعويم العناصر Floats توضيع العناصر Positioning تخطيط الجدول Table layout التخطيط متعدد الأعمدة Multiple-column layout لكل تقنية من هذه التقنيات استخداماتها وإيجابياتها وسلبياتها، ولم تصمم أي تقنية لتعمل بمفردها، وبالتالي حين تفهم سبب تصميم تخطيط معين ستمتلك القدرة على اتخاذ قرار استخدام التخطيط المناسب للمهمة التي تواجهك. الانسياب الاعتيادي لعناصر الصفحة وهو الأسلوب الذي يستخدمه المتصفح افتراضيًا في ترتيب العناصر في صفحات HTML عندما لا تتخذ أي خطوة لترتيب هذه العناصر. لنلق نظرة على المثال التالي: <p>I love my cat.</p> <ul> <li>Buy cat food</li> <li>Exercise</li> <li>Cheer up friend</li> </ul> <p>The end!</p> سيرتب المتصفح عناصر الصفحة السابقة افتراضيًا كالتالي: See the Pen css-layout-1 by Hsoub Academy (@HsoubAcademy) on CodePen. لاحظ كيف يعرض المتصفح عناصر HTML وفق ترتيب ظهورها في الشيفرة تمامًا ومتلاصقة عنصرًا فوق الآخر. إذ يعرض أولًا الفقرة النصية ثم القائمة غير المرتبة ومن ثم يعرض الفقرة النصية الثانية. تُدعى العناصر التي تُرتب فوق بعضها بالعناصر الكتلية block elements، بينما تُدعى العناصر التي تظهر إلى جانب بعضها في السطر ذاته -مثل الكلمات الموجودة في الفقرة- بالعناصر السطرية inline elements. ملاحظة: يُدعى الاتجاه الذي تُرتب فيه العناصر الكتلية (من الأعلى للأسفل في مثالنا) باتجاه الكتلة والذي قد يكون عموديًا في المحتوى المكتوب باللغة الإنكليزية أو العربية مثلً لأن اتجاه الكتابة فيها أفقي. كما يكون اتجاه الكتلة أفقيًا عندما يُكتب المحتوى بلغة تنساب حروفها من الأعلى للأسفل مثل اليابانية. أما الاتجاه السطري فهو اتجاه انسياب الكلمات في المحتوى. سيزود الانسياب الاعتيادي التخطيط المناسب للكثير من العناصر بالطريقة ضمن الصفحة، لكن ستحتاج في التخطيطات الأكثر تعقيدًا إلى تغيير هذا السلوك مستخدمًا بعض الأدوات التي تتيحها CSS. مع ذلك، عليك أن تبدأ من صفحة HTML مهيكلة جيدًا كي تكون قادرًا على التعامل مع الطريقة التي تُرتب فيها العناصر افتراضيًا بدلًا من معارضتها. تُستخدم الأساليب التالية في CSS لتغيير الترتيب الافتراضي للعناصر: الخاصية display: تغيّر القيم الأساسية لهذه الخاصية وهي block أو inline أو inline-block كيفية سلوك العناصر في الانسياب الاعتيادي، كأن يجعل عنصرًا كتليًا مثلًا يسلك سلوك عنصر سطري (نموذج الصندوق في CSS وستجد أيضًا أساليب كاملة للتخطيط تُفعّل عند اختيار قيم معينة للخاصية display مثل grid و flexbox أو التي تغيّر توضّع العنصر داخل العنصر الأب). التعويم floats: وذلك بتطبيق الخاصية، فاختيار القيمة left لهذه الخاصية مثلًا تدفع العناصر لتلتف نحو الجانب اليساري للعنصر كما في الصور التي يحاذيها نص إلى اليسار في مجلة. الخاصية position: تتيح لك هذه الخاصية ضبط موقع صندوق العنصر ضمن صناديق أخرى ضبطًا دقيقًا. يُعد الوضع static وضعًا افتراضيًا في الانسياب الاعتيادي، ومن الممكن أيضًا وضع العنصر بشكل مختلف باستخدام القيم الأخرى لهذه الخاصية، كأن يكون موقع العنصر ثابتًا أعلى المتصفح. تخطيط الجدول: يمكن استخدام الميزات التي تسمح بتنسيق جداول HTML مع العناصر الأخرى من خلال الخاصية display والقيمة table والخاصيات الأخرى المرتبطة بها. التخطيط متعدد الأعمدة: توضّع خاصيات الأعمدة -columns محتوى الكتلة ضمن أعمدة بشكل مشابه لما تراه في الصحف. الخاصية display تعتمد الأساليب الرئيسية في تخطيط الصفحات باستخدام CSS على استخدام قيم الخاصية display، إذ تسمح هذه القيم في تغيير الطريقة الافتراضية التي تُعرض بها الأشياء. ولكل عنصر قيمة افتراضية لهذه الخاصية في الانسياب الاعتيادي، أي الطريقة الاعتيادية التي يسلكها هذا العنصر. فعناصر الفقرة النصية <p> المكتوبة باللغة العربية مثلًا تتوضع فوق بعضها لأن التنسيق الافتراضي للعرض هو display: block. ولو أنشأت رابطًا ضمن فقرة نصية فسيبقى الرابط على نفس السطر مع بقية كلمات الفقرة ولن يقفز إلى السطر الثاني لأن التنسيق الافتراضي لعرض هذا العنصر هو display: inline. بالإمكان تغيير العرض التقليدي بتغيير قيم الخاصية display. فعنصر القائمة مثلًا <li> يسلك سلوك الكتلة افتراضيًا، أي تظهر هذه العناصر في المحتوى المكتوب بالعربية أو الانجليزية تحت بعضها. وعندما نضبط الخاصية display على inline، ستُعرض العناصر إلى جانب بعضها كفقرة نصية اعتيادية. تظهر أهمية هذه الطريقة في تغيير طريقة عرض أي عنصر من عناصر HTML دون المساس بدلالته (أي الغرض من استخدامه). بالإضافة إلى إمكانية تغيير طريقة عرض العناصر من كتلية إلى سطرية وبالعكس، ستجد الكثير من أساليب التخطيط التي تبدأ باستخدام الخاصية display. لكن ما يحدث عادة هو الحاجة إلى استخدام قيم أخرى لهذه الخاصية، وأكثرها أهمية في سياق مناقشتنا هما القيمتان display: flex و display: grid. مفهوم الصندوق المرن Flexbox يأتي مصطلح الصندوق المرن Flexbox كاسم مختصر لوحدة من وحدات CSS تُعرف بتخطيط الصندوق المرن Flexible Box Layout، وقد صممت هذه الوحدة لتسهيل ترتيب الأشياء في اتجاه واحد كأن تكون في صفٍ أو عمود. ولاستخدام الصندوق المرن لابد من تطبيق الخاصية display:flex على العنصر الأب الذي ترغب في ترتيب العناصر ضمنه بشكل مرن، وبالتالي ستصبح هذه العناصر مرنة. وسنرى ذلك في المثال البسيط التالي: ضبط قيمة الخاصية display على flex تعرض شيفرة HTML السابقة عنصرًا حاويًا <div> يمتلك صنف التنسيق wrapper ويضم داخله ثلاث عناصر من نفس النوع. ستُعرض هذه العناصر افتراضيًا كعناصر كتلية تحت بعضها البعض طالما أن لغة الكتابة سطرية مثل الإنكليزية في هذا المثال. لكن عند إضافة الخاصية display: flex إلى تنسيق العنصر الأب، سترتب العناصر نفسها على شكل أعمدة ثلاث، إذ تصبح هذه العناصر عناصر مرنة وتتأثر ببعض القيم الأساسية التي يطبقها الصندوق المرن على العنصر الأب. وقد ظهرت تلك العناصر ضمن سطر واحد وثلاث أعمدة لأن القيمة الأساسية للخاصية flex-direction في العنصر الأب هي row. وتمتد هذه العناصر أيضًا من ناحية الارتفاع لأن القيمة الأساسية للخاصية align-items للعنصر الأب هي stretch، أي أن العناصر تمتد بالاتجاه العمودي لتشغل ارتفاع العنصر الحاوي (العنصر الأب) والذي يحدده في هذه الحالة ارتفاع العنصر الأطول من بين العناصر الثلاث. تصطف العناصر إلى جانب بعضها بدءًا من طرف الحاوية وتترك أية مساحات فارغة لم يشغلها أي من هذه العناصر لتأتي بعدها وحتى نهاية السطر. .wrapper { display: flex; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> سيرتب المتصفح عناصر الصفحة السابقة الآن كالتالي: See the Pen css-layout-2 by Hsoub Academy (@HsoubAcademy) on CodePen. ضبط خاصيات المرونة إضافة إلى الخاصيات التي يمكن تطبيقها على الحاوية المرنة، ستجد بعض الخاصيات التي يمكن تطبيقها على العناصر المرنة داخله أيضًا. يمكن لهذه الخاصيات بمشاركة أشياء أخرى أن تغيّر ترتيب العناصر المرنة كي تتمكن من التمدد والتقلص وفقًا للمساحة المتاحة لها. كمثال على ذلك، يمكن إضافة الخاصية flex إلى جميع الأبناء وإعطائها القيمة 1 مما يجعل العناصر تتمدد لتملأ العنصر الأب بدلًا من ترك مساحات فارغة في نهاية السطر. فإن كانت هناك مساحات إضافية ستغدو هذه العناصر أوسع وتتقلص إن لم تكن هناك مساحات كافية. وإن أضفت عناصر إضافية إلى الحاوية سيصغر حجم جميع العناصر لتوفر مكانًا للعنصر الجديد وستشغل العناصر كلها كامل مساحة الحاوية. .wrapper { display: flex; } .wrapper > div { flex: 1; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> سنحصل على النتيجة التالية: See the Pen css-layout-3 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: ما ذكرناه مقدمة مختصرة جدًا عما يمكن فعله في تخطيط الصندوق المرن وسنرى تفاصيل أوفى في مقال قادم. وكما صُمم تخطيط الصندوق المرن ليرتب العناصر باتجاه واحد، صُمم تخطيط الشبكة لترتيبها باتجاهين أي ضمن صفوف وأعمدة. ضبط الخاصية display على القيمة grid كما هو الحال في الصندوق المرن، نطبّق تخطيط الشبكة بإسناد القيمة الخاصة به إلى خاصية العرض display:grid. يستخدم المثال التالي نفس شيفرة HTML التي استخدمناها في المثال السابق. وإضافة إلى استخدام الخاصية display: grid، سنعرف بعض مسارات الصفوف والأعمدة في العنصر الأب باستخدام الخاصيتين grid-template-rows و grid-template-columns على التوالي. وقد عرفنا ثلاث أعمدة اتساع كل منها 1fr (جزء واحد من كل) وصفين ارتفاع كل منهما 100px. لا حاجة لوضع أي قواعد تنسيق للعناصر الأبناء لأنها تُرتب تلقائيًا ضمن الشبكة: .wrapper { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 100px 100px; gap: 10px; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> <div class="box4">Four</div> <div class="box5">Five</div> <div class="box6">Six</div> </div> ستكون النتيجة كالتالي: See the Pen css-layout-4 by Hsoub Academy (@HsoubAcademy) on CodePen. وضع العناصر ضمن الشبكة بمجرد أن تبني الشبكة، ستتمكن من وضع عناصر أخرى ضمنها بشكل صريح بدلًا من الاعتماد على سلوك التوضع التلقائي. نعرّف في المثال التالي الشبكة نفسها التي عرفناها سابقًا، لكن بوجود ثلاث عناصر أبناء فقط. وضبطنا سطري البداية و النهاية باستخدام الخاصيتين grid-column و grid-row مما يجعل العنصر يمتد على عدة مسارات. .wrapper { display: grid; grid-template-columns: 1fr 1fr 1fr; grid-template-rows: 100px 100px; gap: 10px; } .box1 { grid-column: 2 / 4; grid-row: 1; } .box2 { grid-column: 1; grid-row: 1 / 3; } .box3 { grid-row: 2; grid-column: 3; } <div class="wrapper"> <div class="box1">One</div> <div class="box2">Two</div> <div class="box3">Three</div> </div> ستظهر النتيجة على هذا النحو: See the Pen css-layout-4 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: يعرض المثالان السابقان فكرة بسيطة عن قوة تخطيط الشبكة، وسنفصل أكثر في مقال قادم. لنتابع في بقية هذا المقال بعض التخطيطات الأخرى الأقل أهمية كتخطيطات رئيسية لصفحة الويب، لكنها مفيدة في مهام أخرى. وستجد عند فهم طبيعة المهام التي يؤديها كل تخطيط أن التخطيط الملائم لمكوّن معين من صفحتك سيكون واضحًا أغلب الأحيان. تعويم العنصر: الخاصية float يغير تعويم العنصر سلوكه وعناصر الكتلة الأخرى التي تأتي بعده في الانسياب الاعتيادي. تتوضع العناصر المعومة إلى اليمين أو اليسار وتُنقل من مكانها ثم يعوم حولها بقية المحتوى. للخاصية القيم التالية: left: تعويم العنصر إلى اليسار. right: تعويم العنصر إلى اليمين. none: لا تسمح بتعويم العنصر وهي القيمة الافتراضية. inherit: قيمة الخاصية float للعنصر هي نفسها قيم هذه الخاصية في العنصر الأب. نعوّم في المثال التالي عنصر <div> إلى اليسار ونحدد له هامشًا margin نحو اليمين لدفع المحتوى المحيط به قليلًا. يعطينا هذا التنسيق تأثير التفاف النص حول صندوق العنصر المعوّم، وهذا أهم ما تريد معرفته عن التعويم كما يُستخدم في تصميم الويب المعاصر. <h1>Simple float example</h1> <div class="box">Float</div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien. </p> .box { float: left; width: 150px; height: 150px; margin-right: 30px; } ستظهر النتيجة على هذا النحو: See the Pen css-layout-5 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: سنشرح التعويم بشكل كامل في مقال لاحق عن خاصيات التعويم. فقد استخدم تلك الخاصيات قبل ظهور تقنيات الصندوق المرن والشبكة لإنشاء تخطيطات متعددة الأعمدة. وقد تصادف هذه التخطيطات في بعض مواقع الويب لهذا سنغطيها أيضًا في مقال مستقل. تقنيات ضبط موقع العنصر تساعدك هذه التقنيات في نقل عنصر من المكان الذي ينبغي أن يحتله ضمن الانسياب الاعتيادي للعناصر إلى مكان آخر. ولا يُعد تموضع العنصر أسلوبًا في إنشاء تخطيطات رئيسية للصفحة، بل هو أقرب إلى ترتيب وضبط الموقع المخصص لكل عنصر في الصفحة بدقة. لكنك ستجد تقنيات مفيدة للحصول على أنماط تخطيط مخصصة تعتمد على استخدام الخاصية position. إن فهم فكرة التموضع ستساعدك على فهم الانسياب الاعتيادي للعناصر، وما الذي يعنيه نقل العنصر خارج هذا الانسياب. يوجد عمومًا خمسة أنواع تجدر معرفتها لتموضع العناصر: التوضع الساكن Static positioning: وهو الوضع الافتراضي لجميع العناصر، ويعني ببساطة وضع العنصر في مكانه الاعتيادي ضمن تخطيط الصفحة. التوضع النسبي Relative positioning: يساعدك على تعديل موقع عنصر في الصفحة وذلك بنقله بالنسبة إلى مكانه الاعتيادي، إضافة إلى جعله فوق عناصر أخرى في الصفحة Overlapping. التوضع المطلق Absolute positioning: يُخرج العنصر تمامًا من الانسياب العتيادي وكأنه يقع ضمن طبقة خاصة به. وبهذه الطريقة ستتمكن من تثبيت العنصر بالنسبة إلى حواف أقرب عنصر أب ثابت الموقع (وإن لم يكن هناك واحد سيكون هذا العنصر بالطبع <html>). لهذا الوضع أهميته في إنشاء تخطيطات معقدة مثل الصناديق متعددة النوافذ التي تتراكب فيها نوافذ المحتوى فوق بعضها لتُعرض وتختفي عند الطلب، أو لوحات المعلومات التي تضبط لتكون خارج الشاشة ثم تظهر إلى جانب الصفحة بالنقر على زر تحكم مخصص. التوضع الثابت Fixed positioning: يشابه الوضع المطلق إلا أنه يُثبِّت موضع العنصر بالنسبة إلى نافذة عرض المتصفح وليس لموقع عنصر آخر. لهذا الوضع فائدته في إنشاء تأثيرات هامة مثل قوائم التنقل التي تبقى دائمًا في نفس المكان على الشاشة بينما يكون المحتوى متحركًا عند تمرير الصفحة. التوضع اللاصق Sticky positioning: طريقة جديدة لتوضيع العناصر تجعلها تتصرف وكأنها في الوضع النسبي position: relative حتى تصل إلى حد معين بالنسبة لنافذة العرض عنها تتصرف وكأنها في الوضع الثابت position: fixed. مثال بسيط عن تموضع العناصر كي نألف العمل مع تقنيات التخطيط السابقة، سنعرض مثالين سريعين لكل منهما بنية HTML نفسها (ترويسة تليها ثلاثة فقرات نصية) كالتالي: <h1>Positioning</h1> <p>I am a basic block level element.</p> <p class="positioned">I am a basic block level element.</p> <p>I am a basic block level element.</p> تُنسق شيفرة HTML افتراضيًا باستخدام شيفرة CSS التالية: body { width: 500px; margin: 0 auto; } p { background-color: rgb(207, 232, 220); border: 2px solid rgb(79, 185, 227); padding: 10px; margin: 10px; border-radius: 5px; } إليك خرج الشيفرة السابقة على المتصفح: See the Pen css-layout-6 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع النسبي: يتيح لنا التوضع النسبي إزاحة العنصر خارج نطاق الانسياب الاعتيادي، بمعنى إمكانية إنجاز مهام مثل تحريك أيقونة قليلًا لتحاذي نص أو عنوان. لفعل ذلك، بالإمكان تطبيق قواعد التنسيق التالية: .positioned { position: relative; top: 30px; left: 30px; } أعطينا في شيفرة التنسيق السابقة القيمة relative للخاصية position العائدة إلى الفقرة الموجودة في الوسط. بالطبع لن يظهر تأثير ذلك قبل أن نضبط أيضًا قيمًا للخاصيتين top و left، إذ تجعلان العنصر ينزاح إلى الأسفل واليمين. قد ترى أن ما حدث هو عكس ما تتوقعه، لكن فكر بالموضوع على أنك دفعت العنصر من جانبيه العلوي واليساري وبالتالي ستكون النتيجة انزياحه نحو الأسفل واليمين. إليك نتيجة الشيفرة السابقة: See the Pen css-layout-7 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع المطلق ويستخدم لإخراج العنصر كليًا من مجرى الانسياب الاعتيادي، وإعادة توضيعه بإزاحته مقدارًا محددًا عن حواف الكتلة التي تحتويه. بالعودة إلى مثالنا السابق (دون تطبيق التوضع)، سنضيف قواعد التنسيق التالية لإنجاز التوضع المطلق: .positioned { position: absolute; top: 30px; left: 30px; } أعطينا في شيفرة التنسيق السابقة القيمة absolute للخاصية position العائدة إلى الفقرة الموجودة في الوسط، واستخدمنا الخاصيتين top و left كما سبق. إليك النتيجة: See the Pen css-layout-8 by Hsoub Academy (@HsoubAcademy) on CodePen. كما ترى، الأمر مختلف تمامًا هذه المرة! لقد انفصل العنصر بالكامل عن تخطيط الصفحة وبقي أعلاها، بينما بقيت الفقرتين النصيتين الأخريين في مكانهما دون أن تتأثرا بموقع الفقرة التي غيرنا أسلوب توضعها. وكذلك نجد اختلاف تأثير الخاصيتين top و left على الموقع في حالتي التوضع النسبي والمطلق. إذ تُحسب الإزاحة في حالة التوضع المطلق بالنسبة لأعلى ويسار الصفحة. التموضع الثابت يزيل التموضع الساكن العنصر من مجرى الانسياب الاعتيادي كما يفعل التوضع المطلق، لكن الإزاحة ستُطبق في هذه الحالة بالنسبة غلى نافذة العرض وليس بالنسبة إلى العنصر الأب (الكتلة الحاوية). وطالما أن العنصر يبقى ثابتًا بالنسبة إلى نافذة العرض، يمكن استخدام هذا التموضع لإحداث تأثيرات مميزة كالقوائم التي تبقى في مكانها عند تمرير محتوى الصفحة. وكمثال عن هذا التوضع، نجد شيفرة HTML تضم ثلاث فقرات نصية يسبقها صندوق <div> نضبط خاصية position له على القيمة fixed. <h1>Fixed positioning</h1> <div class="positioned">Fixed</div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. </p> <p> Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac ornare ex malesuada et. </p> <p> In vitae convallis lacus. Aliquam erat volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin eros pharetra congue. Duis ornare egestas augue ut luctus. Proin blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, eget fermentum sapien. </p> .positioned { position: fixed; top: 30px; left: 30px; } وهذه النتيجة: See the Pen css-layout-9 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع اللاصق وهي الأسلوب الأخير الذي نناقشه فيما يخص التوضّع. تمزج هذه القيمة بين التوضع النسبي والتوضع الثابت، فعندما نُطبق القاعدة position: sticky على عنصر سيتحرك مع المحتوى عند تمريره حتى يصل إلى إزاحة محددة سلفًا بالنسبة لنافذة العرض وعندها يبقى في هذا الموقع كما لو طبقنا القاعدة position: fixed. .positioned { position: sticky; top: 30px; left: 30px; } النتيجة: See the Pen css-layout-10 by Hsoub Academy (@HsoubAcademy) on CodePen. تخطيط الجدول تُعد جداول HTML طريقة جيدة في عرض البيانات القابلة للجدولة، لكنها استُخدمت قبل سنوات عدة -حين لم تكن CSS مدعومة جيدًا من قبل المتصفحات- من قبل المطورين لتخطيط صفحة ويب بأكملها. إذ استخدمت أسطر وأعمدة الجدول لاحتواء ترويسة الصفحة وتذييلها وأعمدتها، ونفع الأمر حينها مع وجود مشاكل جدية تتعلق بمرونة الجداول وكم الشيفرة الكبير اللازم لصياغتها وصعوبة تنقيحها، ناهيك عن الأخطاء الدلالية في استخدامها، فهي دلاليًا غير مخصصة لاحتواء عناصر أخرى بل لعرض البيانات وبالتالي ستجد قارئات الشاشة صعوبة في تتبع تخطيطات الجداول. تُعرض جداول HTML ضمن صفحة الويب وفقًا لمجموعة من خاصيات CSS تُعرّف تخطيط الجدول، ويمكن استخدام نفس تلك الخاصيات لترتيب عناصر أخرى غير الجداول بطريقة تُوصف أحيانًا بجداول CSS. يعرض المثال القادم هذا النمط من الاستخدام، وتجدر الملاحظة هنا أن استخدام تخطيط جداول CSS يُعد طريقة قديمة حاليًا، ولا يجب استخدامه إلا لدعم المتصفحات الأقدم التي لا تدعم تخطيط الشبكة أو الصندوق المرن. لنلق نظرة على مثالنا المكون من توصيف بسيط لنموذج HTML لكل عنصر إدخال فيه عنوان، كما وضعنا عنوانًا ضمن فقرة نصية، وغلفنا كل زوج (عنصر إدخال/ عنوان) ضمن عنصر <div> لتنسيق التخطيط. <form> <p>First of all, tell us your name and age.</p> <div> <label for="fname">First name:</label> <input type="text" id="fname" /> </div> <div> <label for="lname">Last name:</label> <input type="text" id="lname" /> </div> <div> <label for="age">Age:</label> <input type="text" id="age" /> </div> </form> بالنسبة لشيفرة CSS فمعظمها معروفة ماعدا استخدام الخاصية display. إذ ضبطت طريقة عرض العناصر <form> و <div> و <label> و <input> لتظهر على شكل جدول وصفوف في جدول وخلايا على الترتيب. ستظهر كل العناصر مبدئيًا كما لو أنها ضمن جدول معياري مما يجعل العناوين وعناصر الدخل على نفس السوية تلقائيًا. كل ما علينا فعله بعد ذلك تغيير الأبعاد قليلًا وضبط الهوامش وغير ذلك ليبدو مظهر العناصر أفضل. لاحظ كيف طبقنا قاعدة التنسيق ;display: table-caption على الفقرة النصية التي تمثل العنوان لتبدو وكأنها عنوان جدول، وكذلك القاعدة ;caption-side: bottom كي يظهر عنوان الجدول في الأسفل لأجل التنسيق فقط حتى لو كانت الفقرة قبل عناصر الإدخال <input> في الشيفرة المصدرية. html { font-family: sans-serif; } form { display: table; margin: 0 auto; } form div { display: table-row; } form label, form input { display: table-cell; margin-bottom: 10px; } form label { width: 200px; padding-right: 5%; text-align: right; } form input { width: 300px; } form p { display: table-caption; caption-side: bottom; width: 300px; color: #999; font-style: italic; } إليك نتيجة الشيفرة السابقة: See the Pen css-layout-11 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: لن يُغطي موضوع تخطيط الجدول أكثر كونه تقنية تخطيط قديمة. التخطيط متعدد الجداول تزودنا وحدة التخطيط متعدد الجداول في CSS بآلية لترتيب المحتوى ضمن أعمدة كما تُكتب الأعمدة في صحيفة. وعلى الرغم من أن قراءة الأعمدة أقل فائدة في صفحات ويب لأن الزائر يُضطر إلى تمرير المحتوى إلى الأعلى والأسفل باستمرار، لكن تقنية ترتيب المحتوى ضمن أعمدة تبقى مهمة بحد ذاتها. ولكي نحول كتلة إلى حاوية متعددة الأعمدة، نستخدم الخاصية column-count التي تخبر المتصفح كم عدد الأعمدة التي نحتاجها أو الخاصية column-width التي تخبر المتصفح أن يملأ الحاوية بأكبر عدد ممكن من الأعمدة ذات الاتساع المحدد. نبدأ في مثالنا التالي بشيفرة HTML ضمن عنصر الحاوية <div> الذي يمتلك الصنف container. <div class="container"> <h1>Multi-column Layout</h1> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla luctus aliquam dolor, eu lacinia lorem placerat vulputate. Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed auctor cursus massa at porta. </p> <p> Nam vulputate diam nec tempor bibendum. Donec luctus augue eget malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, facilisis sed est. Nam id risus quis ante semper consectetur eget aliquam lorem. </p> <p> Vivamus tristique elit dolor, sed pretium metus suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt eget purus in interdum. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. </p> </div> أسندنا القيمة 200px للخاصية column-width كي ندفع المتصفح إلى إنشاء أكبر عدد ممكن من الأعمدة ذات الاتساع 200 بكسل ضمن الحاوية، وسيجري تقاسم أية مساحات فارغة زائدة بين الأعمدة. .container { column-width: 200px; } وإليك نتيجة الكود السابق: See the Pen css-layout-12 by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة ناقشنا في هذا المقال جميع تقنيات تخطيط صفحات الويب التي يجدر معرفتها بصورة موجزة، وسنتابع الحديث عن هذه التقنيات بالتفصيل وحالات استخدامها المختلفة مقالات أخرى. ترجمة -وبتصرف- للمقال: Introduction to CSS layout اقرأ أيضًا استخدام خطوط الكتابة في الويب مع CSS تعرف على أساسيات لغة CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS وحدات القياس والقيم في CSS
  10. يُعد عنوان URL بالإضافة إلى النص التشعبي Hypertext وبروتوكول HTTP أحد المفاهيم المفتاحية للويب فهو الآلية التي تستخدمها المتصفحات browsers للوصول أي مورد موجود في الويب. عنوان URL هو مجرّد عنوان فريد لمورد على الويب، نظريًا يدل كل عنوان URL صحيح على مورد فريد ومحدد كصفحة HTML أو ملف CSS أو صورة أو غيرها من الموارد، ولكن في واقع هناك بعض الاستثناءات، وأكثرها شيوعًا عنوان URL يشير إلى مورد لم يعد موجودًا أو تغيّر موقعه. وطالما أن المورد يُمثّل بعنوان URL والخادم هو من يتعامل مع عنوان URL فلذلك تقع على عاتق مالك الخادم مهمة إدارة الموارد وعناوين URL المرتبطة بها بعناية. سنتعرف في هذا المقال على تفاصيل عنوان URL وكيفية عمل هذه العناوين على الويب. ننصحك قبل الشروع في إكمال قراءة المقال أن تطلع على مقال كيف تعمل شبكة الإنترنت؟ وأن يكون مفهوم الروابط التشعبية واضحًا بالنسبة لك. تشريح عنوان URL إليك بعض الأمثلة عن عناوين URL: https://developer.mozilla.org https://developer.mozilla.org/en-US/docs/Learn/ https://developer.mozilla.org/en-US/search?q=URL يمكن كتابة أيًا من تلك العناوين في شريط العنوان في المتصفح لتحميل الصفحة أو المورد المرتبط به. يتكون عنوان URL من أجزاء مختلفة بعضها إلزامي وبعضها اختياري. لاحظ الأجزاء المظللة في العنوان التالي والتي تُعد الأكثر أهمية في عنوان URL: لتوضيح الفكرة يمكن أن نشبّه عنوان URL بعنوان بريدي نمطي، إذ يمثل المخطط Scheme الخدمة البريدية التي تريد استخدامها، ويمثل اسم النطاق Domain name اسم المدينة أو البلدة، وستعمل المنفذ Port عمل الرمز البريدي، وسيمثل المسار Path البناء الذي ينبغي تسليم البريد إليه. أما المعاملات Parameters فستمثل أي معلومات إضافية كرقم الشقة وستمثل المرساة Anchor مستلم الرسالة الفعلي الذي وجَّهت رسالتك إليه. بروتوكول عنوان URL وهو القسم الأول من URL ويشير إلى البروتوكول الذي ينبغي استخدامه لطلب مورد معين من الخادم (البروتوكول هو طريقة لإعداد آلية لتبادل البيانات ونقلها عبر شبكة من الحواسيب). يُستخدم عادة برتوكولي HTTP و HTTPS (النسخة الآمنة) في مواقع الويب ولا بد من مخاطبة صفحات الويب باستخدام أحدهما. مع ذلك تدرك المتصفحات تمامًا كيف تتعامل مع بروتوكولات Schemes أخرى مثل :mailto (لفتح واجهة بريد إلكتروني) فلا تتفاجأ إن رأيت بروتوكولات أخرى. التصريح Authority يأتي بعد البروتوكول التصريح ويفصل بينهما النمط //:. يتضمن التصريح في حال وجوده اسم النطاق (www.example.com مثلًا) ورقم المنفذ (80 مثلًا) يفصل بينهما الرمز :. يشير النطاق Domain إلى خادم الويب الذي يُرسل إليه الطلب ويكون عادة على شكل اسم، كما يمكن استخدام عنوان آي بي لكن استخدامه نادر وغير ملائم في معظم الأحيان. يشير المنفذ Port إلى البوابة المستخدمة للوصول إلى المورد. يُحذف المنفذ عادة عند استخدام الخادم المنافذ المعيارية للبروتوكول HTTP، إذ يستخدم المنفذ 80 لبروتوكول HTTP والمنفذ 443 لبروتوكول HTTPS عند منح الإذن بالوصول إلى المورد. ما عدا ذلك لا بد من كتابة المنفذ المستخدم. في بعض الحالات لا يستخدم URL قسم التصريح كما هو الحال في واجهات البريد الإلكتروني (mailto:foobar). إذ يحتوي URL مخططًا فقط دون تصريح، لذلك لن تجد النمط //، وإنما ستجد فقط النقطتين المتعامدتين التي تعمل فقط كفاصل بين المخطط وعنوان البريد الإلكتروني. المسار إلى مورد ويمثل الطريق للوصول إلى مورد موجود على خادم ويب (path/to/myfile.html/‎ مثلًا). مثَّلت هذه المسارات في بدايات الويب الموقع الفيزيائي لملف على خادم، لكنه حاليًا مجرد اختصار يتعامل معه الخادم بطريقة معينة للوصول إلى المورد وغالبًا لا يشير إلى المكان الحقيقي لوجود المورد. المعاملات وهي قائمة من العناصر الثنائية على شكل"مفتاح/قيمة" يفصل بينها الرمز & مثل القائمة key1=value1&key2=value2 كما في الصورة السابقة. يمكن لخادم ويب استخدام هذه المعاملات في تنفيذ عمليات إضافية قبل أن يعيد المورد المطلوب. لكل خادم ويب قواعده الخاصة المتعلقة بمعالجة المعاملات، ومن الأفضل دومًا أن تسأل مالك الخادم عن طريقة تعامل خادمه مع المعاملات. المربط يمثل المربط # (ويشار إليه المرساة أحيانًا) طريقة للاشارة إلى جزء معين من المورد نفسه، وتشبه فكرته فكرة "الاشارة المرجعية" داخل المورد نفسه. فلو كان المورد ملف HTML مثلًا، سينتقل المتصفح إلى النقطة التي يُعرّفها المربط "#"، وفي حال كان المربط يشير إلى نقطة زمنية معينة من ملفات الصوتية أو الفيديو سيحاول المتصفح الانتقال إلى الزمن الذي يحدده المربط. وتجدر الإشارة هنا، إلى أن القسم الذي يقع بعد الرمز "#" (ويُدعى "مُعرِّف القطعة Fragment Identifier") لن يُرسل أبدًا إلى الخادم مع الطلب. كيف تستخدم URL بإمكانك كتابة أي URL ضمن شريط العنوان في المتصفح لتحضر المورد الذي يمثله، لكن هذا الاستخدام ما هو إلا رأس الهرم فقط! تستخدم لغة HTML عناوين URL بكثرة لكي: تنشئ روابطًا مع مستندات أخرى باستخدام العنصر <a>. تربط مستندًا مع موارده عبر عناصر متعددة مثل <link> أو <script>. تعرض الوسائط مثل الصور (باستخدام العنصر <img>) والفيديو (باستخدام العنصر <video>) والملفات الصوتية (باستخدام العنصر<audio>). تعرض ملفات HTML أخرى باستخدام العنصر <iframe>. تستخدم التقنيات الأخرى مثل CSS وJavaScript عناوين URL بكثرة أيضًا، وهذه التقنيات هي ما تشكل الويب في الواقع. عناوين URL المطلقة والنسبية تُعد الأمثلة التي أوردناها سابقًا عن عنوان URL عناوينًا مطلقة Absolute URLs، لكن لا يزال هناك نمط آخر وهي العناوين النسبية Relative URLs ولا بدّ من الإشارة إلى الفوارق بينهما بشيء من التفصيل. تعتمد الأجزاء التي ينبغي استخدامها من عنوان URL والأخرى التي يمكن إهمالها على السياق الذي يستخدم فيه عنوان URL. فمثلًا في شريط عنوان متصفحك لا يوجب أي سياق محدد لعناوين URL لهذا لا بدّ من كتابة URL بشكله الكامل (أو المطلق) كما فعلنا في الأمثلة السابقة. لا حاجة بالطبع في حالة المتصفح أن تذكر البروتوكول لأن المتصفح يستخدم البروتوكول HTTP افتراضيًا، ولا نذكر أيضًا المنفذ الذي نحتاجه إلا في الحالة التي نتصل فيها مع الخادم عبر منفذ غير اعتيادي، ولكن لا بد من كتابة بقية أجزاء عنوان URL. عندما يُستخدم عنوان URL داخل مستند كصفحة HTML ستختلف الأمور قليلًا. إذ يمتلك المتصفح في هذه الحالة عنوان URL الخاص بالمستند ككل، وبالتالي سيتمكن من استخدام معلوماته لإكمال الأجزاء الناقصة لعنوان URL الموجود داخل المستند. يمكن التمييز بين عنوان URL المطلق والنسبي بالنظر فقط إلى جزء المسار، فإذا بدأ عنوان URL بالرمز / فسيحضر المتصفح المورد من أعلى عنوان جذري للخادم دون الحاجة إلى المكان الذي يشير إليه المستند. لنلق نظرة على بعض الأمثلة لتوضيح الأمر. أمثلة عن عناوين URL مطلقة يُعد العنوان الآتي مطلقًا أو كاملًا https://developer.mozailla.org/docs/learn بينما يعد العنوان التالي غير كامل إذ سيستخدم هذا العنوان البروتوكول الضمني للمستند الذي يتضمن عنوان URL. developer.mozilla.org/en-us/docs/Learn// أما العنوان الآتي فهو اسم نطاق ضمني، وهي الحالة الأكثر استخدامًا لعناوين URL المطلقة داخل مستندات HTML. /en-us/docs/learn/ يستخدم المتصفح في هذه الحالة نفس البروتوكول واسم النطاق المستخدمان في تحميل المستند الذي يحتوي على عنوان URL لاحظ أنه لا يمكن حذف اسم النطاق دون حذف البروتوكول أيضًا. أمثلة عن عناوين URL نسبية لتفهم الأمثلة التالية بطريقة صحيحة، سنفترض أن عناوين URL ستُستدعى من داخل المستند الموجود على عنوان URL التالي: https://developer.mozilla.org/en-us/docs/Learn يستخدم العنوان "Skills/Infrastructure/Understanding_URLs" فكرة الموارد الفرعية، فطالما أنّ عنوان URL لم يبدأ بالرمز / سيحاول المتصفح أن يجد المستند في مجلد فرعي ضمن المجلد الذي يحتوي المورد الحالي أي سنرغب في مثالنا في الوصول إلى عنوان URL التالي: https://developer.mozilla.org/en-us/docs/Learn/Skills/Infrastructure/Understanding_URLs. بينما يستخدم العنوان "‎../CSS/display" فكرة التراجع إلى الخلف في مسار الشجرة، ونستخدم في هذه الحالة النمط /.. الموروث من نظام ملفات يونكس لكي نخبر المتصفح أن ينتقل إلى المجلد الأعلى مباشرة. أي أننا نريد هنا الوصول إلى عنوان URL التالي: https://developer.mozilla.org/en-US/docs/Learn/../CSS/display والذي يمكن اختصاره إلى: https://developer.mozilla.org/en-US/docs/CSS/display عناوين URL الدلالية بالرغم من الصبغة التقنية الواضحة لهذا النوع، إلا أن عناوين URL الدلالية Semantic URLs هي طريقة دخول إلى موقع ويب معين يمكن للبشر فهمها. أي يمكن أن يحفظها المستخدم بسهولة ويدخلها بنفسه في شريط عنوان المتصفح، وطالما أنّ المستخدمين هم مركز اهتمام الويب، فمن أفضل الممارسات التي ينبغي نشرها هي بناء ما يُدعى عناوين URL واضحة ومقروءة Clean URL، وهي عناوين تستخدم كلمات في صلب موضوع محتوى العناوين يمكن لأي شخص فهمها دون أن تكون لديه أي فكرة تقنية عن كيفية إنجاز الأمر. لا علاقة بالطبع للدلالات اللغوية بالحواسيب، فلربما بدت لك عناوين URL كخليط من الرموز العشوائية، لكن هناك إيجابيات عدة لكتابة URL مقروء من قبل المستخدم منها: سهولة تعديلها. تجعل الكثير من النقاط أكثر وضوحًا للمستخدم مثل الموقع الذي يتواجد فيه وما الذي يفعله في هذا الموقع أو ما الذي يقرأه أو يتفاعل معه على الويب. يمكن لعناوين URL المقروءة أن تحسن ترتيب وتصنيف الصفحات في محركات البحث. ترجمة -وبتصرف- للمقال What is a URL. اقرأ أيضًا ما هو خادم الويب؟ ما هي الأدوات المستخدمة في بناء مواقع ويب؟ ما هي أدوات مطوري الويب المدمجة في المتصفحات؟ ما هي أسماء النطاقات في شبكة الإنترنت؟
  11. تحدثنا في مقال سابق عن التنسيقات الأساسية للنصوص وخطوط الكتابة في CSS، وسنتعمق في مقالنا هذا في استكشاف خطوط الكتابة المستخدمة في الويب وتفاصيلها، وكيفية استخدام خطوط مخصصة في صفحاتك لتجعلها أكثر تنوعًا وخصوصية. ننصحك قبل إكمال قراءة المقال أن تطلع على أساسيات لغة HTML ومقال التنسيقات الأساسية للنصوص وخطوط الكتابة في CSS. مراجعة سريعة لعائلات خطوط الكتابة يمكن التحكم بالخط المستخدم في كتابة محتوى HTML باستخدام الخاصية font-family التي تأخذ اسم عائلة خط واحد أو أكثر كقيم لها. وبهذا يبحث المتصفح عند عرض صفحة الويب ضمن قائمة الخطوط المتوفرة في نظام التشغيل حتى يجد العائلة المطلوبة: p { font-family: Helvetica, "Trebuchet MS", Verdana, sans-serif; } تعمل هذه الطريقة دون مشاكل، لكن خيارات المطورين محدودة عادةً؛ فهنالك عدد محدود جدًا من الخطوط التي يمكن أن تضمن وجودها في جميع أنظمة التشغيل والتي تُعرف بخطوط الويب الآمنة. لهذا بإمكانك وضع قائمة من الخطوط التي تشاء تتبعها بالبدائل الآمنة ومن ثم الخط الافتراضي لنظام التشغيل، لكن سيزيد ذلك طبعًا حمل العمل نظرًا للاختبارات المطلوبة للتأكد من عمل تصميمك وفقًا لكل خط تضعه. خطوط الويب هناك بديلٌ يعمل جيدًا ويعتمد على CSS التي تسمح بتخصيص ملفات لخطوط كتابة تجدها على الويب، بحيث يمكنك تنزيلها واستخدامها مع موقعك، ويعني ذلك أن جميع المتصفحات التي تدعم ميزة CSS هذه ستعرض المحتوى بالخط الذي اخترته تمامًا. نستخدم أولًا الخاصية @font-face في بداية ملف CSS، وتحدد ملف أو ملفات الخطوط التي ينبغي تنزيلها: @font-face { font-family: "myFont"; src: url("myFont.woff2"); } ثم نستخدم بعد ذلك اسم عائلة الخط التي خصصناها ضمن font-face@ كي نطبق الخط المخصص أينما نشاء وبالطريقة التقليدية: html { font-family: "myFont", "Bitstream Vera Serif", serif; } قد تغدو هذه الطريقة أعقد قليلًا، وهذا ما سنناقشه تاليًا. إليك بعض النقاط المهمة التي ينبغي تذكرها جيدًا: لا تكون ملفات الخطوط مجانية غالبًا، وعليك أن تدفع ثمنها أو أن تخضع لشروط رخصة الاستخدام كأن تشير إلى صاحب الخط في شيفرتك أو موقعك، لهذا لا تحاول سرقة الخطوط واستخدامها دون الإشارة الواضحة إلى مصدرها. تدعم كل المتصفحات الرئيسية ملفات الخطوط من نوع WOFF/WOFF2 (خطوط ويب مفتوحة التنسيق بنسختيها الأولى والثانية)، وتدعم كذلك المتصفحات الأقدم مثل IE9 (المنتشر منذ عام 20111) ملفات الخطوط WOFF. تدعم خطوط WOFF2 مواصفات TrueType و OpenType كاملةً بما في ذلك الخطوط المتغيرة variable fonts والخطوط الملونة chromatic fonts ومجموعات الخطوط font collections. طريقة ترتيب ملفات الخطوط مهمة، فلو زوّدت المتصفح بعدة ملفات للخطوط كي ينزّلها، سيختار المتصفح أول ملف قادر على تفسيره، لهذا لا بُد من وضع الخطوط التنسيقات المفضّلة في البداية -أي WOFF2- تليها التنسيقات الأقدم. فعندما لا يفهم المتصفح تنسيقًا، سينتقل إلى التنسيق الذي يليه في القائمة. إذا أردت العمل مع المتصفحات القديمة، عليك تضمين التنسيق EOT (النمط المفتوح المضمّن Embedded Opentype) أو التنسيق TrueType Font -أو اختصارًا TTF- أو تنسيق SVG. وسنتعلم في هذا المقال استخدام Fontsquirrel Webfont Generator للحصول على الملفات المطلوبة. ملاحظة يمكنك استخدام محرّر خطوط فايرفوكس Firefox Font Editor لفحص وتغيير الخطوط المستخدمة في صفحتك فيما ومعرفة إن كانت خطوط ويب صالحة أم لا. تطبيق عملي: مثال عن استخدام خطوط ويب سنحاول بناء مثال توضيحي عن استخدام خطوط ويب. من الصعب طبعًا توضيح ذلك من خلال مثال تفاعلي مدمج، لهذا نطلب منك اتباع الخطوات التي نوردها تاليًا كي تفهم فكرة العملية. حمّل الملفين web-font-start.html و web-font-start.css واستخدمهما بمثابة نقطة انطلاق، ثم ضِف شيفرتك ضمنها (اطلع على المثال المباشر). انسخ الملفين السابقين إلى مجلد جديد على حاسوبك. ستجد في الملف بعض قواعد CSS التي تنسّق التخطيط الأساسي وتنسيق الكتابة في المثال. إيجاد خطوط للكتابة نستخدم في مثالنا خطين من خطوط الويب، أولهما للعناوين والآخر لمتن الصفحة، وعلينا بدايةً إيجاد الملفات التي تضم هذه الخطوط. تُصنع الخطوط من قبل مختصين وتُخزّن ضمن ملفات بتنسيقات مختلفة، وستجد الخطوط ضمن المواقع التالية عمومًا: موزعي الخطوط المجانية: وهي مواقع تتيح للمستخدمين تنزيل خطوط مجانية (مع وجود بعض شروط لترخيص الاستخدام أحيانًا، كأن تشير إلى مصمم الخط). من هذه المواقع نجد: Font Squirrel, [dafont و Everything Fonts. موزعي الخطوط المدفوعة: وهي مواقع تقدم خطوطًا مدفوعة الثمن مثل fonts.com أو myfonts.com ، ويمكنك أيضًا شراء الخطوط من منتجيها مباشرةً، مثل Linotype، أو Monotype، أو Exljbris. خدمات خطوط الكتابة عبر الإنترنت: وهي مواقع تخزّن الخطوط وتسمح لك باستخدامها، مما يسهل العملية برمتها. ابحث الآن عن بعض الخطوط في موقع Font Squirrel وانتقي خطين الأول للعناوين (لا بأس بالخطين nice display أو slab serif font) والآخر أقل لمعانًا وأوضح قراءةً للفقرات. انقر على زر التنزيل عندما تجد الخطوط المناسبة وخزّن الملفات ضمن المجلد نفسه الذي وضعت فيه ملفات الشيفرة. لا تكترث إذا كانت الملفات بتنسيق TTF أو تنسيق Open Type Fonts -أو اختصارًا OTF. فُكَّ ضغط الملفات من حزمتي الخطوط، إذ توزّع ملفات الخطوط عادةً مثل ملفات مضغوطة ZIP تضم ملفات الخطوط ومعلومات الترخيص. وقد تجد ضمن الحزمة أكثر من خط، إذ توزّع بعض الخطوط مثل عائلة واحدة مع عدة توزيعات، مثل الخط الرفيع أو النحيف thin والمتوسط medium والغامق bold والمائل italic والمائل النحيف thin italic وهكذا. ولا نريد منك في هذا التطبيق سوى اختيار خطين مناسبين. ملاحظة: يمكنك في موقع Font Squirrel أن تختار الخطوط من خلال الفئات المتوفرة تحت قسم "إيجاد الخطوط Find fonts" كي تقلل عدد الخيارات المتاحة. توليد الشيفرة المطلوبة نحتاج الآن إلى توليد الشيفرة اللازمة وتنسيقات الخط، لهذا اتبع التعليمات التالية لكل خط: تأكد من أنك حققت متطلبات الترخيص، إذا كنت ستستخدم الخط في مشروع تجاري أو مشروع ويب. انتقل إلى موّلد خطوط الويب Webfont Generator من Fontsquirrel. ارفع ملفات الخطوط باستخدام زر "Upload Fonts". فعّل زر التحقق الذي يحمل العنوان "Yes, the fonts I'm uploading are legally eligible for web embedding". انقر زر "Download your kit" لتنزّل مجموعتك. بعد إكمال المولّد معالجة الملفات، ينبغي أن تكون الملفات جاهزة للتنزيل بتنسيق ZIP، نزّلها في نفس مجلد CSS و HTML، وإذا أردت دعم المتصفحات القديمة، اختر النمط "خبير Expert" في موّلد خطوط ويب، ثم اختر التنسيقات SVG و EOT و TTF قبل تنزيل المجموعة. تحدد خدمات ويب التي تولّد الخطوط حجمًا محددًا للملفات، لهذا فكّر باستخدام أحد الأدوات التالية: sfnt2woff-zopfli للتحويل من ttf إلى woff. fontforge للتحويل من ttf إلى svg. batik ttf2svf للتحويل من ttf إلى svg. woff2 للتحويل من ttf إلى woff2. استخدام الشيفرة في مثالنا استخرج الملفات من الملف المضغوط الذي نزّلته، وستجد ضمن المجلد الناتج العناصر المفيدة التالية: نسختين من كل خط على شكل ملفين يحملان اللاحقتين woff. و woff2. ملف HTML تجريبي لكل خط. حمّل هذه الملفات في متصفحك لترى كيف سيبدو الخط في السياقات المختلفة. ملف تنسيق stylesheet.css يضم شيفرة font-face@ الموّلدة والتي تحتاجها. لاستخدام الخطوط في مثالك، اتبع الخطوات التالية: أعد تسمية المجلد المستخرج من الملف المضغوط باسم بسيط سهل التذكر مثل "fonts". افتح الملف "stylesheet.css" وانسخ القاعدة font-face@ إلى ملف التنسيق "web-font-start.css" الذي أنشأته، وتذكر أن تكون في أعلى الملف وقبل أي قاعدة CSS أخرى، إذ لا بد من إدراج الخطوط قبل أن تتمكن من استخدامها في موقعك. تشير كل دالة من الدوال ()url إلى ملف خط نريد إدراجه ضمن ملف CSS، ولا بد من التأكد من صحة المسار إلى ملف الخط المطلوب، لهذا أضِف العبارة /fonts قبل بداية كل مسار (عدّل ما تراه مناسبًا). يمكنك الآن استخدام هذه الخطوط مثل أي خط ويب آمن أو خط من خطوط نظام التشغيل. إليك مثالًا: @font-face { font-family: "zantrokeregular"; src: url("fonts/zantroke-webfont.woff2") format("woff2"), url("fonts/zantroke-webfont.woff") format("woff"); font-weight: normal; font-style: normal; } font-family: "zantrokeregular", serif; ينبغي أن ينتهي بك المطاف إلى صفحة تجريبية تعرض بعض الخطوط الجميلة، ولأن الخطوط المختلفة قد صُممت بقياسات مختلفة، قد يكون عليك تعديل حجمها والفراغات فيما بين الأحرف والكلمات. ملاحظة: إذا واجهتك أية مشاكل في تنفيذ الأمر بالشكل الصحيح، قارن بين نسختك مع ملفاتنا المكتملة web-font-finished.html و web-font-finished.css. كما يمكنك تحميل الشيفرة من غيت هب GitHub أو تشغيل المثال المنتهي مباشرةً. استخدام خدمات خطوط الكتابة عبر الإنترنت تخزّن خطوط الكتابة عبر الإنترنت خطوط الكتابة وتقدمها لك دون الحاجة إلى استخدام شيفرة font-face@ فكل ما يتطلبه الأمر عادةً هو إدراج سطر بسيط أو اثنين من الشيفرة ضمن موقعك حتى يعمل كل شيء على ما يرام. من الأمثلة على هذه الخدمات نجد Adobe Fonts و Cloud.typography. تعتمد معظم هذه الخدمات على مبدأ الاشتراك ما عدا Google Fonts فهو استثناء عنها كونه خدمة مجانية وخاصة لأعمال الاختبارات السريعة وكتابة الأمثلة التوضيحية Demos. من السهل استخدام معظم هذه الخدمات، لهذا لن نتحدث عنها مطوّلًا، بل سنلقي نظرة على خدمة Google Fonts كي تفهم الفكرة. استخدم لهذا الغرض الملفين "web-font-start.html" و "web-font-start.css" أيضًا بمثابة نقطة انطلاق. انتقل موقع Google Fonts. ابحث عن خط الكتابة الذي تفضله، أو استخدم المرشحات filters أعلى الصفحة لعرض أنواع الخطوط التي ترغب في انتقاء بعضها، ثم اختر خطين منها. لاختيار عائلة خط، انقر على زر العرض ثم انقر على الزر ⊕ إلى جانب الخط. عندما تختار عائلات الخطوط، انقر على زر عرض عائلات الخطوط المختارة "View your selected families" في أعلى يمين الصفحة. انسخ سطر HTML الذي يظهر على الشاشة ثم الصقه في ترويسة ملف HTML. ضعه فوق العنصر <link> الموجود أصلًا كي يجري إدراج الخط قبل أن تستخدمه بواسطة CSS. عليك الآن نسخ تصريحات CSS الموجودة ضمن ملف CSS كما تراه ملائمًا حتى تُطبّق الخط على عناصر HTML. ملاحظة: ستجد نسخةً مكتملةً على google-font.html و google-font.css إذا أردت التحقق من نسختك بالمقارنة مع نسختنا (ألق نظرةً على عملها). تفاصيل أكثر عن قاعدة font-face@ لنستكشف صياغة font-face@ التى ولّدها موقع Fontsquirrel، ستبدو إحدى مجموعات القواعد على النحو التالي: @font-face { font-family: "zantrokeregular"; src: url("zantroke-webfont.woff2") format("woff2"), url("zantroke-webfont.woff") format("woff"); font-weight: normal; font-style: normal; } لنرى ما هو دور كل تصريح: font-family: يُخصّص هذا التصريح لاختيار اسم مناسب يشير إلى الخط، وقد يكون أي شيء تريده طالما أنك تستخدمه بنفس الطريقة ضمن ملف CSS. src: يُخصص هذا التصريح لتحديد المسار إلى موقع ملف الخط الذي تريد إدراجه ضمن شيفرة CSS (الجزء url منه)، ولتحديد تنسيق ملف الخط (الجزء format منه)، أما الجزء الأخر في كل حالة فهو اختياري لكنه تصريح مفيد لأنه يسمح للمتصفحات أن تحدد بسرعة أي خط يمكنها استخدامه. يمكن أن نبني أيضًا قائمةً من التصريحات تفصل بينها الفاصلة ,، ومن المفيد ترتيب هذه التصريحات ابتداءً من التنسيق الأكثر تفضيلًا مثل WOFF2، لأن المتصفح سيحاول تطبيق التصريحات على نحوٍ متسلسل ابتداءً من التصريح الأول. font-weight/font-style: يخصص هذان التصريحان سماكة الخط إضافةً إلى كونه مائلًا أو لا؛ فإذا أدرجت عدة سماكات للخط نفسه، يمكنك حينها تخصيص سماكة ونمط محددين ثم استخدام الخاصيتين font-weight/font-style للتبديل بين السماكات والأنماط المختلفة الأخرى. ملاحظة: يمكنك تخصيص قيم محددة للخاصيتين font-variant و font-stretch من أجل خطوط الويب، كما يمكنك -في المتصفحات الحديثة- تخصيص قيمة للخاصية unicode-range وهي مجال محدد من المحارف التي ترغب في استخدامها خارج مجموعة محارف خط الويب وذلك في المتصفحات التي تدعم ذلك، وستُنزّل حينها هذه المحارف فقط تحاشيًا لتنزيل ما لا حاجة له. الخطوط المتغيرة توجد تقنية جديدة للخطوط تدعمها المتصفحات وتُدعى الخطوط المتغيرة variable fonts، وهي خطوط تسمح بتشارك عدة توزيعات لواجهة خط الكتابة في ملف واحد بدلًا من وجود ملف مستقل لكل اتساع أو سماكة أو نمط. هذا الموضوع متقدم ولن نناقشه في مقالنا. الخلاصة بهذا المقال نكون قد أنهينا سلسلة المقالات التي تتعلق بأساسيات تنسيق النصوص باستخدام CSS. لا تتردد في المشاركة وطرح الأسئلة حول النقاط التي تعتقد أنها غامضة في قسم التعليقات أسفل كل مقال من مقالات السلسلة. ترجمة -وبتصرف- لمقال Web fonts. اقرأ المزيد المقال السابق تنسيق الروابط باستخدام CSS. تنسيق نصوص صفحات الويب باستخدام CSS. التنسيقات الأساسية للعناصر في CSS. أساسيات لغة CSS.
  12. نناقش في هذا المقال طريقة تنسيق الروابط في لغة CSS ونعرفك على طرق مختلفة لتحسين مظهرها على موقع الويب الخاص بك، ونعرفك على الحالات المختلفة للروابط وطريقة تنسيقها بفعالية أكبر بالاستفادة من آلية استخدام الأصناف الزائفة pseudo classes في CSS، ونناقش تنسيق الروابط في المواضع المألوفة المختلفة مثل روابط قوائم التنقل، والتبويبات الفرعية tabs. ننصحك قبل إكمال قراءة المقال أن تطلع على أساسيات لغة HTML ومقال التنسيقات الأساسية للنصوص وخطوط الكتابة في CSS. لنلق نظرة على بعض الروابط التشعبية اطلعنا في مقال سابق على أفضل الطرق المتبعة في إنشاء وإضافة الروابط التشعبية إلى صفحات HTML، وسنتابع في هذا المقال استعراض أفضل الممارسات المتبعة في تنسيق هذه الروابط. حالة الرابط لا بد قبل كل شيء من فهم مصطلح "حالة الرابط link state" والذي يدل على الحالات المختلفة التي يكون عليها الرابط. إذ يجري تنسيق الرابط وفقًا لحالته باستخدام الأصناف الزائفة: حالة الرابط Link: وهي الحالة التي يكون فيها للرابط وجهة (وليس مجرد مربط anchor باسم محدد). تُنسّق هذه الحالة من خلال الصنف الزائف link:. حالة رابط جرت زيارته Visited: وهي الحالة التي نُقر فيها على الرابط وخزّن ذلك في ذاكرة المتصفح. تُنسّق هذه الحالة باستخدام الصنف الزائف visited:. حالة رابط عند مرور مؤشر الفأرة Hover: تُنسّق هذه الحالة باستخدام الصنف الزائف hover:. إضافة أيقونات إلى الروابط حالة تركيز الدخول إلى الرابط Focus: وذلك من خلال الانتقال إليه عبر الضغط المتكرر على زر Tab في لوحة المفاتيح أو برمجيًا باستخدام ()HTMLElement.focus وتُنسّق هذه الحالة باستخدام الصنف الزائف focus:. حالة الرابط الفعّال Active: وهي الحالة التي يُنقر فيها على الرابط وتُنسّق باستخدام الصنف الزائف :active. التنسيقات الافتراضية يعرض المثال التالي الطريقة التي يبدو عليها الرابط وطريقة سلوكه الافتراضي، إذ تقوم لغة CSS بتكبير حجم النص وتجعله في المنتصف كي يبرز ويصبح أكثر وضوحًا. ستتمكن في هذا المثال من مقارنة السلوك أو المظهر الافتراضي لروابطك مع سلوك ومظهر الروابط الأخرى المنسّقة باستخدام خاصيات CSS إضافية، إذ يكون للروابط ذات التنسيق الافتراضي الخصائص التالية: تحتوي خطوطًا أسفلها. تكون الروابط التي لم تُزر بعد زرقاء اللون. تكون الروابط التي جرت زيارتها بنفسجية اللون. يغيّر تحريك مؤشر الفأرة فوق الرابط أيقونة الفأرة إلى أيقونة على شكل يد. يحيط إطار بالروابط التي اكتسبت تركيز الدخل. يمكنك نقل التركيز على رابط في الصفحة بالضغط المتكرر على زر Tab (في نظام ماك لا بد من الضغط على زري Option و Tab معًا). تكون الروابط الفعالّة حمراء اللون. حاول أن تنقر وتستمر بالضغط على الرابط. تكون شيفرة HTML للرابط كما يلي: <p><a href="#">A simple link</a></p> أما شيفرة CSS فهي كالتالي: p { font-size: 2rem; text-align: center; } لتعطي النتيجة التالية: ملاحظة: تنقلك جميع روابط الأمثلة إلى أعلى الصفحات التي تضمها، وتُستخدم الأجزاء الفارغة "#"=href لإنشاء أمثلة بسيطة والتأكد أن الأمثلة المعروضة والمحتواة ضمن إطار <iframe> تعمل جيدًا. بقيت هذه التنسيقات كما هي منذ الأيام الأولى للمتصفحات خلال تسعينات القرن الماضي، إذ يتوقع المستخدمون سلوك هذه الروابط بناءً على تلك الألوان وسيؤدي تغييرها إلى إرباك الكثيرين. ولا يعني هذا طبعًا التخلي عن تنسيق الروابط نهائيًا، بل عدم المغالاة في تنسيقها إلى درجة بعيدة عما هو متوقع، فعليك على الأقل أن: تستخدم خط أسفل الرابط فقط للرابط وليس لأية أغراض أخرى، وإن لم تشأ وضع هذا الخط حاول تظليل الرابط بطريقة أخرى. تمنح الرابط طريقةً يتفاعل فيها مع تمرير مؤشر الفأرة فوقه أو عندما ينتقل تركيز الدخل إليه، وأن تجد طريقةً مشابهةً ليتفاعل الرابط عند تفعيله. يمكنك تغيير أو إلغاء هذه التنسيقات باستخدام خاصيات CSS التالية: color: لتلوين نص الرابط. cursor: لتنسيق مؤشر الفأرة، ولا تحاول إلغاء هذا التنسيق ما لم يكن السبب وجيهًا. outline: لتنسيق الإطار المحيط بالكتابة. والإطار أمر مشابهة للحواف borders، إلا أن الحواف ستشغل مساحة من صندوق العنصر على عكس الإطار الذي يتوضّع فوق الخلفية فقط، وللإطار فائدة في تحسين شمولية الوصول، ولا ينبغي إلغاؤه ما لم تجد طريقة أخرى لتدل على رابط تركيز focused link. ملاحظة: لست مضطرًا إلى التقيد بالخاصيات السابقة فقط، بل يمكنك استخدام أية خاصيات تجدها مناسبة لتنسيق الروابط. تنسيق بعض الروابط بعد أن اطلعنا على التنسيقات الافتراضية بشيء من التفصيل، سنلقي نظرةً على مجموعة من التنسيقات النمطية التي نتخذها بمثابة قاعدة انطلاق ثم نملؤها بالقواعد التي نريد: a { } a:link { } a:visited { } a:focus { } a:hover { } a:active { } لترتيب القواعد في الشيفرة السابقة أهميته لأن تنسيقات الروابط تُبنى فوق بعضها بعضًا؛ إذ ستُطبّق مثلًا التنسيقات ضمن القاعدة الأولى على كل القواعد التي تليها، وعندما يُفعّل الرابط، يستجيب غالبًا إلى تمرير مؤشر الفأرة. لهذا، لو وضعت القواعد بترتيب خاطئ وحاولت تغيير نفس الخاصيات في كل قاعدة، فلن يعمل التنسيق كما تتوقع. إليك هذه العبارة لتتذكر الترتيب الصحيح لكتابة القواعد "LoVe Fears HAte" حيث يشير حرف L إلى حالة الرابط Link وحرف V إلى الحالة Visited …إلخ. لنضع الآن بعض التنسيقات ضمن القواعد التي رتبناها قبل قليل: body { width: 300px; margin: 0 auto; font-size: 1.2rem; font-family: sans-serif; } p { line-height: 1.4; } a { outline: none; } a:link { color: #6900ff; } a:visited { color: #a5c300; } a:focus { text-decoration: none; background: #bae498; } a:hover { text-decoration: none; background: #cdfeaa; } a:active { background: #6900ff; color: #cdfeaa; } إليك شيفرة HTML البسيطة التالية كي نطبّق عليها التنسيقات: <p> There are several browsers available, such as <a href="#">Mozilla Firefox</a>, <a href="#">Google Chrome</a>, and <a href="#">Microsoft Edge</a>. </p> ستكون النتيجة على النحو التالي: ما الذي فعلناه إذًا؟ يبدو مظهر الروابط مختلفًا عن التنسيق النمطي، لكنه يقدّم تجربةً مماثلة يفهم من خلالها الزائر ما الذي يحدث: لن نناقش أول قاعدتين فهما واضحتان تمامًا. تستخدم القاعدة الثالثة المحدد a للتخلص من إطار الرابط، والذي يختلف التعامل معه من متصفح لآخر. استخدمنا بعد ذلك المحددين a:link و a:visited لضبط بعض التغييرات اللونية للروابط التي زارها المستخدم والتي لم يزرها بعد ليستطيع التمييز بينهما. استخدمنا في القاعدتين التاليتين المحددين a:focus و a:hover كي لا يظهر خط أسفل الروابط التي يتحرك مؤشر الفأرة فوقها أو تكتسب تركيز الدخل مع تغيير لون الخلفية. استخدمنا أخيرًا المحدد a:active لمنح الرابط لونًا معاكسًا عند تفعيله كي يظهر أن أمرًا مهمًا قد حدث. تطبيق عملي: نسق روابطك الخاصة نطلب إليك في هذا التطبيق العملي أن تستفيد من مجموعة القواعد الفارغة التي تعاملنا معها سابقًا كي تضيف التنسيقات الخاصة بك كي تعرض الروابط بطريقة جميلة. استخدم مخيلتك وابتكر تنسيقات جديدة، فقد تصل إلى شيء جديد جميل ومفيد بقدر ما فعلناه. إذا ارتكبت خطأً يمكنك النقر على الزر "Reset"، وإذا لم تتمكن من فعل شيء، ألق نظرةً على الحل بالنقر على زر "Show solution". إضافة أيقونات إلى الروابط من الممارسات الشائعة عند إنشاء الروابط تزويدها بأيقونة تشير إلى طبيعة المحتوى الذي يقود إليه كل رابط، لهذا سنلقي نظرةً على مثال بسيط يضيف أيقونة إلى رابط خارجي (يقود إلى موقع آخر). يبدو شكل هذه الأيقونة عادةً مثل سهم يشير إلى خارج الصندوق، وسنستخدم أيقونة من موقع icon8.com. إليك شيفرة HTML و CSS التي ستساعدنا على عرض الفكرة: شيفرة HTML: <p> For more information on the weather, visit our <a href="#">weather page</a>, look at <a href="https://en.wikipedia.org/">weather on Wikipedia</a>, or check out <a href="https://www.nationalgeographic.org/topics/resource-library-weather/"> weather on National Geographic</a>. </p> شيفرة CSS: body { width: 300px; margin: 0 auto; font-family: sans-serif; } p { line-height: 1.4; } a { outline: none; text-decoration: none; padding: 2px 1px 0; } a:link { color: blue; } a:visited { color: purple; } a:focus, a:hover { border-bottom: 1px solid; } a:active { color: red; } a[href^="http"] { background: url("external-link-52.png") no-repeat 100% 0; background-size: 16px 16px; padding-right: 19px; } النتيجة: ما الذي حدث إذًا؟ سنتجاوز توضيح أغلب شيفرة التنسيقات لأننا تحدثنا عنها قبل قليل وننتقل إلى القاعدة الأخيرة المهمة. لقد وضعنا صورة خلفية مخصصة للرابط وهي أيقونة الموقع الخارجي، وبنفس الطريقة التي اتبعناها في إضافة نقاط مخصصة إلى القوائم في المقال السابق. لكننا استخدمنا هنا الخاصية المختصرة background بدلًا من ضبط كل خاصية على حدى، كما ضبطنا مسار الصورة واستخدمنا القيمة no-repeat كي لا تظهر سوى نسخة واحدة من الصورة. ضبطنا كذلك موقع الصورة كي يكون 100% إلى يمين النص دون أن يكون لها هامش علوي 0px. استخدمنا أيضًا الخاصية background-size لتحديد أبعاد الصورة. ومن الأفضل عمليًا أن نختار صورة أكبر ثم نعيد ضبط أبعادها كما يتطلبه صفحات ويب المتجاوبة. لن تعمل هذه الطريقة طبعًا على المتصفحات الأقدم من IE9، لهذا عليك أن تغير أبعاد الصورة بنفسك ثم تستخدمها، إذا أردت دعم تلك المتصفحات. ضبطنا أخيرًا قيمة الحاشية من جهة اليمين باستخدام الخاصية padding-right كي نعطي صورة الخلفية مساحة كافية لتظهر دون أن تتداخل مع النص. كلمة أخيرة: كيف لنا أن تختار فقط الروابط الخارجية لتنسيقها بهذا الشكل مع وجود روابط داخلية؟ إذا كتبت عناوين روابط HTML بالشكل الصحيح، عليك في هذه الحالة استخدام عناوين URL المطلقة للروابط الخارجية، وسيكون من المفيد استخدام العناوين النسبية للروابط الداخلية (كما في الرابط الأول في المثال السابق). لهذا السبب ستظهر العبارة "http" في العناوين الخارجية فقط (كما في العنوانين الثاني والثالث في المثال السابق)، وعندها يمكننا استخدام محدد الخواص التالي ["a[href^="http الذي يحدّد عن عناصر <a> ولكن فقط التي تمتلك الخاصية href مع قيمة تبدأ بالكلمة http، وهذا كل مافي الأمر. حاول أن تعود إلى التطبيق العملي السابق وأن تطبق هذه التقنية. ملاحظة: قد تبدو قيمة الخاصية href في مثالنا غريبة فقد استخدمنا روابط وهمية لا تنقلك إلى أي مكان، والسبب في ذلك أنك ستتمكن من تحميل هذه الروابط ضمن المحرر التفاعلي لو كانت حقيقية وبالتالي ستفقد الشيفرة الموجودة. ملاحظة: لا تقلق إن لم تكن لديك معرفة بالخلفيات أو بتصميم الويب المتجاوب فقد شرحناها في مقالات أخرى يمكنك الاطلاع عليها. تنسيق الروابط على هيئة أزرار يمكنك استخدام الأساليب التي خبرتها حتى الآن في مقالنا بطرق أخرى، حيث يمكنك استخدام حالة تمرير مؤشر الفأرة مع عناصر أخرى مثلًا وليس بالضرورة مع الروابط، وقد ترغب أن تتفاعل الفقرات وعناصر القوائم وغيرها مع مرور مؤشر الفأرة فوقها. ومن الشائع أيضًا تنسيق الروابط لتبدو مثل الأزرار في بعض الحالات، كما في قائمة التنقل ضمن الموقع، إذ تُوصّف عناصر القائمة على هيئة روابط وتُنسّق بعدها لتبدو مثل مجموعة من أزرار التحكم التي تنقل المستخدم من صفحة إلى أخرى في الموقع نفسه. لنرى كيف نفعل ذلك. إليك شيفرة HTML: <nav class="container"> <a href="#">Home</a> <a href="#">Pizza</a> <a href="#">Music</a> <a href="#">Wombats</a> <a href="#">Finland</a> </nav> وشيفرة CSS: body, html { margin: 0; font-family: sans-serif; } .container { display: flex; gap: 0.625%; } a { flex: 1; text-decoration: none; outline: none; text-align: center; line-height: 3; color: black; } a:link, a:visited, a:focus { background: palegoldenrod; color: black; } a:hover { background: orange; } a:active { background: darkred; color: white; } ستكون النتيجة على النحو التالي: نُعرّف في شيفرة HTML عنصر قائمة التنقل <nav> التي تمتلك صنف التنسيق "container". تضم هذه القائمة الروابط المطلوبة، بينما تُنسق شيفرة CSS القائمة والروابط معًا. تنص القاعدة الثانية على ما يلي: الحاوية هي Flexbox: أي أن العناصر ضمنها مرنة، وتتوضع على نحوٍ متجاور حتى تملأ المساحة المخصصة لها. ستكون المسافة بين العناصر 0.625% من اتساع القائمة. تُنسِّق القاعدة الثالثة الروابط: يعني التصريح flex: 1 أن اتساع العناصر سيكون مضبوطًا كي تشغل جميعها المساحة المخصصة للحاوية. ألغينا الخط أسفل الرابط text-decoration والإطار الذي يحيط به outline عند انتقال التركيز إليه، كي لا يفسدا مظهر الأزرار. تُستخدم التصريحات الثلاثة الأخيرة لتوسيط النص ضمن الرابط، وضبط ارتفاع العنصر على القيمة 3 عبر الخاصية line-height لمنح الرابط بعض الارتفاع والمساعدة على توسيط النص عموديًا، وأخيرًا ضبط لون النص ليكون أسودًا. الخلاصة نأمل أن تكون المعلومات التي قدمناها في مقالنا قد لامست فعلًا ما تحتاجه في تنسيق الروابط، وسننتقل في المقال التالي إلى تفصيل طريقة استخدام خطوط الكتابة المخصصة (أو خطوط الويب كما هو شائع) ضمن موقعك. ترجمة -وبتصرف- للمقال- Styling Links اقرأ المزيد المقال السابق تنسيق القوائم في CSS. HTML و CSS للمبتدئين: مقدمة إلى تنسيقات CSS. أساسيات لغة CSS. تأثيرات حركية على عناصر قوائم HTML باستخدام CSS
  13. يوضح هذا المقال كيفية تعريف صفحة لإنشاء كائنات النوع 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. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي، ويجب حفظ القيمة بعد إدخالها وستُنقَل إلى صفحة تفاصيل نوع الكتاب: الخطأ الوحيد الذي نتحقق منه من طرف الخادم هو أن حقل نوع الكتاب يجب ألّا يكون فارغًا. توضح لقطة الشاشة التالية كيف ستبدو قائمة الأخطاء إن لم تقدّم نوع كتاب (المحدَّد باللون الأحمر): ملاحظة: تستخدم عملية التحقق من صحة البيانات التابع 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. اقرأ المزيد المقال السابق إنشاء موقع مكتبة محلية باستخدام Express. الاستمارات (forms) في متصفح الويب وكيفية التعامل معها في جافاسكربت. تطبيق عملي لتعلم جانغو - الجزء الثامن: العمل مع الاستمارات Forms. إرسال البيانات واستلامها عبر الشبكة في جافاسكريبت
  14. لا تختلف القوائم lists في سلوكها عن النصوص في المجمل، لكنك ستجد بعض خاصيات CSS مخصصة للقوائم لا بد من الإطلاع عليها، والاطلاع على أفضل الممارسات في تنسيقها وهذا ما سيعرضه هذا المقال. ننصحك قبل إكمال قراءة المقال أن تطلع على أساسيات لغة HTML ومقال التنسيقات الأساسية للنصوص وخطوط الكتابة في CSS. مثال عن قائمة بسيطة حتى نبدأ العمل سنعرض مثالًا عن قائمة بسيطة، وخلال تقدم المقال سنلقي نظرةً على القوائم المرتبة وغير المرتبة وقوائم الوصف التي تتمتع بميزات تنسيقية متشابهة إضافةً إلى ميزات مخصصة لكل منها. يمكنك الوصول إلى المثال بشكله الأصلي غير المنسّق عبر غيت هب GitHub والحصول على الشيفرة المصدرية. <h2>Shopping (unordered) list</h2> <p> Paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference. </p> <ul> <li>Hummus</li> <li>Pita</li> <li>Green salad</li> <li>Halloumi</li> </ul> <h2>Recipe (ordered) list</h2> <p> Paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference. </p> <ol> <li>Toast pita, leave to cool, then slice down the edge.</li> <li> Fry the halloumi in a shallow, non-stick pan, until browned on both sides. </li> <li>Wash and chop the salad.</li> <li>Fill pita with salad, hummus, and fried halloumi.</li> </ol> <h2>Ingredient description list</h2> <p> Paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference, paragraph for reference. </p> <dl> <dt>Hummus</dt> <dd> A thick dip/sauce generally made from chick peas blended with tahini, lemon juice, salt, garlic, and other ingredients. </dd> <dt>Pita</dt> <dd>A soft, slightly leavened flatbread.</dd> <dt>Halloumi</dt> <dd> A semi-hard, unripened, brined cheese with a higher-than-usual melting point, usually made from goat/sheep milk. </dd> <dt>Green salad</dt> <dd>That green healthy stuff that many of us just use to garnish kebabs.</dd> </dl> إذا عرضت الشيفرة ضمن متصفح، ثم تصفّحت قائمة العناصر باستخدام أدوات مطوري ويب ستلاحظ بعض التنسيقات الافتراضية المطبّقة: للقائمة غير المرتبة <ul> والمرتبة <ol> هوامش علوية وسفلية بحجم 16px (أو 1em)، وحاشية يسارية padding-left بحجم 40px (أو 2.5em). في حال ضُبطت الخاصية dir من اليمين إلى اليسار right-to-left أو rtl للعناصر ul و ol، سيظهر تأثير الحاشية اليمنية padding-right والتي لها القيمة الافتراضية 40px (أو 2.5em). لا تملك عناصر القائمة <li> مجموعة تنسيقات افتراضية. للعنصر <dl> هوامش علوية وسفلية بحجم 16px (أو 1em) لكن دون حواشي. للعنصر <dd> هامش يساري margin-left حجمه 40px (أو 2.5em). للعنصر <p> الذي أضفناه على سبيل الموازنة هوامش علوية وسفلية بحجم 16px كما هو حال الأنواع المختلفة للقوائم. التعامل مع التباعد في القوائم لا بد من ضبط تنسيق القوائم بطريقة تحافظ على نفس التباعد مع العناصر المحيطة بها مثل الفقرات والصور، وهو ما يُعرف بالتناسق العمودي vertical rhythm، وتحافظ على نفس التباعد الأفقي مع بعضها. يمكنك الاطلاع على المثال الجاهز على غيت هب وتنزيل الشيفرة المصدرية. إليك شيفرة CSS المستخدمة في تنسيق النص وتنسيق التباعد: /* General styles */ html { font-family: Helvetica, Arial, sans-serif; font-size: 10px; } h2 { font-size: 2rem; } ul, ol, dl, p { font-size: 1.5rem; } li, p { line-height: 1.5; } /* Description list styles */ dd, dt { line-height: 1.5; } dt { font-weight: bold; } تعرِّف القاعدة الأولى خط كتابة لكامل الموقع حجمه الأساسي 10px، وترث هذه القاعدة جميع عناصر الصفحة. تعرّف القاعدتين الثانية والثالثة حجم خط نسبي (مقاس بواحدة em) مخصص للعناوين <h> ولأنواع القوائم المختلفة (يرثها أبناء عناصر القائمة) والفقرات. أي سيكون لكل قائمة وفقرة حجم الخط نفسه ونفس التباعد من الأعلى والأسفل مما يحافظ على التناسق العمودي. تخصص القاعدة الرابعة نفس ارتفاع السطر line-height في الفقرات وعناصر القوائم أي سيكون التباعد نفسه بين أسطر الفقرات وعناصر القوائم، ويساعد ذلك في الحفاظ على التناسق العمودي. تُطبَّق القاعدتان الخامسة والسادسة على قوائم الوصف وتعطي نفس ارتفاع السطر بين عناصر القائمة والوصف كما هو الحال بين عناصر القوائم السابقة والفقرات. كما جعلنا الخط سميكًا في الوصف ليتميز عن عناصر القائمة. خاصيات التنسيق المخصصة للقوائم بعد أن ألقينا نظرة على التقنيات المتبعة في تحديد التباعد في القوائم، ننتقل إلى استكشاف بعض الخاصيات التي خصصت لتنسيق القوائم. هناك ثلاث خاصيات لا بد من الاطلاع عليها بدايةً ويمكن أن تُطبّق على القوائم المرتبة <ol> أو غير المرتبة <ul>: list-style-type: تضبط نوع النقاط المستخدمة في القائمة كأن تكون دائرية أو مربعة مثلًا في القوائم غير المرتبة، أو أن تكون أرقامًا أو أحرفًا في القوائم المرتبة. list-style-position: تضبط موضع النقاط في عناصر القائمة إن كانت ستظهر داخل القائمة أو خارجها. list-style-image: تسمح باستخدام صورة مخصصة في قوائم التعداد النقطي بدلًا من المربع أو الدائرة النمطية. تنسيق قوائم التعداد النقطي تُستخدم الخاصية list-style-type كما أشرنا سابقًا لضبط نمط النقاط المستخدمة في القوائم النقطية. وفي مثالنا، سنضبط القائمة المرتبة لتعرض ترقيمًا رومانيًا بأحرف كبيرة: ol { list-style-type: upper-roman; } ستكون النتيجة على النحو التالي: بإمكانك إيجاد خيارات أكثر من خلال الاطلاع على صفحة الخاصية list-style-type على موسوعة حسوب. موضع النقاط تحدد الخاصية list-style-position إن كانت النقاط ستظهر داخل عناصر القائمة أو خارجها قبل بداية كل عنصر. تأخذ الخاصية القيمة الافتراضية outside التي تجعل النقاط خارج عناصر القائمة كما في الصورة السابقة، لكن إن ضبطنا قيمة الخاصية على inside على النحو التالي: ol { list-style-type: upper-roman; list-style-position: inside; } ستكون النتيجة مشابهة للشكل التالي: استخدام صور مخصصة للنقاط تسمح الخاصية list-style-image باستخدام صور مخصصة لنقاط القائمة، وبطريقة بسيطة جدًا: ul { list-style-image: url(star.svg); } لكن هناك محدودية في استخدام هذه الخاصية من ناحية التحكم بموقع النقاط وحجمها وغيرها، لهذا ننصحك باستخدام مجموعة الخاصيات المتعلقة بالخلفية background والتي تعرفنا عليها في مقال سابق، وإليك كيفية استخدامها: ul { padding-left: 2rem; list-style-type: none; } ul li { padding-left: 2rem; background-image: url(star.svg); background-position: 0 0; background-size: 1.6rem 1.6rem; background-repeat: no-repeat; } ما فعلناه في الشيفرة السابقة هو كالتالي: ضبطنا قيمة الحاشية اليسارية padding-left للعنصر <ul> على 20px بدلًا من القيمة الافتراضية 40px، وطبقنا نفس الحاشية على عناصر القائمة غير المرتبة كي تبقى بمحاذاة عناصر القائمة المرتبة وقائمة الوصف على الرغم من وجود حاشية لصور الخلفية ضمن كل عنصر، ولو لم نفعل ذلك، ستتداخل صور الخلفية مع نص العنصر ويبدو الأمر فوضويًا. ضبطنا الخاصية list-style-type على القيمة none كي لا تظهر النقاط بشكلها الافتراضي لأننا سنستخدم خاصيات الخلفية background للتعامل معها. وضعنا نقطة مخصصة قبل كل عنصر في القائمة غير المرتبة، وإليك الخاصيات التي استخدمناها لتخصيص هذه النقطة: background-image: تعطي مرجعًا إلى مسار ملف الصورة التي نستخدمها بصفتها نقطة. background-position: تضبط مكان ظهور الخلفية ضمن العنصر وقد جعلناها في حالتنا 0 0 مما يعني أن النقطة ستظهر أعلى يسار العنصر. background-size: تضبط حجم صورة الخلفية، ومن الناحية العملية لا بد أن يكون حجم صورة الخلفية بحجم عنصر القائمة (أو أكبر أو أصغر بشيء بسيط)؛ وطالما أننا نستخدم الحجم 1.6rem (أو 16px) والذي يتناسب تمامًا مع حجم حاشية مقدارها 20px يمكن أن نضع النقاط ضمن عنصر القائمة دون مشاكل، سيجعل مجموع 16 بكسل مع 4 بكسل من الفراغ الذي يحيط بالنقطة الأمر مناسبًا. background-repeat: تتكرر الصورة افتراضيًا حتى يمتلئ الفراغ المتاح لها، وطالما أننا نحتاج إلى نسخة واحدة من الصورة التي تمثل النقطة، ضبطنا قيمة الخاصية على no-repeat. ستكون نتائج عملنا كما في الصورة التالية: الخاصة المختصرة list-style يمكنك ضبط الخاصيات الثلاث السابقة باستخدام الخاصية المختصرة list-style، إذ تستطيع مثلًا أن تستبدل الشيفرة التالية: ul { list-style-type: square; list-style-image: url(example.png); list-style-position: inside; } بالشيفرة المختصرة التالية: ul { list-style: square url(example.png) inside; } يمكن أن ترتب القيم الثلاث بأية طريقة، كما يمكنك استخدام إحداها أو اثنتين منها أو ثلاثتها. توضع القيم الافتراضية التالية: disc و none و outside في حال لم تُحدد قيمتها صراحة. إذا استُخدمت الخاصية type مع الخاصية image، سيُستخدم النوع type إن لم ينجح تحميل الصورة المحددة. التحكم بطريقة ترقيم القوائم قد تحتاج أحيانًا إلى تطبيق طريقة تعداد مختلفة للقوائم المرتبة المختلفة، كأن تبدأ الترقيم بالرقم 3 مثلًا أو أن تعد مثنى مثنى، لهذا تتيح لك HTML و CSS بعض الطرق لتنفيذ ذلك. الخاصية start تتيح لك الخاصية start الخاصة بعنصر القائمة المرتبة <ol> أن تحدد الرقم الذي تبدأ منه التعداد. إليك مثالًا: <ol start="4"> <li>Toast pita, leave to cool, then slice down the edge.</li> <li> Fry the halloumi in a shallow, non-stick pan, until browned on both sides. </li> <li>Wash and chop the salad.</li> <li>Fill pita with salad, hummus, and fried halloumi.</li> </ol> ستكون النتيجة على النحو التالي: الخاصية reversed تجعل الخاصية reversed التعداد عكسيًا ابتداءً من قيمة محددة. إليك مثالًا: <ol start="4" reversed> <li>Toast pita, leave to cool, then slice down the edge.</li> <li> Fry the halloumi in a shallow, non-stick pan, until browned on both sides. </li> <li>Wash and chop the salad.</li> <li>Fill pita with salad, hummus, and fried halloumi.</li> </ol> ستكون النتيجة على النحو التالي: ملاحظة: إن كان عدد عناصر القائمة أكبر من القيمة التي تحددها الخاصية start عند استخدام التعداد العكسي، فسيستمر التعداد نحو الصفر ومنه إلى القيم السالبة. الخاصية value تسمح لك الخاصيةvalue بتخصيص رقم لكل عنصر من عناصر القائمة. إليك مثالًا: <ol> <li value="2">Toast pita, leave to cool, then slice down the edge.</li> <li value="4"> Fry the halloumi in a shallow, non-stick pan, until browned on both sides. </li> <li value="6">Wash and chop the salad.</li> <li value="8">Fill pita with salad, hummus, and fried halloumi.</li> </ol> ستكون النتيجة على النحو التالي: ملاحظة: حتى لو استخدمت تعدادًا ليس رقميًا للخاصية list-style-type، ستحتاج إلى استخدام القيم الرقمية المكافئة ضمن الخاصية value. تطبيق عملي نطلب منك في هذا التطبيق العملي الاستفادة من الأفكار التي طرحناها في المقال لتنسيق قائمة متداخلة. ستجد ضمن المحرر التفاعلي التالي شيفرة HTML الخاصة بالقائمة ونريد منك أن: تعطي القائمة غير المرتبة تعدادًا نقطيًا. تعطي عناصر القائمة غير المرتبة والمرتبة ارتفاع سطر يعادل 1.5 من حجم خطها. تعطي عناصر القائمة المرتبة نقاط تعداد على شكل أحرف لاتينية صغيرة. تحاول تعديل تنسيق القوائم كما يحلو لك. إن ارتكبت خطأً انقر على زر "reset" ليعود كل شيء كما كان، وإن أردت إظهار الحل الصحيح انقر على الزر "show solution". الخلاصة من السهل تنسيق القوائم إن اعتدت التعامل مع أساسياتها وخاصياتها المميزة، وسننتقل في المقال التالي إلى شرح طريقة تنسيق الروابط التشعبية. ترجمة -وبتصرف- للمقال Styling lists. اقرأ المزيد المقال السابق التنسيقات الأساسية للنصوص وخطوط الكتابة في CSS تنسيق القوائم (Lists) في CSS تعرف على أساسيات لغة CSS هيكلية لغة CSS
  15. سنتعرف في هذا المقال على كيفية إنشاء الصفحة الرئيسية لموقع المكتبة المحلية LocalLibrary وصفحات قوائم الكتب والمؤلفين ونسخ الكتب وأنواعها، وسنوضح كيفية تنسيق التاريخ باستخدام المكتبة Luxon، كما سنتعرف على كيفية إنشاء صفحات تفاصيل الكتب وأنواعها ونسخها وتفاصيل المؤلفين. صفحة الموقع الرئيسية ستكون الصفحة الأولى التي سننشئها هي الصفحة الرئيسية لموقع الويب، والتي يمكن الوصول إليها إما من جذر الموقع / أو من الدليل catalog‎/، مما سيؤدي إلى عرض بعض النصوص الثابتة التي تصف الموقع، مع الأعداد المحسوبة ديناميكيًا لأنواع السجلات المختلفة في قاعدة البيانات. أنشأنا مسبقًا وجهةً Route للصفحة الرئيسية، ولإكمال هذه الصفحة، يجب تحديث دالة المتحكم لجلب أعداد السجلات من قاعدة البيانات وإنشاء عرض (قالب) يمكننا استخدامه لعرض الصفحة. ملاحظة: سنستخدم مكتبة Mongoose للحصول على معلومات قاعدة البيانات، لذا لا بد من إعادة قراءة قسم البحث عن السجلات من مقال استخدام قاعدة البيانات باستخدام مكتبة Mongoose. الوجهة Route أنشأنا وِجهات صفحة الفهرس في مقال الوجهات والمتحكمات، إذ عرّفنا جميع دوال الوجهات في الملف "‎/routes/catalog.js": // الحصول على صفحة الدليل الرئيسية router.get("/", book_controller.index); //يُر‫بَط مع /catalog/ لأننا نستورد الوجهة مع البادئة ‎ /catalog تملك دالة متحكم الكتاب index المُمرَّرة بوصفها معاملًا (book_controller.index) تقديم بديل placeholder implementation مُعرَّف في الملف ‎/controllers/bookController.js: exports.index = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Site Home Page"); }); تُعَد هذه الدالة هي دالة المتحكم التي نوسّعها للحصول على معلومات من نماذجنا ثم عرضها باستخدام عرض (قالب). المتحكم Controller تحتاج دالة المتحكم index إلى جلب معلومات حول عدد سجلات الكتب Book ونسخ الكتب BookInstance (جميعها) ونسخ الكتب BookInstance (المتاحة) والمؤلفين Author وأنواع الكتب Genre الموجودة في قاعدة البيانات، وعرض هذه البيانات في قالب لإنشاء صفحة HTML، ثم إعادتها في استجابة HTTP. افتح الملف ‎/controllers/bookController.js، إذ سترى الدالة index()‎ المستورَدة بالقرب من أعلى الملف. const Book = require("../models/book"); const asyncHandler = require("express-async-handler"); exports.index = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Site Home Page"); }); ضع جزء الشيفرة التالي مكان الشيفرة السابقة، فأول شيء تفعله هذه الشيفرة هو استيراد (أو طلب require()‎) جميع النماذج Models، إذ نحتاج إلى ذلك لأننا سنستخدمها للحصول على عدد المستندات. تطلب الشيفرة أيضًا "express-async-handler" الذي يوفّر مغلِّفًا Wrapper لالتقاط الاستثناءات التي تُرمَى في دوال معالج الوِجهة. const Book = require("../models/book"); const Author = require("../models/author"); const Genre = require("../models/genre"); const BookInstance = require("../models/bookinstance"); const asyncHandler = require("express-async-handler"); exports.index = asyncHandler(async (req, res, next) => { // الحصول على تفاصيل عدد الكتب ونسخها والمؤلفين وأنواع الكتب (على ا‫لتوازي) const [ numBooks, numBookInstances, numAvailableBookInstances, numAuthors, numGenres, ] = await Promise.all([ Book.countDocuments({}).exec(), BookInstance.countDocuments({}).exec(), BookInstance.countDocuments({ status: "Available" }).exec(), Author.countDocuments({}).exec(), Genre.countDocuments({}).exec(), ]); res.render("index", { title: "Local Library Home", book_count: numBooks, book_instance_count: numBookInstances, book_instance_available_count: numAvailableBookInstances, author_count: numAuthors, genre_count: numGenres, }); }); نستخدم التابع countDocuments()‎ للحصول على عدد نسخ كل نموذج، ويُستدعَى هذا التابع في نموذج مع مجموعة اختيارية من الشروط لمطابقتها وإعادة كائن استعلام Query. يمكن تنفيذ الاستعلام من خلال استدعاء التابع exec()‎، والذي يعيد وعدًا Promise إما يجري الوفاء به مع نتيجة أو يُرفض إذا كان هناك خطأ في قاعدة البيانات. تُعَد الاستعلامات الخاصة بأعداد المستندات مستقلة عن بعضها بعضًا، لذا نستخدم التابع Promise.all()‎ لتنفيذها على التوازي، ويعيد هذا التابع وعدًا جديدًا ننتظره باستخدام await حتى ينتهي، إذ يتوقف التنفيذ مؤقتًا في هذه الدالة عند await. يجري الوفاء بالوعد الذي يعيده all()‎ عند اكتمال جميع الاستعلامات، ويستمر تنفيذ دالة معالج الوجهة، ويملأ هذا الوعد المصفوفةَ بنتائج استعلامات قاعدة البيانات. نستدعي بعد ذلك التابع res.render()‎، ونحدّد عرضًا (قالبًا) بالاسم "index" وكائنات تربط نتائج استعلامات قاعدة البيانات مع قالب العرض، إذ تتوفر البيانات على شكل أزواج مفتاح-قيمة key-value، ويمكن الوصول إليها في القالب باستخدام المفتاح. لاحظ أن الشيفرة البرمجية بسيطة جدًا لأنه يمكننا افتراض نجاح استعلامات قاعدة البيانات، وإذا فشلت أيٌّ من عمليات قاعدة البيانات، فسيكتشف asyncHandler()‎ الاستثناء المُرمَى ويمرّره إلى معالج البرمجية الوسيطة التالية next في السلسلة. العرض View افتح العرض "‎/views/index.pug" وضع فيه المحتوى التالي بدلًا من محتواه الموجود مسبقًا: extends layout block content h1= title p Welcome to #[em LocalLibrary], a very basic Express website developed as a tutorial example on the Mozilla Developer Network. h1 Dynamic content p The library has the following record counts: ul li #[strong Books:] !{book_count} li #[strong Copies:] !{book_instance_count} li #[strong Copies available:] !{book_instance_available_count} li #[strong Authors:] !{author_count} li #[strong Genres:] !{genre_count} يُعَد العرض واضحًا، إذ نوسّع القالب الأساسي layout.pug من خلال تعديل الكتلة block التي اسمها 'content'. سيكون العنوان h1 الأول هو النص المُهرَّب للمتغير title المُمرَّر إلى التابع render()‎، ولاحظ استخدام 'h1=‎' بحيث يجري التعامل مع النص الذي يليه بوصفه تعبير جافا سكريبت، ثم نضمّن فقرةً تعرّف بالمكتبة المحلية LocalLibrary. نسرد عدد النسخ من كل نموذج تحت عنوان المحتوى الديناميكي Dynamic content. لاحظ أن قيم القالب للبيانات هي المفاتيح المُحدَّدة عند استدعاء التابع render()‎ في دالة معالج الوِجهة. ملاحظة: لم نهرّب قيم العد، إذ استخدمنا صيغة {}!، لأن قيم العد محسوبة، ولكن إذا وفّر المستخدمون النهائيون المعلومات، فسنهرّب المتغير لعرضه. كيف تبدو الصفحة الرئيسية؟ أنشأنا حتى الآن كل ما هو مطلوب لعرض صفحة الفهرس، لذا شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي: ملاحظة: لن تتمكّن من استخدام روابط الشريط الجانبي حاليًا لأن عناوين URL والعروض والقوالب الخاصة بهذه الصفحات غير مُعرَّفة بعد، وإذا حاولت الوصول إليها، فستحصل على أخطاء مثل "NOT IMPLEMENTED: Book list" اعتمادًا على الرابط الذي تنقر عليه، إذ تُحدَّد هذه السلسلة النصية الحرفية -التي سنضع مكانها البيانات المناسبة- في المتحكمات المختلفة التي تكون موجودة ضمن الملف "controllers". صفحة قائمة الكتب سنقدّم الآن صفحة قائمة الكتب، إذ تعرض هذه الصفحة قائمةً بجميع الكتب الموجودة في قاعدة البيانات مع مؤلفها والعنوان الخاص بكل كتاب، والذي يكون رابطًا تشعبيًا hyperlink لصفحة تفاصيل الكتاب المرتبطة به. المتحكم يجب أن تحصل دالة متحكم قائمة الكتب على قائمة بجميع كائنات الكتاب Book في قاعدة البيانات، ثم تفرزها وتمرّرها إلى القالب لعرضها. افتح الملف "‎/controllers/bookController.js"، وابحث عن تابع المتحكم book_list()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية: // عرض قائمة جميع الكتب exports.book_list = asyncHandler(async (req, res, next) => { const allBooks = await Book.find({}, "title author") .sort({ title: 1 }) .populate("author") .exec(); res.render("book_list", { title: "Book List", book_list: allBooks }); }); يستدعي معالج الوجهة Route Handler الدالة find()‎ للنموذج Book، ويختار إعادة العنوان title والمؤلف author فقط لأننا لسنا بحاجة إلى الحقول الأخرى (سيعيد أيضًا الحقل ‎_id والحقول الافتراضية)، ويفرز النتائج حسب العنوان أبجديًا باستخدام التابع sort()‎. نستدعي أيضًا التابع populate()‎ للنموذج Book مع تحديد الحقل author، مما سيؤدي إلى استبدال معرّف مؤلف الكتاب المُخزَّن بتفاصيل المؤلف الكاملة، ثم يُستدعَى التابع exec()‎ في نهاية السلسلة التعاقبية daisy-chained لتنفيذ الاستعلام وإعادة الوعد. يستخدم معالج الوِجهة await لانتظار الوعد، مما يؤدي إلى إيقاف التنفيذ حتى استقراره. إذا جرى الوفاء بالوعد، فستُحفَظ نتائج الاستعلام في المتغير allBooks ويستمر المعالج في التنفيذ. يستدعي الجزء الأخير من معالج الوِجهة التابع render()‎، ويحدد القالب book_list (.pug)‎ ويمرر قيم title و book_list إلى القالب. العرض View أنشئ العرض "‎/views/book_list.pug" وضع فيه المحتوى التالي: extends layout block content h1= title ul each book in book_list li a(href=book.url) #{book.title} | (#{book.author.name}) else li There are no books. يوسّع العرض القالبَ الأساسي layout.pug ويعدّل الكتلة block التي اسمها 'content'، ويعرض العنوان title الذي مرّرناه من المتحكم (باستخدام التابع render()‎) ويتكرر عبر المتغير book_list باستخدام صيغة each-in-else. يُنشَأ عنصر قائمة لكل كتاب، إذ تعرض هذه القائمة عنوان الكتاب كرابط إلى صفحة تفاصيل الكتاب متبوعًا باسم المؤلف، وإذا لم يكن هناك كتب في book_list، فستُنفَّذ تعليمة else، ويعرض نص عدم وجود كتب "There are no books". ملاحظة: نستخدم book.url لتوفير رابط إلى سجل تفاصيل كل كتاب (قدّمنا هذه الوجهة، ولكن لم نقدّم الصفحة الخاصة بها بعد)، وهي خاصية افتراضية لنموذج الكتاب Book الذي يستخدم الحقل ‎_id الخاص بنسخة النموذج لإنشاء مسار عنوان URL فريد. من المهم تعريف كل كتاب على سطرين، باستخدام الشريط العمودي | في السطر الثاني، ويُعَد هذا الأسلوب ضروريًا لأنه إذا كان اسم المؤلف موجودًا في السطر السابق، فسيكون جزءًا من الرابط التشعبي. كيف تبدو صفحة قائمة الكتب؟ شغّل التطبيق (راجع قسم اختبار الوجهات من مقال الوجهات والمتحكمات للاطلاع على الأوامر ذات الصلة) وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط "جميع الكتب All books". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي: صفحة قائمة نسخ الكتب BookInstance سنقدّم الآن قائمة بجميع نسخ الكتب BookInstance في المكتبة، إذ يجب أن تتضمن الصفحة عنوان الكتاب Book المرتبط بكل نسخة كتاب BookInstance (مرتبطة برابط إلى صفحة تفاصيلها) مع المعلومات الأخرى في نموذج BookInstance، بما في ذلك الحالة والناشر والمعرِّف الفريد لكل نسخة، ويجب ربط نص المعرّف الفريد برابط إلى صفحة تفاصيل نسخة الكتاب BookInstance. المتحكم يجب أن يحصل متحكم قائمة BookInstance على قائمة بجميع نسخ الكتاب، ويجب ملء معلومات الكتاب المرتبطة بها، ثم تمرير القائمة إلى القالب لعرضها. افتح الملف "‎/controllers/bookinstanceController.js"، وابحث عن تابع المتحكم bookinstance_list()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية: // عرض قائمة جميع نسخ الكتب exports.bookinstance_list = asyncHandler(async (req, res, next) => { const allBookInstances = await BookInstance.find().populate("book").exec(); res.render("bookinstance_list", { title: "Book Instance List", bookinstance_list: allBookInstances, }); }); يستدعي معالج الوجهة الدالةَ find()‎ للنموذج BookInstance، ثم يتبعه في سلسلة تعاقبية استدعاءٌ للتابع populate()‎ مع حقل الكتاب book، والذي سيضع مستند كتاب Book كامل مكان معرّف الكتاب المُخزَّن لكل BookInstance، ثم يُستدعَى التابع exec()‎ في نهاية السلسلة التعاقبية لتنفيذ الاستعلام وإعادة الوعد. يستخدم معالج الوِجهة await لانتظار الوعد، مما يؤدي إلى إيقاف التنفيذ حتى استقراره. إذا جرى الوفاء بالوعد، فستُحفَظ نتائج الاستعلام في المتغير allBookInstances ويستمر المعالج في التنفيذ. يستدعي الجزء الأخير من الشيفرة البرمجية التابع render()‎، ويحدد القالب bookinstance_list (.pug)‎ ويمرر قيم title و bookinstance_list إلى القالب. العرض View أنشئ العرض "‎/views/bookinstance_list.pug" وضع فيه المحتوى التالي: extends layout block content h1= title ul each val in bookinstance_list li a(href=val.url) #{val.book.title} : #{val.imprint} - if val.status=='Available' span.text-success #{val.status} else if val.status=='Maintenance' span.text-danger #{val.status} else span.text-warning #{val.status} if val.status!='Available' span (Due: #{val.due_back} ) else li There are no book copies in this library. يماثل هذا العرض العروض الأخرى، إذ يوسّع التخطيط، ويبدّل كتلة المحتوى content، ويعرض العنوان title المُمرَّر من المتحكم، ويتكرر عبر جميع نسخ الكتاب في bookinstance_list. نعرض لكل نسخة حالتها، وإن لم يكن الكتاب متاحًا، فسيُعرَض تاريخ استرجاعه المتوقع. قدّمنا ميزة جديدة، إذ يمكننا استخدام الصيغة النقطية بعد الوسم لإسناد صنف، لذلك سيُصرَّف span.text-success إلى <span class="text-success"‎>، ويمكن كتابته أيضًا في Pug بالشكل التالي: span(class="text-success")‎ كيف تبدو صفحة قائمة نسخ الكتب؟ شغّل التطبيق، وافتح متصفحك على العنوان "http://localhost:3000/‎" ثم حدد رابط جميع نسخ الكتاب All book-instances. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي: تنسيق التاريخ باستخدام مكتبة Luxon لا يبدو العرض الافتراضي للتواريخ في نماذجنا جميلًا: Mon Apr 10 2020 15:49:58 GMT+1100 (AUS Eastern Daylight Time) لذا سنوضح في هذا القسم كيفية تحديث صفحة قائمة نسخ الكتاب BookInstance List من القسم السابق من أجل عرض حقل due_date بتنسيق أكثر ملاءمة مثل: Apr 10th, 2023. الطريقة التي سنستخدمها هي إنشاء خاصية افتراضية في نموذج BookInstance، والتي تعيد التاريخ المُنسَّق. سنطبّق التنسيق الفعلي باستخدام مكتبة Luxon، وهي مكتبة قوية وحديثة وسهلة الاستخدام لتحليل التواريخ والتحقق من صحتها ومعالجتها وتنسيقها وجعلها تواريخًا محلية. ملاحظة: يمكن استخدام مكتبة Luxon لتنسيق السلاسل النصية مباشرةً في قوالب Pug، أو يمكننا تنسيق السلسلة النصية في عدد من الأماكن الأخرى. يتيح استخدام خاصية افتراضية الحصول على التاريخ المُنسَّق باستخدام الطريقة نفسها للحصول على تاريخ الاسترجاع due_date حاليًا. تثبيت مكتبة Luxon أدخل الأمر التالي في جذر المشروع: npm install luxon إنشاء الخاصية الافتراضية افتح الملف "‎./models/bookinstance.js"، ثم استورد مكتبة Luxon في أعلى هذا الملف كما يلي: const { DateTime } = require("luxon"); ضِف الخاصية الافتراضية due_back_formatted بعد خاصية URL مباشرةً. BookInstanceSchema.virtual("due_back_formatted").get(function () { return DateTime.fromJSDate(this.due_back).toLocaleString(DateTime.DATE_MED); }); ملاحظة: يمكن لمكتبة Luxon استيراد السلاسل النصية بتنسيقات متعددة وتصديرها إلى كلٍّ من التنسيقات المُعرَّفة مسبقًا والتنسيقات الحرة free-form formats، إذ نستخدم في حالتنا التابع fromJSDate()‎ لاستيراد سلسلة تاريخ جافا سكريبت و toLocaleString()‎ لإخراج التاريخ بتنسيق DATE_MED باللغة الإنجليزية: Apr 10th, 2023. اطلع على توثيق مكتبة Luxon الخاص بالتنسيق للحصول على معلومات حول التنسيقات الأخرى وجعل سلسلة التاريخ عالمية. تحديث العرض افتح العرض "‎/views/bookinstance_list.pug" وضع due_back_formatted بدلًا من due_back. if val.status != 'Available' //span (Due: #{val.due_back} ) span (Due: #{val.due_back_formatted} ) إذا انتقلت إلى جميع نسخ الكتاب All book-instances في الشريط الجانبي، فيجب أن ترى أن جميع تواريخ الاسترجاع أجمل بكثير. صفحة قائمة المؤلفين يجب أن تعرض صفحة قائمة المؤلفين قائمة بجميع المؤلفين في قاعدة البيانات، مع ربط اسم كل مؤلف برابط إلى صفحة تفاصيل المؤلف المرتبطة به، ويجب إدراج تاريخ الميلاد وتاريخ الوفاة بعد الاسم الموجود على السطر نفسه. المتحكم يجب أن تحصل دالة متحكم قائمة المؤلفين على قائمة بجميع نسخ المؤلف Author، ثم تمرّرها إلى القالب لعرضها. افتح الملف "‎/controllers/authorController.js"، ثم ابحث عن تابع المتحكم المُصدَّر author_list()‎ بالقرب من أعلى الملف وضع مكانه الشيفرة البرمجية التالية: // عرض قائمة جميع المؤلفين exports.author_list = asyncHandler(async (req, res, next) => { const allAuthors = await Author.find().sort({ family_name: 1 }).exec(); res.render("author_list", { title: "Author List", author_list: allAuthors, }); }); تتبع دالة متحكم الوجهة النمط نفسه المُتبَع في صفحات القوائم الأخرى، إذ تعرّف استعلامًا لنموذج المؤلف Author باستخدام دالة find()‎ للحصول على جميع المؤلفين والتابع sort()‎ لفرزهم حسب family_name بترتيب أبجدي، ثم يُستدعَى التابع exec()‎ في نهاية السلسلة التعاقبية لتنفيذ الاستعلام وإعادة الوعد الذي يمكن أن تنتظره الدالة باستخدام await. يعرض معالج الوجهة قالب author_list(.pug)‎ بعد الوفاء بالوعد، ويمرر عنوان title الصفحة وقائمة المؤلفين (allAuthors) باستخدام مفاتيح القالب. العرض أنشئ العرض "‎/views/author_list.pug" وضع المحتوى التالي بدل المحتوى الموجود مسبقًا: extends layout block content h1= title ul each author in author_list li a(href=author.url) #{author.name} | (#{author.date_of_birth} - #{author.date_of_death}) else li There are no authors. شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع المؤلفين "All authors". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو الصفحة كما يلي: ملاحظة: لا يُعَد مظهر تواريخ العمر الافتراضي للمؤلف جميلًا، ويمكنك تحسينه باستخدام الأسلوب نفسه الذي استخدمناه في قائمة نسخ الكتب BookInstance من خلال إضافة الخاصية الافتراضية للعمر الخاص بنموذج المؤلف Author. يمكن ألا يكون المؤلف ميتًا أو يمكن أن يكون لديه بيانات ولادة أو وفاة مفقودة، لذا يجب تجاهل التواريخ المفقودة أو المراجع التي تشير إلى خاصيات غير موجودة. تتمثل إحدى طرق التعامل مع ذلك في إعادة إما تاريخ مُنسَّق أو سلسلة فارغة اعتمادًا على ما إذا كانت الخاصية مُعرَّفةً أم لا، مثل: return this.date_of_birth ? DateTime.fromJSDate(this.date_of_birth).toLocaleString(DateTime.DATE_MED) : ''; صفحة قائمة أنواع الكتب Genre - التحدي يجب عليك في هذا القسم تقديم صفحة قائمة أنواع الكتب، إذ يجب أن تعرض الصفحة قائمة بجميع أنواع الكتب الموجودة في قاعدة البيانات، مع ربط كل نوع برابط إلى صفحة التفاصيل المرتبطة به بحيث تحصل على الصفحة التالية: يجب أن تحصل دالة متحكم قائمة الأنواع على قائمة بجميع نسخ الأنواع Genre، ثم تمرّرها إلى القالب لعرضها باتباع الخطوات التالية: يجب تعديل التابع genre_list()‎ في الملف "‎/controllers/genreController.js". يماثل تقديم هذا التابع تقريبًا متحكم التابع author_list()‎، ثم يجب فرز النتائج حسب الاسم بترتيب تصاعدي. يجب تسمية القالب المراد عرضه بالاسم genre_list.pug. يجب أن يمرر القالب المراد عرضه متغيرات العنوان title (الذي هو 'Genre List') و genre_list (قائمة أنواع الكتب المُعادة من دالة رد النداء Genre.find()‎). يجب أن يتطابق العرض مع لقطة الشاشة أو المتطلبات السابقة، إذ يجب أن يكون له بنية أو تنسيق مشابه جدًا لعرض قائمة المؤلفين باستثناء أن أنواع الكتب لا تحتوي على تواريخ. صفحة تفاصيل أنواع الكتب Genre يجب أن تعرض صفحة تفاصيل أنواع الكتب المعلومات الخاصة بنسخة نوع كتاب معينة باستخدام قيمة الحقل ‎_id المُولَّد تلقائيًا بوصفه المعرِّف. يُرمَّز معرّف سجل نوع الكتاب المطلوب في نهاية عنوان URL ويُستخرَج تلقائيًا بناءً على تعريف الوجهة (‎/genre/:id)، ثم يجري الوصول إليه ضمن المتحكم باستخدام معاملات الطلب: req.params.id. يجب أن تعرض الصفحة اسم نوع الكتاب وقائمة بجميع الكتب في هذا النوع مع روابط إلى صفحة تفاصيل كل كتاب. المتحكم افتح الملف "‎/controllers/genreController.js" واطلب وحدة الكتاب Book في بداية الملف، إذ يجب أن يطلب الملف باستخدام الدالة require()‎ الوحدة Genre و "express-async-handler". const Book = require("../models/book"); ابحث عن تابع المتحكم genre_detail()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية: // عرض صفحة التفاصيل لنوع كتاب معين exports.genre_detail = asyncHandler(async (req, res, next) => { // الحصول على تفاصيل نوع الكتاب وجميع الكتب المرتبطة به (على‫ التوازي) const [genre, booksInGenre] = await Promise.all([ Genre.findById(req.params.id).exec(), Book.find({ genre: req.params.id }, "title summary").exec(), ]); if (genre === null) { // لا توجد نتائج const err = new Error("Genre not found"); err.status = 404; return next(err); } res.render("genre_detail", { title: "Genre Detail", genre: genre, genre_books: booksInGenre, }); }); نستخدم التابع Genre.findById()‎ أولًا للحصول على معلومات نوع كتاب لمعرّف معين، والتابع Book.find()‎ للحصول على جميع سجلات الكتب التي لها نفس معرّف نوع الكتاب المرتبط بها. لا يعتمد هذان الطلبان على بعضهما، لذا نستخدم التابع Promise.all()‎ لتشغيل استعلامات قاعدة البيانات على التوازي، إذ وضّحنا هذا الأسلوب لتشغيل الاستعلامات على التوازي عندما أنشأنا [الصفحة الرئيسية](رابط المقال السابق)). ننتظر باستخدام await الوعد المُعاد، ثم نتحقق من النتائج عندما يستقر؛ فإذا لم يكن نوع الكتاب موجودًا في قاعدة البيانات (أي يمكن أن يكون محذوفًا)، فسيعيد التابع findById()‎ بنجاح بدون نتائج، ونريد في هذه الحالة عرض صفحة عدم وجوده "not found"، لذلك ننشئ كائن خطأ Error ونمرّره إلى دالة البرمجية الوسيطة التالية next في السلسلة. ملاحظة: تنتقل الأخطاء -المُمرَّرة إلى دالة البرمجية الوسيطة التالية next- إلى شيفرة معالجة الأخطاء التي جرى إعدادها عندما أنشأنا التطبيق الهيكلي (اطلع على قسم معالجة الأخطاء لمزيد من المعلومات). إذا عُثِر على نوع الكتاب genre، فسنستدعي التابع render()‎ لتقديم العرض، إذ يكون قالب العرض هو genre_detail (.pug)‎. تُمرَّر قيم title و genre و booksInGenre إلى القالب باستخدام المفاتيح المقابلة (title و genre و genre_books). العرض أنشئ العرض "‎/views/genre_detail.pug" وضع فيه المحتوى التالي: extends layout block content h1 Genre: #{genre.name} div(style='margin-left:20px;margin-top:20px') h4 Books dl each book in genre_books dt a(href=book.url) #{book.title} dd #{book.summary} else p This genre has no books يشابه هذا العرض جميع قوالبنا الأخرى، ولكن يتمثل الاختلاف الرئيسي في أننا لا نستخدم title المُمرَّر إلى العنوان الأول، بالرغم من استخدامه في قالب layout.pug الأساسي لضبط عنوان الصفحة. كيف تبدو صفحة تفاصيل نوع الكتاب؟ شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع أنواع الكتب "All genres"، ثم حدّد أحد هذه الأنواع مثل النوع "Fantasy". إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو صفحتك كما يلي: ملاحظة: يمكن أن تحصل على خطأ مشابه لما يلي: Cast to ObjectId failed for value " 59347139895ea23f9430ecbb" at path "_id" for model "Genre" وهو خطأ Mongoose سببه عدم تحويل req.params.id أو أي معرّف ID آخر إلى ()mongoose.Types.ObjectId، ويعود السبب الأكثر شيوعًا لذلك إلى اختلاف الرقم المعرّف عن المعرّف الحقيقي، ويمكن حل هذه المشكلة من خلال استخدام ()Mongoose.prototype.isValidObjectId لفحص المعرّف إذا كان صالحًا. صفحة تفاصيل الكتاب يجب أن تحصل صفحة تفاصيل الكتاب على المعلومات الخاصة بكتابٍ Book معين يُحدَّد باستخدام قيمة الحقل ‎_id المُولَّد تلقائيًا، إضافةً إلى معلومات حول كل نسخة مرتبطة به في المكتبة BookInstance. ينبغي ربط أيّ مؤلف أو نوع أو نسخة كتاب نعرضها برابط إلى صفحة التفاصيل المرتبطة بهذا العنصر. المتحكم افتح الملف "‎/controllers/bookController.js"، وابحث عن تابع المتحكم book_detail()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية: // عرض صفحة تفاصيل كتاب معين exports.book_detail = asyncHandler(async (req, res, next) => { // الحصول على تفاصيل الكتب ونسخ الكتب لكتاب معين const [book, bookInstances] = await Promise.all([ Book.findById(req.params.id).populate("author").populate("genre").exec(), BookInstance.find({ book: req.params.id }).exec(), ]); if (book === null) { // لا توجد نتائج const err = new Error("Book not found"); err.status = 404; return next(err); } res.render("book_detail", { title: book.title, book: book, book_instances: bookInstances, }); }); ملاحظة: لا نحتاج إلى طلب أيّ وحدات إضافية حاليًا، إذ استوردنا مسبقًا الاعتماديات عندما قدّمنا متحكم الصفحة الرئيسية. استخدمنا الأسلوب نفسه الموضح في صفحة تفاصيل نوع الكتاب، إذ تستخدم دالةُ متحكم الوجهة التابعَ Promise.all()‎ للاستعلام عن الكتاب Book المُحدَّد والنسخ المرتبطة به BookInstance على التوازي. إذا لم يُعثَر على كتاب مطابق، فسيُعاد كائن خطأ مع الخطأ "404‎: Not Found"، وإذا عُثِر على الكتاب، فستُعرَض معلومات قاعدة البيانات المسترجَعة باستخدام قالب "book_detail". نمرر results.book.title أثناء عرض صفحة الويب، نظرًا لاستخدام المفتاح "title" لإعطاء اسم لصفحة الويب كما هو مُعرَّف في ترويسة القالب "layout.pug". العرض أنشئ العرض "‎/views/book_detail.pug" وضِف إليه المحتوى التالي: extends layout block content h1 Title: #{book.title} p #[strong Author:] a(href=book.author.url) #{book.author.name} p #[strong Summary:] #{book.summary} p #[strong ISBN:] #{book.isbn} p #[strong Genre:] each val, index in book.genre a(href=val.url) #{val.name} if index < book.genre.length - 1 |, div(style='margin-left:20px;margin-top:20px') h4 Copies each val in book_instances hr if val.status=='Available' p.text-success #{val.status} else if val.status=='Maintenance' p.text-danger #{val.status} else p.text-warning #{val.status} p #[strong Imprint:] #{val.imprint} if val.status!='Available' p #[strong Due back:] #{val.due_back} p #[strong Id:] a(href=val.url) #{val._id} else p There are no copies of this book in the library. وضّحنا كل شيء تقريبًا في هذا القالب سابقًا، فلا حاجة للإعادة. ملاحظة: يمكن تقديم قائمة أنواع الكتب المرتبطة بالكتاب في القالب على النحو التالي، إذ تُضاف فاصلة بعد كل نوع مرتبط بالكتاب باستثناء النوع الأخير: p #[strong Genre:] each val, index in book.genre a(href=val.url) #{val.name} if index < book.genre.length - 1 |, كيف تبدو صفحة تفاصيل الكتاب؟ شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع الكتب All books، وحدد أحد هذه الكتب. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو صفحتك كما يلي: صفحة تفاصيل المؤلف يجب أن تعرض صفحة تفاصيل المؤلف معلومات المؤلف Author، والذي يُحدَّد باستخدام قيمة الحقل ‎_id المُولَّد تلقائيًا، إضافةً إلى قائمة بجميع كائنات الكتاب Book المرتبطة بهذا المؤلف Author. المتحكم افتح الملف "‎/controllers/authorController.js"، ثم ضِف السطر التالي إلى بداية الملف لطلب -باستخدام الدالة require()‎- الوحدة Book التي تحتاجها صفحة تفاصيل المؤلف، ويجب أن تكون الوحدات الأخرى مثل "express-async-handler" موجودة مسبقًا. const Book = require("../models/book"); ابحث عن تابع المتحكم author_detail()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية: // عرض صفحة تفاصيل مؤلف مُحدَّد exports.author_detail = 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) { // لا توجد نتائج const err = new Error("Author not found"); err.status = 404; return next(err); } res.render("author_detail", { title: "Author Detail", author: author, author_books: allBooksByAuthor, }); }); يماثل هذا الأسلوب المتبع تمامًا الأسلوب الموضح في صفحة تفاصيل نوع الكتاب، إذ تستخدم دالةُ متحكم الوجهة التابعَ Promise.all()‎ للاستعلام عن المؤلف Author ونسخ الكتب Book المرتبطة بها على التوازي. إذا لم يُعثَر على مؤلف مطابق، فسيُرسَل كائن خطأ إلى برمجية Express الوسيطة لمعالجة الأخطاء، وإذا عُثِر على المؤلف، فستُعرَض معلومات قاعدة البيانات المُسترجَعة باستخدام قالب تفاصيل المؤلف "author_detail". العرض أنشئ العرض "‎/views/author_detail.pug" وضع فيه المحتوى التالي: extends layout block content h1 Author: #{author.name} p #{author.date_of_birth} - #{author.date_of_death} 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 This author has no books. وضّحنا كل شيء تقريبًا في هذا القالب سابقًا، فلا حاجة للإعادة. كيف تبدو صفحة تفاصيل المؤلف؟ شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدد رابط جميع المؤلفين All Authors، ثم حدد أحد المؤلفين. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن يبدو موقعك كما يلي: ملاحظة: لا تبدو تواريخ عمر المؤلف جميلة، لذا سنحسّنها في التحدي الأخير من هذا المقال. صفحة تفاصيل نسخ الكتب BookInstance يجب أن تعرض صفحة تفاصيل نسخ الكتاب BookInstance المعلومات الخاصة بكل نسخة كتاب والمُحدَّدة باستخدام قيمة الحقل ‎_id المُولَّد تلقائيًا، إذ تتضمن هذه المعلومات اسم الكتاب Book بوصفه رابطًا إلى صفحة تفاصيل الكتاب، إضافةً إلى معلومات أخرى في السجل. المتحكم افتح الملف "‎/controllers/bookinstanceController.js"، ثم ابحث عن تابع المتحكم bookinstance_detail()‎ المُصدَّر وضع مكانه الشيفرة البرمجية التالية: // عرض صفحة تفاصيل نسخة كتاب معينة exports.bookinstance_detail = asyncHandler(async (req, res, next) => { const bookInstance = await BookInstance.findById(req.params.id) .populate("book") .exec(); if (bookInstance === null) { // لا توجد نتائج const err = new Error("Book copy not found"); err.status = 404; return next(err); } res.render("bookinstance_detail", { title: "Book:", bookinstance: bookInstance, }); }); يشابه هذا التقديم ما استخدمناه لصفحات تفاصيل النماذج الأخرى، إذ تستدعي دالة متحكم الوجهة التابع BookInstance.findById()‎ مع معرّف نسخة كتاب معين مُستخرَج من عنوان URL (باستخدام الوجهة Route)، ويمكن الوصول إليه في المتحكم باستخدام معاملات الطلب: req.params.id، ثم تستدعي التابع populate()‎ للحصول على تفاصيل الكتاب Book المرتبط به. إذا لم يُعثَر على نسخة كتاب BookInstance مطابقة، فسيُرسَل خطأ إلى برمجية Express الوسيطة، وإلّا فستُعرَض البيانات المُعادة باستخدام طريقة العرض bookinstance_detail.pug. العرض أنشئ العرض "‎/views/bookinstance_detail.pug" وضع فيه المحتوى التالي: extends layout block content h1 ID: #{bookinstance._id} p #[strong Title:] a(href=bookinstance.book.url) #{bookinstance.book.title} p #[strong Imprint:] #{bookinstance.imprint} p #[strong Status:] if bookinstance.status=='Available' span.text-success #{bookinstance.status} else if bookinstance.status=='Maintenance' span.text-danger #{bookinstance.status} else span.text-warning #{bookinstance.status} if bookinstance.status!='Available' p #[strong Due back:] #{bookinstance.due_back} وضّحنا كل شيء تقريبًا في هذا القالب سابقًا، فلا حاجة للإعادة. كيف تبدو صفحة تفاصيل نسخ الكتاب؟ شغّل التطبيق وافتح متصفحك على العنوان "http://localhost:3000/‎"، ثم حدّد رابط جميع نسخ الكتب All book-instances، وحدّد أحد هذه العناصر. إذا جرى إعداد كل شيء بصورة صحيحة، فيجب أن تبدو موقعك كما يلي: تحدى نفسك تستخدم معظم التواريخ المعروضة حاليًا على الموقع تنسيق جافا سكريبت الافتراضي مثل: Tue Oct 06 2020 15:49:58 GMT+1100 (AUS Eastern Daylight Time)‎ لذا يتمثل التحدي في هذا المقال في تحسين مظهر عرض التاريخ لمعلومات عمر المؤلف Author (تاريخ الوفاة والميلاد) ولصفحات تفاصيل نسخ الكتب لاستخدام التنسيق Oct 6th, 2016. ملاحظة: يمكنك استخدام نفس الأسلوب الذي استخدمناه لقائمة نسخ الكتب في المقال السابق من خلال إضافة خاصية العمر Lifespan الافتراضية إلى نموذج المؤلف Author واستخدام مكتبة Luxon لتنسيق سلاسل التاريخ. يجب عليك اتباع الخطوات التالية لإكمال هذا التحدي: ضع المتغير due_back_formatted مكان due_back في صفحة تفاصيل نسخ الكتاب. حدّث نموذج المؤلف Author لإضافة خاصية العمر الافتراضية، إذ يجب أن يبدو مثل: date_of_birth - date_of_death، ويكون لكلا القيمتين تنسيق التاريخ BookInstance.due_back_formatted. استخدم Author.lifespan في جميع العروض حيث تستخدم حاليًا date_of_birth و date_of_death صراحةً. ترجمة -وبتصرُّف- للمقالات Home page و Book list page و BookInstance list page و Date formatting using luxon و Author list page and Genre list page challenge و Genre detail page و Book detail page و Author detail page و BookInstance detail page and challenge. اقرأ المزيد المقال السابق مقدمة إلى القوالب Template في Express: إنشاء القالب الأساسي لموقع مكتبة محلية مثالًا. تطبيق عملي لتعلم Express - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية. تطبيق عملي لتعلم إطار عمل جانغو - الجزء الأول: إنشاء موقع ويب هيكلي لمكتبة محلية.
  16. نحاول في هذا المقال أن نضعك على الطريق الصحيح لإتقان تنسيق النصوص باستخدام CSS. لهذا سنطلعك تدريجيًا وبالتفصيل على أساسيات التنسيق بما في ذلك ضبط حجم الخط وعائلته وتنسيقاته، إضافةً إلى الخاصيات المختصرة، ومحاذاة النصوص وغيرها من التأثيرات، كما سنتعلم ضبط التباعد بين الأسطر والحروف. ننصحك قبل إكمال قراءة المقال أن تطلع على أساسيات لغة HTML وأساسيات لغة CSS. ما الذي يتضمنه تنسيق النصوص من خلال CSS؟ إذا عملت على ملفات HTML أوCSS مسبقًا، فأنت تعرف حتمًا أن النص الموجود داخل العنصر يُوضع ضمن صندوق المحتوى الخاص بالعنصر، إذ يبدأ النص من الزاوية اليسارية العليا لمنطقة المحتوى (أو من اليمينية العليا في اللغات التي تُكتب من اليمين إلى اليسار) وينساب نحو نهاية السطر. حالما يصل النص إلى نهاية السطر ينتقل تلقائيًا إلى سطر جديد وينساب نحو نهاية السطر من جديد وهكذا حتى يُكتب كامل النص ضمن صندوق المحتوى المخصص. إذًا، يسلك المحتوى النصي سلوك سلسلة من العناصر السطرية المتجاورة المرتبة في سطور، ولا تُطبق عملية الانتقال إلى سطر جديد حتى بلوغ نهاية السطر، أو أن تجبرها يدويًا على الانتقال إلى سطر جديد باستخدام العنصر <br>. ملاحظة: إذا لم تكن على دراية عن الأفكار التي طرحتها في الفقرة السابقة، عد إلى مقال نموذج الصندوق Box Model كي تتزود ببعض المعلومات المفيدة عن نظرية النموذج الصندوقي قبل المتابعة في هذا المقال. يمكن تقسيم خاصيات CSS المستخدمة في تنسيق النصوص ضمن فئتين أساسيتين، سنلقي نظرةً عليهما تباعًا في هذا المقال: تنسيقات خطوط الكتابة Font styles: وهي الخاصيات التي تؤثر مباشرةً على خط الكتابة، مثل نوع الخط المطبّق وحجمه وسماكته وغير ذلك. تنسيقات تخطيط النص Text layout styles: وهي الخاصيات التي تؤثر على مقدار المساحات الفارغة وغيرها من ميزات تخطيط النص، التي تسمح مثلًا بتغيير المسافة الفارغة بين الأسطر والحروف، ومحاذاة النص ضمن الحيز المحدد للمحتوى. ملاحظة: تذكّر أن النص الموجود ضمن عنصر سيتأثر بالخاصيات وكأنه كيان واحد، وبالتالي لن تتمكن من اختيار جزء من النص ثم تنسيقه على نحوٍ مختلف إذا لم تغلف هذا الجزء ضمن عنصر مناسب مثل <span>، أو أن تستخدم عنصر زائف مخصص لاستهداف النصوص مثل first-letter::، الذي يختار الحرف الأول من النص الذي يحتويه عنصر ما، أو first-line::، الذي يختار السطر الأول من نص ضمن عنصر، أو selection::، الذي يستهدف النص الذي جرى تحديده من خلال مؤشر الفأرة. خطوط الكتابة سننتقل مباشرةً الآن إلى خاصيات تنسيق خطوط الكتابة من خلال المثال التالي الذي نطبق خلاله بعض خاصيات CSS على شيفرة HTML التالية: <h1>Tommy the cat</h1> <p>Well I remember it as though it were a meal ago…</p> <p> Said Tommy the Cat as he reeled back to clear whatever foreign matter may have nestled its way into his mighty throat. Many a fat alley rat had met its demise while staring point blank down the cavernous barrel of this awesome prowling machine. Truly a wonder of nature this urban predator — Tommy the cat had many a story to tell. But it was a rare occasion such as this that he did. </p> بإمكانك إيجاد المثال في حالته النهائية على غيت هب GitHub، كما يمكنك تنزيل الشيفرة المصدرية أيضًا. الألوان تضبط الخاصية color لون الخلفية لمحتوى العنصر الذي تختاره، وهو عادةً نص، كما تتضمن أشياء أخرى، مثل لون الخط الذي يمر من وسط النص، أو أسفله والذي تضبطه الخاصية text-decoration. تقبل الخاصية color قيمًا بأية وحدات لونية تعرّفها CSS: p { color: red; } تجعل الشيفرة السابقة النص في الفقرة باللون الأحمر بدلًا من اللون الأسود الذي يستخدمه المتصفح افتراضيًا لعرض الفقرات لتظهر بالشكل التالي: عائلات الخطوط يمكنك استخدام الخاصية font-family التي تسمح لك بتخصيص خط أو مجموعة خطوط كي يطبقها المتصفح على عنصر تختاره، ويجب الانتباه إلى أن المتصفح لن يطبق أي خط كتابة ما لم يكن موجودًا على الجهاز الذي يستضيف موقع الويب، وإلا سيستخدم خط الكتابة الافتراضي. إليك هذا المثال البسيط: p { font-family: arial; } تطبق الشيفرة السابقة نوع الخط "arial" على جميع الفقرات النصية في الصفحة، وهو خط كتابة موجود على أي حاسوب. خطوط الكتابة الآمنة على الويب هناك عدد محدد من أنواع الخطوط التي تتوفر عادةً في جمع الأنظمة، وبالتالي يمكن استخدامها دون أن نقلق من تجاهل المتصفح لها. لهذا، دُعيت هذه الخطوط بخطوط الويب الآمنة safe web fonts. يرغب مطورو الويب غالبًا بتخصيص الخطوط التي يستخدمونها لعرض المحتوى النصي في الصفحات، وتكمن المشكلة في إيجاد طريقة معرفة أن خطًا معينًا سيكون متوفرًا على الجهاز الذي يستضيف الصفحة. لن نتمكن من معرفة هذه المعلومة تحديدًا في كل الحالات، لكن تُعرف خطوط الكتابة الآمنة في الويب بأنها متاحة في جميع نسخ أنظمة التشغيل تقريبًا بما في ذلك ويندوز وماك أو إس ومعظم توزيعات لينكس المعروفة وأندرويد وأي أو إس iOS. تتغير قائمة خطوط الكتابة الآمنة مع تغيير أنظمة التشغيل، لكن من المنطقي أن تفكر باستخدام خطوط ويب الآمنة التالية وعلى الأقل حاليًا، إذ زادت شعبية الكثير منها بفضل مبادرة مايكروسوفت التي دُعيت Core fonts for the Web في أواخر التسعينات وبداية الألفية الجديدة: الاسم النمط الأساسي ملاحظات Arial sans-serif يُعدّ أيضًا اختيار نوع الخط "Helvetica" بدلًا من "Arial" من الممارسات الأفضل على الرغم من أن وجهي الخطين متطابقين تمامًا، لكن "Helvetica" يعطي خطًا أكثر وضوحًا رغم كونه أقل توفرًا. Courier New monospace لبعض أنظمة التشغيل نسخة بديلة (ربما أقدم) من الخط "Courier New" يُدعى "Courier". لهذا من الممارسات الأفضل أن تستخدم كليهما مع تفضيل للنوع "Courier New". Georgia serif Times New Roman serif لبعض أنظمة التشغيل نسخة بديلة (ربما أقدم) من الخط "Times New Roman" يُدعى "Times". لهذا من الممارسات الأفضل أن تستخدم كليهما مع تفضيل للنوع "Times New Roman". Trebuchet MS sans-serif لا بد من الانتباه عن استخدام هذا الخط، فهو منتشر بصورةٍ واسعة ضمن أنظمة تشغيل الهواتف المحمولة. Verdana sans-serif ملاحظة: ستجد في كثير من المصادر ومنها cssfontstack.com قوائم لخطوط الويب الآمنة المتاحة على ويندوز وماك أو إس وبالتالي تساعدك في اتخاذ قرار بنوع الخط الذي تستخدمه في صفحاتك. ملاحظة: هناك طريقة لتنزيل خطوط مخصصة لصفحة ويب تسمح لك بتخصيص الخطوط في صفحتك بالطريقة التي تراها مناسبة. هذا الأمر معقد قليلًا وسنناقشه لاحقًا. الخطوط الافتراضية تعرّف CSS خمسة أسماء معممة لأنواع الخطوط هي: serif و sans-serif و monospace و cursive و fantasy. هذه الأسماء معممة جدًا ويمكن أن تختلف واجهات خطوط الكتابة المُستخدمة من هذه الأسماء من متصفح لآخر، ومن نظام تشغيل لآخر. يمثّل هذا الوضع ما يُسمى "سيناريو الحالة الأسوأ worst case scenario"، إذ سيحاول المتصفح جاهدًا في انتخاب خط الكتابة الأكثر قربًا من الخطوط الموجودة، وفعلًا من السهل التعامل مع الخطوط serif و sans-serif و monospace فهي متوقّعة وستكون النتيجة التي يعرضها المتصفح معقولة، بينما يصعب توقع سلوك المتصفح مع الخطين cursive و fantasy لهذا يجدر الحذر عند استخدامها، واختبار العمل باستمرار. تُعرّف الأسماء الخمسة للخطوط على النحو التالي: المصطلح التعريف serif وهي خطوط تنتهي فيها حواف الحروف بنتوءات (وهي تفاصيل صغيرة تظهر في نهاية خطوط الحروف) sans-serif وهي الخطوط التي لا تنتهي حواف الحروف بنتوءات. monospace وهي خطوط يكون لكل المحارف فيها الاتساع ذاته، وتُستخدم عادةً في كتابة وتنظيم الشيفرة، أي لضبط المحاذاة. cursive وهي الخطوط التي يُنوى استخدامها لمحاكاة خط اليد، من ناحية الإنسياب وترابط الأحرف. fantasy وهي الخطوط المُستخدمة لأغراض التزيين. قوائم الخطوط طالما أننا لن نضمن توفر جميع الخطوط التي ننوي استخدامها عن تصميم الصفحات (فحتى خطوط ويب الآمنة قد تخفق أحيانًا)، يمكنك اختيار قائمة من الخطوط font stack ليختار المتصفح منها خطًا. يتضمن ذلك اختيار مجموعة من القيم للخاصية font-family تفصل بينها فواصل ,. p { font-family: "Trebuchet MS", Verdana, sans-serif; } في هذه الحالة، يبدأ المتصفح باختيار الخط الأول من القائمة ليرى إن كان متاحًا على الجهاز فيطبّقه، فإن لم يكن كذلك ينتقل إلى التالي وهكذا. من الجيد تزويد المتصفح بأسماء خطوط معممة مناسبة في نهاية القائمة، فإن لم يكن أي خط من الخطوط التي تريدها متاحًا، يمكن للمتصفح أن يختار حينها على الأقل ما يشابهها. لتوضيح هذه الفكرة، تأخذ الفقرات النصية خط المتصفح الافتراضي "serif" إن لم تكن بقية الخيارات في قائمة الخطوط متوفرة، وهذا الخط غالبًا "Times New Roman"، وهذا لن يخدمك إن كنت تريد خطًا من النوع "sans-serif". ملاحظة: لا بد من إحاطة أسماء خطوط الكتابة المكونة من أكثر من كلمة (أي تحتوي على مسافات بيضاء) بإشارات تنصيص مزدوجتين "" مثل "Trebuchet MS". تحذير: ينبغي اقتباس أي اسم عائلة خطوط يمكن تفسيره عن طريق الخطأ على أنه اسم عائلة معمّم أو كلمة مفتاحية على مستوى CSS. يمكن تضمين أسماء عائلات الخطوط مثل <custom-ident> أو <string>، لكن لا بُد من تضمين أسماء عائلات الخطوط التي من الممكن أن يكون لها نفس قيمة خاصية CSS، مثل خاصية initial أو inherit، أو CSS التي لها نفس أسماء الخطوط المعممة، مثل sans-serif أو fantasy، على أنها سلاسل نصية، وإلا سيُفسّر اسم عائلة الخط على أنه كلمة CSS مفتاحية أساسية مكافئة أو اسم عائلة معمّم. لدى استخدام أسماء العائلات على أنها كلمات مفتاحية، يجب عدم اقتباس أسماء العائلات المعمّمة مثل serif و sans-serif و monospace و cursive وكذلك الأمر بالنسبة لكلمات CSS المفتاحية العامة، إذ لا تُفسّر السلاسل النصية على أنها كلمات CSS مفتاحية. مثال عن قائمة خطوط لنضف إلى مثالنا السابق الخطوط من النوع المعمم "sans-serif": p { color: red; font-family: Helvetica, Arial, sans-serif; } وستكون النتيجة على النحو التالي: حجم الخط اطلعنا في مقال قيم ووحدات CSS على وحدات الطول والحجم التي نستخدمها، ورأينا أن الخاصية font-size التي تضبط حجم الخط قد تأخذ قيمًا من معظم وحدات القياس المستخدمة (بما في ذلك النسب المئوية). لكن أكثر ما ستصادفه من وحدات القياس في تقدير حجم النصوص هي: البكسل px: وهو عدد البكسلات التي تقيس ارتفاع النص، وهي وحدة مطلقة تنتج دائمًا القيمة نفسها لارتفاع الخط في جميع الحالات تقريبًا. em: عندما نضبط حجم الخط على1‎ em، فهذا يعني أننا نختار للعنصر الذي ننسقه نفس حجم الخط للعنصر الأب (وبدقة أكبر، اتساع الحرف M في الخط المستخدم في تنسيق العنصر الأب). سيكون الأمر مربكًا إذا كانت هناك الكثير من العناصر المتداخلة والمنسقة بخطوط كتابة مختلفة، ومع ذلك فالأمر قابل للتنفيذ كما سنرى تاليًا. لماذا عليّ ذلك؟ سيكون الأمر عاديًا بمجرد أن تعتاد عليه، وستتمكن من استخدام في تحديد أبعاد أي شيء وليس النصوص فقط، ويمكن استخدام واحدة القياس em لتقدير أبعاد جميع العناصر في الصفحة مما يُسهّل الصيانة. rem: وهي واحدة مشابهة لواحدة em، إلا أن ‎1 rem ستعادل في هذه الحالة حجم الخط المستخدم لتنسيق العنصر الجذري root element وليس العنصر الأب. يسهّل هذا الأمر الحسابات المستخدمة في تقدير الأبعاد المختلفة، لكن قد تعاني إن أردت دعم المتصفحات الأقدم، فلا يقبل المتصفح إنترنت إكسبلور 8 وما دون هذه الواحدة مثلًا. يرث العنصر حجم الخط من العنصر الأب، وتبدأ الوراثة انطلاقًا من العنصر الجذري للصفحة ككل <html>. ويُعد 16px القياس الافتراضي لحجم خط العنصر الجذري في جميع المتصفحات، إذ ستأخذ جميع الفقرات النصية، أو أية عناصر أخرى لا يعطيها المتصفح قيمةً افتراضيةً مخصصة ضمن العنصر الجذري قياسًا نهائيًا قيمته 16px، وتأخذ عناصر أخرى قيمًا افتراضية مختلفة مثل العنصر <h1> الذي يأخذ القياس 2em افتراضيًا وسيكون قياسه النهائي 32px. ستأخذ الأمور منحًى مربكًا أكثر عندما تبدأ تغيير حجم الخط لعناصر متداخلة؛ فلو كان لديك العنصر <article> في صفحتك وضبط حجم الخط فيه على 1.5em (يقابل 24px)، وأردت أن تجعل حجم خط الفقرات ضمن هذا العنصر 20px فما هو الحجم المناسب مقاسًا بواحدة em؟ <!-- 16px القياس الأساسي للعنصر الجذري--> <article> <!-- 1.5em إن كان القياس في هذا العنصر--> <p>My paragraph</p> <!-- 20px كيف أحسب القياس --> </article> لا بد أن تجعل القيمة على النحو 20/24em أي ‎0.8333 em. فإجراء العمليات الحسابية قد يكون مربكًا بعض الشيء، لهذا عليك الإنتباه عند تنسيق الأشياء. من الأفضل إذًا استخدام rem حيثما أمكن لتبقي الأمور بسيطة وتفادى ضبط حجم الخط font-size للعناصر الحاوية ما أمكن. تنسيق الخطوط وثخانتها وتحويل حالة حروف النصوص وتزيينها تزودنا CSS بمجموعة من الخاصيات التي تغيّر ثخانة النص وتبرزه نذكر منها: font-style: تُستخدم لتحويل النص إلى الشكل المائل أو العكس، وتأخذ القيم التالية (نادرًا ما تستخدمها، إلا إن أردت فعلًا جعل الخط مائلًا لسبب وجيه): normal: يجعل الخط نمطيًا (يلغي الإمالة). italic: يجعل الخط مائلًا إن كان الخط يدعم ذلك، بينما يجعله منحرفًا قليلًا oblique إن لم يدعم الخط ذلك. oblique: يقلّد الإمالة نوعًا ما بجعل الأحرف منحرفة قليلًا عن الوضع النمطي. font-weight: تحدد هذه الخاصية مقدار سماكة أو ثخانة الخط، ولها عدة قيم: "light-" و "normal-" و "bold-" و "extrabold-" و "black-" في حال احتجت إلى ذلك، لكنك لن تستخدم عمليًا سوى "normal" و "bold": normal و bold: تجعل الخط سميكًا أو طبيعيًا. lighter و bolder: تجعل الخط أسمك أو أخف بدرجة واحدة من سماكة العنصر الأب. 100 – 900: قيم عددية لمقدار سماكة خط الكتابة وتقدم طريقة لتحكم أفضل بالسماكة بدلًا من الكلمات المفتاحية. text-transform: تساعدك على تحويل حالة الحروف في النص: none: تمنع أية تحويلات. uppercase: تحوّل جميع الحروف إلى حروف كبيرة. lowercase: تحوّل جميع الحروف إلى حروف صغيرة. capitalize: تحوّل الأحرف الأولى من الكلمات إلى حروف كبيرة. full-width: تكتب أي حرف ضمن مربع ثابت الاتساع (أي يشغل كل حرف نفس المساحة) مما يسمح بمحاذاة المحارف المختلفة مثل اللاتينية والآسيوية مثلًا. text-decoration: تمكّن أو تلغي تزيين النص (تُستخدم غالبًا في إزالة الخط الموجود تحت الروابط التشعبية عند تنسيقها): none: إزالة أي تزيين في النص. underline: يضع خطًا تحت النص. overline: يضع خطًا فوق النص. line-through: يضع خطًا وسط النص. تقبل الخاصية text-decoration قيمًا متعددة إذا أردت إضافة عدة تزيينات في الوقت عينه. إليك مثالًا: text-decoration: underline overline ولا بد من الإشارة إلى أن text-decoration هي خاصية مختصرة للخاصيات text-decoration-line و text-decoration-style و text-decoration-color، لهذا يمكنك استخدام توليفة من هذه التنسيقات لإظهار الأثر المطلوب. إليك مثالًا: text-decoration: line-through red wavy لنلق نظرةً على المثال التالي الذي يضيف خاصيتين من الخاصيات السابقة معًا: html { font-size: 10px; } h1 { font-size: 5rem; text-transform: capitalize; } h1 + p { font-weight: bold; } p { font-size: 1.5rem; color: red; font-family: Helvetica, Arial, sans-serif; } إليك النتيجة: إلقاء الظلال على النصوص يمكن إلقاء الظل على النص باستخدام الخاصية text-shadow التي تأخذ حتى أربعة قيم كما يظهر في المثال التالي: text-shadow: 4px 4px 5px red; أما القيم الأربعة فهي: الإزاحة الأفقية للظل عن النص الأصلي: وتأخذ معظم وحدات قياس الطول والأبعاد المتاحة في CSS، لكن ما ستستخدمه عادةً هو px. تزيح القيم الموجبة الظل نحو اليمين والسالبة إلى اليسار، ولا بد من استخدام هذه القيمة. الإزاحة العمودية للظل عن النص الأصلي: تشابه من ناحية الوظيفة الإزاحة الأفقية إلا أنها للأعلى والأسفل وليست لليمين واليسار، ولا بُد من استخدام هذه القيمة. نصف قطر التمويه blur: وتشير القيم الأعلى إلى أن الظل أوسع انتشارًا، وتأخذ القيمة 0 إن لم تُستخدم (لا يوجد إماهة). يمكن أن تأخذ هذه القيمة أية وحدات لقياس الطول أو الأبعاد في CSS. اللون الأساسي للظل: ويأخذ جميع القيم اللونية في CSS. وإن لم تُستخدم، فسيكون لون الظل افتراضيًا هو اللون الحالي المأخوذ من الخاصية color للعنصر. الظلال المتعددة يُمكن تطبيق عدة ظلال على النص باستخدام عدة قيم منفصلة عن بعضها بفواصل: h1 { text-shadow: 1px 1px 1px red, 2px 2px 1px red; } إذا طبقنا هذا التنسيق على العنصر <h1> على مثالنا السابق ستكون النتيجة على النحو التالي: تخطيط النص لنلقِ نظرةً الآن على الخاصيات التي تؤثر على تخطيط layout النص: محاذاة النصوص تُستخدم الخاصية text-align في محاذاة النصوص ضمن صندوق الحاوية. تأخذ الخاصية القيم التالية التي تعمل بنفس الطريقة التي تستخدمها المحررات النصية النمطية: left: تُحاذي النص إلى اليسار. right: تُحاذي النص إلى اليمين. center: تجعل النص في وسط الحاوية. justify: تجعل النص ممتدًا ذو فراغات مختلفة الأحجام ليجعل اتساع جميع الأسطر في النص متساويًا. قد يبدو مظهر النص سيئًا وخاصةً إذا احتوى فقرات تضم الكثير من الكلمات الطويلة؛ فإذا كنت ستستخدم هذه الخاصية، لا بد من استخدام خاصية أخرى هي hyphens لتوزيع بعض الكلمات الطويلة على سطرين. إذا طبقنا الخاصية text-align: center على العنصر <h1> في مثالنا السابق: html { font-size: 10px; } h1 { font-size: 5rem; text-transform: capitalize; text-shadow: 1px 1px 1px red, 2px 2px 1px red; text-align: center; } h1 + p { font-weight: bold; } p { font-size: 1.5rem; color: red; font-family: Helvetica, Arial, sans-serif; } ستكون النتيجة على النحو التالي ارتفاع السطر تضبط الخاصية line-height ارتفاع كل سطر من أسطر النص، وتأخذ جميع وحدات القياس في CSS إضافةً إلى قيم بدون وحدات unit-less تعمل مثل مضاعفات وهي عمليًا أفضل الخيارات. عند استخدام قيم بلا وحدات سيُحسب جداء حجم الخط font-size في هذه القيمة وتكون النتيجة هي ارتفاع الخط. سيبدو النص أفضل وأسهل قراءة عندما تتباعد الأسطر عن بعضها، ويكون عادةً الفراغ المزدوج (1.5-2) هو التباعد المناسب بين الأسطر، وكي نجعل التباعد بين الأسطر في مثالنا بقيمة 1.6 ما علينا سوى كتابة الشيفرة التالية: p { line-height: 1.6; } وعند تطبيقها على عنصر الفقرة <p> في مثالنا السابق: html { font-size: 10px; } h1 { font-size: 5rem; text-transform: capitalize; text-shadow: 1px 1px 1px red, 2px 2px 1px red; text-align: center; } h1 + p { font-weight: bold; } p { font-size: 1.5rem; color: red; font-family: Helvetica, Arial, sans-serif; line-height: 1.6; } ستكون النتيجة على النحو التالي: الفراغات بين الحروف والكلمات تسمح الخاصيتان letter-spacing و word-spacing بضبط الفراغات بين الحروف والكلمات في النص. لن تستخدمهما كثيرًا، لكنك قد تحتاجهما لإعطاء النص مظهرًا مخصصًا، أو لتحسين مظهر خطوط معينة ذات طبيعة متراصة. تأخذ هذه الخاصية معظم وحدات قياس الأبعاد والأطوال. لإظهار طريقة عمل هاتين الخاصيتين سنطبق تباعدًا بين كلمات وأحرف أول سطر من العنصر <p> في مثالنا السابق: p::first-line { letter-spacing: 4px; word-spacing: 4px; } ثم نصيّر شيفرة HTML التالية: html { font-size: 10px; } h1 { font-size: 5rem; text-transform: capitalize; text-shadow: 1px 1px 1px red, 2px 2px 1px red; text-align: center; letter-spacing: 2px; } h1 + p { font-weight: bold; } p { font-size: 1.5rem; color: red; font-family: Helvetica, Arial, sans-serif; line-height: 1.6; letter-spacing: 1px; } ستكون النتيجة على النحو التالي: خاصيات أخرى تستحق الملاحظة تعطيك الخاصيات التالية فكرةً عن طريقة تنسيق النصوص في صفحات الويب، لكن هناك الكثير غيرها. سنغطي هنا أكثر الخاصيات أهمية، لهذا لا بد من استكشاف طريقة عملها بمجرد أن تتقن الخاصيات التي شرحناها سابقًا: خاصيات تنسيق الخط: font-variant: تبدّل بين الأحرف الصغيرة والنص العادي. font-kerning: تبدل بين وضع التداخل kerning والوضع الطبيعي. font-feature-settings: تبدّل بين ميزات الخطوط من النوع OpenType. font-variant-alternates: تتحكم باستخدام المحارف البديلة لواجهة خط كتابة معين. font-variant-caps: يتحكم باستخدام بدائل المحارف الكبيرة. font-variant-east-asian: تتحكم باستخدام المحارف البديلة لخطوط كتابة اللغات الشرق آسيوية مثل اليابانية والصينية. font-variant-ligatures: تُستخدم للتحكم بكيفية استخدام المحارف المترابطة (محرفين مرتبطين على شكل محرف واحد) والمحارف ذات الحركات. font-variant-numeric: تتحكم باستخدام بدائل محارف الأرقام والأعداد الكسرية وعلامات الترتيب. font-variant-position: تتحكم باستخدام بدائل المحارف التي تشغل مواقع أصغر مثل المحارف التي تُكتب منخفضة أو مرتفعة. font-size-adjust: تعديل الحجم المرئي للخط بمعزل عن حجمه الحقيقي. font-stretch: تُستخدم للتبديل بين النسخ الممتدة stretched الممكنة لخط معين. text-underline-position: يحدد موقع الخط الذي يُرسم أسفل النص عند استخدام القيمة underline للخاصية text-decoration-line. text-rendering: تحاول أن تحسّن في النص عند تصييره. خاصيات تخطيط النص: text-indent: تحدد مقدار الفراغ الأفقي الواجب تركه قبل بداية السطر الأول من النص. text-overflow: تعرّف طريقة تنبيه المستخدم عندما لا يُعرض المحتوى في حالة الطفحان overflow. white-space: تعرّف كيفية التعامل مع المسافات الفارغة وعناصر الانتقال إلى سطر جديد ضمن العناصر. word-break: تحدّد إذا ما كان مسموحًا تجزئة الكلمة عند الانتقال إلى سطر جديد. direction: تعرّف اتجاه خط الكتابة. يتعلق الأمر عادةً بلغة الكتابة ومن الأفضل ترك الأمر للغة HTML كي تتعامل مع هذا الجزء لارتباطه بسياق النص. hyphens: تفعّل أو تعطّل تجزئة الكلمات على سطرين في اللغات التي تدعم ذلك. line-break: تشدد أو تتسامح بالانتقال إلى سطر جديد في اللغات الآسيوية. text-align-last: تحدد محاذاة آخر سطر من كتلة أو أي سطر قبل أن يُجبر النص على الانتقال إلى سطر جديد. text-orientation: تحدد جهة انسياب الكلمات ضمن السطر. overflow-wrap: تحدد إن كان يُسمح للمتصفح أن ينتقل بالنص إلى سطر جديد لتفادي طفحان المحتوى أو لا. writing-mode: تحدد إن كان اتجاه الأسطر أفقي أو عمودي واتجاه انسياب الأسطر اللاحقة. الخاصيات المختصرة لتنسيق خط الكتابة يمكن ضبط الكثير من قيم خاصيات تنسيق النصوص من خلال الخاصية المختصرة font، وتكتب قيمها وفق التسلسل التالي: font-style و font-variant و font-weight و font-stretch و font-size و line-height و font-family. وتُعد الخاصيتين font-size و font-family ضروريتان فقط (يجب تحديد قيمهما). لا بد من الإشارة إلى ضرورة وضع شرطة أمامية مائلة "/" بين الخاصيتين font-size و line-height كما في المثال التالي: font: italic normal bold normal 3em/1.5 Helvetica, Arial, sans-serif; تطبيق عملي: حاول أن تتدرب على تنسيق النصوص نريد منك في هذا التطبيق أن تستخدم محرر الشيفرة التفاعلي التالي لتتدرب على استخدام خاصيات تنسيق النصوص وتكتشف طريقة عملها وما يمكن أن تقدمه من فائدة. بإمكانك أن تستخدم ملفات HTML/CSS التي نزّلتها أو العمل على شيفرتك الخاصة. إن ارتكبت خطأً، انقر على زر "مسح Reset" لإلغاء كل ما فعلته والعمل من جديد. الخلاصة نأمل أن تكون قد استمتعت واستفدت مما قدمناه في مقالنا عن تنسيق النصوص في CSS، وسنتابع العمل في المقال القادم على تنسيق قوائم HTML. ترجمة -وبتصرف- للمقال Fundamentals text and font styling اقرأ المزيد المقال السابق تنظيم الشيفرة في CSS. تعرف على أساسيات لغة CSS توثيق لغة CSS العربي. تنسيق نصوص صفحات الويب باستخدام CSS. التنسيقات الأساسية للعناصر في CSS.
  17. يقدِّم هذا المقال الأساسيات اللازمة لبناء جداول HTML، إذ يغطي مفاهيم مثل الصفوف والأعمدة والخلايا والترويسات وكيفية ضبط الخلايا لتمتد على عدة أعمدة وصفوف، كما يعلمك طريقة تجميع الخلايا في العمود نفسه لتنسيقها معًا. ما هو الجدول؟ الجدول هو بنية لهيكلة البيانات على صورة صفوف rows وأعمدة column -أي بيانات مجدولة- لتسهيل عملية البحث عن القيم التي تبدي ترابطًا فيما بينها مثل ربط الأشخاص بأعمارهم أو بالأيام التي يعملون فيها أسبوعيًا أو بأيام التدريب في النادي. استُخدِمت الجداول بكثرة ومنذ زمن بعيد، وتقدِّم الصورة التالية دليلنا على ذلك، فهي مستند لعملية إحصاء في الولايات المتحدة يعود إلى عام 1800: وبالتالي ليس غريبًا أن يهتم مخترعو HTML بإيجاد وسائل لهيكلة بيانات الويب على هيئة جداول. كيف يعمل الجدول؟ الغاية من الجدول واضحة، إذ يسهِّل استخدام الجداول تفسير المعلومات بالربط البصري بين ترويسة عمود وترويسة صف، ولنلق نظرةً على الجدول التالي باحثِين عن كوكب غازي ضخم شبيه بالمشتري Jovian له 62 قمرًا، إذ يمكنك إيجاد الكوكب المطلوب بالربط بين العمود الصحيح و الصف الصحيح. عندما تُنجز الجداول في HTML بالصورة الصحيحة، سيسهل على تقنيات الوصول السهل مثل قارئات الشاشة التعامل مع المحتوى الذي تقدِّمه، أي ينبغي أن يُحسِّن استخدام الجداول تجربة الأشخاص ذوي الإعاقة البصرية وسليمي البصر على حد سواء. تنسيق الجداول ألق نظرةً على الجدول السابق نفسه لكن من خلال مثال مباشر على جيت-هاب، ولاحظ أنّ هذا الأخير أسهل قراءةً من الجدول الذي عرضناه في المقال، وذلك لأننا طبقنا أدنى درجات التنسيق عند بنائه موازنةً بنسخة جيت-هاب. لا نخفيك سرًا أنّ الجداول التي تتمتع بمستوى تنسيق CSS رفيع أنها أكثر تأثيرًا في الويب، لهذا لا بد من الاهتمام جيدًا بهيكلة البيانات في جداول HTML بالإضافة إلى التنسيق الجيد، لكننا سنركز في سلسلة المقالات هذه على الجزء المتعلق بلغة HTML فقط، إذ سنزود الجداول في هذه السلسلة بالحد الأدنى من تنسيقات CSS التي تُسهِّل قراءة محتوياتها موازنةً بالجداول غير المنسقة، كما يمكنك تنزيل ملف CSS و قالب HTML الذي يُستخدم لتطبيق التنسيقات من المستودع المخصص على جيت-هاب، إذ سيمنحك هذان الملفان نقطة انطلاق جيدة عند العمل على جداول HTML. ما الحالات التي لا ينبغي فيها استخدام جداول HTML؟ تُستخدَم الجداول لعرض البيانات القابلة للجدولة فهمي مصممة لهذا الغرض، كما يستخدِمها البعض أيضًا في تخطيط صفحات الويب مثل أن يضم أحد الصفوف الترويسة وآخر يضم أعمدة المحتوى الرئيسي وآخر للتذيل، وهذا أمر خاطئ كليًا، وقد استُخدمت هذه الطريقة في تخطيط الصفحات فيما مضى عندما كان دعم تنسيق CSS في المتصفحات المختلفة متفاوتًا وسيئًا، لكنها أقل انتشارًا في أيامنا هذه، إلا أنك قد تراها بين الفينة والأخرى. إذًا نختصر فنقول: يُعَدّ استخدام تخطيط الجداول بدلًا من تقنيات تخطيط CSS فكرةً سيئةً، وإليك الأسباب الرئيسية: يؤثر تخطيط الجدول على سهولة وصول ذوي المشاكل البصرية: تفسِّر قارئات الشاشة التي يستخدمها فاقدِي البصر وسوم العناصر الموجودة في صفحة HTML وتقرأ محتوياتها، وطالما أنّ الجدول ليس الأداة الصحيحة لتخطيط الصفحة وأنّ توصيفه أكثر تعقيدًا من تقنيات تخطيط CSS، فسيربك مستخدِميها ما تنطقه تلك القارئات. ينتج عن الجداول وسومًا مختلطةً: تضم الجداول هيكليةً أعقد من تلك المستخدَمة عادةً في تخطيط الصفحة، وقد يزيد ذلك من صعوبة كتابة الشيفرة وصيانتها وتنقيحها. الجداول غير متجاوبة تلقائيًا: إذا استخدمت العناصر المناسبة مثل <header> أو <article> أو <div> أو <section>، فسيكون عرضها 100% من عرض العنصر الأب تلقائيًا، ويقاس في المقابل عرض الجداول افتراضيًا وفقًا لحجم محتواها، لذلك يحتاج الأمر إلى قياسات أخرى ليعمل تخطيط الصفحة جيدًا على الأجهزة المختلفة. تطبيق: إنشاء جدول تحدثنا طويلًا عن المفهوم النظري وحان الوقت لنبني جدولًا بسيطًا: انسخ الملفين blank-template.html و minimal-table.css إلى حاسوبك وضعهما في مجلد جديد. يُوضع محتوى كل جدول بين وسمَي البداية والنهاية للعنصر <table>، لذلك ضع عنصرَي الجدول داخل جسم ملف HTML. تُعَدّ الخلية أصغر حاوية ضمن الجدول وننشئها باستخدام العنصر <td> اختصارًا لبيانات جدول table data، ولنضف الشيفرة التالية داخل العنصر <table>: <td>Hi, I'm your first cell.</td> انسخ الشيفرة السابقة ثلاث مرات إضافية إذا أردت تكوين صف من أربعة خلايا: <td>Hi, I'm your first cell.</td> <td>I'm your second cell.</td> <td>I'm your third cell.</td> <td>I'm your fourth cell.</td> يمثِّل كل عنصر <td> خليةً واحدةً، وتمثِّل هذه العناصر بمجموعها الصف الأول الذي يكبر بإضافة عناصر جديدة، ولإنهاء الصف الأول والانتقال إلى الصف الثاني نستخدِم العنصر <tr> اختصارًا لصف جدول table row: ضع الخلايا الأربعة التي أنشأتها سابقًا ضمن وسمَي البداية والنهاية للعنصر <tr>: <tr> <td>Hi, I'm your first cell.</td> <td>I'm your second cell.</td> <td>I'm your third cell.</td> <td>I'm your fourth cell.</td> </tr> نكون بهذا الشكل قد أنشأنا الصف الأول، ولا بدّ من تغليف خلايا جديدة <td>ضمن عنصر <tr> جديد: نتيجة العمل لا بدّ أن يظهر الجدول كما يلي: ملاحظة: يمكنك إيجاد المثال السابق بصيغته النهائية ضمن المستودع المخصص له على جيت-هاب. عناصر ترويسة جدول لنركِّز الآن على ترويسات الجدول، وهي خلايا خاصة تلاحظها في بداية الصف أو العمود وتحدد طبيعة البيانات التي يضمها كما في الخليتين Person و Age في مثالنا الأول، ولتوضيح فائدة الترويسات سنلقي نظرةً على هذا المثال: <table> <tr> <td>&nbsp;</td> <td>Knocky</td> <td>Flor</td> <td>Ella</td> <td>Juan</td> </tr> <tr> <td>Breed</td> <td>Jack Russell</td> <td>Poodle</td> <td>Streetdog</td> <td>Cocker Spaniel</td> </tr> <tr> <td>Age</td> <td>16</td> <td>9</td> <td>10</td> <td>5</td> </tr> <tr> <td>Owner</td> <td>Mother-in-law</td> <td>Me</td> <td>Me</td> <td>Sister-in-law</td> </tr> <tr> <td>Eating Habits</td> <td>Eats everyone's leftovers</td> <td>Nibbles at food</td> <td>Hearty eater</td> <td>Will eat till he explodes</td> </tr> </table> يصيّر المتصفح الشيفرة السابقة إلى الجدول التالي: إنّ المشكلة في هذا الجدول هي صعوبة الوصول إلى البيانات المطلوبة كما ينبغي، على الرغم من وضوح الغاية منه، لكن إذا برزت ترويسات الأعمدة والصفوف بصورة ما، فسيتحسن الوضع. تطبيق: ترويسات الجداول سنحاول في هذا التطبيق تحسين الجدول السابق: احفظ نسخةً عن الملفين dogs-table.html و minimal-table.css في مجلد جديد على حاسوبك، إذ يحتوي ملف HTML على المعلومات ذاتها التي يعرضها الجدول السابق. استخدم العنصر <th> -اختصارًا لترويسة جدول table header- لتمييز الخلية الأولى على أنها ترويسة دلاليًا وبصريًا، إذ يعمل هذا العنصر بالطريقة نفسها التي يعمل بها العنصر <td> إلا أنه يعرِّف ترويسةً وليس خليةً عاديةً. انتقل إذًا إلى ملف HTML واستبدل العناصر <th> بكل العناصر <td> التي تحيط بخلايا الترويسة. احفظ التغييرات التي أجريتها على ملف HTML ثم حمّله في المتصفح وستلاحظ كيف ستبدو الخلايا الأولى على أساس ترويسات. ملاحظة: يمكنك الاطلاع على الملف بشكله النهائي على جيت-هاب، كما يمكنك الاطلاع على شيفرته المصدرية. ما هي فوائد الترويسات؟ جاوبنا بالفعل على جزء من هذا السؤال، إذ يسهِّل استخدامها استخلاص المعلومات ويحسِّن مظهر الجدول عمومًا. ملاحظة: تمتلك ترويسات الجداول تنسيقًا افتراضيًا محددًا، إذ تُكتَب بخط ثخين وتتوضَّع في مركز الخلية حتى ولو لم تطبق بنفسك أية تنسيقات، وذلك لتمييزها. تمتلك الترويسات فوائدًا إضافيةً، إذ يسمح لك استخدام السمة scope -التي سنتعرف إليها لاحقًا- بدعم الوصول السهل، وذلك بربط كل ترويسة بكل البيانات التي يضمها الصف نفسه أو العمود، إذ ستتمكن عندها قارئات الشاشة من قراءة كامل الصف أو العمود، وهذا بالطبع أكثر فائدةً. تمكين الخلايا من الامتداد على عدة صفوف أو خلايا قد نرغب أحيانًا في تمديد الخلايا لتشمل عدة صفوف أو أعمدة، ولنأخذ مثلًا الجدول التالي الذي يعرض بعض أسماء الحيوانات، فقد نرغب في بعض الحالات بعرض أسماء الإناث والذكور إلى جانب النوع وقد لا نرغب بذلك، وفي كلتا الحالتين نريد أن يمتد اسم الحيوان على كامل الجدول، وإليك الشيفرة الأولية: <table> <tr> <th>Animals</th> </tr> <tr> <th>Hippopotamus</th> </tr> <tr> <th>Horse</th> <td>Mare</td> </tr> <tr> <td>Stallion</td> </tr> <tr> <th>Crocodile</th> </tr> <tr> <th>Chicken</th> <td>Hen</td> </tr> <tr> <td>Rooster</td> </tr> </table> لكن لا يعطي الخرج النتيجة التي نريدها تمامًا: نريد مثلًا أن يمتد "Hippopotamus" أو "Crocodile" مقدار عمودين، وأن يمتد Horse" و "Chicken" مقدار صفين، إذ تمتلك ترويسة الجدول لهذه الغاية السمتَين colspan و rowspan اللتين تقبلان قيمًا بلا واحدة تدل على عدد الصفوف أو الأعمدة التي نريد أن تمتد الترويسة عليهما، لذا ستسمح القيمة "2"=colspan مثلًا لترويسة عمود أن تمتد بمقدار عمودين، إذًا لنستخدِم هاتين السمتين لتحسين مظهر الجدول السابق: احفظ نسخةً عن الملفين animals-table.html و minimal-table.css في مجلد جديد على حاسوبك، إذ يحتوي ملف HTML على المعلومات ذاتها التي يعرضها الجدول السابق. استخدم السمة colspan لتجعل الخلايا "Animals" و "Hippopotamus" و "Crocodile" تمتد بمقدار عمودَين. استخدم السمة rowspan لتجعل الخلايا "Horse" و "Chicken" تمتد بمقدار صفَّين. احفظ التغيرات التي أجريتها على الملف ثم حمّله في المتصفح لترى النتيجة. ملاحظة: يمكنك إيجاد المثال السابق بصيغته النهائية ضمن المستودع المخصص له على جيت-هاب. تزويد الأعمدة ببعض التنسيقات تقدِّم HTML طريقةً لتنسيق محتوى عمود بأكمله (عناصر <col> وعناصر <colgroup>)، وقد وُجِدت هذه الطريقة لأنّ عملية تنسيق كل عنصر <td> أو <th> على حدة في عمود ستكون مزعجةً وغير فعّالة وسيكون الأمر كذلك عند استخدام محدِّد تنسيق معقَّد مثل nth -child:. ملاحظة: تنسيق العمود بهذه الطريقة مخصص لبعض السمات فقط، وهي:border و background و width و visibility، فإذا احتجت لاستخدام خصائص أخرى، فلا بدّ من تنسيق كل عنصر <td> أو <th> على حدة، أو استخدام محدد تنسيق معقّد مثل nth -child:. لنلق نظرةً على هذا المثال: <table> <tr> <th>Data 1</th> <th style="background-color: yellow">Data 2</th> </tr> <tr> <td>Calcutta</td> <td style="background-color: yellow">Orange</td> </tr> <tr> <td>Robots</td> <td style="background-color: yellow">Jazz</td> </tr> </table> ستكون النتيجة كما يلي: إنّ الطريقة المتبعة في الشيفرة السابقة ليست ملائمةً، إذ علينا كتابة التنسيق في كل خلية من خلال العمود، فمن السهل في هذه الحالة -إن كنا نعمل على مشروع واقعي- استخدام السمة class وتحديد التنسيق المطلوب مرةً واحدةً ضمن ملف تنسيق منفصل، وبدلًا من ذلك يمكننا تحديد معلومات التنسيق هذه مرةً واحدةً فقط ضمن عنصر <col> يقع داخل العنصر <colgroup> الذي يُدرج مباشرةً أسفل وسم بداية الجدول <table>، وإليك الطريقة: <table> <colgroup> <col> <col style="background-color: yellow"> </colgroup> <tr> <th>Data 1</th> <th>Data 2</th> </tr> <tr> <td>Calcutta</td> <td>Orange</td> </tr> <tr> <td>Robots</td> <td>Jazz</td> </tr> </table> عرّفنا عمودين اثنين، الأول فارغ ولا يحتوي على أي تنسيق لكنه ضروري لكي لا يتغير تنسيق العمود الثاني المطبق، وإذا أردت تنسيق كلا العمودَين، فاكتف بعنصر <col> واحد يحمل التنسيق المطلوب شرط استخدام السمة span: <colgroup> <col style="background-color: yellow" span="2"> </colgroup> تأخذ هذه السمة قيمًا على صورة أرقام بلا واحدة مثل سمة colspan وسمة rowspan التي تدل على عدد الأعمدة التي سيطبَّق عليها التنسيق. تطبيق: تنسيق أعمدة ترى في الجدول التالي برنامجًا زمنيًا لمواعيد مدرِّسة لغات، ولدى هذه المدرّسة صفًا جديدًا لتعليم الهولندية Dutch يوم الجمعة بأكمله، وتدرِّس الألمانية German على فترات يومَي الثلاثاء والخميس، وتريد المدرّسة تلوين أعمدة الأيام التي تُدرِّس فيها: أعد إنشاء الجدول السابق متبعًا الخطوات التالية: احفظ نسخةً عن الملف timetable.html في مجلد جديد على حاسوبك، إذ يحتوي ملف HTML على المعلومات ذاتها التي يعرضها الجدول السابق ما عدا معلومات تنسيق الأعمدة. أضف العنصر <colgroup> إلى العنصر <table> وتحت وَسم البداية مباشرةَ، ثم أضف إلى هذا العنصر عناصر <col> لإضافة تنسيقات إلى الأعمدة. لا تغيِّر تنسيق أول عمودين. أضف لونًا إلى خلفية العمود الثالث بضبط قيمة السمة style على ;background-color:#97DB9A. استخدم عرضًا مختلفًا للعمود الرابع بضبط قيمة السمة style على ;width: 42px. أضف لونًا إلى خلفية العمود الخامس بضبط قيمة style على ;background-color:#97DB9A. أضف لونًا مختلفًا وإطارًا إلى العمود السادس لتشير إلى أنه يوم مميز، إذ ستبدأ بتدريس لغة جديدة، ولذلك اضبط قيمة style على ;background-color:#DCC48E; border:4px solid #C1437A إذا وجدت نفسك عالقًا أمام مشكلة ما أو أردت التحقق من عملك، فيمكنك مراجعة النسخة النهائية للمثال على جيت-هاب. خلاصة عرضنا في هذا المقال أساسيات إنشاء جداول في HTML، إذ سنلقي نظرةً في المقال التالي على ميزات أكثر تقدمًا، مع الأخذ بالحسبان سهولة وصول ذوِي الإعاقة البصرية. للمزيد من التفاصيل، ارجع إلى توثيق الجداول في موسوعة حسوب. ترجمة -وبتصرَّف- للمقال HTML table basics. اقرأ أيضًا كيفية إنشاء جدول أنيق بـ HTML و CSS جداول html ذات رأسية وأعمدة ثابتة باستخدام jQuery الجداول (Tables) في CSS
  18. القالب Template هو ملف نصي يحدّد بنية أو تخطيط ملف الخرج مع استخدام العناصر البديلة لتمثيل مكان إدراج البيانات عند عرض القالب، ويُشار إلى القوالب في إطار عمل Express بوصفها عروضًا Views. خيارات قوالب Express يمكن استخدام إطار عمل Express مع العديد من محرّكات عرض القوالب المختلفة، إذ سنستخدم في هذا المقال Pug (المعروفة سابقًا باسم Jade) لقوالبنا، والتي تُعَد لغة قوالب Node الأكثر شيوعًا، وتصف نفسها بأنها صيغة نظيفة وحساسة للمسافات لكتابة شيفرة HTML وهي متأثرة كثيرًا بلغة Haml. تستخدم لغات القوالب المختلفة طرقًا مختلفة لتعريف التخطيط Layout وتحديد العناصر البديلة للبيانات، إذ يستخدم بعضها شيفرة HTML لتعريف التخطيط، ويستخدم بعضها الآخر تنسيقات توصيف مختلفة يمكن تحويلها إلى شيفرة HTML. تُعَد Pug من النوع الثاني، إذ تستخدم تمثيلًا للغة HTML، إذ تُمثَّل الكلمةُ الأولى في أيّ سطر عنصرَ HTML، وتُستخدَم المسافة البادئة في الأسطر اللاحقة لتمثيل التداخل، وتكون النتيجة تعريف صفحة تُترجَم مباشرةً إلى شيفرة HTML، ولكنها أكثر إيجازًا وأسهل قراءة. ملاحظة: سلبية استخدام Pug أنها حساسة للمسافات البادئة والمسافات البيضاء، فإذا أضفتَ مسافة إضافية في المكان الخطأ، فقد تحصل على رمز خطأ غير مساعد، ولكن يصبح من السهل جدًا قراءة قوالبك وصيانتها بعد الانتهاء منها. ضبط القوالب ضبطنا موقع المكتبة المحلية LocalLibrary لاستخدام Pug عندما أنشأنا موقع الويب الهيكلي، إذ يجب أن تُضمَّن وحدة pug بوصفها اعتمادية في ملف package.json الخاص بموقع الويب، ويجب أن ترى إعدادات الضبط التالية في الملف app.js؛ إذ تخبرنا هذه الإعدادات أننا نستخدم Pug بوصفها محرّك عرض، وأن إطار عمل Express يجب أن يبحث عن القوالب في المجلد الفرعي "‎/views". // إعداد محرك العروض app.set("views", path.join(__dirname, "views")); app.set("view engine", "pug"); إذا بحثتَ في مجلد العروض views، فسترى ملفات "‎.pug" للعروض الافتراضية الخاصة بالمشروع، ويتضمن ذلك عرض الصفحة الرئيسية index.pug والقالب الأساسي layout.pug الذي يجب أن نضع مكانه محتوانا. /express-locallibrary-tutorial //the project root /views error.pug index.pug layout.pug صيغة القوالب يوضّح ملف القالب التالي العديد من ميزات Pug الأكثر فائدة، فأول شيء يجب ملاحظته هو أن الملف يربط بنية ملف HTML نموذجي مع الكلمة الأولى في كل سطر (تقريبًا) التي تكون عنصر HTML، ومع المسافة البادئة التي تُستخدَم للإشارة إلى العناصر المتداخلة، فمثلًا يوجد عنصر body داخل عنصر html، وتوجد عناصر الفقرة (p) داخل عنصر body وغير ذلك، وتكون العناصر غير المتداخلة مثل الفقرات الفردية موجودةً في أسطر منفصلة. doctype html html(lang="en") head title= title script(type='text/javascript'). body h1= title p This is a line with #[em some emphasis] and #[strong strong text] markup. p This line has un-escaped data: !{'<em> is emphasized</em>'} and escaped data: #{'<em> is not emphasized</em>'}. | This line follows on. p= 'Evaluated and <em>escaped expression</em>:' + title <!-- مباشرةً HTML يمكنك إضافة تعليقات--> // يمكنك ‫إضافة تعليقات جافاسكربت ذات سطر واحد وتُنشَأ في تعليقات HTML //- ‫يضمن تقديم تعليق جافاسكربت مؤلف من سطر واحد مع "-//" عدم عرض التعليق بوصفه شيفرة HTML p A line with a link a(href='/catalog/authors') Some link text | and some extra text. #container.col if title p A variable named "title" exists. else p A variable named "title" does not exist. p. Pug is a terse and simple template language with a strong focus on performance and powerful features. h2 Generate a list ul each val in [1, 2, 3, 4, 5] li= val تُعرَّف سمات العنصر بين أقواس بعد العنصر المرتبط بها، وتُعرَّف السمات ضمن الأقواس في قوائم مفصولٌ بينها بفواصل أو بمسافات بيضاء لأزواج أسماء السمات وقيمها مثل: script(type='text/javascript'), link(rel='stylesheet', href='/stylesheets/style.css') أو meta(name='viewport' content='width=device-width initial-scale=1') جرى تهريب escaped قيم جميع السمات مثل تحويل محارف مثل المحرف "<" إلى ما يكافئها من شيفرة HTML مثل "‎&gt;‎" لمنع هجمات حقن شيفرة جافا سكريبت أو هجمات السكريبتات العابرة للمواقع cross-site scripting attacks - أو اختصارًا XSS. إذا كان الوسم متبوعًا بعلامة مساواة، فسيُتعامَل مع النص التالي بوصفه تعبير جافا سكريبت، فمثلًا سيكون محتوى الوسم h1 في السطر الأول متغير title (إما مُعرَّفًا في الملف أو مُمرَّرًا إلى القالب من إطار عمل Express). يكون محتوى الفقرة في السطر الثاني سلسلة نصية متعاقبة مع المتغير title. يكون السلوك الافتراضي في كلتا الحالتين هو تهريب السطر. h1= title p= 'Evaluated and <em>escaped expression</em>:' + title إذا لم يكن هناك رمز مساواة بعد الوسم، فسيُتعامَل مع المحتوى بوصفه نصًا عاديًا، إذ يمكنك ضمن النص العادي إدخال البيانات التي جرى تهريبها والتي لم يجرِ تهريبها باستخدام الصيغتين {}# و {}! على التوالي، ويمكنك أيضًا إضافة شيفرة HTML خام ضمن النص العادي. p This is a line with #[em some emphasis] and #[strong strong text] markup. p This line has an un-escaped string: !{'<em> is emphasized</em>'}, an escaped string: #{'<em> is not emphasized</em>'}, and escaped variables: #{title}. ملاحظة: سترغب دائمًا في تهريب البيانات من المستخدمين باستخدام صيغة {}#، وقد تُعرَض البيانات التي يمكن الوثوق بها، مثل أعداد السجلات المُولَّدة وما إلى ذلك دون تهريب القيم. يمكنك استخدام محرف الشريط العمودي ('|') في بداية السطر للإشارة إلى النص العادي، فمثلًا سيُعرَض النص الإضافي التالي على سطر الرابط نفسه، ولكنه لن يمثل رابطًا: a(href='http://someurl/') Link text | Plain text تسمح Pug بإجراء عمليات شرطية باستخدام if و else و else if و unless مثل: if title p A variable named "title" exists else p A variable named "title" does not exist يمكنك أيضًا إجراء عمليات الحلقة أو التكرار باستخدام صيغة each-in أو while، إذ أنشأنا في جزء الشيفرة البرمجية التالي حلقةً تكرارية تمر عبر عناصر مصفوفة لعرض قائمة متغيرات. لاحظ استخدام ''li=‎' لتقييم "val" بوصفه متغيرًا، ويمكن أيضًا تمرير القيمة التي تكررها إلى القالب بوصفها متغيرًا. ul each val in [1, 2, 3, 4, 5] li= val تدعم الصيغة أيضًا التعليقات (التي يمكن عرضها في الخرج أو عدم عرضها وفق ما تريده)، والمخاليط Mixins لإنشاء كتل من الشيفرة البرمجية قابلة لإعادة الاستخدام، وتعليمات الحالة، والعديد من الميزات الأخرى. اطلع على توثيق Pug لمزيد من المعلومات. توسيع القوالب من المعتاد أن يكون لجميع الصفحات بنية مشتركة عبر الموقع، بما في ذلك شيفرة HTML المعيارية لرأس وتذييل الصفحة وقسم التنقل navigation وغير ذلك، لذا تسمح Pug بالتصريح عن قالب أساسي ثم توسيعه، وتبديل الأجزاء المختلفة من كل صفحة بدلًا من إجبار المطورين على تكرار هذه الشيفرة المتداولة "Boilerplate" في كل صفحة. يبدو مثلًا القالب الأساسي layout.pug الذي أنشأناه في المشروع الهيكلي كما يلي: doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') body block content يُستخدَم الوسم block لتمييز أقسام المحتوى التي يمكن استبدالها في قالب مشتق، فإن لم يُعاد تعريف الكتلة، فسيُستخدَم تقديمها في الصنف الأساسي. يوضح القالب index.pug الافتراضي -الذي أنشأناه لمشروعنا الهيكلي- كيفية تعديل القالب الأساسي، إذ يحدّد الوسم extends القالب الأساسي المراد استخدامه، ثم نستخدم block section_name للإشارة إلى المحتوى الجديد للقسم الذي سنعدّله. extends layout block content h1= title p Welcome to #{title} القالب الأساسي لموقع المكتبة المحلية LocalLibrary فهمنا كيفية توسيع القوالب باستخدام Pug، وسنبدأ الآن بإنشاء قالب أساسي لمشروعنا، والذي سيحتوي على شريط جانبي مع روابط للصفحات التي نريد إنشائها في المقالات لاحقًا مثل صفحات عرض وإنشاء الكتب وأنواعها والمؤلفين وغير ذلك، وسيحتوي على منطقة للمحتوى الرئيسي سنعدّلها في كل صفحة من صفحاتنا. افتح القالب ‎/views/layout.pug وضع الشيفرة التالية مكان المحتوى الموجود مسبقًا: doctype html html(lang='en') head title= title meta(charset='utf-8') meta(name='viewport', content='width=device-width, initial-scale=1') link(rel="stylesheet", href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css", integrity="sha384-xOolHFLEh07PJGoPkLv1IbcEPTNtaed2xpHsD9ESMhqIYd0nLMwNLD69Npy4HI+N", crossorigin="anonymous") script(src="https://code.jquery.com/jquery-3.5.1.slim.min.js", integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj", crossorigin="anonymous") script(src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/js/bootstrap.min.js", integrity="sha384-+sLIOodYLS7CIrQpBjl+C7nPvqq+FbNUBDunl/OZv93DB7Ln/533i8e/mZXLi/P+", crossorigin="anonymous") link(rel='stylesheet', href='/stylesheets/style.css') body div(class='container-fluid') div(class='row') div(class='col-sm-2') block sidebar ul(class='sidebar-nav') li a(href='/catalog') Home li a(href='/catalog/books') All books li a(href='/catalog/authors') All authors li a(href='/catalog/genres') All genres li a(href='/catalog/bookinstances') All book-instances li hr li a(href='/catalog/author/create') Create new author li a(href='/catalog/genre/create') Create new genre li a(href='/catalog/book/create') Create new book li a(href='/catalog/bookinstance/create') Create new book instance (copy) div(class='col-sm-10') block content يتضمن القالب شيفرة جافا سكريبت و CSS من بوتستراب Bootstrap لتحسين تخطيط وعرض صفحة HTML؛ إذ يُعَد استخدام بوتستراب أو أي إطار عمل ويب آخر من طرف العميل طريقةً سريعة لإنشاء صفحة جذابة يمكن أن تتناسب مع أحجام المتصفحات المختلفة، ويتيح أيضًا التعامل مع عرض الصفحة دون الحاجة إلى الدخول في التفاصيل، إذ نريد فقط التركيز على الشيفرة البرمجية من طرف الخادم حاليًا. ملاحظة: تكون السكربتات مُحمَّلة على أصول مختلطة، لذا يجب السماح صراحةً بتحميل هذه الملفات لاحقًا عندما نضيف برمجية وسيطة للأمان. اطلع على فقرة استخدام حزمة Helmet للحماية من الثغرات المعروفة من مقال النشر في بيئة الإنتاج لمزيد من المعلومات. يجب أن يكون التخطيط واضحًا إلى حدٍ ما إذا قرأت القسم السابق. لاحظ استخدام كتلة block content بوصفها عنصرًا بديلًا للمكان الذي سيوضع فيه محتوى صفحاتنا. يشير القالب الأساسي أيضًا إلى ملف CSS محلي (style.css) الذي يوفر بعض التنسيق الإضافي. افتح الملف "‎/public/stylesheets/style.css" وضع شيفرة CSS التالية مكان محتواه: .sidebar-nav { margin-top: 20px; padding: 0; list-style: none; } لدينا الآن قالب أساسي لإنشاء صفحات مع شريط جانبي، وسنستخدمه في المقالات التالية لتعريف الصفحات الفردية. ترجمة -وبتصرُّف- للمقالين Template primer و LocalLibrary base template. اقرأ المزيد المقال السابق: تطبيق عملي لتعلم Express - الجزء الخامس: النشر في بيئة الإنتاج كيفية استخدام القوالب في تطبيقات فلاسك Flask. مدخل إلى إطار عمل الويب Express وبيئة Node مدخل إلى express إنشاء مدوّنة باستخدام Node.js و Express (الجزء الأول)
  19. تنظيم شيفرة CSS حالما تبدأ العمل على ملفات تنسيق ضخمة ومشاريع كبيرة ستكتشف أن صيانة ومتابعة ملفات التنسيق الضخمة ليس أمرًا سهلًا، لهذا سنلقي نظرةً في هذا المقال على بعض أفضل الممارسات التي ينبغي اتباعها في كتابة تنسيقات CSS لتسهيل الصيانة والتعديل وبعض الحلول التي يستخدمها الآخرون وتحسّن القدرة على متابعة وصيانة شيفرات التنسيق. ننصحك قبل المتابعة في قراءة هذه المقال أن: تطلع على أساسيات استخدام الحاسوب وتصفح الويب. تحضّر بيئة عمل بسيطة كما تحدثنا مسبقًا في المقال تثبيت البرمجيات الأساسية، وتتفهم طريقة إنشاء وإدارة الملفات كما شرحناها في مقال التعامل مع الملفات. تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات مدخل إلى HTML. تتفهم أساسيات CSS كما شرحناها في سلسلة المقالات خطواتك الأولى في CSS. نصائح لترتيب شيفرات CSS إليك بعض الاقتراحات العامة لطرق تنظيم وترتيب ملفات شيفرة CSS: هل هناك نموذج محدد في تنسيق شيفرة المشروع؟ إذا كنت تعمل ضمن فريق على مشروع موجود مسبقًا، عليك أن تتحقق أولًا من وجود دليل خاص بتنسيق شيفرة CSS ضمن المشروع، إذ لا بد أن تلتزم بالإرشادات الموجودة في الدليل بدلًا من تطبيق ما تفضّله أنت من القواعد. لا توجد غالبًا معايير لتحديد ما هي الطريقة الصحيحة أو الخاطئة لتنفيذ المطلوب، لكن ما يهم فعلًا هو التناسق consistency. الحفاظ على التناسق إذا كنت تعمل وفق قواعد خاصة بمشروع أو كنت تعمل وحيدًا، لا بد من المحافظة على تناسق أسلوب العمل. يمكنك أن تضمن التناسق بطرق عدة، كأن تستخدم نفس أسلوب تسمية الأصناف، أو اتباع طريقة ثابتة في وصف الألوان، أو المحافظة على الأسلوب ذاته في كتابة الشيفرة، كأن تستخدم مفتاح الجدولة Tab دائمًا لإزاحة الشيفرة ضمن الملف، أو تستخدام مفتاح المساحة الفارغة Space بدلًا عنه وفي حال استخدمت المساحة الفارغة ما هو مقدار المساحة الفارغة الذي تعتمده. يساعدك وجود قواعد ثابتة وموحدة تتبعها باستمرار على تقليل العبء الفكري عند كتابة CSS نظرًا لوجود قراراتٍ قد اتخذتها مسبقًا. تنسيق قواعد CSS لتكون سهلة القراءة يستخدم المطورون طريقتين أساسيتين في كتابة شيفرات CSS أحدها كتابة جميع القواعد على نفس السطر على النحو التالي: .box {background-color: #567895; } h2 {background-color: black; color: white; } بينما يفضل آخرون كتابة كل قاعدة على سطر جديد كما يلي: .box { background-color: #567895; } h2 { background-color: black; color: white; } لن يؤثر ذلك على تنفيذ تلك القواعد، لكن من الواضح أن كتابة كل خاصية وإلى جوارها قيمتها ضمن سطر جديد أسهل قراءةً. التعليقات على الشيفرة تساعد التعليقات التي تضيفها إلى الشيفرة أي مطوّر سيعمل مستقبلًا على شيفرتك، كما تساعدك أيضًا في تذكر ما كتبته عند عودتك إلى العمل على ملف التنسيق بعد انقطاع. /*CSS هذا مثال عن التعليقات في يمكن كتابة التعليق على أكثر من سطر */ من النصائح المفيدة في هذا الصدد هي كتابة كتلة من التعليقات بين الأقسام المنطقية في ملف التنسيق أيضًا كي يتسنى لك الوصول إلى أي منها بسهولة عند قراءة ملفك أو استخدامها مثل عبارات للبحث عن القسم المطلوب من شيفرة CSS. إذا استخدمت نصًا لا يظهر ضمن الشيفرة، يمكن الانتقال من قسم يبدأ بالمحرفين || إلى آخر بالبحث عن هذين المحرفين. /* || تنسيقات عامة */ /* … */ /* || تنسيق خطوط الكتابة */ /* … */ /* || تنسيق الترويسات وعناصر التنقل الرئيسية */ /* … */ لا حاجة إلى كتابة تعليقات حول كل ما تفعله شيفرتك، فالكثير من القواعد تشرح نفسها بنفسها، لكن ما تحتاج إلى التعليق عليه هو عادةً نقاط محددة اتخذت قرارًا عندها لأسباب خاصة، فقد تستخدم خاصية محددة بطريقة معينة لتحاشي مشكلات التوافق مع المتصفحات الأقدم. إليك مثالًا: .box { background-color: red; /* fallback for older browsers that don't support gradients */ background-image: linear-gradient(to right, #ff0000, #aa0000); } قد تستخدم كذلك قواعد تنسيق وجدتها في دورة تعليمية لكنها ليس واضحة تمامًا أي لا تشرح نفسها بنفسها، لذا من الأفضل في هذه الحالة إضافة تعليق يضم عنوان URL لتلك الدورة التعليمية. ستدرك أهمية ذلك عندما تعود إلى هذا الملف بعد فترة طويلة محاولًا تذكر أنك تعلمت شيئًا من دورة تعليمية مميزة حول موضوع تحتاجه ولا تتذكر أين اطلعت عليها. تقسيم ملف CSS إلى أقسام منطقية من الجيد أن تضع التنسيقات العامة الأكثر شيوعًا في بداية الملف، ونقصد بذلك جميع التنسيقات التي تطبّق على العناصر عمومًا ما لم ترد تخصيص قواعد محددة لعنصر محدد، وستضع غالبًا قواعد لتنسيق العناصر التالية: متن صفحة الويب body. الفقرات p. العناوين الرئيسية h1, h2, h3, h4, h5. القوائم المرتبةul وغير المرتبة ol. خصائص الجداول table. الروابط التشعبية. نضع في هذا القسم من ملف الشيفرة التنسيق الافتراضي لعناصر الموقع مثل تنسيقات جداول البيانات والقوائم وهكذا: /* || قواعد تنسيق عامة */ body { /* … */ } h1, h2, h3, h4 { /* … */ } ul { /* … */ } blockquote { /* … */ } يمكن أن نعرّف بعد هذا القسم مجموعةً من الأصناف المساعدة، مثل الأصناف التي تزيل تنسيق بعض القوائم التي نعرضها بأكثر من طريقة، فإذا أردت استخدام خيارات تنسيق متعددة وتعرف أنك ستغيّر تنسيق العديد من العناصر المختلفة لإنجاز كل خيار، من الأفضل إذًا وضع قواعد كل خيار في صنف خاص في هذا القسم. /* || الأصناف المساعدة */ .nobullets { list-style: none; margin: 0; padding: 0; } /* … */ نضع بعد ذلك كل التنسيقات التي تُستخدم على كامل الموقع، مثل تخطيط الصفحات الأساسي والترويسات وتنسيق أدوات التنقل وما شابه في قسم خاص: /* || تنسيقات كامل الصفحات */ .main-nav { /* … */ } .logo { /* … */ } نخصص أخيرًا قسمًا يضم تنسيقات CSS لأغراض مخصصة، أو ما يمكن أن يتغير مع سياق الصفحة، أو لصفحات معينة، أو حتى لمكوّنات معينة: /* || صفحات المخزن */ .product-listing { /* … */ } .product-box { /* … */ } يساعدك هذا الترتيب على الأقل في تكوين صورة عن المكان الذي تبحث فيه ضمن ملف الشيفرة عن شيءٍ ما تريد تغييره أو استبداله. تجنب استخدام محددات شديدة التخصيص عندما تبني محددًا مخصصًا جدًا، ستجد أنك ستعيد استخدام فقرات من شيفرتك مرارًا لتطبيق نفس قواعد هذا المحدد على عناصر أخرى، فلو أردت بناء محدد يستهدف الفقرات <p> التي تمتلك الصنف box وتتواجد ضمن العناصر <article> التي تمتلك الصنف main على النحو التالي: article.main p.box { border: 1px solid #ccc; } وأردت أن تكرر نفس القواعد على عناصر لا تمتلك الصنف main أو على عنصر آخر غير، فعليك حينها إضافة محدد جديد إلى هذه القواعد، أو بناء مجموعة جديدة من القواعد. يمكنك بدلًا من ذلك استخدام محدد بسيط box. لتطبيق قواعدك على أي عنصر يمتلك الصنف box: .box { border: 1px solid #ccc; } لاشك أن قد تضطر أحيانًا إلى إنشاء محدد شديد التخصيص، لكن هذا الأمر استثنائي عمومًا وليس شائعًا. تقسيم ملفات التنسيق الكبيرة إلى ملفات أصغر في الحالات التي تحتاج فيها إلى تنسيقات مختلفة جدًا لأجزاء محددة من موقعك، قد ترى ضرورة وجود ملف يتضمن القواعد العامة، إضافةً إلى ملفات أصغر تضم قواعد مخصصة لتنسيق تلك الأجزاء، ويمكنك طبعًا ربط أي صفحة باكثر من ملف تنسيق، إذ تنص قواعد التعاقب أن التنسيقات الموجودة في آخر ملف يربط مع الصفحة ستطبّق أولًا. لنفترض على سبيل المثال أن قسمًا من موقعك هو متجر إلكتروني ويحتاج إلى الكثير من القواعد المختلفة لتنسيق قوائم المنتجات والنماذج الخاصة بعمل المتجر، فمن المنطقي هنا وضع تلك القواعد في ملف تنسيق خاص يرتبط فقط مع صفحات المتجر، وهذا يسهل ترتيب شيفرتك وتنظيمها أكثر، وفي الحالات التي يعمل فيها أكثر من مطوّر على كتابة التنسيقات، ستقل منطقيًا عدد الحالات التي يُضطر فيها أكثر من مطور للعمل على الملف ذاته. أدوات أخرى قد تساعد في تنظيم شيفرة CSS ليس لدى تنسيقات CSS طريقةً خاصةً كي تُنظّم، لهذا يعتمد مستوى تناسقها عليك في المقام الأول. لقد طوّرت مجتمعات الويب أدوات كثيرة ونُهجًا تساعدك في إدارة مشاريع CSS الضخمة، وقد تصادف بعضها عند العمل مع آخرين، وطالما أنها مفيدة غالبًا، سنتحدث عن بعضها باختصار. أساليب CSS بدلًا من ابتكار قواعد خاصة في كتابة CSS، قد تستفيد من أحد النهج التي صممها مجتمع الويب واختبرها في مشاريع كثيرة. أساليب CSS هي أساسًا دليل لكتابة التنسيقات تتخذ نهجًا مهيكلًا في كتابة الشيفرة وتنظيمها. تميل هذه الأساليب إلى تصيير CSS مطوّلًا مقارنةً بما قد تفعله إذا كتبت وحسّنت كل محدد ليضم مجموعةً مخصصةً من القواعد لتنسيق المشروع. قد تكون هيكلية الملفات أضخم عند اعتماد أحد هذه الأساليب، وطالما أن الكثير منها واسع الاستخدام، فقد يفهم المطورون الآخرون هذا النهج ويستخدمونه لكتابة شيفرات CSS بنفس الأسلوب بدلًا من محاولة فهم أسلوبك الخاص. أسلوب OOCSS تدين معظم النهج التي ستواجهها بالفضل إلى مبدأ تنسيقات CSS غرضية التوجه Object Oriented CSS -أو اختصارًا OOCSS، وهو نهج اشتهر نتيجةً لأعمال نيكول سوليفان Nicole Sullivan. تنص الفكرة الأساسية لهذا النهج على فصل شيفرة CSS إلى كائنات يمكن استخدامها من جديد في أي مكان تريده في موقعك، ومن الأمثلة النموذجية لنهج OOCSS هو النمط الذي يوصف بأنه كائن الوسائط المتعددة Media Object، ويتكون هذا النمط من صورة ثابتة الحجم أو فيديو أو غيرها من العناصر من ناحية، كما يضم حاوية مرنة للمحتوى من ناحية أخرى. يُستخدم هذا النمط كثيرًا ضمن مواقع الويب لعرض التعليقات أو القوائم وغيرها. إذا لم تستخدم نهج OOCSS، لربما ستنشئ قواعد CSS مخصصة للأجزاء المختلفة التي استًخدم فيها هذا النمط، إذ قد تنشئ مثلًا صنفين أحدهما يُدعى comment ويضم جملةً من القواعد التي تنسق أجزاء المكوّن، والآخر يُدعى list-item ويضم نفس القواعد التي يضمها الصنف مع استثناءات بسيطة، إذ يضم الصنف list-item حافةً سفلية وليس لصورته حواف بينما لصور الصنف comment حواف على محيطها. .comment { display: grid; grid-template-columns: 1fr 3fr; } .comment img { border: 1px solid grey; } .comment .content { font-size: 0.8rem; } .list-item { display: grid; grid-template-columns: 1fr 3fr; border-bottom: 1px solid grey; } .list-item .content { font-size: 0.8rem; } في المقابل، ستنشئ إذا اعتمدت نهج OOCSS نمطًا واحدًا يُدعى media يضم كل التنسيقات المشتركة بين النمطين السابقين، وسيكون صنفًا أساسيًا يضم الأشياء التي تحدد كائن الوسائط المتعددة عمومًا، ثم يمكن إضافة صنف إضافي للتعامل مع الاختلافات البسيطة، وهذا ما يوسّع التنسيق السابق من عدة نواحٍ: .media { display: grid; grid-template-columns: 1fr 3fr; } .media .content { font-size: 0.8rem; } .comment img { border: 1px solid grey; } .list-item { border-bottom: 1px solid grey; } إذ ستحتاج إلى تطبيق كلا الصنفين media و comment ضمن شيفرة HTML: <div class="media comment"> <img src="" alt="" /> <div class="content"></div> </div> وكذلك ستحتاج إلى كلا الصنفين media و list-item لتنسيق عناصر القائمة: <ul> <li class="media list-item"> <img src="" alt="" /> <div class="content"></div> </li> </ul> ما فعلته نيكول سوليفان عند توصيف هذا النهج وتطويره هو دفع الأشخاص الذين لا يتبعون تمامًا نهج OOCSS لإعادة استخدام CSS عمومًا بهذا الأسلوب، وبالتالي أثرت على طريقة فهمنا لتنظيم شيفرات التنسيق. أسلوب BEM هو اختصار لعبارة Block Element Modifier وبالعربية معدّل عنصر الكتلة. تُعرّف الكتلة في BEM على أنها كيان قائم بذاته، مثل الأزرار والقوائم والشعارات. العنصر هو شيء ما، مثل عناصر القائمة أو العنوان الذي يرتبط ارتباطًا وثيقًا بالكتلة ويقع داخلها؛ أما المُعدّل modifier فهو راية تشير إلى تغيّر في تنسيق الكتلة أو العنصر. ستميز الشيفرة التي تستخدم هذا النهج من الاستخدام المكثف للشرطة السفلية والشرطة العادية ضمن أصناف CSS. يمكن على سبيل الاطلاع رؤية الأصناف المطبّقة على شيفرة HTML الخاصة بالصفحة BEM Naming conventions: <form class="form form--theme-xmas form--simple"> <label class="label form__label" for="inputId"></label> <input class="form__input" type="text" id="inputId" /> <input class="form__submit form__submit--disabled" type="submit" value="Submit" /> </form> تتشابه الأصناف الإضافية هنا مع تلك المستخدمة في مثال OOCSS، لكنها تستخدم الأسلوب الصارم في تسمية الأصناف وفق BEM. يُستخدم BEM كثيرًا في مشاريع ويب الأضخم، ويكتب الكثيرون تنسيقات CSS وفق هذا الأسلوب. من المؤكد أنك ستواجه الكثير من الأمثلة والدورات التعليمية التي تستخدم صياغة BEM دون أن تذكر سبب هيكلة CSS فيها بهذا الشكل. بعض الأنظمة الشائعة الأخرى لتنسيق شيفرة CSS هناك الكثير من الأنظمة الشائعة الأخرى، بما في ذلك معمارية CSS القابلة للتوسع والتقسيم Scalable and Modular Architecture for CSS -أو اختصارًا SMACSS- التي أنشأها جوناثان سنوك Jonathan Snook، و ITCSS لمؤلفها هاري روبيرتس Harry Roberts، و Atomic CSS (ACSS) من إنتاج ياهو أساسًا. إذا واجهتك بعض المشاريع التي تستخدم أيًا من الأساليب السابقة، ستجد لحسن الحظ العديد من المقالات والأدلة التي ترشدك إلى طريقة كتابة الشيفرات وفقها، لكن الناحية السلبية فيها هي التعقيد الزائد وخاصة عند كتابة تنسيقات لمشروعات صغيرة. الأنظمة الجاهزة لكتابة CSS من الطرق الأخرى المستخدمة في تنظيم شيفرة CSS هي استغلال بعض الأدوات المتاحة لمطوري الواجهات الأمامية، والتي تتيح نهجًا قريبًا من النهج البرمجي لكتابة CSS. فهناك العديد من الأدوات التي نشير إليها على أنها أدوات معالجة أولية pre-processors أو نهائية post-processors؛ إذ تعمل المعالجات الأولية على الملفات الخام وتحوّلها إلى ملفات تنسيق؛ بينما تعمل المعالجات النهائية على ملفات التنسيق الجاهزة، ربما لتحسينها كي تُحمّل بسرعة أكبر. لا بد أن تكون بيئة التطوير التي تستخدمها قادرةً على تنفيذ سكربتات المعالجات الأولية والثانوية قبل أن تفكر في استخدام تلك الأدوات. تدعم الكثير من محررات الشيفرة هذه الأمور، كما تستطيع تثبيت أدوات تعتمد على أسطر الأوامر لمساعدتك في ذلك. من أكثر المعالجات الأولية شعبيةً هي Sass. لن نتوسع في شرح هذا المعالج بل سنكتفي بتوضيح بعض الأشياء التي يقدمها Sass، فهي مفيدة حقًا من باب تنظيم شيفرة التنسيق حتى لو لم تستخدم أية ميزات أخرى تقدمها. وإذا أردت تعلم المزيد، يمكنك العودة إلى توثيق Sass على موسوعة حسوب. تعريف متغيرات تمتلك CSS حاليًا خاصيات مخصصة أصيلة، مما يقلل أهمية ميزة المتغيرات، لكن من الأمور الجيدة التي تقدمها Sass في هذا الصدد هي القدرة على تعريف جميع الألوان والخطوط المستخدمة في المشروع على شكل إعدادات، ثم استخدام المتغيرات ضمن كامل المشروع، فلو أدركت مثلًا أنك استخدمت الدرجة الخاطئة من اللون الأزرق، عليك عندها تغييرها في مكان واحد فقط. عند استخدام متغير يُدعى base-color$ على سبيل المثال كما في السطر الأول من الشيفرة التالية، يمكننا عندها استخدامه في أي مكان من ملف الشيفرة نحتاج فيه إلى هذا اللون: $base-color: #c6538c; .alert { border: 1px solid $base-color; } عندما تُصرّف شيفرة Sass سينتهي بك الأمر إلى شيفرة CSS التالية: .alert { border: 1px solid #c6538c; } تصريف ملفات تنسيق المكونات رأينا سابقًا أن أحد طرق تنظيم CSS هي تقسيم الملف إلى ملفات أصغر، وعند استخدام Sass، يمكنك الاستفادة من هذا الأسلوب إلى حد بعيد وسينتج لديك الكثير من الملفات الصغيرة جدًا إلى درجة وجود ملف منفصل لكل مكوّن. ستتمكن عند استخدام الوظيفة "partials" المدمجة مع Sass من تصريف جميع هذه الملفات إلى عدد أقل بكثير من الملفات النهائية التي تربطها بموقع الويب، فقد يكون لديك مثلًا عدة ملفات تنسيق ضمن مجلد واحد، ولتكن foundation/_code.scss و foundation/_lists.scss و foundation/_footer.scss و foundation/_links.scss وغيرها، يمكنك حينها استخدام القاعدة use@ لتضمينها ضمن ملفات أخرى: // foundation/_index.scss @use "code"; @use "lists"; @use "footer"; @use "links"; إذا جرى تحميل جميع الأجزاء ضمن الملف "index" كما حدث في الشيفرة السابقة، يمكن حينها تحميل كامل المجلد ضمن ملف آخر بخطوة واحدة: // style.scss @use "foundation"; ملاحظة: من السهل تجريب Sass باستخدام موقع CodePen، إذ ستتمكن من تمكين Sass عند العمل مع شيفرة CSS عن طريق إعدادات "Pen"، وعندها سيشغّل CodePen محلل Sass كي ترى نتيجة عملك وكأنك طبقت قواعد CSS نمطية. قد ترى أحيانًا أن دورات تعليم CSS تستخدم Sass بدلًا من CSS النمطية في أمثلتها المكتوبة عن طريق CodePen، لهذا سيفيدك الإطلاع على ذلك قليلًا. تحسين الشيفرة باستخدام أدوات المعالجة النهائية إذا كنت تقلق من زيادة حجم ملفات CSS بزيادة كمية التعليقات مثلًا أو زيادة المساحات الفارغة، فخطوة المعالجة النهائية مفيدة هنا في التخلص من أي شيء غير ضروري في نسخة الإنتاج. من الأمثلة على أدوات المعالجة النهائية هو cssnano. الخلاصة هذا هو المقال الأخير ضمن مجموعة المقالات التي تغطي وحدات بناء CSS، وكما ترى يمكنك اتباع أساليب عدة في متابعة رحلتك في استكشاف CSS انطلاقًا من هذه النقطة. ترجمة -وبتصرف- للمقال Organizing your CSS اقرأ أيضًا تنسيقات المتصفحات المخصصة ودعمها وأداءها في CSS مدخل إلى Sass مواضيع متقدمة في CSS أنواع محددات التنسيق في CSS
  20. قد تجد أن شيفرة CSS أحيانًا لا تنفّذ ما هو متوقع منها، فقد تعتقد أن محددّا ما لا بد وأن يطابق العنصر الذي تريده، لكن لا شيء يحدث، أو قد يكون الحجم الذي تريده لصندوق ما أكبر مما هو متوقع. لهذا سنرشدك في هذا المقال كيف تتعامل وتنقح اخطاءً كهذه في CSS، كما سنتحدث عن طريقة استخدام أدوات مطوري ويب DevTools الموجودة في كل المتصفحات الحديثة لتقصي المشاكل التي تواجهك. ننصحك قبل المتابعة في قراءة هذه المقال أن: تطلع على أساسيات استخدام الحاسوب وتصفح الويب. تحضّر بيئة عمل بسيطة كما تحدثنا مسبقًا في المقال تثبيت البرمجيات الأساسية، وتتفهم طريقة إنشاء وإدارة الملفات كما شرحناها في مقال التعامل مع الملفات. تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات مدخل إلى HTML. تتفهم أساسيات CSS كما شرحناها في سلسلة المقالات خطواتك الأولى في CSS. الوصول إلى أدوات مطوري ويب عبر المتصفحات ستجد في مقال سابق بعنوان "أدوات مطوري ويب المدمجة في المتصفحات" دليلًا حديثًا يصف كيفية الوصول إلى أدوات مطوري ويب في متصفحات ويب ومنصات متعددة، وطالما أنك ستختار غالبًا متصفحًا محددًا عندما تبدأ تطوير المواقع، فستألف التعامل مع الأدوات المدمجة معه، لكن من المهم أن تعرف كيف تصل إلى هذه الأدوات في متصفحات أخرى، وهذا ما يساعدك على تكوين صورة واضحة عن الطريقة التي تصيّر فيها المتصفحات المختلفة صفحات الويب. سترى أيضًا كيف تركز المتصفحات على نقاط مختلفة عندما تبني أدوات تطوير الويب الخاصة بها، إذ يقدم لك متصفح فايرفوكس مثلًا أدوات ممتازة للتمثيل المرئي لتخطيطات CSS تسمح لك بتفحص وتحرير تخطيط الشبكة Grid Layouts أو الصندوق المرن Flexbox أو الأشكال Shapes وغيرها. وتمتلك معظم المتصفحات عمومًا الأدوات الأساسية نفسها كالأدوات المستخدمة في تفحّص الخاصيات وقيمها المطبقة على عناصر صفحة الويب، إضافةً إلى توفير إمكانية لتغيير هذه القيم من خلال نافذة المحرر. سنلقي نظرةً في هذا المقال على بعض الميزات المفيدة التي تقدمها أدوات مطوري ويب المدمجة مع متصفح فايرفوكس والمخصصة للتعامل مع CSS، لهذا السبب سنستخدم ملفًا معدًا لهذا الغرض، ويمكنك تحميله في نافذة جديدة ضمن المتصفح إذا أردت العمل عليه ومتابعة قراءة المقال في نفس الوقت، لكن لا تنسَ أن تفتح أدوات مطوري ويب كما أوردنا في المقال الذي أشرنا إليه قبل قليل. أسلوب شجرة DOM مقابل أسلوب عرض المصدر قد يثير الفرق بين ما تراه عند عرض الشيفرة المصدرية لصفحة الويب أو إلقاء نظرة على ملف HTML الموجود على الخادم، وما يمكن أن تراه في نافذة HTML Pane الموجودة ضمن أدوات مطوري ويب القلق لديك لاسيما إذا كنت حديث العهد بأدوات مطوري ويب DevTools، فعلى الرغم من أنهما متشابهتان نوعًا ما إلا أن هناك بعض الاختلافات. عندما يصيّر المتصفح ملف HTML ويبني شجرة DOM، قد يسوّي بعض شيفرات HTML، كأن يصحح تلقائيًا بعض الأخطاء أو الشيفرات المكتوبة خطأ؛ فعندما تغلق عنصرًا بطريقة خاطئة كأن تنهي الوسم <h2> بالوسم <h3/>، سيكتشف المتصفح ما تعنيه ويصحح ذلك تلقائيًا ضمن شجرة DOM، كما ستعرض شجرة DOM أية تغيرات ناتجة عن تطبيق شيفرة جافا سكريبت. تمثّل في المقابل الشيفرة المصدرية شيفرة HTML المخزّنة على الخادم، وهذا ما تعرضه شجرة HTML الخاصة بأدوات مطوري ويب، وهكذا ستبقى في صورة ما يجري في الواقع. استعراض قواعد CSS المطبقة على الصفحة اختر عنصرًا من عناصر صفحتك بالنقر عليه بالزر الأيمن ثم اختر " استعراض Inspect" من القائمة، أو اختر العنصر من شجرة HTML إلى يسار أدوات مطوري ويب. حاول اختيار العنصر الذي يمتلك الصنف box1 وهو أول عنصر في الصفحة ويحيط به صندوق رسمت حوله حواف. سترى إذا نظرت إلى "عارض القواعد Rules view" إلى يمين شيفرة HTML قواعد CSS المطبقة على العنصر مع قيمها بما في ذلك القواعد المطبقة مباشرةً من خلال المحدد box1 وتلك الموروثة من العنصر الأب وهو <body> في حالتنا. لهذا فائدته في التحقق من قواعد مطبقة لم تتوقع وجودها والتي قد تكون موروثة من العنصر الأب وتحتاج إلى إلغائها وفقًا لموضع العنصر في سياق الصفحة، كما تفيد في توسيع الخاصيات المختصرة لفهم آلية تطبيقها كما هي حال الخاصية المختصرة margin في مثالنا. انقر على زر السهم الصغير لتوسيع عرض الخاصيّة المختصرة كي ترى الخاصيات المختلفة التي تضمها وقيمها، كما يمكنك تغيير القيم في لوحة عارض القواعد إذا كانت مفعّلة. عندما تنقر على الخاصية بالفأرة وتطيل النقر، ستظهر صناديق تحقق لجميع القواعد ويمكنك عندها تفعيل أو إلغاء تفعيل أي قاعدة. يمكنك استخدام الطريقة السابقة للمقارنة بين حالتين تُطبق قواعد معينة في إحداها ولا تطبق في الثانية، كما تساعدك في التنقيح كأن تتحقق مثلًا من عدم استخدام الخاصية border-radius التي يسبب وجودها أخطاءً في تطبيق التخطيط الذي تعتمده. تعديل قيم الخاصيات يمكنك أيضًا من خلال أدوات مطوري ويب تعديل قيم الخاصيات أيضًا إضافةً إلى تفعيلها أو تعطيلها، فقد ترغب في تجريب ألوان أخرى أو تغيير حجم عنصر ما، إذ توفّر أدوات مطوري ويب وقتك في تحرير قواعد CSS وإعادة تحميل الصفحة مع القيم الجديدة. اختر المحدد (box1 مثلًا)، ثم انقر على الدائرة الملونة الصغيرة التي تظهر اللون المطبق على الحواف ليظهر لك مربع اختيار اللون. انقر على اللون الذي تريده وسترى كيف سيُطبق اللون الجديد مباشرةً على صفحتك. وبنفس الأسلوب ستكون قادر على تغيير قيم أية خاصية مثل حيّز الحواف أو تنسيقها. إضافة خاصية جديدة تستطيع أيضًا إضافة خاصيات جديدة من خلال أدوات مطوري ويب، فقد لا ترغب مثلًا أن يرث الصندوق حجم الخط من العنصر الأب <body> بل تريد ضبط حجم الخط بنفسك. تساعدك أدوات مطوري ويب في تجربة هذا الأمر قبل تغيير القواعد في ملف CSS. انقر على القوس المعقوص في القاعدة كي تدخل تصريحًا جديدًا ضمنها، ثم ابدأ بكتابة اسم الخاصية وستعرض لك أدوات مطوري ويب قائمة إكمال تلقائية للقواعد التي تطابق الحرف الذي تكتبه، وبعد أن تختار الخاصية font-size، ادخل القيمة التي تراها مناسبة. يمكنك أيضًا النقر على الزر "+" لإضافة تصريح جديد إلى نفس القاعدة، أو إضافة قواعد جديدة. ملاحظة: ستجد بعض الميزات المفيدة الأخرى في "عارض القواعد" كأن تجد الخاصيات التي أسندت إليها قيم خاطئة قد شطبت. نظرة أقرب إلى نموذج الصندوق في CSS ناقشنا في مقال سابق نموذج الصندوق المتبع في تنسيق CSS، كما تعرفنا على حقيقة وجود نموذج صندوق بديل يغيّر طريقة حساب حجم العنصر بناءً على الحجم الذي تريد، إضافةً إلى قيم الحشوات padding والحواف borders. لهذا، يمكنك الاستعانة بأدوات مطوري ويب في فهم كيفية حساب أبعاد العناصر، إذ تظهر لك لوحة "عارض التخطيط Layout view" عرضًا لنموذج الصندوق وقد أحاط بالعنصر الذي اخترته إضافة إلى وصف الخاصيات وقيمها وكيفية تموضع العنصر، ويتضمن ذلك أيضًا وصفًا للخاصيات التي لم تستخدمها صراحةً على العنصر بل تلك التي تأخذ قيمًا أساسية. من الخاصيات التي تجد تفصيلها في هذه اللوحة الخاصية box-sizing التي تحدد نموذج الصندوق المستخدم مع العنصر الذي تختاره، ولكي ترى كيفية عملها تابع المثال التالي: قارن بين كلا الصندوقين الذين يمتلكان الصنفين box1 و box2 ولهما نفس الحيّز width وقيمته (400 بكسل) لكن يبدو الصندوق box1 أوسع. لاحظ من خلال لوحة التخطيط كيف يستخدم هذا الصندوق القيمة content-box التي تأخذ الحجم الذي تضعه للصندوق ثم تضيف إليه حيّز الحشوة padding width وحيّز الإطار border width لهذا سيبدو أوسع. يستخدم العنصر الذي يمتلك الصنف box2 القيمة border-box التي تطرح قيمة حيّز الحشوة والإطار من الحيّز الذي وضعته، وهذا يعني أن حيّز الصندوق سيبقى مساويًا للحيّز الذي اخترته بدايةً وهو (400 بكسل). حل المشاكل المتعلقة بتخصيص محددات CSS قد تجد صعوبةً أحيانًا في تطبيق بعض قواعد CSS أثناء تطوير صفحات ويب، وخاصةً عند العمل على تنسيقات جاهزة على موقع موجود، فلن تتمكن من تطبيق القواعد التي تريدها على العنصر مهما حاولت. ما يحدث عادةً في هذه الحالات هو وجود محدد معين يلغي التغييرات التي تحاول تطبيقها كونه أكثر تخصيصًا (أي له الأولوية)، وهنا ستساعدك أدوات مطوري ويب كثيرًا. ستجد في الملف الذي نعمل عليه كلمتان محاطتان بالعنصر <em>، أحدهما تُعرض باللون البرتقالي والأخرى بالزهري، وإليك قاعدة CSS المطبقة: em { color: hotpink; font-weight: bold; } لكن هنالك أيضًا وقبل هذه القاعدة المحدد special.: .special { color: orange; } لقد ناقشنا موضوع تخصيص المحددات في المقال الذي يتحدث عن تعاقب قواعد CSS والوراثة ورأينا أن محددات الصنف class selectors أكثر تخصيصًا من محددات العناصر element selectors وبالتالي ستطبق قواعد هذه المحددات. ستساعدك أدوات مطوري ويب في حل هذا النوع من المشاكل، خاصةً إذا كانت المعلومات مختبئة في مكان ما من ملف تنسيق ضخم. استعرض الوسم <em> ذو الصنف special.، وستدلك أدوات مطوري ويب أن اللون البرتقالي هو ما يُطبّق وستجد أن الخاصية المطبقة على الوسم <em> قد شُطبت، وهكذا ستلاحظ أنه محدد الصنف قد تجاوز محدد العنصر. تفاصيل أكثر عن أدوات مطوري ويب المدمجة مع متصفح فايرفوكس ستجد الكثير من المعلومات عن أدوات مطوري ويب من خلال شبكة مطوري موزيلا MDN، كما يمكنك إلقاء نظرة على قسم أدوات مطوري ويب لتتعمق في الأفكار التي طرحناها باختصار في هذا المقال. تنقيح مشاكل CSS تقدم لك أدوات مطوري ويب دعمًا كبيرًا لحل مشاكل تنسيق CSS، لهذا إذا واجهتك المشاكل ووجدت أن التنسيق المطبق يختلف عما تتوقع، فقد تساعدك الخطوات التالية: تراجع خطوة إلى الخلف وألق نظرة على المشكلة قد تكون الأخطاء في كتابة الشيفرة أمرًا محبطًا وخاصة عند التعامل مع لغة CSS، لأنك لن ترى غالبًا أية رسالة خطأ كي تبحث عن سببها في الإنترنت وتجد الحل. إذا أصابك الإحباط فاترك المشكلة كما هي لفترة، اخرج لتتمشى قليلًا أو تحدث مع زميلك، أو اعمل على موضوع آخر، فقد يظهر الحل فجأة إذا توقفت عن التفكير في المشكلة، فإن لم يظهر ستجد أن العمل على حلها من جديد بعد الاستراحة سيكون أسهل. هل شيفرة HTML و CSS مكتوبة جيدًا؟ تتوقع المتصفحات أن تكون شيفرات HTML و CSS مكتوبةً صحيحًا بدون أخطاء، ومع هذا تتساهل المتصفحات كثيرًا مع الأخطاء وتحاول جهدها عرض صفحاتك حتى لو احتوت اخطاءً في التوصيف أو التنسيق، إذ تحاول المتصفحات توقع ما تريده، وقد تتخذ قرارات تخالف ما نويت إنجازه. إضافةً إلى ذلك قد يتخذ متصفحان مختلفان قرارين مختلفين حيال نفس المسألة، لذلك عليك أن تنفّذ شيفراتك ضمن محللات validator خاصة لاستكشاف هذا النوع من الأخطاء وإصلاحها أولًا، مثل محلّل CSS ومحلل HTML. هل يدعم المتصفح الخاصية التي تستخدمها وقيمها؟ تتجاهل المتصفحات خاصيات CSS التي لا تفهمها، فإذا لم تكن الخاصية أو القيمة مدعومةً من قبل المتصفح فلن تلاحظ ذلك، إلا أن الخاصية أو القيمة لن تعملا. تُظلل أدوات مطوري ويب الخاصيات والقيم غير المدعومة بطريقة معينة كما تظهر لقطة الشاشة التالية، إذ لا يدعم المتصفح المستخدم القيمة subgrid للخاصية grid-template-columns: يمكنك أيضًا إلقاء نظرة على جداول دعم المتصفحات المختلفة للخاصيات والقيم، إذ تظهر هذه الجداول دعم المتصفح لخاصية أو قيمة كما يُظهر إمكانية فشل المتصفح في استخدام الخاصية أو القيم في أشكال معينة للاستخدام. هل هناك شيء آخر يتجاوز قواعد CSS التي تكتبها؟ تظهر هنا أهمية المعلومات التي تعلمتها عن تخصيص المحددات، فإذا كان هناك فعلًا ما يتجاوز القاعدة التي تريد تطبيقها ستضطر حينها إلى الدخول في متاهة البحث عن الموضوع ولن يكون الأمر سهلًا. قد تساعدك هنا أدوات مطوري ويب في تحديد الخاصية التي تُطبّق وبالتالي ستتمكن من اختيار محدد أكثر تخصيصًا لتطبيق القواعد التي تريدها. حاول بناء اختبار محدود للتحقق من مشكلتك إذا لم تُحلّ المشكلة باتباع الخطوات السابقة، لا بد حينها من التحري عن صلب المشكلة أكثر، وأفضل شيء يمكنك فعله في هذه الحالة هو ما يُدعى "الاختبار المحدود Reduced test case"؛ فالقدرة على اختزال مشكلتك مهارة مفيدة جيدًا، إذ تساعدك على إيجاد المشاكل في شيفرتك وشيفرة زملائك، وتمكنك من الإبلاغ عن الثغرات وطب المساعدة بفعالية أكبر. أما الاختبار المحدود فهو شيفرة تعرض المشكلة بأبسط شكل ممكن دون أي ارتباط بالمحتوى المجاور ودون أية تنسيقات مسبقة، ويعني ذلك اجتزاء الشيفرة التي تسبب المشكلة وصنع مثال بسيط عنها يعرض الشيفرة أو الميزة التي سببت حدوث المشكلة. ينبغي عليك اتباع الخطوات التالية لبناء الاختبار المحدود: إذا كانت شيفرة HTML مولّدة ديناميكيًا (عبر نظام إدارة محتوى مثلًا)، ابنِ نسخةً ساكنةً من الخرج الذي يسبب المشكلة. قد تساعدك في هذا الأمر بعض المواقع، مثل CodePen التي يُعد موقعًا مفيدًا لاستضافة الاختبارات المحدودة، إضافةً إلى إمكانية الوصول إليها عبر الإنترنت وبالتالي ستسهل مشاركتها مع الزملاء. يمكنك أن تبدأ العملية بعرض الشيفرة المصدرية للصفحة، ثم نسخ شيفرة HTML إلى موقع CodePen، ثم تحضر تنسيقات CSS أو شيفرة جافا سكريبت وإضافتها إلى الاختبار أيضًا. تحقق عندها إذا استمرت المشكلة في الظهور أم لا. إذا لم يحل حذف شيفرة جافا سكريبت المشكلة، لا حاجة لتضمين ملفات جافا سكريبت في الاختبار المحدود، لكن إذا حل حذفها المشكلة، حاول حذف أكبر قدر ممكن من الشيفرة لإبقاء الشيفرة التي تسبب المشكلة فقط. احذف شيفرة HTML التي لا تتعلق بالمشكلة، وحاول أن تحذف المكوّنات أو حتى العناصر الرئيسية للتخطيط، أي حاول باختصار أن تقلل حجم الشيفرة قدر الإمكان كي تبقى فقط تلك التي سببت المشكلة. احذف تنسيقات CSS التي لا تتعلق بالمشكلة. قد تكتشف خلال تنفيذك الخطوات السابقة سبب المشكلة، أو تتمكن على الأقل من تجاوزها وإعادتها عن طريق حذف بعض الأشياء أو إعادتها. قد يفيدك أيضًا إضافة بعض المكوّنات إلى شيفرتك عندما تكتشف دليلًا إلى المشكلة، كما ننصحك بعرض ما فعلته على من تطلب منه مساعدتك. ربما ستكون المعلومات التي تحصل عليها كافية للبحث عن المشاكل المحتملة والعمل على حلها. إذا استمرت المعاناة في البحث عن المشكلة، ستساعدك المعلومات التي حصلت عليها من الاختبار المحدود في طلب العون عن طريق نشرها ضمن منتديات متخصصة أو عرضها على زميل، إذ ستزداد فرصة الحصول على المساعدة إن استطعت أن تُظهر كيف اختزلت المشكلة واستطعت تحديد مكانها تمامًا، وهكذا قد يتمكن مطوّر أكثر خبرة من رصد المشكلة بسرعة ويوجهك نحو الحل الصحيح أو على الأقل تساعده في إلقاء نظرة سريعة على المشكلة على أمل تقديم بعض المساعدة؛ أما في الحالة التي تكون فيها مشكلتك ثغرة في المتصفح نفسه، فقد يساعدك الاختبار المحدود في صياغة ملف الإبلاغ عن ثغرات وتوجيهه إلى المسؤول عن الأمر. عندما تغدو أكثر خبرة في استخدام تنسيقات CSS، ستكون أسرع في فهم المشاكل، لكن مع ذلك، تواجه أكثر المطورين خبرة بعض المشاكل المحيّرة، لهذا لا بد من اتباع طريقة منهجية لحل المشكلة، وعادة ما يكون بناء اختبار محدود وشرح هذه المشكلة لزملاء آخرين مفتاحًا للحل. الخلاصة تعرفنا في هذا المقال على أساليب تنقيح شيفرة CSS، وحاولنا تزويدك ببعض المهارات المفيدة التي تعتمد عليها عندما تبدأ بتنقيح CSS وغيرها من الشيفرات التي تواجهها لاحقًا خلال مسيرتك المهنية. ترجمة -وبتصرف- لمقال Debugging CSS اقرأ أيضًا التنسيقات الأساسية للعناصر في CSS تنسيقات المتصفحات المخصصة ودعمها وأداءها في CSS كيفية فهم وإنشاء قواعد CSS وحدات القياس والقيم في CSS التحكم في تموضع العناصر في CSS
  21. أنشأتَ واختبرتَ موقع المكتبة المحلية LocalLibrary، ويجب الآن تثبيته على خادم ويب عام ليصل إليه موظفو المكتبة وأعضاؤها عبر الإنترنت. يقدّم هذا المقال نظرة عامة حول كيفية البحث عن مضيف لنشر موقعك، وما عليك فعله لتجهيز موقعك لمرحلة الإنتاج. المتطلبات الأساسية: إكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال عرض بيانات المكتبة والعمل مع الاستمارات. الهدف: معرفة مكان وكيفية نشر تطبيق Express في بيئة الإنتاج. يجب استضافة موقعك بعد الانتهاء منه (أو الانتهاء منه "بما يكفي" لبدء الاختبار العام) في مكان أعم ويمكن الوصول إليه من أجهزة أخرى غير حاسوبك الشخصي الخاص بالتطوير. عملتَ حتى الآن في بيئة تطوير باستخدام خادم ويب تطوير Express/Node لمشاركة موقعك على المتصفح أو الشبكة المحلية، وشغّلتَ موقعك باستخدام إعدادات تطوير (غير آمنة) بحيث يمكن الوصول إلى معلومات تنقيح الأخطاء والمعلومات الخاصة الأخرى. يجب عليك أولًا تنفيذ الخطوات التالية قبل أن تتمكّن من استضافة موقع ويب خارجيًا: اختيار بيئة لاستضافة تطبيق Express. إجراء بعض التغييرات على إعدادات مشروعك. إعداد بنية تحتية على مستوى الإنتاج لتخديم موقع الويب. يقدّم هذا المقال بعض الإرشادات حول الخيارات المتاحة لاختيار موقع استضافة، ونظرة ًعامة مختصرة على ما يجب تطبيقه لتجهيز تطبيق Express لبيئة الإنتاج، ويقدم مثالًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على خدمة الاستضافة السحابية Railway. ما هي بيئة الإنتاج؟ بيئة الإنتاج هي البيئة التي يوفّرها حاسوب الخادم إذ ستشغِّل موقعك للاستخدام الخارجي، وتشمل هذه البيئة ما يلي: عتاد الحاسوب الذي يعمل عليه الموقع. نظام التشغيل، مثل لينكس أو ويندوز. وقت التشغيل الخاص بلغة البرمجة ومكتبات أطر العمل التي كُتِب عليها موقعك. خادم الويب المُستخدَم لتخديم الصفحات والمحتويات الأخرى، مثل خادم إنجن إكس Nginx وأباتشي Apache. البنية التحتية لخادم الويب وتتضمن خادم الويب والوكيل العكسي Reverse Proxy وموازن الحِمل Load Balancer وغير ذلك. قواعد البيانات التي يعتمد عليها موقعك. يمكن أن يكون حاسوب الخادم موجودًا في مقر عملك ومتصلًا بالإنترنت من خلال رابط سريع، ولكن يمكن استخدام حاسوب مستضاف على السحابة، وهذا يعني تنفيذ شيفرتك البرمجية على بعض الحواسيب البعيدة (أو على حاسوب افتراضي) في مركز أو مراكز بيانات شركتك المضيفة. يقدّم الخادم البعيد عادةً مستوًى مضمونًا من موارد المعالجة، مثل وحدة المعالجة المركزية والذاكرة RAM وذاكرة التخزين وغير ذلك والاتصال بالإنترنت بسعر معين. يشار إلى هذا النوع من عتاد المعالجة والتشبيك الذي يمكن الوصول إليه عن بُعد باسم البنية التحتية كخدمة Infrastructure as a Service -أو IaaS اختصارًا. يوفّر العديد من بائعي خدمة IaaS خيارات التثبيت المُسبَق لنظام تشغيل معين، والذي يجب تثبيت مكونات بيئتك الإنتاجية الأخرى عليه، ويسمح لك البائعون الآخرون باختيار بيئات كاملة الميزات، ويمكن أن يتضمن ذلك إعداد بيئة Node الكاملة. ملاحظة: يمكن للبيئات المبنية مسبقًا أن تسهّل إعداد موقعك لأنها تقلل من عملية الضبط Configuration، ولكن يمكن أن تقيّدك الخيارات المتاحة بخادم غير مألوف لك (أو بمكونات أخرى) ويمكن أن تستند إلى نسخة أقدم من نظام التشغيل. يُفضَّل غالبًا تثبيت المكونات بنفسك حتى تحصل على المكونات التي تريدها، وبالتالي ستعرف من أين تبدأ عندما تحتاج إلى ترقية أجزاء من النظام. يدعم مزوّدو الاستضافة الآخرون إطار عمل Express بوصفه جزءًا من استضافة المنصة كخدمة Platform as a Service -أو PaaS اختصارًا، فلا داعي للقلق في هذا النوع من الاستضافة بشأن معظم أجزاء بيئتك الإنتاجية (الخوادم وموازنو الحِمل load balancers وغيرها) لأن المنصة المضيفة تهتم بهذه الأشياء نيابةً عنك، مما يجعل النشر سهلًا جدًا، لأنك تحتاج فقط إلى التركيز على تطبيق الويب وليس على بنية الخادم التحتية الأخرى. سيختار بعض المطورين المرونة المتزايدة التي توفرها استضافة IaaS على حساب استضافة PaaS، بينما سيقدّر المطورون الآخرون تقليل تكاليف الصيانة وسهولة توسيع نطاق استضافة PaaS. يكون إعداد موقعك على نظام PaaS أسهل بكثير في البداية، وهذا ما سنفعله في هذا المقال. ملاحظة: إذا اخترت مزوّد استضافة متوافق مع Node/Express، فيجب أن يقدّم إرشادات حول كيفية إعداد موقع Express باستخدام عمليات ضبط مختلفة لخادم الويب وخادم التطبيق والوكيل العكسي وغير ذلك. اختيار مزود الاستضافة يوجد العديد من مزوّدات الاستضافة المعروفة التي تدعم بنشاط أو تعمل بصورة جيدة مع بيئة Node و Express، ويوفّر هؤلاء البائعون أنواعًا مختلفة من البيئات (IaaS و PaaS) ومستويات مختلفة من موارد المعالجة والشبكات بأسعار مختلفة. ملاحظة: هناك الكثير من حلول الاستضافة، ويمكن أن تتغير خدماتها وأسعارها بمرور الوقت. سنقدّم بعض الخيارات، ولكن يجدر بك التحقق من هذه الخيارات وغيرها قبل اختيار مزود الاستضافة. إليك بعض الأشياء التي يجب مراعاتها عند اختيار المضيف: مدى انشغال موقعك المُحتمَل وتكلفة البيانات وموارد المعالجة المطلوبة لتلبية هذا المطلب. مستوى الدعم للتوسّع أفقيًا (إضافة المزيد من الأجهزة) وعموديًا (الترقية إلى أجهزة أقوى) وتكاليف ذلك. مكان مراكز بيانات المزوّد، أي المكان الذي يكون الوصول إليه أسرع. أداء وقت التشغيل ووقت التعطل السابقَين للمضيف. الأدوات المتوفرة لإدارة الموقع، لمعرفة إذا كانت سهلة الاستخدام وآمنة، مثل استخدام بروتوكول SFTP أو استخدام بروتوكول FTP. أطر العمل المبنية مسبقًا لمراقبة خادمك. القيود المعروفة، إذ سيوقِف بعض المضيفين عمدًا خدمات معينة مثل البريد الإلكتروني، ويقدّم البعض الآخر فقط عددًا معينًا من ساعات "النشاط" في بعض مستويات الأسعار، أو يقدّم فقط قدرًا صغيرًا من التخزين. الفوائد الإضافية، إذ ستقدّم بعض المزوّدات أسماء نطاقات مجانية ودعمًا لشهادات SSL التي يمكن أن يتعيّن عليك دفع ثمنها. معرفة ما إذا كان المستوى "المجاني" الذي تعتمد عليه تنتهي صلاحيته بمرور الوقت، وما إذا كانت تكلفة التهجير Migrating إلى مستوًى أغلى تعني أنه كان من الأفضل استخدام بعض الخدمات الأخرى من البداية. هناك عددٌ قليل جدًا من المواقع التي توفر بيئات معالجة "مجانية" مخصصة للتقييم والاختبار في البداية، وتكون عادةً بيئات مُقيَّدة أو محدودة الموارد إلى حدٍ ما، ويجب أن تدرك أنه يمكن أن تنتهي صلاحيتها بعد فترة أولية أو يكون لديها قيود أخرى، ولكنها رائعة لاختبار المواقع ذات حركة المرور المنخفضة في بيئة مُستضافة، ويمكن أن توفر تهجيرًا سهلًا للدفع مقابل المزيد من الموارد عندما يصبح موقعك أكثر انشغالًا. تشمل الخيارات الشائعة في هذه الفئة Railway و Python Anywhere و Amazon Web Services و Microsoft Azure وغير ذلك. يقدّم معظم المزوّدين مستوًى أساسيًا مخصصًا لمواقع الإنتاج الصغيرة، والذي يوفّر مستويات أكثر فائدة من قدرة المعالجة وقيودًا أقل. يُعَد Heroku و Digital Ocean و Python Anywhere أمثلة على مزودي الاستضافة المشهورة التي لديها مستوى معالجة أساسي غير مكلف نسبيًا (في نطاق يتراوح بين 5 و 10 دولارات أمريكية شهريًا). ملاحظة: تذكّر أن السعر ليس معيار الاختيار الوحيد، فإذا كان موقعك ناجحًا، فيمكن أن تكون قابلية التوسع هي الأهم. تجهيز موقعك للنشر الأشياء الرئيسية التي يجب التفكير فيها عند نشر موقعك هي أمان الويب والأداء، إذ ستحتاج في الحد الأدنى إلى إزالة تعقّب المكدس المُضمَّن في صفحات الخطأ أثناء التطوير، وترتيب التسجيل، وضبط الترويسات المناسبة لتجنب العديد من هجمات الأمان الشائعة، لذا سنحدّد في الأقسام الفرعية التالية أهم التغييرات الواجب إجراؤها على تطبيقك. ملاحظة: هناك نصائح مفيدة أخرى في توثيق Express، لذا اطلع على أفضل ممارسات عملية الإنتاج: الأداء والموثوقية والأمان، واعتبارات نشر مشاريع Node.js وExpress على الويب. ضبط قاعدة البيانات استخدمنا حتى الآن قاعدة بيانات واحدة ثابتة في الملف app.js، وبسبب عدم احتواء قاعدة بيانات التطوير على معلومات نخشى من كشفها أو إتلافها، فليس هناك خطورة في حال تسريبها، ولكن في حال كنا نتعامل مع بيانات حقيقية، مثل معلومات المستخدم الشخصية، ستكون حماية تلك البيانات أمرًا مهمًا جدًا. نريد عادةً أن نكون قادرين على امتلاك قاعدة بيانات مختلفة لكل من الإنتاج والتطوير وكذلك الاحتفاظ بقاعدة بيانات الإنتاج منفصلة أيضًا عن الشيفرة المصدرية بهدف حمايتها. إذا كان مزود الاستضافة الخاص بك داعمًا لمتغيرات البيئة من خلال واجهة الويب، ستكون إحدى الطرق لتحقيق ما سبق هي جعل الخادم يحصل على عنوان URL لقاعدة البيانات من متغير البيئة، لذلك سنعدّل موقع المكتبة المحلية LocalLibrary للحصول على معرّف URI لقاعدة البيانات من بيئة نظام التشغيل (إذا كان معرّفًا)، وإلّا فسنستخدم قاعدة بيانات التطوير الخاصة بنا. افتح الملف app.js وابحث عن السطر الذي يضبط متغير اتصال قاعدة بيانات MongoDB، والذي سيبدو كما يلي: const mongoDB = "mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&w=majority"; ضع الشيفرة التالية مكان السطر السابق، إذ تستخدم هذه الشيفرة process.env.MONGODB_URI للحصول على سلسلة الاتصال النصية من متغير البيئة MONGODB_URI إذا جرى ضبطه، واستخدم عنوان URL لقاعدة بياناتك بدلًا من العنصر البديل فيما يلي: // إعداد‫ اتصال mongoose const dev_db_url = const mongoose = require("mongoose"); mongoose.set("strictQuery", false); const dev_db_url = "mongodb+srv://your_user_name:your_password@cluster0.lz91hw2.mongodb.net/local_library?retryWrites=true&w=majority"; const mongoDB = process.env.MONGODB_URI || dev_db_url; main().catch((err) => console.log(err)); async function main() { await mongoose.connect(mongoDB); } ضبط متغير البيئة NODE_ENV على القيمة production يمكننا إزالة تعقّب المكدس في صفحات الخطأ من خلال ضبط متغير البيئة NODE_ENV على قيمة الإنتاج 'production'، إذ يُضبَط على قيمة التطوير 'development' افتراضيًا. يؤدي ضبط المتغير على قيمة الإنتاج إلى إنشاء رسائل خطأ أقل تفصيلًا، بالإضافة إلى التخزين المؤقت لقوالب العرض وملفات CSS المُولَّدة من توسّعات شيفرة CSS، وتشير الاختبارات إلى أن ضبط متغير البيئة NODE_ENV على الإنتاج يمكن أن يحسن أداء التطبيق بمقدار ثلاثة أضعاف. يمكن إجراء هذا التغيير إما باستخدام export، أو ملف بيئة، أو نظام تهيئة لنظام التشغيل. ملاحظة: يُعَد ذلك تغييرًا تجريه في إعداد بيئتك بدلًا من إجرائه في تطبيقك، ولكنه جدير بالذكر حاليًا، إذ سنوضح كيفية ضبطه لمثال الاستضافة الآتي. التسجيل المناسب يمكن أن يكون لتسجيل الاستدعاءات تأثير على موقع الويب الذي يمتلك حركة مرور عالية، إذ يمكن أن تحتاج إلى تسجيل نشاط موقع الويب في بيئة الإنتاج، مثل تعقّب حركة المرور، أو تسجيل استدعاءات واجهة برمجة التطبيقات، ولكن يجب أن تحاول تقليل كمية التسجيل المُضافة لأغراض تنقيح الأخطاء Debugging. تتمثل إحدى طرق تقليل تسجيل "تنقيح الأخطاء" في بيئة الإنتاج في استخدام وحدة مثل الوحدة debug التي تسمح لك بالتحكم في التسجيل الذي يُجرَى من خلال ضبط متغير بيئة، فمثلًا يوضح جزء الشيفرة البرمجية التالي كيف يمكنك إعداد تسجيل المؤلف "author"، إذ يُصرَّح عن المتغير debug بالاسم author، وستُعرَض البادئة "author" تلقائيًا لجميع السجلات من هذا الكائن. const debug = require("debug")("author"); // عرض استمارة‫ تحديث المؤلف باستخدام GET exports.author_update_get = asyncHandler(async (req, res, next) => { const author = await Author.findById(req.params.id).exec(); if (author === null) { // لا توجد نتائج debug(`id not found on update: ${req.params.id}`); const err = new Error("Author not found"); err.status = 404; return next(err); } res.render("author_form", { title: "Update Author", author: author }); }); يمكنك بعد ذلك تفعيل مجموعة معينة من السجلات من خلال تحديدها بوصفها قائمة مفصول بين عناصرها بفواصل في متغير بيئة DEBUG، ويمكنك ضبط المتغيرات لعرض سجلات المؤلف والكتاب كما هو موضح فيما يلي (المحارف البديلة Wildcards مدعومة أيضًا): # في نظام ويندوز set DEBUG=author,book # في نظام لينكس export DEBUG="author,book" ملاحظة: يمكن أن تحل استدعاءات debug محل التسجيل الذي ربما طبّقته مسبقًا باستخدام console.log()‎ أو console.error()‎، لذا ضع التسجيل باستخدام وحدة debug مكان استدعاءات console.log()‎ في شيفرتك البرمجية، وشغّل التسجيل وأوقفه في بيئة التطوير من خلال ضبط المتغير DEBUG ولاحظ تأثير ذلك على التسجيل. إذا كنت بحاجة إلى تسجيل نشاط موقع الويب، يمكنك استخدام مكتبة تسجيل مثل Winston أو Bunyan. اطلع على أفضل ممارسات عملية الإنتاج: الأداء والموثوقية لمزيد من المعلومات. استخدم ضغط gzip أو deflate للاستجابة تضغط خوادم الويب غالبًا استجابة HTTP المُرسَلة إلى العميل، مما يقلل من الوقت المطلوب للعميل للحصول على الصفحة وتحميلها. سيعتمد تابع الضغط المُستخدَم على توابع فك الضغط التي يدعمها العميل في الطلب، إذ ستُرسَل الاستجابة بحيث تكون غير مضغوطة عندما تكون توابع الضغط غير مدعومة. يمكنك إضافة ذلك إلى موقعك باستخدام برمجية الضغط الوسيطة compression، لذا ثبّتها في جذر مشروعك من خلال تشغيل الأمر التالي: npm install compression افتح الملف "‎./app.js" واطلب مكتبة الضغط. ضِف مكتبة الضغط إلى سلسلة البرمجيات الوسيطة باستخدام التابع use()‎ الذي يجب أن يظهر قبل أي وجهات Routes تريد ضغطها (جميع الوجهات في حالتنا): const catalogRouter = require("./routes/catalog"); // استيراد الوجهات ‫لمنطقة الدليل "catalog" من موقعك const compression = require("compression"); // إنشاء ‫كائن تطبيق Express const app = express(); // … app.use(compression()); // ضغط جميع الوجهات app.use(express.static(path.join(__dirname, "public"))); app.use("/", indexRouter); app.use("/users", usersRouter); app.use("/catalog", catalogRouter); // إضافة وجهات‫ الدليل catalog إلى سلسلة البرمجيات الوسيطة // … ملاحظة: لن تَستخدمَ هذه البرمجيات الوسيطة لموقع الويب الذي يمتلك حركة مرور عالية في بيئة الإنتاج، لذا يمكنك استخدام وكيل عكسي مثل Nginx بدلًا من ذلك. استخدام حزمة Helmet للحماية من الثغرات المعروفة تُعَد Helmet حزمة برمجيات وسيطة يمكنها ضبط ترويسات HTTP المناسبة التي تساعد في حماية تطبيقك من ثغرات الويب المعروفة. اطّلع على توثيقها للحصول على مزيد من المعلومات حول الترويسات التي تضبطها والثغرات الأمنية التي تحميك منها. ثبّتها في جذر مشروعك من خلال تشغيل الأمر التالي: npm install helmet افتح الملف "‎./app.js" واطلب مكتبة helmet، ثم ضِف الوحدة إلى سلسلة البرمجيات الوسيطة باستخدام التابع use()‎: const compression = require("compression"); const helmet = require("helmet"); // إنشاء كائن ت‫طبيق Express const app = express(); // إض‫افة حزمة helmet إلى سلسلة البرمجيات الوسيطة // ضبط‫ ترويسات CSP للسماح بتخديم Bootstrap و Jquery app.use( helmet.contentSecurityPolicy({ directives: { "script-src": ["'self'", "code.jquery.com", "cdn.jsdelivr.net"], }, }), ); // … يمكن أن نضيف التابع app.use(helmet());‎ لإضافة مجموعة فرعية من الترويسات المتعلقة بالأمان والتي تكون منطقية لمعظم المواقع، ولكننا نضمّن بعض سكربتات بوتستراب bootstrap و jQuery في قالب موقع المكتبة المحلية LocalLibrary الأساسي، مما يؤدي إلى انتهاك سياسة أمان المحتوى Content Security Policy -أو CSP اختصارًا- الافتراضية الخاصة بحزمة helmet، والتي لا تسمح بتحميل السكربتات العابرة للمواقع. يمكن السماح بتحميل هذه السكربتات من خلال تعديل ضبط helmet بحيث تُضبَط موجّهات CSP للسماح بتحميل السكربت من النطاقات المُشار إليها، ويمكنك بالنسبة لخادمك إضافة أو تعطيل ترويسات محددة حسب الحاجة باتباع الإرشادات الخاصة باستخدام helmet. إضافة معدل محدود إلى وجهات واجهة برمجة التطبيقات API تُعَد Express-rate-limit حزمة برمجية وسيطة يمكن استخدامها للحد من الطلبات المتكررة لواجهات برمجة التطبيقات API والنقاط النهائية، فهناك العديد من الأسباب التي يمكن أن تؤدي إلى زيادة الطلبات على موقعك مثل هجمات حجب الخدمة أو هجمات القوة الغاشمة أو حتى مجرد عميل أو سكربت لا يتصرف كما هو متوقع، ويمكن أن تُحاسَب على حركة المرور الإضافية بغض النظر عن مشاكل الأداء التي يمكن أن تنشأ عن كثرة الطلبات التي تتسبب في إبطاء خادمك. يمكن استخدام حزمة Express-rate-limit للحد من عدد الطلبات التي يمكن إجراؤها على وجهة معينة أو مجموعة من الوِجهات. يمكنك الاطلاع على مقال الهجمات الأمنية Security Attacks في الشبكات الحاسوبية على أكاديمية حسوب لمزيد من المعلومات حول أنواع الهجمات الأمنية. ثبّت هذه الحزمة في جذر مشروعك من خلال تشغيل الأمر التالي: npm install express-rate-limit افتح الملف "‎./app.js" واطلب مكتبة express-rate-limit كما يلي، ثم ضِف الوحدة إلى سلسلة البرمجيات الوسيطة باستخدام التابع use()‎: const compression = require("compression"); const helmet = require("helmet"); const app = express(); // إعداد محدِّد المعدل إلى عشرين طلبًا في الدقيقة كحد أعلى const RateLimit = require("express-rate-limit"); const limiter = RateLimit({ windowMs: 1 * 60 * 1000, // ‫1 دقيقة max: 20, }); // طبّق محدّد المعدّل على جميع الطلبات app.use(limiter); // … ملاحظة: يحدّد الأمر السابق جميع الطلبات لتكون 20 طلبًا في الدقيقة، ولكن يمكنك تغييره حسب الحاجة. يمكن أيضًا استخدام خدمات خارجية مثل Cloudflare إذا كنت بحاجة إلى مزيد من الحماية المتقدمة ضد هجمات حجب الخدمة أو أنواع أخرى من الهجمات. تطبيق عملي: تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway يقدّم هذا القسم شرحًا عمليًا لكيفية تثبيت موقع المكتبة المحلية LocalLibrary على منصة Railway. سبب استخدام Railway اخترنا استخدام منصة Railway لعدة أسباب هي: تمتلك Railway مستوًى مجانيًا لخطة بداية مع بعض القيود، إذ من المهم أن تكون بأسعار مقبولة لجميع المطورين. تهتم Railway بمعظم البنية التحتية، فلا حاجة للقلق بشأن الخوادم وموازني الحِمل والوكلاء العكسيين وغير ذلك، مما يجعل البدء أسهل بكثير. تركّز Railway على تجربة المطور للتطوير والنشر، مما يؤدي إلى وجود منحنى تعليمي أسرع وأسلس من العديد من البدائل الأخرى. تُعَد المهارات والمفاهيم التي ستتعلمها عند استخدام Railway قابلة للتحويل، إذ تمتلك Railway بعض الميزات الجديدة الممتازة، ولكن تستخدم خدماتُ الاستضافة الشائعة الأخرى العديدَ من الأفكار والأساليب نفسها. لا تؤثر قيود الخدمات والخطط على استخدامنا لمنصة Railway في مثالنا، فمثلًا: تقدّم خطة البداية 500 ساعة فقط من وقت النشر المستمر كل شهر و5 دولارات من الرصيد الذي يُستهلَك بناءً على الاستخدام، ويُعاد ضبط الساعات والرصيد ويجب إعادة نشر المشاريع في نهاية كل شهر. تعني هذه القيود أنه يمكنك تشغيل هذا المثال بصورة مستمرة لمدة 21 يومًا تقريبًا، ويُعَد ذلك أكثر من كافٍ للتطوير والاختبار، ولكن لن تتمكّن من استخدام هذه الخطة لموقع حقيقي للإنتاج. تحتوي بيئة خطة البداية على 512 ميجابايت فقط من الذاكرة RAM و1 جيجابايت من ذاكرة التخزين، وهذا أكثر من كافٍ لمثالنا. لا توجد سوى منطقة واحدة مدعومة وهي الولايات المتحدة الأمريكية حاليًا، إذ يمكن أن تكون الخدمة خارج هذه المنطقة أبطأ أو تحظرها القوانين المحلية. يمكن العثور على قيود أخرى في توثيق خطط Railway للدفع. تبدو الخدمة موثوقة جدًا، وإذا كانت مناسبة لك، فإن الأسعار يمكن التنبؤ بها، ويكون توسيع تطبيقك سهلًا جدًا. تعَد Railway مناسبة لاستضافة مثالنا، ولكن يجب أن تأخذ الوقت الكافي لتحديد ما إذا كانت مناسبة لموقعك. كيفية عمل Railway يُشغَّل كل تطبيق ويب في حاوية افتراضية معزولة ومستقلة خاصة به، لذا يجب أن تكون Railway قادرة على إعداد البيئة والاعتماديات المناسبة، وفهم كيفية إطلاقها لتنفيذ تطبيقك. تجعل Railway هذا الأمر سهلًا، إذ يمكنها التعرف تلقائيًا على العديد من أطر عمل وبيئات تطبيقات الويب المختلفة وتثبيتها بناءً على استخدامها "للمصطلحات الشائعة"، فمثلًا تتعرّف Railway على تطبيقات Node لأن لديها ملف package.json، ويمكنها تحديد مدير الحزم المُستخدَم للبناء من ملف "القفل Lock". إذا احتوى التطبيق على ملف package-lock.json مثلًا، فستعرف Railway أنها ستستخدم مدير حزم npm لتثبيت الحزم، وإذا وجدت yarn.lock فستعرف أنها ستستخدم yarn. ستبحث Railway -بعد تثبيت جميع الاعتماديات- عن سكربتات بالاسم "build" و "start" في ملف الحزمة، وستستخدمها لبناء وتشغيل الشيفرة البرمجية. ملاحظة: تستخدم Railway حزمة Nixpacks للتعرف على العديد من أطر عمل تطبيقات الويب المكتوبة بلغات برمجة مختلفة. لا حاجة إلى معرفة أيّ شيء آخر لهذا المقال، ولكن يمكنك معرفة المزيد حول خيارات نشر تطبيقات Node في توثيق Nixpacks. يمكن للتطبيق بعد تشغيله ضبط نفسه باستخدام المعلومات المقدمة في متغيرات البيئة، فمثلًا يجب أن يحصل التطبيق الذي يستخدم قاعدة بيانات على العنوان باستخدام متغير، ويمكن أن تستضيف Railway خدمة قاعدة البيانات نفسها أو على أي مزوّد آخر. يتفاعل المطورون مع Railway من خلال موقع Railway وباستخدام أداة واجهة سطر أوامر CLI خاصة، إذ تسمح لك واجهة CLI بربط مستودع غيت هب GitHub محلي بمشروع Railway، ورفع المستودع من الفرع المحلي إلى الموقع المباشر، وفحص سجلات العملية الجارية، وإعداد متغيرات الضبط والحصول عليها وغير ذلك. من أهم الميزات أنه يمكنك استخدام واجهة CLI لتشغيل مشروعك المحلي مع متغيرات البيئة نفسها للمشروع المباشر. يجب وضع تطبيق Express الخاص بنا في مستودع غيت git وإجراء بعض التغييرات الطفيفة ليعمل تطبيقنا على Railway، ثم يمكننا إعداد حساب على Railway وتثبيت موقعنا وقاعدة البيانات وتجربة عميل Railway. وهذا هو كل ما تحتاجه من معلومات في البداية. إنشاء مستودع للتطبيق على غيت هب GitHub تتكامل Railway تكاملًا وثيقًا مع غيت هب ونظام التحكم في إصدارات الشيفرة المصدرية git، ويمكنك ضبطها لنشر التحديثات تلقائيًا عند إجراء تغييرات إلى مستودع أو فرع معين على غيت هب، أو يمكنك دفع فرع الشيفرة المحلية الحالي مباشرةً إلى النشر الخاص بمنصة Railway باستخدام واجهة CLI. ملاحظة: يُعَد استخدام نظام إدارة الشيفرة المصدرية مثل غيت هب ممارسة جيدة لتطوير البرمجيات، فإذا كنت تستخدم غيت هب لإدارة شيفرتك المصدرية مسبقًا، فتخطى هذه الخطوة. هناك العديد من الطرق للعمل مع git، ومن أسهلها إعداد حساب على غيت هب أولًا وإنشاء المستودع عليه، ثم المزامنة معه محليًا كما يلي: انتقل إلى موقع GitHub الرسمي وأنشئ حسابًا عليه. انقر على ارتباط + في شريط الأدوات العلوي وحدّد خيار "مستودع جديد New repository" بعد تسجيل الدخول. املأ جميع الحقول في هذه الاستمارة، إذ يمكن أن تكون هذه الحقول غير إلزامية، ولكن يُوصَى بها بشدة. أدخل اسم المستودع الجديد والوصف، فمثلًا يمكنك استخدام الاسم "express-locallibrary-tutorial" والوصف "Local Library website written in Express (Node)‎" (موقع المكتبة المحلية المكتوب باستخدام Express). اختر الخيار Node في قائمة الاختيار Add .gitignore. اختر الترخيص المفضل لديك في قائمة الاختيار Add license. تحقق من تهيئة المستودع باستخدام README. تحذير: سيجعل الوصول الافتراضي العام "Public" جميع الشيفرة البرمجية المصدرية -بما في ذلك اسم المستخدم وكلمة المرور لقاعدة البيانات- مرئيةً لأي شخص على الإنترنت، لذا تأكد من أن الشيفرة البرمجية المصدرية لا تقرأ اعتماديات إلّا من متغيرات البيئة ولا تحتوي على أيّ اعتماديات ثابتة، وإلّا حدد الخيار خاص "Private" للسماح فقط لأشخاص محدَّدين برؤية الشيفرة المصدرية. اضغط على إنشاء مستودع Create repository. انقر فوق الزر الأخضر نسخ Clone أو تنزيل Download في صفحة مستودعك الجديد. انسخ قيمة URL من حقل النص الموجود في مربع الحوار الذي يظهر، فإذا استخدمت اسم المستودع "express-locallibrary-tutorial"، فيجب أن يكون عنوان URL مثل العنوان: https://github.com/<your_git_user_id>/express-locallibrary-tutorial.git. انتهينا من إنشاء المستودع، ويجب الآن نسخه على حاسوبك المحلي باتباع الخطوات التالية: أولًا، ثبّت git على حاسوبك المحلي، إذ يمكنك العثور على نسخ لأنظمة مختلفة. ثانيًا، افتح موجه الأوامر أو الطرفية وانسخ مستودعك باستخدام عنوان URL الذي نسخته سابقًا كما في الأمر التالي: git clone https://github.com/<your_git_user_id>/express-locallibrary-tutorial.git سيؤدي هذا الأمر إلى إنشاء المستودع في المجلد الحالي. ثالثًا، انتقل إلى مجلد المستودع باستخدام الأمر التالي: cd express-locallibrary-tutorial أخيرًا، انسخ ملفات تطبيقك المصدرية في مجلد المستودع، ثم اجعلها جزءًا من المستودع باستخدام git كما يلي: أولًا، انسخ تطبيق Express إلى هذا المجلد باستثناء المجلد "‎/node_modules" الذي يحتوي على ملفات الاعتماديات التي يجب جلبها من مدير حزم npm حسب الحاجة. ثانيًا، افتح موجه الأوامر أو الطرفية واستخدم الأمر add لإضافة جميع الملفات إلى git كما يلي: git add -A ثالثًا، استخدم الأمر status للتحقق من صحة جميع الملفات التي تريد تثبيتها commit، إذ نريد تضمين الملفات المصدرية، وليس الملفات الثنائية والمؤقتة وما إلى ذلك، إذ يجب أن تبدو كما يلي: > git status ويكون الخرج الناتج بعد تنفيذ هذا الأمر على النحو التالي: On branch main Your branch is up-to-date with 'origin/main'. Changes to be committed: (use "git reset HEAD <file>..." to unstage) new file: … رابعًا، ثبّت باستخدام commit الملفات في مستودعك المحلي عندما تكون راضيًا عن النتيجة، إذ يكافئ ذلك الإقرار بالتغييرات وجعلها جزءًا رسميًا من المستودع المحلي. git commit -m "First version of application moved into GitHub" خامسًا، لم يتغيّر المستودع البعيد حتى الآن، لذا يجب مزامنة (باستخدام الأمر push) مستودعك المحلي مع مستودع GitHub البعيد باستخدام الأمر التالي: git push origin main يجب أن تكون قادرًا عند اكتمال هذه العملية على العودة إلى صفحة غيت حيث أنشأت مستودعك، وتحديث الصفحة، ويجب ملاحظة رفع كامل التطبيق. يمكنك الاستمرار في تحديث مستودعك مع تغير الملفات باستخدام دورة أوامر الإضافة add والتثبيت commit والدفع push. ملاحظة: يمكنك الآن إنشاء نسخة احتياطية من شيفرة مشروعك البرمجية، إذ يمكن أن تكون بعض التغييرات التي سنجريها في الأقسام التالية مفيدة للنشر (أو للتطوير) على أي خدمة استضافة، ويمكن أن تكون بعض التغييرات الأخرى غير مفيدة. أفضل طريقة لذلك هي استخدام git لإدارة الإصدارات، إذ يمكنك باستخدامه الرجوع إلى إصدار سابق معين، ويمكنك الاحتفاظ به في فرع منفصل عن التغييرات الإنتاجية واختيار أيّ تغييرات للتنقل بين فروع الإنتاج والتطوير. يستحق تعلم غيت Git الجهد المبذول، لذا اطلع على مجموعة مقالات Git في أكاديمية حسوب. يُعَد نسخ ملفاتك في موقع آخر الطريقة الأسهل، ولكن يمكنك استخدام أيّ طريقة تتناسب مع معرفتك لنظام git. تحديث التطبيق ليعمل على Railway سنشرح في هذا القسم التغييرات التي يجب إجراؤها على تطبيق المكتبة المحلية LocalLibrary ليعمل على Railway، ولكن لن تمنعك هذه التغييرات من استخدام الاختبار وسير العمل المحلي الذي تعلمناه مسبقًا. ضبط نسخة Node يحتوي الملف package.json على كل ما تحتاجه Railway للعمل على اعتماديات تطبيقك وتحديد الملف الواجب تشغيله لبدء تشغيل موقعك، ولكن المعلومات المهمة الوحيدة المفقودة من الملف package.json الحالي هي نسخة Node، إذ يمكنك العثور على نسخة Node التي نستخدمها للتطوير من خلال إدخال الأمر التالي: >node --version v16.17.1 افتح الملف package.json وأضِف المعلومات التالية في قسم engines > node (مع استخدام رقم النسخة في نظامك): { "name": "express-locallibrary-tutorial", "version": "0.0.0", "engines": { "node": ">=16.17.1" }, "private": true, // … لاحظ أن هناك طرقًا أخرى لتوفير نسخة Node على Railway، ولكننا نستخدم الملف package.json لأنه الأسلوب الذي تدعمه العديد من الخدمات على نطاق واسع. لاحظ أيضًا أن Railway لن تستخدم بالضرورة نسخة Node الدقيقة التي تحددها، إذ ستستخدم نسخةً لها رقم النسخة الرئيسي نفسه. الحصول على الاعتماديات وإعادة الاختبار لنختبر الموقع مرةً أخرى ونتأكد من أنه لم يتأثر بأيٍّ من التغييرات التي أجريناها. أولًا، يجب جلب الاعتماديات (تذكّر أننا لم ننسخ المجلد node_modules في شجرة git)، إذ يمكنك ذلك من خلال تشغيل الأمر التالي في طرفيتك ضمن جذر المشروع: npm install شغّل الموقع (اطّلع على فقرة اختبار الوجهات في مقال الوجهات والمتحكمات لمعرفة الأوامر ذات الصلة) وتحقق من أن الموقع لا يزال يتصرف كما هو متوقع. حفظ التغييرات على غيت هب GitHub احفظ الآن جميع التغييرات التي أجريتها على غيت هب، وأدخِل الأوامر التالية في الطرفية أثناء وجودك في مستودعك: git add -A git commit -m "Added files and changes required for deployment" git push origin main يجب أن نكون جاهزين الآن لبدء نشر موقع المكتبة المحلية LocalLibrary على Railway. الحصول على حساب على Railway يجب أولًا إنشاء حساب لبدء استخدام Railway باتباع الخطوات التالية: اذهب إلى موقع Railway الرسمي وانقر على ارتباط تسجيل الدخول Login في شريط الأدوات العلوي. اختر GitHub في النافذة المنبثقة لتسجيل الدخول باستخدام اعتماديات GitHub الخاصة بك. يمكن أن تحتاج بعد ذلك إلى الانتقال إلى بريدك الإلكتروني والتحقق من حسابك. ستسجل بعد ذلك الدخول إلى لوحة تحكم Railway. النشر على Railway من GitHub يجب الآن إعداد Railway لنشر موقع مكتبتنا من GitHub، لذا اختر أولًا خيار لوحة التحكم Dashboard من القائمة العلوية للموقع، ثم حدّد زر مشروع جديد New Project: ستعرض Railway قائمةً بالخيارات الخاصة بالمشروع الجديد، بما في ذلك خيار نشر مشروع من قالب أنشأته لأول مرة في حسابك على GitHub، وعددًا من قواعد البيانات، لذا حدد خيار النشر Deploy from GitHub repo. ستُعرَض جميع المشاريع في مستودعات GitHub التي شاركتها مع Railway أثناء عملية الإعداد، ولكنك ستختار مستودع GitHub الخاص بموقع المكتبة المحلية: ‎<user-name>/express-locallibrary-tutorial. أكّد النشر من خلال تحديد خيار النشر حالًا Deploy Now. ستحمّل Railway بعد ذلك مشروعك وتنشره، مع عرض التقدم في نافذة عمليات النشر Deployments، ثم سترى شيئًا يشبه ما يلي عند اكتمال النشر بنجاح: اختر الآن نافذة الإعدادات Settings، ثم انتقل إلى الأسفل إلى قسم النطاقات Domains، واضغط على زر توليد نطاق Generate Domain. سيؤدي ذلك إلى نشر الموقع ووضع النطاق في مكان الزر كما هو موضح فيما يلي: حدّد عنوان URL للنطاق لفتح تطبيق المكتبة. لاحظ فتح موقع المكتبة المحلية باستخدام بيانات التطوير الخاصة بك لأننا لم نحدّد قاعدة بيانات الإنتاج. تجهيز وتوصيل قاعدة بيانات MongoDb لننشئ الآن قاعدة بيانات MongoDB الخاصة بالإنتاج لاستخدامها بدلًا من استخدام بيانات التطوير، إذ سننشئ قاعدة البيانات بوصفها جزءًا من مشروع تطبيق Railway، بالرغم من أنه لا يوجد ما يمنعك من إنشائها في مشروعها المنفصل أو استخدام قاعدة بيانات MongoDB Atlas لبيانات الإنتاج، كما فعلت مع قاعدة بيانات التطوير. حدّد الخيار Dashboard من قائمة الموقع العلوية على Railway ثم حدد مشروع تطبيقك، الذي يحتوي حاليًا على خدمة واحدة فقط لتطبيقك، والتي يمكن اختيارها لضبط المتغيرات وتفاصيل الخدمة الأخرى، ثم حدّد الزر جديد New، والذي يستخدم لإضافة خدمات إلى المشروع الحالي. حدد قاعدة البيانات Database عندما يُطلَب منك تحديد نوع الخدمة لإضافتها. حدّد بعد ذلك خيار الإضافة Add MongoDB لبدء إضافة قاعدة البيانات. ستجهز Railway بعد ذلك خدمةً تحتوي على قاعدة بيانات فارغة في المشروع نفسه، وسترى عند الانتهاء كلًا من التطبيق وخدمات قاعدة البيانات في عرض المشروع. حدّد خدمة MongoDB لعرض معلومات حول قاعدة البيانات، ثم افتح نافذة "الاتصال Connect" وانسخ "عنوان URL لاتصال Mongo"، وهو عنوان قاعدة البيانات. يمكن جعل تطبيق المكتبة يصل إلى هذا العنوان من خلال إضافته إلى عملية التطبيق باستخدام متغير بيئة، لذا افتح أولًا خدمة التطبيق، ثم حدّد نافذة المتغيرات Variables واضغط على زر متغير جديد New Variable. أدخِل اسم المتغير MONGODB_URI وعنوان URL للاتصال الذي نسخته لقاعدة البيانات، فالمتغير MONGODB_URI هو اسم متغير البيئة الذي ضبطنا التطبيق منه لقراءة عنوان قاعدة البيانات، وسيظهر لديك ما يلي: حدّد بعد ذلك زر الإضافة Add لإضافة المتغير. تعيد Railway تشغيل تطبيقك عندما تحدّث المتغيرات. إذا فحصتَ الصفحة الرئيسية الآن، فيجب أن تظهِر قيمًا صفرية لأعداد الكائنات، إذ تعني التغييرات السابقة أننا نستخدم الآن قاعدة بيانات جديدة (فارغة). متغيرات الضبط الأخرى تذكّر من القسم السابق أننا بحاجة إلى ضبط NODE_ENV على قيمة الإنتاج 'production' لتحسين الأداء وإنشاء رسائل خطأ أقل تفصيلًا، ويمكننا فعل ذلك في الشاشة نفسها التي ضبطنا منها المتغير MONGODB_URI. افتح خدمة التطبيق، ثم حدد نافذة المتغيرات Variables، إذ سترى أن المتغير MONGODB_URI مُعرَّف مسبقًا، واضغط على زر متغير جديد New Variable. أدخل NODE_ENV بوصفه اسم المتغير الجديد و production بوصفه اسم البيئة، ثم اضغط على زر الإضافة Add. ضُبِط وأُعِدّ تطبيق المكتبة المحلية للاستخدام في بيئة الإنتاج، ويمكنك إضافة البيانات عبر واجهة موقع الويب ويجب أن تعمل بالطريقة نفسها التي كانت تعمل بها أثناء عملية التطوير (مع وصول أقل إلى معلومات تنقيح الأخطاء للصفحات غير الصالحة). ملاحظة: إذا أدرتَ إضافة بعض البيانات للاختبار، فيمكنك استخدام السكربت populatedb مع عنوان URL لقاعدة بيانات MongoDB الخاصة بالإنتاج كما ناقشنا في مقال استخدام قاعدة البيانات باستخدام مكتبة Mongoose. تثبيت العميل نزّل وثبّت عميل Railway لنظام تشغيلك المحلي باتباع التعليمات الواردة في توثيق Railway. ستتمكن من تشغيل الأوامر بعد تثبيت العميل. تتضمن بعض العمليات الأكثر أهمية نشر المجلد الحالي لحاسوبك إلى مشروع Railway المرتبط به دون الحاجة للرفع على GitHub، وتشغيل مشروعك محليًا باستخدام الإعدادات نفسها الموجودة على خادم الإنتاج. يمكنك الحصول على قائمة بجميع الأوامر الممكنة من خلال إدخال الأمر التالي في الطرفية: railway help تنقيح الأخطاء Debugging يوفّر عميل Railway الأمر logs لإظهار السجلات الأخيرة (يتوفر سجل كامل على الموقع لكل مشروع): railway logs الخلاصة وصلنا إلى نهاية مقالنا حول إعداد تطبيقات Express في بيئة الإنتاج، وكذلك إلى نهاية سلسلة مقالات إطار عمل Express، لذا نأمل أنها كانت مفيدة لك، ولا تنسَ أنه يمكنك التحقق من النسخة الكاملة من الشيفرة المصدرية على GitHub. ترجمة -وبتصرُّف- للمقال Express Tutorial Part 7: Deploying to production. اقرأ المزيد المقال السابق تطبيق عملي لتعلم Express - الجزء الرابع: عرض بيانات المكتبة والعمل مع الاستمارات. مرحلة نشر التطبيق في عملية تطوير الويب. تطبيق عملي لتعلم جانغو - الجزء 10: نشر تطبيق جانغو في بيئة الإنتاج.
  22. لن يكون تنسيق الجداول أمرًا مهمًا، لكن علينا فعل ذلك أحيانًا، لهذا سنوّفر لك في هذا المقال دليلًا لتعرض جداول HTML بأسلوب أنيق إضافة إلى تطبيق بعض التقنيات الخاصة في تنسيقها. ننصحك قبل المتابعة في قراءة هذه المقال أن: تطلع على أساسيات العمل مع HTML. تطلع على أساسيات العمل مع الجداول في HTML كما شرحناها في المقال أساسيات بناء الجداول فيHTML. تتفهم أساسيات CSS كما شرحناها في سلسلة المقالات خطواتك الأولى في CSS. الجدول النمطي في HTML لنبدأ العمل بإلقاء نظرة على هيكلية الجدول النمطي في HTML من خلال الجدول التالي الذي يوصّف بعض الفرق الموسيقية في المملكة المتحدة. ستبدو الشيفرة على النحو التالي: <table> <caption> A summary of the UK's most famous punk bands </caption> <thead> <tr> <th scope="col">Band</th> <th scope="col">Year formed</th> <th scope="col">No. of Albums</th> <th scope="col">Most famous song</th> </tr> </thead> <tbody> <tr> <th scope="row">Buzzcocks</th> <td>1976</td> <td>9</td> <td>Ever fallen in love (with someone you shouldn't've)</td> </tr> <tr> <th scope="row">The Clash</th> <td>1976</td> <td>6</td> <td>London Calling</td> </tr> <!-- several other great bands --> <tr> <th scope="row">The Stranglers</th> <td>1974</td> <td>17</td> <td>No More Heroes</td> </tr> </tbody> <tfoot> <tr> <th scope="row" colspan="2">Total albums</th> <td colspan="2">77</td> </tr> </tfoot> </table> توصّف الشيفرة السابقة الجدول المطلوب جيدًا بفضل بعض الميزات، مثل scope و caption وغيرها، لكن لسوء الحظ لن يكون مظهرة جيدًا عندما يُصيَّر على الشاشة. يُطبق المتصفح التنسيق الافتراضي على الجدول السابق لهذا تراه صعب القراءة وممل، وهنا يأتي دور CSS لتحسين مظهره. تنسيق الجدول السابق سنعمل في هذه الفقرة على تنسيق الجدول السابق باتباع الخطوات التالية: حضّر نسخةً من الشيفرة السابقة عن طريق تنزيلها من غيت -هب، ثم نزّل الصورتين (noise و leopardskin)، وضع الملفات الثلاثة في مجلد واحد على حاسوبك. أنشئ تاليًا الملف "style.css" واحفظه في نفس المجلد السابق. اربط ملف CSS بملف HTML بوضع سطر الشيفرة التالي ضمن الوسم <head>: <link href="style.css" rel="stylesheet" /> توسعة الجدول وتخطيطه لا بد قبل كل شيء من ضبط المساحات في الجدول وضبط تخطيطه، إذ يبدو بحال سيئة، وحتى ننجز الأمر، ضِف الشيفرة التالية إلى الملف "style.css": /* spacing */ table { table-layout: fixed; width: 100%; border-collapse: collapse; border: 3px solid purple; } thead th:nth-child(1) { width: 30%; } thead th:nth-child(2) { width: 20%; } thead th:nth-child(3) { width: 15%; } thead th:nth-child(4) { width: 35%; } th, td { padding: 20px; } إليك أهم النقاط التي يجدر ملاحظتها في الشيفرة السابقة: ضبط قيمة الخاصية table-layout على القيمة fixed أمرٌ جيدٌ عادةً، فهو يجعل سلوك الجدول متوّقع افتراضيًا، إذ تأخذ أعمدة الجدول في الحالة الطبيعية حجمها وفقًا لكمية المحتوى التي تضمه، وهذا ما قد يعطي نتائج غريبة قليلًا، لكن باستخدام القاعدة table-layout: fixed ستتمكن من ضبط حيّز الأعمدة وفقًا لحيّز ترويساتها ومن ثم التعامل مع المحتوى وفقًا لذلك. لهذا استخدمنا أربع قواعد تنسيق مختلفة (thead th:nth-child(n لنستهدف الترويسات الأربع للجدول، بحيث يختار المحدد nth-child: العنصر الابن "n" الذي يمثّل العنصر <th> ضمن السلسلة، داخل العنصر <thread>، وأعطينا كلًا من الترويسات حيّزًا بنسبة مئوية من الحيّز الكلي. سيأخذ الآن كل عمود حيّز الترويسة وهي طريقة جيدة لتحديد الحجم. استخدمنا أيضًا فكرة أن تكون قيمة الخاصية width هي %100، وبالتالي سيملأ الجدول أي حاوية تضمه، وسيتجاوب جيدًا عند تغيير شاشة العرض، على الرغم من أنه يتطلب بعض العمل الإضافي ليبدو جميلًا ضمن الشاشة الأضيق. يُعد ضبط قيمة الخاصية border-collapse على collapse أسلوبًا معياريًا في تنسيق أي جدول. فعندما تضبط حواف الخلايا ضمن الجدول، سيظهر فراغ فيما بينها افتراضيًا، وقد لا يبدو الأمر جميلًا (إلا إن أردت فعلًا أن تظهر). لاحظ الصورة التالية: لكن بتطبيق القاعدة ;border-collapse: collapse ستختفي حواف الخلايا ويبدو الجدول على النحو التالي: وضعنا حوافًا border حول كامل الجدول، لأننا سنحتاج إليه عندما نضع حوافًا حول ترويسة وتذييل الجدول لاحقًا، إذ سيبدو الجدول غريب الشكل إذا لم نضع له حوافًا من الخارج وسينتهي الأمر بوجود فراغات مزعجة. وضعنا حشوة padding حول العنصرين <th> و <td> لكي نمنح البيانات المكتوبة بعض الفسحة ليظهر الجدول أكثر قبولًا. سيبدو الجدول حتى اللحظة على النحو التالي: بعض التنسيقات الكتابية البسيطة سنحاول تاليًا ترتيب النص قليلًا، لهذا سنبحث عن خط كتابة مناسب ضمن خطوط جوجل ليلائم محتوى الجدول الذي يتحدث عن فرق موسيقية، كما يمكنك اختيار أي خط كتابة آخر يعجبك؛ فكل ما عليك فعله في هذه الحالة هو استبدال العنصر <link> في شيفرتنا وخاصية التنسيق font-family مع تصريحه بالقيم التي تحددها لك مكتبة خطوط جوجل. أضف بدايةً العنصر <link> إلى ترويسة ملف HTML قبل العنصر <link> الموجود أصلًا: <link href="https://fonts.googleapis.com/css?family=Rock+Salt" rel="stylesheet" type="text/css" /> أضف بعد ذلك تنسيقات CSS إلى الملف style.css تحت ما هو موجود أصلًا: /* typography */ html { font-family: "helvetica neue", helvetica, arial, sans-serif; } thead th, tfoot th { font-family: "Rock Salt", cursive; } th { letter-spacing: 2px; } td { letter-spacing: 1px; } tbody td { text-align: center; } tfoot th { text-align: right; } لا يوجد هنا حقيقيةً أية تنسيقات مخصصة تمامًا للجداول، وكل ما نفعله هو التلاعب بخطوط الكتابة لتسهيل قراءة المحتوى: اخترنا الخط sans-serif وهو خيار تزييني بحت، كما اخترنا خط كتابة مخصص في ترويسة الجدول داخل العناصر <thead> و <tfoot> ليناسب محتواهما. أضفنا بعض المساحات الفارغة letter-spacing ضمن ترويسة الجدول وخلاياه لأهمية ذلك في تسهيل قراءة المحتوى، وهو مجددًا خيار تزييني. وضعنا النص في منتصف كل خلية <tbody> كي تحاذي الترويسات، علمًا أنها تحاذى افتراضيًا إلى اليسار من خلال ضبط الخاصية text-align على left، بينما تُحاذى الترويسات إلى المنتصف center. سيبدو المظهر أفضل عمومًا إذا كانت محاذاة الترويسات <tfoot> ومحتوى الخلايا وفق النمط ذاته، وسنكتفي بالخط السميك bold لنص الترويسات كي تتميز عن سائر خلايا الجدول. ستبدو النتيجة أفضل الآن: الرسوميات والألوان لنتجه الآن نحو الرسوميات والألوان لإعطاء الجدول بعض الخصوصية في التنسيق ليلائم المحتوى، وسنبدأ بإضافة شيفرة CSS التالية إلى أسفل الملف style.css: /* graphics and colors */ thead, tfoot { background: url(leopardskin.jpg); color: white; text-shadow: 1px 1px 1px black; } thead th, tfoot th, tfoot td { background: linear-gradient( to bottom, rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.5) ); border: 3px solid purple; } تذكّر أن ما فعلناه ليس مخصصًا للجداول تحديدًا لكن من الأفضل إلقاء نظرة: أضفنا صورة خلفية للجدول باستخدام الخاصية background-image على العنصر <thead> و <tfoot> وغيرنا لون color جميع النصوص داخل الترويسات والتذييل إلى اللون الأبيض ومنحناها تأثير الظل text-shadow لتبدو مقروءةً على نحوٍ أفضل. أضفنا أيضًا تدرجًا لونيًا خطيًا Linear gradient إلى العنصرين <th> و <td> الخاصين بالترويسات والتذييل لتبدو الخلفية أجمل، كما منحناهما حوافًا بنفسجية. من المفيد أن يكون لديك مجموعةً من العناصر المتداخلة كي تطبق التنسيق على شكل طبقات متتالية، وقد كان ممكنًا تطبيق خلفيات وتدرجات لونية على عناصر الترويسات والتذييل في نفس الوقت، لكننا قررنا أن نفصل بينهما كي نحرص على دعم المتصفحات الأقدم التي لا تدعم صور الخلفيات أو التدرج اللوني. خطوط حمار الوحش Zebra قررنا تخصيص هذه الفقرة لعرض طريقة إظهار صفوف الجدول بلونين متناوبين والتي تدعى خطوط حمار الوحش zebra stripes، مما يسهّل قراءة البيانات وتحليلها. لهذا أضف الشيفرة التالية إلى ملف style.css: /* zebra striping */ tbody tr:nth-child(odd) { background-color: #ff33cc; } tbody tr:nth-child(even) { background-color: #e495e4; } tbody tr { background-image: url(noise.png); } table { background-color: #ff33cc; } رأينا سابقًا كيف يُستخدم المحدد nth-child: لاستهداف عنصر ابن محدد، كما يُمكن أن يُعطى صيغة لها معاملات وبالتالي يمكن اختيار سلسلة من العناصر لاستهدافها؛ فالصيغة 2n-1 ستختار جميع الأبناء ذوي الترتيب الفردي مثل الولد الأول والثالث والخامس وهكذا؛ كما تُستخدم الصيغة 2n لاختيار الأبناء ذوي الترتيب الزوجي. استخدمنا في الشيفرة التعليمتين odd و even اللتين تنفذان الفكرة التي تحدثنا عنها، وهكذا سنعطي الصفوف الزوجية لونًا متوهجًا مختلفًا عن الفردية. استخدمنا أيضًا خلفية متكررة لكل على جميع الصفوف وهي صورة نصف شفافة من النوع png. لتغطي الصفوف معطية انطباعًا متشابكًا texture لمظهر الجدول. منحنا كامل الجدول خلفية لونية كي تظهر الصفوف ملونة في المتصفحات التي لا تدعم المحدد nth-child:. سيبدو الجدول الآن بالشكل التالي: قد يبدو ما فعلناه بالجدول مبالغًا فيه، وقد لا يناسب ذوقك، لكنها طريقة لأن نقول أن الجداول يمكن تنسيقها لتكون أكثر حيوية. تنسيق العناوين هناك شيء أخير سنفعله وهو تنسيق عنوان الجدول. لهذا عليك إضافة الشيفرة التالية إلى أسفل الملف style.css: /* caption */ caption { font-family: "Rock Salt", cursive; padding: 20px; font-style: italic; caption-side: bottom; color: #666; text-align: right; letter-spacing: 1px; } لا شيء مميز في تلك الشيفرة باستثناء الخاصية caption-side التي أسندنا إليها القيمة bottom وهذا ما يجعل عنوان الجدول في الأسفل إضافةً إلى وضع بعض لمسات التنسيق الأخرى عليه ليكون تنسيق الجدول ككل قد اكتمل كما في الشكل التالي: تلميحات سريعة لتنسيق الجداول إليك قائمة بأفضل نقاط التنسيق التي ناقشناها في المقال: بسّط شيفرة HTMl الخاصة بالجدول قدر المستطاع، وامنح عناصر الجدول المرونة الكافية كأن تضبط القياسات بنسب مئوية وبهذا سيكون الجدول أكثر استجابة. استخدم قاعدة التنسيق table-layout: fixed كي تنشئ جدولًا بتخطيط نمطي يمكّنك من ضبط أبعاد الأعمدة بسهولة باستخدام الخاصية width على ترويساتها <th>. استخدم القاعدة border-collpse: collapseلتخفي حواف الخلايا مما يعطي الجدول مظهرًا أكثر أناقة. استخدم خطوط حمار الوحش لتمنح أسطر الجدول لونين متناوبين وبالتالي سهولة أكبر في القراءة. استخدم الخاصية text-align لمحاذاة النصوص في عنصر الترويسة <th> والتذييل <td> كي يظهر الجدول أكثر أناقة وأسهل متابعة. الخلاصة بعد أن تعرفنا في هذه المقل على طريقة تنسيق الجداول، لا بد من الانطلاق إلى أفكار جديدة، لهذا سنتابع في المقال التالي تنقيح شيفرة CSS، وكيفية حل المشاكل المتعلقة بالتخطيطات التي لا تبدو كما هو مطلوب أو الخاصيات التي لا تعطي التأثير المطلوب الذي تعتقده. سنعرّج أيضّا على طريقة استخدام أدوات مطوري ويب DevTools المدمجة مع المتصفحات لإيجاد حلول لمشاكلنا. ترجمة -وبتصرف- لمقال Styling tables. اقرأ أيضًا كيفية إنشاء جدول أنيق بـ HTML و CSS أساسيات بناء الجداول في HTML الجداول (Tables) في CSS HTML و CSS للمبتدئين: مقدمة إلى تنسيقات CSS
  23. أصبحنا الآن جاهزين لإضافة الصفحات التي تعرض كتب موقع المكتبة المحلية LocalLibrary وبيانات أخرى، إذ ستتضمن هذه الصفحات صفحةً رئيسية توضح عدد السجلات لكل نوع نموذج Model وعددًا من صفحات القائمة والصفحات التفصيلية لجميع النماذج، وبالتالي سنكتسب خبرةً عملية في الحصول على السجلات من قاعدة البيانات واستخدام القوالب. سنشرح أيضًا في هذا المقال كيفية العمل مع استمارات HTML في إطار عمل Express باستخدام لغة القوالب Pug، إذ سنناقش كيفية كتابة استمارات لإنشاء المستندات وتحديثها وحذفها من قاعدة بيانات الموقع. المتطلبات الأساسية: إكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال الوِجهات Routes والمتحكمات Controllers. الهدف: فهم كيفية إجراء عمليات قاعدة البيانات غير المتزامنة باستخدام async/await، واستخدام لغة القوالب Pug، والحصول على البيانات من عنوان URL في دوال المتحكمات، وكتابة الاستمارات للحصول على البيانات من المستخدمين وتحديث قاعدة البيانات باستخدام هذه البيانات. عرض بيانات المكتبة عرّفنا في المقالات السابقة نماذجَ Mongoose التي يمكننا استخدامها للتفاعل مع قاعدة بيانات وأنشأنا بعض سجلات المكتبة الأولية، ثم أنشأنا جميع الوِجهات Routes اللازمة لموقع المكتبة المحلية LocalLibrary، ولكن مع دوال متحكمات وهمية dummy، وهي دوال متحكمات هيكلية تعيد فقط رسالة "not implemented" عند الوصول إلى الصفحة. تتمثل الخطوة التالية في توفير عمليات تقديم مناسبة للصفحات التي تعرض معلومات المكتبة، إذ سنتعرّف على تقديم الصفحات التي تعرض استمارات لإنشاء المعلومات أو تحديثها أو حذفها، ويتضمن ذلك تحديث دوال المتحكمات لجلب السجلات باستخدام النماذج وتعريف القوالب لعرض هذه المعلومات للمستخدمين. سنبدأ بتقديم موضوعات عامة أو أولية توضح كيفية إدارة العمليات غير المتزامنة في دوال المتحكمات وكيفية كتابة القوالب باستخدام مكتبة القوالب Pug، ثم سنوفّر عمليات تقديم لكل صفحة من صفحاتنا الرئيسية المُعَدّة للقراءة فقط مع شرح موجز لأي ميزات خاصة أو جديدة تستخدمها، وبالتالي يجب أن يكون لديك فهم جيد وشامل لكيفية عمل الوجهات والدوال غير المتزامنة والعروض Views والنماذج عمليًا. تمر المواضيع التالية بعملية إضافة الميزات المختلفة المطلوبة لعرض صفحات الموقع المطلوبة، إذ يجب أن تطبّق كل قسم منها عمليًا قبل الانتقال إلى القسم التالي: مقدمة إلى القوالب Template، وإنشاء القالب الأساسي لموقع مكتبة محلية مثالًا. إنشاء الصفحة الرئيسية وصفحات القوائم لموقع المكتبة المحلية التي تتضمن: صفحة قائمة الكتب. صفحة قائمة نسخ الكتب BookInstance. تنسيق التاريخ باستخدام مكتبة Luxon. صفحة قائمة المؤلفين وتحدي صفحة قائمة أنواع الكتب. إنشاء صفحات التفاصيل لموقع المكتبة المحلية التي تتضمن: صفحة تفاصيل نوع الكتاب. صفحة تفاصيل الكتاب. صفحة تفاصيل المؤلف. صفحة تفاصيل نسخ الكتاب والتحدي. أنشأنا جميع الصفحات المُعَدّة للقراءة فقط الخاصة بموقعنا وهي: صفحة رئيسية تعرض عدد نسخ كلّ نموذج من نماذجنا، وصفحات قائمة وتفاصيل الكتب ونسخ الكتب والمؤلفين وأنواع الكتب، واكتسبنا الكثير من المعرفة الأساسية حول المتحكمات وإدارة التحكم في التدفق عند استخدام العمليات غير المتزامنة، وإنشاء عروض باستخدام مكتبة القوالب Pug، والاستعلام في قاعدة بيانات الموقع باستخدام النماذج، وتمرير المعلومات إلى العرض، وإنشاء القوالب وتوسيعها، وستعلّمك التحديات أيضًا عن معالجة التاريخ باستخدام مكتبة Luxon. سننشئ الآن استمارات HTML وشيفرة معالجة الاستمارات لبدء تعديل البيانات التي يخزنها الموقع. العمل مع الاستمارات استمارة HTML هي مجموعة مؤلفة من حقل أو عنصر واجهة مستخدم واحد أو أكثر على صفحة الويب، والتي يمكن استخدامها لجمع المعلومات من المستخدمين لإرسالها إلى الخادم، وتُعَد آليةً مرنة لتجميع مدخلات المستخدم نظرًا لوجود مدخلات مناسبة في الاستمارة التي تكون متاحة لإدخال العديد من أنواع البيانات المختلفة مثل مربعات النص ومربعات الاختيار وأزرار الاختيار ومنتقيات التاريخ وما إلى ذلك. تُعَد الاستمارات أيضًا طريقةً آمنة نسبيًا لمشاركة البيانات مع الخادم، لأنها تسمح بإرسال البيانات في طلبات POST مع الحماية من هجمات طلبات التزوير عبر المواقع. يمكن أن يكون التعامل مع الاستمارات معقدًا، إذ يحتاج المطورون إلى كتابة شيفرة HTML للاستمارة، والتحقق من صحة البيانات المُدخَلة وتطهيرها Sanitize بصورة صحيحة على الخادم وربما في المتصفح أيضًا، وإعادة نشر الاستمارة مع رسائل خطأ لإعلام المستخدمين بأي حقول غير صالحة، والتعامل مع البيانات عند إرسالها بنجاح، وأخيرًا الرد على المستخدم بطريقةٍ ما للإشارة إلى النجاح. سنوضح في هذا المقال كيفية تنفيذ هذه العمليات في إطار عمل Express، إذ سنوسّع موقع المكتبة المحلية LocalLibrary للسماح للمستخدمين بإنشاء عناصر وتعديلها وحذفها من المكتبة. ملاحظة: لم نتطرق إلى كيفية تقييد وجهات Routes معينة للمستخدمين المستوثقين أو المُصرَّح لهم، لذلك سيتمكن أيّ مستخدم من إجراء تغييرات على قاعدة البيانات. استمارات HTML اطّلع أولًا على نظرة عامة موجزة على استمارات HTML. ليكن لدينا استمارة HTML البسيطة التالية مع حقل نص واحد لإدخال اسم "الفريق" والتسمية Label المرتبطة به: تُعرَّف الاستمارات في لغة HTML بوصفها مجموعة من العناصر ضمن وسوم <form>…</form> التي تحتوي على عنصر إدخال input واحد على الأقل من النوع type="submit"‎. <form action="/team_name_url/" method="post"> <label for="team_name">Enter name: </label> <input id="team_name" type="text" name="name_field" value="Default name for team." /> <input type="submit" value="OK" /> </form> ضمّنا في المثال السابق حقلًا نصيًا واحدًا فقط لإدخال اسم الفريق، ولكن يمكن أن تحتوي الاستمارة على أيّ عدد من عناصر الإدخال الأخرى والتسميات Labels المرتبطة بها. تعرّف السمة type الخاصة بالحقل نوع عنصر واجهة المستخدم الذي سيُعرَض، وتُستخدَم سمات الاسم name والمعرّف id الخاصة بالحقل لتعريف الحقل في شيفرة جافا سكريبت Javascript أو CSS أو HTML، بينما تعرّف السمة value القيمة الأولية للحقل عند عرضه لأول مرة. تُحدَّد تسمية الفريق المطابقة باستخدام الوسم label (مثل التسمية "Enter name" السابقة)، مع حقل for الذي يحتوي على قيمة معرّف id لحقل الإدخال input المرتبط به. يُعرَض حقل الإدخال submit بوصفه زرًا افتراضيًا، إذ يمكن للمستخدم الضغط عليه لرفع البيانات التي تحتويها عناصر الإدخال الأخرى إلى الخادم، وهي اسم الفريق team_name فقط في مثالنا. تعرّف سمات الاستمارة تابع HTTP وهو method المستخدَم لإرسال البيانات وهدف البيانات على الخادم (السمة action) كما يلي: action: هو المورد أو عنوان URL، إذ ستُرسَل البيانات للمعالجة عند إرسال الاستمارة. إن لم تُضبَط هذه السمة، أو ضُبِطت بوصفها سلسلة نصية فارغة، فستُرسَل الاستمارة إلى عنوان URL للصفحة الحالية. method: تابع HTTP المُستخدَم لإرسال البيانات: إما POST أو GET. يجب دائمًا استخدام تابع POST إذا أدّت البيانات إلى تغييرٍ في قاعدة بيانات الخادم، لأنه يسمح بمقاومة أكبر لهجمات طلبات التزوير عبر المواقع. يجب استخدام تابع GET فقط للاستمارات التي لا تغير بيانات المستخدم (مثل استمارة البحث)، ويوصَى به عندما تريد أن تكون قادرًا على وضع إشارة مرجعية على عنوان URL أو مشاركته. عملية معالجة الاستمارة تستخدم معالجة الاستمارة الأساليب نفسها التي تعلمناها لعرض معلومات النماذج Models، إذ ترسِل الوِجهة طلبنا إلى دالة متحكم تطبّق أيّ إجراءات قاعدة بيانات مطلوبة بما في ذلك قراءة البيانات من النماذج، ثم تولّد صفحة HTML وتعيدها، ولكن ما يعقّد الأمور هو أن الخادم يحتاج أيضًا إلى أن يكون قادرًا على معالجة البيانات التي يقدّمها المستخدم، وإعادة عرض الاستمارة مع معلومات الخطأ عند وجود مشاكل. يوضّح المخطط التالي عملية معالجة طلبات الاستمارة، بدءًا من طلب الصفحة التي تحتوي على استمارة (كما هو موضّح باللون الأخضر): الأشياء الرئيسية التي يجب أن تطبقها شيفرة معالجة الاستمارة هي: عرض الاستمارة الافتراضية في المرة الأولى التي يطلبها المستخدم. يمكن أن تحتوي الاستمارة على حقول فارغة (إذا أنشأتَ سجلًا جديدًا مثلًا)، أو يمكن ملؤها مسبقًا بالقيم الأولية (إذا غيّرتَ سجلًا أو كان لديك قيم أولية افتراضية مفيدة مثلًا). تلقي البيانات التي يرسلها المستخدم في طلب HTTP من النوع POST مثلًا. التحقق من صحة البيانات وتطهيرها. إذا كان هناك بيانات غير صالحة، فأعِد عرض الاستمارة مع أيّ قيم يملأها المستخدم ورسائل خطأ للحقول التي تحتوي على مشاكل. إذا كانت جميع البيانات صالحة، فطبّق الإجراءات المطلوبة، مثل حفظ البيانات في قاعدة البيانات وإرسال إشعار بالبريد الإلكتروني وإعادة نتيجة البحث وتحميل ملف وإلخ. إعادة توجيه المستخدم إلى صفحة أخرى بعد اكتمال جميع الإجراءات. تُقدَّم شيفرة معالجة الاستمارة غالبًا باستخدام وِجهة GET للعرض الأولي للاستمارة ووجهة POST إلى المسار نفسه للتعامل مع التحقق من صحة بيانات الاستمارة ومعالجتها، إذ سنستخدم هذا الأسلوب في هذا المقال. لا يوفّر إطار عمل Express أيّ دعم لعمليات معالجة الاستمارة، ولكن يمكنه استخدام البرمجيات الوسيطة لمعالجة معاملات POST و GET من الاستمارة وللتحقق من صحة أو تطهير قيمها. التحقق من صحة البيانات وتطهيرها يجب التحقق من صحة البيانات الواردة من النموذج وتطهيرها قبل تخزينها كما يلي: تتحقق عملية التحقق من صحة البيانات Validation من أن القيم المُدخَلة مناسبة لكل حقل، أي أنها ضمن المجال الصحيح والتنسيق الصحيح وإلخ وأن هذه القيم متوفرة لجميع الحقول المطلوبة. تزيل أو تستبدل عملية التطهير Sanitization المحارف الموجودة في البيانات التي يمكن أن تُستخدَم لإرسال محتوًى ضار إلى الخادم. سنستخدم في هذا المقال الوحدة express-validator الشائعة لإجراء كلٍّ من التحقق من صحة بيانات الاستمارة وتطهيرها، إذ يمكننا ثثبيتها من خلال تشغيل الأمر التالي في جذر المشروع: npm install express-validator ملاحظة: يوفر دليل وحدة express-validator على غيت هب GitHub نظرة عامة جيدة على واجهة برمجة التطبيقات، لذا نوصيك بقراءته للحصول على فكرة عن جميع إمكانياتها بما في ذلك استخدام التحقق من صحة المخطط Schema Validation وإنشاء أدوات تحقق مخصصة، إذ سنشرح فيما يلي فقط جزءًا مفيدًا لموقع المكتبة المحلية. يمكن استخدام أداة التحقق من صحة البيانات Validator في المتحكمات من خلال تحديد الدوال التي نريد استيرادها من وحدة express-validator كما يلي: const { body, validationResult } = require("express-validator"); هناك العديد من الدوال المتاحة، مما يسمح بفحص وتطهير البيانات الواردة من معاملات الطلب وجسمه وترويساته وملفات تعريف الارتباط cookies وغير ذلك أو جميعها معًا في وقت واحد، إذ سنستخدم في هذا المقال body و validationResult (كما هو مطلوب سابقًا). تُعرَّف الدوال على النحو التالي: أولًا، body([fields, message])‎ التي تحدّد مجموعة من الحقول في متن الطلب (معامل POST) للتحقق من صحة البيانات و/أو تطهيرها مع رسالة خطأ اختيارية يمكن عرضها إذا فشلت الاختبارات. تتمثل معايير التحقق من صحة البيانات وتطهيرها بسلسلة متعاقبة في تابع body()‎، فمثلًا يحدّد السطر التالي أولًا أننا نتحقق من حقل "الاسم name" وأن خطأ التحقق من صحة البيانات سيضبط رسالة الخطأ "اسم فارغ Empty name"، ثم نستدعي تابع التطهير trim()‎ لإزالة المسافة من بداية السلسلة ونهايتها، ثم isLength()‎ للتحقق من أن السلسلة النصية الناتجة ليست فارغة. أخيرًا، نستدعي escape()‎ لإزالة محارف HTML من المتغير الذي يمكن استخدامه في هجمات كتابة سكربتات جافا سكربت العابرة للمواقع. [ // … body("name", "Empty name").trim().isLength({ min: 1 }).escape(), // … ]; يتحقق الاختبار التالي من أن حقل العمر age هو تاريخ صالح ويستخدم optional()‎ لتحديد أن السلاسل النصية الخالية والفارغة لن تفشل في التحقق من صحة البيانات. [ // … body("age", "Invalid age") .optional({ values: "falsy" }) .isISO8601() .toDate(), // … ]; يمكنك أيضًا استخدام سلسلة متعاقبة لأدوات التحقق من صحة البيانات المختلفة وإضافة الرسائل التي تُعرَض إذا كانت أدوات التحقق السابقة صحيحة. [ // … body("name") .trim() .isLength({ min: 1 }) .withMessage("Name empty.") .isAlpha() .withMessage("Name must be alphabet letters."), // … ]; ثانيًا، الدالة validationResult(req)‎ التي تشغّل عملية التحقق من صحة البيانات، وتتيح الأخطاء بصيغة كائن النتيجة validation، وتستدعَى في دالة رد نداء منفصلة كما هو موضح فيما يلي: asyncHandler(async (req, res, next) => { // استخراج رسائل التحقق من صحة البيانات من الطلب const errors = validationResult(req); if (!errors.isEmpty()) { // هناك أخطاء، لذا اعرض الاستمارة مرة أخرى مع القيم المُطهَّرة أو رسائل الأخطاء // يمكن إعادة رسائل الخطأ في مص‫فوفة باستخدام errors.array()‎ } else { // البيانات الواردة من الاستمارة صالحة } }); نستخدم التابع isEmpty()‎ الخاص بنتيجة التحقق من صحة البيانات للتحقق مما إذا كان هناك أخطاء، والتابع array()‎ للحصول على مجموعة رسائل الخطأ (اطلع على قسم معالجة التحقق من صحة البيانات للحصول على مزيد من المعلومات). تُعَد سلاسل التحقق من صحة البيانات وتطهيرها برمجيات وسيطة يجب تمريرها إلى معالج وجهة Express، إذ نطبّق ذلك بطريقة غير مباشرة باستخدام المتحكم، وتعمل أدوات التحقق من صحة البيانات وتطهيرها بالترتيب المُحدَّد عند تشغيل البرمجية الوسيطة. سنغطي بعض الأمثلة الحقيقية عندما نطبق استمارات موقع المكتبة المحلية LocalLibrary. تصميم الاستمارة ترتبط أو تعتمد العديد من النماذج في المكتبة على بعضها بعضًا، إذ يتطلب الكتاب Book مؤلفًا Author ويمكن أن يكون له نوع Genre واحد أو أكثر، مما يؤدي إلى التساؤل حول كيفية التعامل مع الحالة التي يرغب فيها المستخدم في: إنشاء كائن عندما لا تكون الكائنات المرتبطة به موجودةً بعد، مثل كتاب لم يُعرَّف كائن المؤلف فيه. حذف كائن لا يزال كائن آخر يستخدمه مثل حذف كائن النوع Genre الذي لا يزال كائن الكتاب Book يستخدمه. سنبسّط العمل في هذا المشروع بالقول إن الاستمارة يمكنها فقط: إنشاء كائن يستخدم كائنات موجودة فعليًا، لذلك يجب على المستخدمين إنشاء النسخ المطلوبة من المؤلف Author ونوع الكتاب Genre قبل محاولة إنشاء أي كائنات Book. حذف كائن إن لم تشِر إليه كائنات أخرى، فمثلًا لن تتمكّن من حذف كتاب Book حتى حذف جميع كائنات BookInstance المرتبطة به. ملاحظة: يمكن أن يسمح لك التقديم الأكثر مرونة بإنشاء كائنات معتمدة على بعضها البعض عند إنشاء كائن جديد، وحذف أيّ كائن في أي وقت من خلال حذف الكائنات المعتمدة عليه أو إزالة المراجع التي تشير إلى الكائن المحذوف من قاعدة البيانات مثلًا. الوجهات Routes سنحتاج إلى وجهتين لهما نمط عنوان URL نفسه لتقديم شيفرة معالجة الاستمارة، إذ تُستخدَم الوجهة الأولى GET لعرض استمارة فارغة جديدة لإنشاء الكائن، وتُستخدَم الوجهة الثانية (POST) للتحقق من صحة البيانات التي أدخلها المستخدم، ثم حفظ المعلومات وإعادة التوجيه إلى صفحة التفاصيل (إذا كانت البيانات صالحة) أو إعادة عرض الاستمارة مع وجود أخطاء (إذا كانت البيانات غير صالحة). أنشأنا الوجهات مسبقًا لجميع صفحات إنشاء النموذج في الملف "‎/routes/catalog.js" مثل وجهات نوع الكتب التالية: // طل‫ب GET لإنشاء نوع كتاب Genre، إذ يجب أن يأتي قبل الوجهة التي تعرض نوع الكتاب (تستخدم المعرّف id) router.get("/genre/create", genre_controller.genre_create_get); // ط‫لب POST لإنشاء نوع كتاب router.post("/genre/create", genre_controller.genre_create_post); توضّح المواضيع التالية عملية إضافة الاستمارات المطلوبة إلى تطبيقنا، إذ يجب العمل على كل منها ثم الانتقال إلى الموضوع التالي: إنشاء استمارة نوع الكتاب Genre: تعريف صفحة لإنشاء كائنات Genre. إنشاء استمارة المؤلف Author واستمارة الكتاب Book واستمارة نسخة الكتاب BookInstance: تعريف صفحة لإنشاء كائنات Author وتعريف صفحة أو استمارة لإنشاء كائنات Book وتعريف صفحة أو استمارة لإنشاء كائنات BookInstance. حذف استمارة المؤلف Author وتحديث استمارة الكتاب Book: تعريف صفحة لحذف كائنات Author وتعريف صفحة لتحديث كائنات Book. تحدى نفسك طبّق صفحات الحذف لنماذج Book و BookInstance و Genre واربطها بصفحات التفاصيل المتعلقة بها باستخدام الطريقة نفسها لصفحة حذف المؤلف، ويجب أن تتبع الصفحات أسلوب التصميم نفسه بحيث: إذا كان هناك مراجع إلى كائن من كائنات أخرى، فيجب عرض هذه الكائنات الأخرى مع ملاحظة أنه لا يمكن حذف هذا السجل حتى حذف الكائنات. إن لم يكن هناك مراجع أخرى إلى الكائن، فيجب أن يطالب العرض بحذفه، وإذا ضغط المستخدم على زر الحذف، فيجب حذف السجل. إليك بعض النصائح لتطبيقها: يشبه حذفُ الكائن Genre حذفَ الكائن Author تمامًا، فكلا الكائنين يعتمد عليهما الكائن Book، لذلك لا يمكنك حذف الكائن إلّا عند حذف الكتب المرتبطة به في كلتا الحالتين. يحدث الشيء نفسه عند حذف الكائن Book أيضًا، إذ يجب التحقق أولًا من عدم وجود كائنات BookInstance مرتبطة به. يُعَد حذف الكائن BookInstance أسهل ما في الأمر لأنه لا توجد كائنات معتمدة عليه، إذ يمكنك في هذه الحالة العثور على السجل وحذفه. طبّق صفحات التحديث لنماذج BookInstance و Author و Genre، واربطها بصفحات التفاصيل المرتبطة بها بالطريقة نفسها لصفحة تحديث الكتاب. واتبع أيضًا النصائح التالية: تُعَد صفحة تحديث الكتاب التي طبّقناها للتو هي الأصعب، ويمكن استخدام الأنماط نفسها لصفحات التحديث للكائنات الأخرى. يُعَد حقل تاريخ وفاة المؤلف Author وتاريخ ميلاده وحقل تاريخ استرجاع نسخة الكتاب BookInstance تنسيقًا خاطئًا لإدخاله في حقل إدخال التاريخ في الاستمارة، إذ يتطلب بياناتٍ في الاستمارة بالتنسيق: "YYYY-MM-DD". أسهل طريقة للتغلب على ذلك هي تعريف خاصية افتراضية جديدة للتواريخ التي تنسّق التواريخ بصورة مناسبة، ثم استخدام هذا الحقل في قوالب العرض المرتبطة به. إذا واجهتك مشكلة، فهناك أمثلة لصفحات التحديث يمكنك الاطلاع عليها. الخلاصة توفّر حزم Express و node والحزم الخارجية لمدير حزم npm كل ما تحتاجه لإضافة استمارات إلى موقع الويب، إذ تعلمت في هذا المقال كيفية إنشاء استمارات باستخدام Pug والتحقق من صحة بيانات الإدخال وتطهيرها باستخدام express-validator وإضافة السجلات وحذفها وتعديلها في قاعدة البيانات، ويجب أن تفهم الآن كيفية إضافة الاستمارات الأساسية وشيفرة معالجة الاستمارات إلى مواقع Node الخاصة بك. ترجمة -وبتصرُّف- للمقالين Express Tutorial Part 5: Displaying library data و Express Tutorial Part 6: Working with forms. اقرأ أيضًا المقال السابق تطبيق عملي لتعلم Express - الجزء الثالث: الوجهات Routes والمتحكمات Controllers. الاستمارات (forms) في متصفح الويب وكيفية التعامل معها في جافاسكربت. اإرسال الاستمارات (form submit) ومعالجتها في جافاسكربت.
  24. سنُعِدّ في هذا المقال الوجهات Routes (شيفرة معالجة عناوين URL، ويسميها البعض بالموجهات) باستخدام دوال معالجة وهمية dummy لجميع النقاط النهائية للموارد التي سنحتاجها في موقع المكتبة المحلية LocalLibrary. سيكون لدينا في النهاية بنية معيارية لشيفرة معالجة الوجهات، والتي يمكننا توسيعها باستخدام دوال معالجة حقيقية في المقالات اللاحقة، وسيصبح لدينا أيضًا فهم جيد لكيفية إنشاء وجهات معيارية باستخدام إطار عمل Express. المتطلبات الأساسية: الاطلاع على مقال مدخل إلى إطار عمل الويب Express، وإكمال المقالات السابقة من هذه السلسلة بما في ذلك مقال استخدام قاعدة البيانات. الهدف: فهم كيفية إنشاء وجهات بسيطة وإعداد جميع النقاط النهائية لعناوين URL. تعرّفنا في المقال السابق على نماذج Mongoose للتفاعل مع قاعدة البيانات، واستخدمنا سكربتًا مستقلًا لإنشاء بعض سجلات المكتبة الأولية، ويمكننا الآن كتابة الشيفرة البرمجية لتقديم تلك المعلومات للمستخدمين. يجب أولًا تحديد المعلومات التي نريد عرضها في صفحاتنا، ثم تعريف عناوين URL المناسبة لإعادة تلك الموارد، ثم يجب إنشاء الوجهات (معالجات عناوين URL) والعروض Views (القوالب Templates) لعرض تلك الصفحات. يوضح المخطط البياني الآتي التدفق الرئيسي للبيانات والأشياء التي يجب تقديمها عند التعامل مع طلب أو استجابة HTTP، ويُوضّح العروض والوجهات والمتحكّمات التي تمثل الدوال التي تفصل الشيفرة البرمجية لتوجيه الطلبات من هذه الشيفرة التي تعالج الطلبات فعليًا. أنشأنا النماذج مسبقًا، فالأشياء الرئيسية التي يجب إنشاؤها هي: "الوجهات Routes" لتوجيه الطلبات المدعومة وأيّ معلومات مُشفَّرة في عناوين URL للطلب إلى دوال المتحكم المناسبة. دوال المتحكم للحصول على البيانات المطلوبة من النماذج وإنشاء صفحة HTML تعرض البيانات وإعادتها إلى المستخدم لعرضها في المتصفح. العروض (القوالب) التي تستخدمها المتحكمات لتقديم البيانات. يمكن أن يكون لدينا في النهاية صفحات لإظهار القوائم والمعلومات التفصيلية للكتب وأنواعها والمؤلفين ونسخ الكتب، وصفحات لإنشاء السجلات وتحديثها وحذفها. تُعَد هذه المعلومات كثيرة لعرضها في مقال واحد، لذلك سيركز معظم هذا المقال على إعداد الوجهات والمتحكمات لإعادة محتوًى "وهمي"، وسنوسّع توابع المتحكم في مقالاتنا اللاحقة للعمل مع بيانات النموذج. يوفّر القسم الأول التالي نظرةً عامةً موجزة حول كيفية استخدام برمجية Express الوسيطة Router، ثم سنستخدم ذلك في الأقسام اللاحقة عند إعداد وجهات موقع المكتبة المحلية LocalLibrary. مقدمة إلى الوجهات الوجهة هي قسم من شيفرة Express التي تربط بين فعل HTTP، مثل GET و POST و PUT و DELETE وإلخ مع مسار أو نمط URL ودالة تُستدعى لمعالجة هذا النمط. هناك عدة طرق لإنشاء الوجهات، وسنستخدم في هذا المقال البرمجية الوسيطة express.Router لأنها تسمح بتجميع معالجات الوجهات لجزء معين من الموقع مع بعضها البعض والوصول إليها باستخدام بادئة prefix وجهة مشتركة. سنحتفظ بجميع الوجهات المتعلقة بالمكتبة في وحدة "دليل catalog"، وإذا أضفنا وجهات لمعالجة حسابات المستخدمين أو لدوال أخرى، فيمكننا الاحتفاظ بها مُجمَّعةً بصورة منفصلة. ملاحظة: ناقشنا سابقًا وجهات تطبيق Express باختصار في مقال سابق. يشبه استخدام كائن الموجّه Router إلى حدٍ كبير تعريف الوجهات مباشرةً في كائن تطبيق Express باستثناء تقديم دعم أفضل للتقسيم إلى وحدات Modularization (كما سننافش في القسم التالي). يوفر الجزء المتبقي من هذا القسم نظرةً عامة حول كيفية استخدام الكائن Router لتعريف الوجهات. تعريف واستخدام وحدات وجهة منفصلة تقدم الشيفرة البرمجية التالية مثالًا عن كيفية إنشاء وحدة وجهة ثم استخدامها في تطبيق Express. أولًا، ننشئ وجهات لميزة الويكي wiki في وحدة بالاسم "wiki.js"، إذ تستورد هذه الشيفرة البرمجية كائن تطبيق Express، وتستخدمه للحصول على كائن Router، ثم تضيف بعض الوجهات إليه باستخدام التابع get()‎، ثم تصدّر الوحدةُ الكائنَ Router. // ‫wiki.js: ‫وحدة وجهة ويكي Wiki const express = require("express"); const router = express.Router(); // وجهة الصفحة الرئيسية router.get("/", function (req, res) { res.send("Wiki home page"); }); // ‫وجهة صفحة About router.get("/about", function (req, res) { res.send("About this wiki"); }); module.exports = router; ملاحظة: عرّفنا في الشيفرة السابقة دوال رد النداء Callbacks الخاصة بمعالج الوجهة مباشرةً في دوال وجهة، وسنعرّف في موقع المكتبة المحلية LocalLibrary دوال رد النداء هذه في متحكم وحدة منفصل. يمكن استخدام وحدة وجهة في ملف تطبيقنا الرئيسي من خلال طلب وحدة الوجهة (wiki.js) باستخدام الدالة require()‎، ثم استدعاء الدالة use()‎ في تطبيق Express لإضافة كائن Router إلى مسار معالجة البرمجية الوسيطة مع تحديد مسار URL الخاص بالويكي "wiki". const wiki = require("./wiki.js"); // … app.use("/wiki", wiki); ويمكن بعد ذلك الوصول إلى الوجهتين المُعرَّفتين في وحدة وجهة wiki من /wiki/ و /wiki/about/. دوال الوجهة تعرّف الوحدة السابقة بعضًا من دوال الوجهة، إذ تُعرَّف الوجهة "about" (التي سنعيد إنتاجها فيما يلي) باستخدام التابع Router.get()‎، والذي يستجيب فقط لطلبات HTTP من النوع GET. الوسيط الأول لهذا التابع هو مسار URL والوسيط الثاني هو دالة رد نداء تُستدعَى عند تلقي طلب HTTP من النوع GET مع المسار. router.get("/about", function (req, res) { res.send("About this wiki"); }); تأخذ دالة رد النداء ثلاثة وسائط تُسمَّى عادة req و res و next، والتي ستحتوي على كائن طلب HTTP واستجابة HTTP والدالة التالية next في سلسلة البرمجيات الوسيطة. ملاحظة: تُعَد دوال Router برمجيات Express وسيطة، مما يعني أنها يجب إما إكمال (أو الاستجابة) للطلب أو استدعاء الدالة next في السلسلة، إذ نكمل في حالتنا الطلب باستخدام التابع send()‎، لذلك لا يُستخدَم الوسيط next ونختار عدم تحديده. تأخذ دالة router السابقة دالة رد نداء واحدة، ولكن يمكنك تحديد عدة وسائط لدوال رد النداء أو مصفوفة من دوال رد النداء. تُعَد كل دالة جزءًا من سلسلة البرمجيات الوسيطة، وستُستدعَى بالترتيب نفسه لإضافتها إلى السلسلة (إلّا إذا أكملت الدالة السابقة الطلب). تستدعي دالة رد النداء في مثالنا التابع send()‎ في الاستجابة لإعادة السلسلة النصية "About this wiki" عندما نتلقى طلب GET مع المسار ("‎/about"). هناك عدد من توابع الاستجابة الأخرى لإنهاء دورة الطلب/الاستجابة، فمثلًا يمكنك استدعاء التابع res.json()‎ لإرسال استجابة JSON أو التابع res.sendFile()‎ لإرسال ملف. يُعَد التابع render()‎ تابع الاستجابة الذي سنستخدمه غالبًا أثناء بناء المكتبة، والذي ينشئ ويعيد ملفات HTML باستخدام القوالب والبيانات (سنتحدث عن ذلك في مقالٍ لاحق). أفعال HTTP تستخدم أمثلة الوجهات السابقة التابع Router.get()‎ للاستجابة على طلبات HTTP من النوع GET مع مسار معين. يوفّر الكائن Router أيضًا توابع وجهة لجميع أفعال HTTP الأخرى، والتي تُستخدَم غالبًا بنفس الطريقة تمامًا ومن هذه التوابع: post()‎ و put()‎ و delete()‎ و options()‎ و trace()‎ و copy()‎ و lock()‎ و mkcol()‎ و move()‎ و purge()‎ و propfind()‎ و proppatch()‎ و unlock()‎ و report()‎ و mkactivity()‎ و checkout()‎ و merge()‎ و m-search()‎ و notify()‎ و subscribe()‎ و unsubscribe()‎ و patch()‎ و search()‎ و connect()‎. تتصرف الشيفرة البرمجية التالية مثلًا مثل وجهة ‎/about السابقة، ولكنها تستجيب فقط لطلبات HTTP من النوع POST: router.post("/about", (req, res) => { res.send("About this wiki"); }); مسارات الوجهة تعرِّف مساراتُ الوجهة النقاطَ النهائية التي يمكن إجراء الطلبات عندها، فالأمثلة التي رأيناها حتى الآن كانت مجرد سلاسل نصية، واُستخدِمت كما هو مكتوب تمامًا مثل: '/' و '‎/about' و '‎/book' و '‎/any-random.path'. يمكن أن تكون مسارات الوجهة أيضًا أنماطًا من السلاسل النصية التي تستخدم شكلًا من صيغ التعابير النمطية لتعريف أنماط النقاط النهائية التي ستجري مطابقتها. نوضح فيما يلي هذه الصيغة (لاحظ تفسير الشَرطة الواصلة (-) والنقطة (.) حرفيًا باستخدام المسارات المستندة إلى السلاسل النصية): ?: يجب أن تحتوي النقطة النهائية endpoint على 0 أو 1 من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة '‎/ab?cd' مع النقاط النهائية acd أو abcd. +: يجب أن تحتوي النقطة النهائية على واحد أو أكثر من المحرف السابق (أو المجموعة)، فمثلًا سيتطابق مسار الوجهة '‎/ab+cd' مع النقاط النهائية abcd و abbcd و abbbcd وإلخ. *: يمكن أن تحتوي النقطة النهائية على سلسلة نصية عشوائية في مكان المحرف *، فمثلًا سيتطابق مسار الوجهة '‎/ab*cd' مع النقاط النهائية abcd و abXcd و abSOMErandomTEXTcd وإلخ. (): تجميع تطابق مجموعة من المحارف لإجراء عملية أخرى عليها، فمثلًا سينفّذ '‎/ab(cd)?e' تطابق ? على المجموعة (cd)، إذ سيتطابق مع abe و abcde. يمكن أن تكون مسارات الوجهة أيضًا تعابير نمطية Regular Expressions في لغة جافا سكريبت JavaScript، فمثلًا سيتطابق مسار الوجهة التالي مع catfish و dogfish، ولكن لن يتطابق مع catflap و catfishhead وما إلى ذلك. لاحظ أن مسار التعبير النمطي يستخدم صياغة التعابير النمطية، وهي ليست سلسلة نصية بين علامات اقتباس كما في الحالات السابقة. app.get(/.*fish$/, function (req, res) { // … }); ملاحظة: ستستخدم معظم الوجهات في موقع المكتبة المحلية LocalLibrary سلاسلًا نصية ولن تستخدم تعابيرًا نمطية، وسنستخدم أيضًا معاملات الوجهات كما سنناقش في القسم التالي. معاملات الوجهة تُسمَّى معاملات الوجهة بأجزاء Segments عنوان URL المُستخدَمة لالتقاط القيم في مواضع محددة من عنوان URL، إذ تُسبَق هذه المقاطع بنقطتين ثم الاسم (مثل ‎/:your_parameter_name/‎). تُخزَّن القيم المُلتقَطة في كائن req.params من خلال استخدام أسماء المعاملات بوصفها مفاتيحًا، مثل req.params.your_parameter_name. ليكن لدينا مثلًا عنوان URL مُشفَّر يحتوي على معلومات حول المستخدمين والكتب: http://localhost:3000/users/34/books/8989، إذ يمكننا استخراج هذه المعلومات كما هو موضح فيما يلي باستخدام معاملات مسار userId و bookId: app.get("/users/:userId/books/:bookId", (req, res) => { // ‫الوصول إلى userId باستخدام req.params.userId // الوصول إلى‫ bookId باستخدام req.params.bookId res.send(req.params); }); يجب أن تتكون أسماء معاملات الوجهة من "محارف كلمات"، أي A-Z و a-z و0-9 و _. ملاحظة: ستجري مطابقة عنوان "‎/book/create" مع وجهة مثل الوجهة ‎/book/:bookId، والتي ستستخرج قيمة "bookId" الخاصة بالإنشاء 'create'. ستُستخدَم الوجهة الأولى التي تطابق عنوان URL الوارد، لذلك إذا أردتَ معالجة عناوين ‎/book/create بصورة منفصلة، فيجب تعريف معالج الوجهة قبل الوجهة ‎/book/:bookId. هذا كل ما تحتاجه لبدء استخدام الوجهات، ولكن يمكنك العثور على مزيد من المعلومات في مقال كيفية تحديد الوجهات وأنواع طلبات HTTP وفي توثيق Express: التوجيه الأساسي ودليل التوجيه. توضح الأقسام التالية كيفية إعداد الوجهات والمتحكمات لموقع المكتبة المحلية. معالجة الأخطاء في دوال الوجهة تحتوي جميع دوال الوجهة الموضحة سابقًا على وسائط req و res التي تمثل الطلب والاستجابة على التوالي. تُستدعَى دوال الوجهة أيضًا مع وسيط ثالث هو next، والذي يمكن استخدامه لتمرير الأخطاء إلى سلسلة برمجيات Express الوسيطة. توضح الشيفرة التالية كيفية إنجاز ذلك باستخدام مثال استعلام قاعدة البيانات الذي يأخذ دالة رد نداء ويعيد إما خطأ err أو بعض النتائج. تُستدعَى next مع خطأ err بوصفه قيمة معاملها الأول عند إعادة خطأ err، وسينتقل هذا الخطأ في النهاية إلى شيفرة معالجة الأخطاء العامة، وتُعاد البيانات المطلوبة ثم تُستخدَم في الاستجابة في حالة النجاح. router.get("/about", (req, res, next) => { About.find({}).exec((err, queryResults) => { if (err) { return next(err); } // اعرض شيئًا ما في حالة النجاح res.render("about_view", { title: "About", list: queryResults }); }); }); معالجة الاستثناءات في دوال الوجهة يوضح القسم السابق كيف يتوقع إطار عمل Express أن تعيد دوال الوجهة أخطاءً، إذ صُمِّم إطار العمل للاستخدام مع الدوال غير المتزامنة التي تأخذ دالة رد نداء (مع خطأ وووسيط النتيجة)، والتي تُستدعَى عند اكتمال العملية. يُعَد ذلك مشكلة لأننا سنجري لاحقًا استعلامات قاعدة بيانات Mongoose التي تستخدم واجهات برمجة تطبيقات مستندة إلى الوعود promise، والتي يمكن أن تؤدي إلى رمي استثناءات في دوال الوجهة بدلًا من إعادة الأخطاء في دالة رد النداء. يمكن أن يعالج إطار العمل الاستثناءات بصورة صحيحة من خلال اكتشافها ثم توجيهها بوصفها أخطاءً كما هو موضح في القسم السابق. ملاحظة: من المتوقع أن يعالج Express 5 التجريبي استثناءات جافا سكريبت بطريقة أصيلة. ليكن لدينا المثال البسيط من القسم السابق مع وجود About.find().exec()‎ بوصفه استعلام قاعدة بيانات يعيد وعدًا، إذ يمكن كتابة دالة الوجهة ضمن كتلة try...catch كما يلي: exports.get("/about", async function (req, res, next) { try { const successfulResult = await About.find({}).exec(); res.render("about_view", { title: "About", list: successfulResult }); } catch (error) { return next(error); } }); يُعَد ذلك كمًا كبيرًا جدًا من الشيفرة البرمجية المتداولة لإضافتها إلى كل دالة، لذا سنستخدم بدلًا من ذلك وحدة express-async-handler التي تعرّف دالة تغليف تخفي كتلة try...catch والشيفرة البرمجية لتوجيه الخطأ، وبالتالي أصبح المثال نفسه الآن بسيطًا جدًا، لأننا نحتاج فقط إلى كتابة شيفرة برمجية للحالة التي نفترض فيها النجاح: // استيراد الوحدة const asyncHandler = require("express-async-handler"); exports.get( "/about", asyncHandler(async (req, res, next) => { const successfulResult = await About.find({}).exec(); res.render("about_view", { title: "About", list: successfulResult }); }), ); الوجهات اللازمة لموقع المكتبة المحلية سنعرض فيما يلي عناوين URL التي سنحتاجها لصفحاتنا، إذ نضع اسم كل نموذج من نماذجنا (الكتاب ونسخة الكتاب ونوع الكتاب والمؤلف) مكان الكائن object، والكائنات objects هي جمع كائن، والمعرّف id هو نسخة فريدة من الحقل (‎_id) تُعطَى لكل نسخة من نموذج Mongoose افتراضيًا. catalog/‏‎: الصفحة الرئيسية أو صفحة الفهرس. catalog/<objects>/‏‎: قائمة بجميع الكتب أو نسخها أو أنواعها أو المؤلفين، مثل /catalog/books/ و /catalog/genres/ وغير ذلك. catalog/<object>/<id>‎: صفحة التفاصيل لكتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل ‎_id المُحدَّدة، مثل ‎/catalog/book/584493c1f4887f06c0e67d37. catalog/<object>/create: استمارة إنشاء كتاب أو نسخة أو نوع أو مؤلف جديد، مثل ‎/catalog/book/create. catalog/<object>/<id>/update: استمارة لتحديث كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل ‎_id المُحدَّدة، مثل ‎/catalog/book/584493c1f4887f06c0e67d37/update. catalog/<object>/<id>/delete: استمارة لحذف كتاب أو نسخة أو نوع أو مؤلف معين مع قيمة الحقل ‎_id المُحدَّدة، مثل ‎/catalog/book/584493c1f4887f06c0e67d37/delete. لا تشفِّر الصفحة الرئيسية الأولى وصفحات القائمة أيّ معلومات إضافية، فبينما تعتمد النتائج المُعادة على نوع النموذج والمحتوى في قاعدة البيانات، ستبقى الاستعلامات المُنفَّذة للحصول على المعلومات نفسها دائمًا، وبالمثل، ستكون الشيفرة البرمجية المُنفَّذة لإنشاء الكائن نفسها دائمًا. تُستخدَم عناوين URL الأخرى للعمل على نسخةٍ من مستند أو نموذج معين، وتشفّر هوية العنصر في عنوان URL (كما هو موضّح في العنصر <id> سابقًا). سنستخدم معاملات المسار لاستخراج المعلومات المشفرة وتمريرها إلى معالج الوجهة، وسنستخدمها في مقال لاحق لتحديد المعلومات التي نحصل عليها من قاعدة البيانات ديناميكيًا. نحتاج فقط إلى وجهة واحدة لكل مورد من نوع معين من خلال تشفير المعلومات في عنوان URL، مثل استخدام وجهة واحدة للتعامل مع عرض عنصر كتاب واحد. ملاحظة: يسمح إطار عمل Express بإنشاء عناوين URL الخاصة بك بالطريقة التي تريدها، إذ يمكنك تشفير المعلومات في متن عنوان URL أو استخدام معاملات URL من النوع GET (مثل ‎/book/?id=6). يجب أن تظل عناوين URL نظيفة ومنطقية وقابلة للقراءة بغض النظر عن الطريقة التي تستخدمها. سننشئ فيما يلي دوال رد نداء معالجة الوجهة وشيفرة الوجهة البرمجية لجميع عناوين URL السابقة. إنشاء دوال رد النداء لمعالجة الوجهة سننشئ أولًا جميع دوال رد النداء الوهمية أو الهيكلية التي ستُستدعَى لاحقًا قبل أن نعرّف الوجهات، وستُخزَّن دوال رد النداء في وحدات متحكمات منفصلة لكل من Book و BookInstance و Genre و Author، إذ يمكنك استخدام أي بنية ملفات أو وحدات، ولكن تبدو الطريقة التي سنستخدمها مناسبة ودقيقة لهذا المشروع. ابدأ بإنشاء مجلدٍ للمتحكمات في جذر المشروع ‎/controllers، ثم أنشئ ملفات أو وحدات منفصلة للمتحكمات للتعامل مع كل نموذج كما يلي: /express-locallibrary-tutorial //the project root /controllers authorController.js bookController.js bookinstanceController.js genreController.js ستستخدم المتحكمات الوحدة express-async-handler، لذا ثبّتها في المكتبة باستخدام npm قبل المتابعة كما يلي: npm install --save express-async-handler متحكم المؤلف Author افتح الملف "‎/controllers/authorController.js" واكتب فيه الشيفرة البرمجية التالية: const Author = require("../models/author"); const asyncHandler = require("express-async-handler"); // عرض قائمة بجميع المؤلفين exports.author_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author list"); }); // عرض صفحة التفاصيل لمؤلف معين exports.author_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Author detail: ${req.params.id}`); }); // عرض استمارة إنشاء م‫ؤلف باستخدام GET exports.author_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author create GET"); }); // معالجة إنشاء مؤلف باستخدام‫ POST exports.author_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author create POST"); }); // عرض است‫مارة حذف المؤلف باستخدام GET exports.author_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author delete GET"); }); // معالجة ‫حذف المؤلف باستخدام POST exports.author_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author delete POST"); }); // عرض استمارة تحد‫يث المؤلف باستخدام GET exports.author_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author update GET"); }); // معالجة تحديث‫ المؤلف باستخدام POST exports.author_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Author update POST"); }); تطلب الوحدة أولًا نموذج المؤلف Author الذي سنستخدمه لاحقًا للوصول إلى بياناتنا وتحديثها، والمغلِّف asyncHandler الذي سنستخدمه لالتقاط الاستثناءات من دوال معالج الوجهة، ثم تصدِّر دوالًا لكل عنوان من عناوين URL التي نرغب في معالجتها. لاحظ أن عمليات الإنشاء والتحديث والحذف تستخدم الاستمارات Forms، وبالتالي تملك توابعًا إضافية لمعالجة طلبات الاستمارة من النوع Post، وسنناقش هذه التوابع في مقال لاحق. تستخدم جميع الدوال الدالة المُغلِّفة Wrapper السابقة في معالجة الاستثناءات في دوال الوجهة مع وسائط للطلب والاستجابة والدالة التالية next، وتستجيب الدوال بسلسلة نصية تشير إلى أن الصفحة المرتبطة لم تُنشَأ بعد. إذا كان من المتوقع أن تتلقى دالة المتحكم معاملات المسار، فستكون هذه المعاملات هي الخرج الموجود في سلسلة الرسالة النصية (لاحظ req.params.id). لاحظ أنه يمكن ألّا تحتوي بعض دوال الوجهة بعد تقديمها على أي شيفرة برمجية يمكنها أن ترمي استثناءات، ولكن يمكننا تغيير تلك الدوال إلى دوال معالجة وجهة عادية عند الوصول إليها. متحكم نسخة الكتاب BookInstance افتح الملف ‎/controllers/bookinstanceController.js وانسخ الشيفرة البرمجية التالية التي تتبع نمطًا مطابقًا لوحدة المتحكم Author: const BookInstance = require("../models/bookinstance"); const asyncHandler = require("express-async-handler"); // ‫عرض قائمة بجميع نسخ الكتب BookInstance exports.bookinstance_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance list"); }); // ‫عرض صفحة تفاصيل نسخة كتاب BookInstance معينة exports.bookinstance_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: BookInstance detail: ${req.params.id}`); }); // ‫عرض استمارة إنشاء نسخة الكتاب BookInstance باستخدام GET exports.bookinstance_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance create GET"); }); // ‫معالجة إنشاء نسخة كتاب BookInstance باستخدام POST exports.bookinstance_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance create POST"); }); // ع‫رض استمارة حذف نسخة كتاب BookInstance باستخدام GET exports.bookinstance_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance delete GET"); }); // معالجة حذ‫ف نسخة كتاب BookInstance باستخدام POST exports.bookinstance_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance delete POST"); }); // عرض‫ استمارة تحديث نسخة كتاب BookInstance باستخدام GET exports.bookinstance_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance update GET"); }); // معالجة‫ تحديث نسخة الكتاب باستخدام POST exports.bookinstance_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: BookInstance update POST"); }); متحكم نوع الكتاب Genre افتح الملف ‎/controllers/genreController.js والصق فيه النص التالي الذي يتبع نمطًا مطابقًا لملفات Author و BookInstance: const Genre = require("../models/genre"); const asyncHandler = require("express-async-handler"); // عرض ق‫ائمة بجميع أنواع الكتب Genre exports.genre_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre list"); }); // عرض صفحة ت‫فاصيل نوع كتاب Genre معين exports.genre_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Genre detail: ${req.params.id}`); }); // عرض استم‫ارة إنشاء نوع الكتاب Genre باستخدام GET exports.genre_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre create GET"); }); // معالجة إنشاء ‫نوع كتاب Genre باستخدام POST exports.genre_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre create POST"); }); // عرض اس‫تمارة حذف نوع كتاب Genre باستخدام GET exports.genre_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre delete GET"); }); // معال‫جة حذف نوع كتاب Genre باستخدام POST exports.genre_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre delete POST"); }); // عرض استمارة تحد‫يث نوع كتاب Genre باستخدام GET exports.genre_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre update GET"); }); // معالجة ‫تحديث نوع كتاب Genre باستخدام POST exports.genre_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Genre update POST"); }); متحكم الكتاب Book افتح الملف ‎/controllers/bookController.js وانسخ الشيفرة البرمجية التالية، إذ يتبع هذا الملف النمط المتبع نفسه في وحدات المتحكمات الأخرى، ولكنه يحتوي أيضًا على الدالة index()‎ لعرض صفحة الترحيب بالموقع: const Book = require("../models/book"); const asyncHandler = require("express-async-handler"); exports.index = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Site Home Page"); }); // عرض قائمة بجميع الكتب exports.book_list = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book list"); }); // عرض صفحة تفاصيل كتاب معين exports.book_detail = asyncHandler(async (req, res, next) => { res.send(`NOT IMPLEMENTED: Book detail: ${req.params.id}`); }); // عرض استما‫رة إنشاء كتاب باستخدام GET exports.book_create_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book create GET"); }); // معال‫جة إنشاء كتاب باستخدام POST exports.book_create_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book create POST"); }); // عرض است‫مارة حذف كتاب باستخدام GET exports.book_delete_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book delete GET"); }); // معالج‫ة حذف كتاب باستخدام POST exports.book_delete_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book delete POST"); }); // عرض استمارة تحدي‫ث كتاب باستخدام GET exports.book_update_get = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book update GET"); }); // معالجة تحديث كت‫اب باستخدام POST exports.book_update_post = asyncHandler(async (req, res, next) => { res.send("NOT IMPLEMENTED: Book update POST"); }); إنشاء دليل وحدات الوجهة سننشئ الآن وجهات لجميع عناوين URL التي يحتاجها موقع المكتبة المحلية LocalLibrary، والتي ستستدعي دوال المتحكمات التي عرّفناها في الأقسام السابقة. تحتوي البنية الهيكلية للموقع مسبقًا على المجلد "‎./routes" الذي يحتوي على وجهات لصفحة الفهرس index والمستخدمين users. أنشئ ملف وجهة آخر هو catalog.js ضمن هذا المجلد كما يلي: /express-locallibrary-tutorial //the project root /routes index.js users.js catalog.js افتح الملف ‎/routes/catalog.js وانسخ الشيفرة البرمجية التالية: const express = require("express"); const router = express.Router(); // طلب وحدات المتحكمات const book_controller = require("../controllers/bookController"); const author_controller = require("../controllers/authorController"); const genre_controller = require("../controllers/genreController"); const book_instance_controller = require("../controllers/bookinstanceController"); /// وجهات الكتاب /// // الحصول على صفحة الدليل الرئيسية router.get("/", book_controller.index); // ط‫لب GET لإنشاء كتاب. يجب أن يأتي هذا الطلب قبل الوجهات Routes التي تعرض الكتاب (يستخدم المعرّف id) router.get("/book/create", book_controller.book_create_get); // طل‫ب POST لإنشاء كتاب router.post("/book/create", book_controller.book_create_post); // طل‫ب GET لحذف كتاب router.get("/book/:id/delete", book_controller.book_delete_get); // ط‫لب POST لحذف كتاب router.post("/book/:id/delete", book_controller.book_delete_post); // ‫طلب GET لتحديث كتاب router.get("/book/:id/update", book_controller.book_update_get); // ط‫لب POST لتحديث كتاب router.post("/book/:id/update", book_controller.book_update_post); // ط‫لب GET لكتاب واحد router.get("/book/:id", book_controller.book_detail); // ‫طلب GET لقائمة جميع عناصر الكتب router.get("/books", book_controller.book_list); /// وجهات المؤلف /// // ط‫لب GET لإنشاء مؤلف Author. يجب أن يأتي هذا الطلب قبل وجهة المعرّف (مثل عرض مؤلف) router.get("/author/create", author_controller.author_create_get); // ط‫لب POST لإنشاء مؤلف router.post("/author/create", author_controller.author_create_post); // ط‫لب GET لحذف مؤلف router.get("/author/:id/delete", author_controller.author_delete_get); // طل‫ب POST لحذف مؤلف router.post("/author/:id/delete", author_controller.author_delete_post); // ط‫لب GET لتحديث مؤلف router.get("/author/:id/update", author_controller.author_update_get); // ط‫لب POST لتحديث مؤلف router.post("/author/:id/update", author_controller.author_update_post); // ط‫لب GET لمؤلف واحد router.get("/author/:id", author_controller.author_detail); // ط‫لب GET لقائمة جميع المؤلفين router.get("/authors", author_controller.author_list); ‏/// وجهات‫ نوع الكتاب GENRE /// // طل‫ب GET لإنشاء نوع كتاب. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نوع الكتاب (يستخدم المعرّف) router.get("/genre/create", genre_controller.genre_create_get); // طل‫ب POST لإنشاء نوع كتاب router.post("/genre/create", genre_controller.genre_create_post); // طل‫ب GET لحذف نوع كتاب router.get("/genre/:id/delete", genre_controller.genre_delete_get); // ط‫لب POST لحذف نوع كتاب router.post("/genre/:id/delete", genre_controller.genre_delete_post); // ط‫لب GET لتحديث نوع كتاب router.get("/genre/:id/update", genre_controller.genre_update_get); // طل‫ب POST لتحديث نوع كتاب router.post("/genre/:id/update", genre_controller.genre_update_post); // ‫طلب GET لنوع كتاب واحد router.get("/genre/:id", genre_controller.genre_detail); // طل‫ب GET لقائمة جميع أنواع الكتب router.get("/genres", genre_controller.genre_list); ‏/// طر‫ق نسخ الكتب BOOKINSTANCE /// // طل‫ب GET لإنشاء نسخة كتاب BookInstance. يجب أن يأتي هذا الطلب قبل الوجهة التي تعرض نسخة الكتاب (يستخدم المعرّف) router.get( "/bookinstance/create", book_instance_controller.bookinstance_create_get, ); // ط‫لب POST لإنشاء نسخة كتاب router.post( "/bookinstance/create", book_instance_controller.bookinstance_create_post, ); // ط‫لب GET لحذف نسخة كتاب router.get( "/bookinstance/:id/delete", book_instance_controller.bookinstance_delete_get, ); // ط‫لب POST لحذف نسخة كتاب router.post( "/bookinstance/:id/delete", book_instance_controller.bookinstance_delete_post, ); // ط‫لب GET لتحديث نسخة الكتاب router.get( "/bookinstance/:id/update", book_instance_controller.bookinstance_update_get, ); // ط‫لب POST لتحديث نسخة كتاب router.post( "/bookinstance/:id/update", book_instance_controller.bookinstance_update_post, ); // طل‫ب GET لنسخة كتاب واحدة router.get("/bookinstance/:id", book_instance_controller.bookinstance_detail); // ط‫لب GET لقائمة جميع نسخ الكتاب router.get("/bookinstances", book_instance_controller.bookinstance_list); module.exports = router; تطلب الوحدة كائن Express ثم تستخدمه لإنشاء كائن Router، إذ تُضبَط جميع الوجهات في هذا الكائن الذي يُصدَّر لاحقًا. تُعرّف الوجهات إما باستخدام تابع ‎.get()‎ أو تابع ‎.post()‎ للكائن Router، وتُعرّف جميع المسارات باستخدام سلاسل نصية، إذ لا نستخدم أنماط سلاسل نصية أو تعابيرًا نمطية. تستخدم الوجهات التي تعمل على بعض الموارد المحددة (مثل الكتاب) معاملاتِ المسار للحصول على معرّف الكائن من عنوان URL، وتُستورَد جميع دوال المعالجة من وحدات المتحكمات التي أنشأناها في القسم السابق. تحديث وحدة وجهة صفحة الفهرس ضبطنا جميع وجهاتنا الجديدة، ولكن لا يزال لدينا الوجهة إلى الصفحة الأصلية، ولكن لنعيد التوجيه إلى صفحة الفهرس الجديدة التي أنشأناها في المسار '‎/catalog' بدلًا من ذلك. افتح الملف ‎/routes/index.js وضع الدالة التالية بدلًا من الوجهة الحالية: // الحصول على الصفحة الرئيسية router.get("/", function (req, res) { res.redirect("/catalog"); }); ملاحظة: استخدمنا تابع الاستجابة redirect()‎ الذي يؤدي إلى إعادة التوجيه إلى الصفحة المحددة، مع إرسال رمز حالة HTTP الذي هو "302 Found" افتراضيًا، ويمكنك تغيير رمز الحالة المُعاد إن لزم الأمر وتوفير مسارات مطلقة أو نسبية. يمكنك الاطلاع على مقال رموز الإجابة في HTTP والمقال كيفية استكشاف وإصلاح رموز أخطاء HTTP الشائعةعلى أكاديمية حسوب لمزيدٍ من المعلومات حول رموز حالة HTTP. تحديث الملف app.js تتمثل الخطوة الأخيرة في إضافة الوجهات إلى سلسلة البرمجيات الوسيطة في الملف app.js، لذا افتح هذا الملف، واطلب وجهة الدليل بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف: var indexRouter = require("./routes/index"); var usersRouter = require("./routes/users"); const catalogRouter = require("./routes/catalog"); // استيراد الوجهات لمنطقة "الدليل" في الموقع ضِف بعد ذلك وجهة الدليل catalog إلى مكدس البرمجيات الوسيطة بعد الوجهات الأخرى من خلال إضافة السطر الثالث التالي بعد السطرين الآخرين الموجودين مسبقًا في الملف: app.use("/", indexRouter); app.use("/users", usersRouter); app.use("/catalog", catalogRouter); // إضافة وجهات الدليل إلى سلسلة البرمجيات الوسيطة ملاحظة: أضفنا وحدة دليلنا في المسار '‎/catalog' الذي سيكون البادئة لجميع المسارات المحدَّدة في وحدة الدليل، فمثلًا سيكون عنوان URL هو /catalog/books/ للوصول إلى قائمة الكتب. يجب أن يكون لدينا الآن مسارات ودوال هيكلية مُفعَّلة لجميع عناوين URL التي سندعمها في موقع المكتبة المحلية LocalLibrary. اختبار الوجهات أولًا، ابدأ تشغيل موقع الويب بالطريقة المعتادة، فالطريقة الافتراضية في أنظمة التشغيل هي: // نظام ويندوز SET DEBUG=express-locallibrary-tutorial:* & npm start // ماك أو لينكس DEBUG=express-locallibrary-tutorial:* npm start ولكن إذا سبق لك إعداد nodemon، فيمكنك بدلًا من ذلك استخدام ما يلي: npm run serverstart انتقل بعد ذلك إلى عدد من عناوين URL الخاصة بموقع المكتبة المحلية LocalLibrary وتحقق من عدم ظهور صفحة خطأ (HTTP 404). إليك مجموعة صغيرة من عناوين URL: http://localhost:3000/ http://localhost:3000/catalog http://localhost:3000/catalog/books http://localhost:3000/catalog/bookinstances/ http://localhost:3000/catalog/authors/ http://localhost:3000/catalog/genres/ http://localhost:3000/catalog/book/5846437593935e2f8c2aa226 http://localhost:3000/catalog/book/create الخلاصة أنشأنا جميع الوجهات الخاصة بموقعنا، مع دوال المتحكمات الوهمية التي يمكننا ملؤها بتقديم كامل في مقالات لاحقة، وتعلمنا الكثير من المعلومات الأساسية حول وجهات Express والتعامل مع الاستثناءات وبعض الأساليب لبناء الوجهات والمتحكمات. سننشئ في المقال التالي صفحة ترحيب مناسبة للموقع باستخدام العروض (القوالب) والمعلومات المخزنة في نماذجنا، وسننشئ استمارات HTML وشيفرة معالجة الاستمارات لبدء تعديل البيانات التي يخزنها الموقع. ترجمة -وبتصرُّف- للمقال Express Tutorial Part 4: Routes and controllers. اقرأ أيضًا المقال السابق تطبيق عملي لتعلم Express - الجزء الثاني: استخدام قاعدة البيانات باستخدام مكتبة Mongoose. إنشاء خادم ويب في Node.js باستخدام الوحدة HTTP. المتحكّمات Controllers والعروض Views في إطار العمل Rails.
  25. تحدثنا في مقالات سابقة عن طرق عديدة لتحجيم الأشياء ضمن صفحة الويب باستخدام CSS، فمعرفة التأثيرات التي ستُحدثها الميزات المختلفة على تصميمك أمرٌ مهمٌ جدًا، لهذا نلخص في هذا المقال الطرق المختلفة التي يمكن تحديد حجم الأشياء في CSS ونعرّف بعض المصطلحات التي ستساعدك مستقبلًا عن التحجيم sizing. ننصحك قبل المتابعة في قراءة هذه المقال أن: تطلع على أساسيات استخدام الحاسوب وتصفح الويب. تحضّر بيئة عمل بسيطة كما تحدثنا مسبقًا في المقال تثبيت البرمجيات الأساسية، وتتفهم طريقة إنشاء وإدارة الملفات كما شرحناها في مقال التعامل مع الملفات. تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات مدخل إلى HTML. تتفهم أساسيات CSS كما شرحناها في سلسلة المقالات خطواتك الأولى في CSS. الأحجام الطبيعية أو الفعلية للأشياء تمتلك عناصر HTML أحجام طبيعية تأخذها قبل أن تؤثر فيها تنسيقات CSS، وكمثال مباشر على ذلك هو عنصر الصورة، إذ يحتوي ملف الصورة بطبيعته على معلومات عن حجم الصورة، والتي تُدعى باسم الحجم الفعلي intrinsic size، وتتحدد من قبل الصورة نفسها وليس من قبل أي تنسيق يُطبق لاحقًا عليها. عندما نضع هذه الصورة ضمن صفحة دون أن نغير بارتفاعها أو حيّزها width من خلال استخدام سمات العنصر <img> أو باستخدام خاصيات CSS، ستُعرض الصورة باستخدام حجمها الفعلي. وضعنا حول الصورة في المثال التالي إطارًا كي تقدّر حجم الصورة كما يعرّفها ملف الصورة: من ناحية أخرى، لا يمتلك العنصر <div> الفارغ بنفسه أية أحجام، فلو أضفنا هذا العنصر إلى صفحة HTML دون أي محتوى ضمنه ثم وضعنا حوله إطارًا كما فعلنا مع الصورة، فما ستلاحظه هو مجرد خط ضمن الصفحة. هذا الخط هو الإطار المنكمش للعنصر، فلا يوجد محتوى لتتميز حواف الإطار، أما لماذا امتد إطار العنصر على كامل عرض الصفحة، فالسبب أنه عنصر كتلي أي أنه يقع على سطر مستقل ويحتل كامل مساحة هذا السطر. ليس لهذا العنصر في مثالنا ارتفاع (أو بعد وفق اتجاه الكتلة) لعدم وجود أي محتوى ضمنه. حاول أن تضيف بعض العبارات ضمن العنصر الفارغ في المثال السابق وسترى كيف يظهر الإطار بوضوح لأن ما يحدد ارتفاع هذا العنصر هو المحتوى الذي يضمه. إذًا، يأتي حجم العنصر <div> وفق اتجاه الكتلة من حجم المحتوى وبالتالي يأتي الحجم الفعلي لهذا العنصر من حجم المحتوى. باختصار: الحجم الفعلي للعنصر هو حجمه المعرّف من قبل المحتوى الخاص به. ضبط حجم عنصر على قيمة محددة يمكن طبعًا إعطاء أي عنصر ضمن تصميمنا أحجامًا محددةً، وعندها يُشار إلى هذه الحجام أنها أحجام خارجية extrinsic size (عندها لا بد أن يتلائم المحتوى مع حجم العنصر). لو أخذنا العنصر <div> في مثالنا السابق وضبطنا ارتفاعه height وحيّزه width، سيأخذ تمامًا هذه الأحجام أيًا يكن حجم المحتوى الذي سيوضع ضمنه. وقد أشرنا في مقال "التعامل مع طفحان المحتوى خارج صندوق العنصر" أن ضبط ارتفاع العنصر سيسبب طفحان المحتوى إن لم تكن أحجام صندوق العنصر قادرة على استيعاب كامل المحتوى. إذًا، لا بد من الانتباه جيدًا عند ضبط قيمة ارتفاع العنصر على قيمة عددية محددة أو نسبة مئوية حتى لا يحدث طفحان للمحتوى. استخدام النسب المئوية تعمل النسب المئوية في كثيرٍ من الأحيان كما تعمل وحدات الطول، ورأينا في مقال "وحدات القياس والقيم في CSS" أنه بالإمكان استبدال أحدهما بالآخر. عليك عند استخدام النسب المئوية أن تعرف أنها نسبة من شيء ما، ففي حالة صندوق ضمن آخر مثلًا وعندما تحدد حيّز الصندوق الداخلي (الصندوق الابن) كنسبة مئوية فهي نسبة مئوية من حيّز الصندوق الأب. والسبب في ذلك أن النسب المئوية مأخوذةً نسبةً إلى أبعاد الكتلة الحاوية للعنصر؛ فإذا لم نستخدم النسبة المئوية لضبط حيّز العنصر <div> في المثال السابق، فسيشغل 100% من المساحة المتاحة له كونه عنصر كتلي؛ بينما سيشغل مقدارًا محددًا من تلك المساحة إذا حددنا حيّزه بنسبة مئوية منها. تحديد الهوامش والحشوة بنسب مئوية قد تلاحظ سلوكًا غريبًا عند تحديد قيم الهوامش والحشوات بنسب مئوية. نعرض في المثال التالي صندوقًا يضم محتوًى وقد حددنا هامش margin هذا المحتوى ومقدار الحشوة padding بالقيمة 10%. لاحظ أن مقدار الحشوة والهامش في الأعلى والأسفل سيعادل مقدار الهامش على اليمين واليسار. قد تعتقد أن النسبة المئوية للهامش العلوي والسفلي هي نسبة مئوية من ارتفاع العنصر وأن النسبة المؤية للهامش اليميني واليساري هي نسب مئوية من حيّز العنصر، وهذا الأمر ليس صحيحًا. عندما تُستخدم الحشوات والهوامش بقيم مئوية فإنها تُنسب إلى الحجم السطري inline size للكتلة الحاوية، وهو حيّز الكتلة الحاوية عندما تكون لغة الكتابة أفقية الانسياب. لهذا السبب ظهرت قيم الهوامش والحشوات جميعها متساوية في مثالنا السابق وهو 10% من حيّز الصندوق. لا بد من تذكر هذا الأمر جيدًا عندما تخطط لاستخدام النسب المئوية بهذه الطريقة. الأحجام باستخدام البادئتان max و min يمكنك أيضًا أن تجعل أحجام العنصر أكبر ما يمكن أو أصغر ما يمكن في CSS بدلًا من إعطاءه حجمًا ثابتًا، فإذا كان لديك مثلًا صندوق يضم كميةً متغيرةً من المحتوى وأردت أن يكون أقل ارتفاع له مقدارًا محددًا، يمكنك استخدام الخاصية min-height، وعندها سيكون ارتفاع الصندوق على الأقل مساويًا لقيمة هذه الخاصية، لكنه سيزداد تلقائيًا إذا زادت كمية المحتوى واحتاج إلى التوسع عموديًا. يعرض المثال التالي صندوقين ضُبطت قيمة الخاصية min-height لكل منهما على 150 بكسل. الصندوق اليساري فارغ أما اليميني فيضم محتوى يحتاج إلى مساحة أكبر، لهذا فقد توسع عموديًا ليتسع للمحتوى. هذا الأسلوب مفيد جدًا عند التعامل مع كميات متغيرة من المحتوى دون حدوث طفحان. أحد استخدامات الخاصية max-width هو تصغير أبعاد الصورة إن لم يكن هناك متسع من المساحة لعرضها بحيّزها الفعلي ودون أن تشغل حيزًا أكبر من المساحة المخصصة لها. إذا أردت ضبط حيّز الصورة ليكون width: 100%، وكان حيّزها الفعلي أقل من حيّز الحاوية التي تضمها، فسيجبر المتصفح الصورة على الامتداد لتصبح أوسع، وقد يؤدي ذلك إلى تشوه مظهرها، لكن لو استخدمت max-width: 100% وكان حيّز الصورة الفعلي أقل من حيّز الحاوية، فلن تتمدد الصورة لتشغل مساحة أكبر ولن تتعرض للتشوه. استخدمنا في مثالنا التالي الصورة نفسها ثلاث مرات؛ إذ ضُبط حيّز الأولى على width: 100% ووضعت ضمن حاوية أكبر حيّزًا منها وبالتالي تتمدد لتغطي مساحة الحاوية تمامًا؛ وضبط حيّز الثانية ليكون max-width: 100% في نفس الحاوية لكن الصورة لم تتمدد هذه المرة لتشغل مساحة الحاوية؛ أما في الصورة الثالثة فقد ضُبط حيّزها على max-width: 100% ووضعت في حاوية أقل حيّزًا منها فانكمشت الصورة لتتناسب مع حيّز الحاوية. تُستخدم هذه التقنية لجعل الصور متجاوبة عند عرضها على شاشات أصغر، إذ تنكمش لتتلائم مع أحجام شاشة العرض. لكن عليك ألا تستخدم هذه التقنية لتحميل الصورة الكبيرة الحجم ثم تصغيرها ضمن المتصفح، فلا بد من اختيار الصورة بحجم مناسب كي لا تكون أكبر من الحجم المطلوب لأكبر حجم قد تُعرض بها الصورة، إذ سيسبب تحميل الصورة الضخمة بطء المتصفح وسيكلّف الزوار أكثر إذا كانت تكلفة الاتصال وفقًا لحجم البيانات التي تتبادلها. وحدات قياس نافذة العرض تُعرّف نافذة العرض viewport بأنها المساحة المرئية من الصفحة ضمن متصفحك الذي تستخدمه لعرض الصفحة. لنافذة العرض أيضًا حجم، ويوجد في CSS وحدات متعلقة بنافذة العرض، مثل vw وهي وحدة لحيّز نافذة العرض و vh من أجل ارتفاع نافذة العرض. تعادل القيمة 1vh مقدار %1 من ارتفاع نافذة العرض ويعادل 1vw مقدار %1 من حيّز نافذة العرض، ويمكنك استخدام هذه الوحدات لضبط أحجام الصناديق والنصوص. يعرض المثال التالي صندوقًا أبعاده 20vh و 20vw. يضم الصندوق الحرف A وقد ضُبط حجم الخط font-size داخل الصندوق على 10vh. إذا غيّرت قيم vh و vw سيغيّر ذلك حجم الصندوق أو حجم الخط، وسيؤدي تغيير حجم نافذة العرض إلى تغيير حجم الصندوق وحجم خط الكتابة أيضًا، لأن حجمه مقاسٌ بالنسبة إلى نافذة العرض. حتى ترى التغييرات التي ذكرناها لا بد من عرض الشيفرة السابقة ضمن نافذة جديدة لمتصفحك كي تتمكن من إعادة تحجيم المتصفح. يمكنك أيضًا استعراض المثال التجريبي الموجود على غيت-هب من خلال متصفحك ثم غيّر حجم نافذة المتصفح لترى ما الذي سيحدث. من المفيد تحجيم الأشياء بالنسبة إلى نافذة العرض في تصميمك، فإذا أردت مثلًا عرض صورة على كامل شاشة الجهاز ثم يأتي بعدها بقية المحتوى عند التمرير إلى الأسفل، اضبط ارتفاع ذلك الجزء من الصفحة ليكون 100vh، وسيدفع ذلك بقية المحتوى إلى الأسفل ولن يظهر ضمن نافذة العرض حتى تنتقل بالتمرير إلى الأسفل. الخلاصة تعرفنا في هذا المقال على بعض الأفكار المهمة التي يمكن أن تستخدمها عند تحجيم العناصر في صفحات الويب. سيغدو موضوع التحجيم مهمًا أكثر عندما تنتقل للعمل مع تخطيطات CSS كي تتقن الاختلافات بين التخطيطات المختلفة، لهذا من الجيد أن تستوعب هذه الأفكار قبل دراسة التخطيطات. ترجمة -وبتصرف- للمقال Sizing items in CSS. اقرأ أيضًا وحدات القياس والقيم في CSS كيفية تعديل حجم عنصر HTML والمساحة المحيطة به باستخدام CSS التنسيقات الأساسية للعناصر في CSS
×
×
  • أضف...