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

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

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

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

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

نوع المحتوى


التصنيفات

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

التصنيفات

  • مقالات برمجة عامة
  • مقالات برمجة متقدمة
  • 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. قدمنا في مقال سابق مدخلًا إلى مفاهيم البرمجة كائنية التوجه في جافا سكريبت، وناقشنا مثالًا عن استخدام مبادئها لنمذجة مدرسين وطلاب في مدرسة. كما تحدثنا أيضًا عن إمكانية استخدام الكائنات المجردة prototype والدوال البانية constructor لتنفيذ نماذج مشابهة، والميزات المرتبطة بمفاهيم البرمجة غرضية التوجه التقليدية التي تقدمها جافا سكريبت. سنتوسع في شرح هذه الميزات في مقالنا، وعليك الانتباه إلى أن الميزات التي نشرحها ليست طريقة جديدة لدمج الكائنات classes، وأن الكائنات ستستخدم دائمًا كائنات prototype خلف الكواليس. وكل ما هنالك أنها وسيلة لتسهيل بناء سلسلة الكائنات prototype. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات HTML. أساسيات عمل CSS أساسيات جافاسكريبت كما شرحناها في سلسلة المقالات السابقة. أساسيات البرمجة كائنية التوجه في جافا سكربت كما شرحناها في مقال أساسيات العمل مع الكائنات في جافاسكربت، ومقال الوراثة باستخدام الكائنات في جافاسكربت إضافة إلى مفاهيم أساسية في البرمجة كائنية التوجه. اﻷصناف والدوال البانية يمكن التصريح عن صنف باستخدام الكلمة المحجوزة new، إليك كيفية تعريف الصنف Personالذي تعاملنا معه في مقال سابق: class Person { name; constructor(name) { this.name = name; } introduceSelf() { console.log(`Hi! I'm ${this.name}`); } } تُصرّح الشيفرة السابقة صنفًا يُدعى person له: الخاصية name. دالة بانية لها معامل وحيد name ويُستخدم لتهيئة قيمة الخاصية name للكائن الجديد. تابع ()introduceSelf يمكنه اﻹشارة إلى خاصيات الكائن باستخدام التعليمة this. يُعد التصريح ;name في البداية اختياريًا يمكن الاستغناء عنه، إذ يُنشئ السطر ;this.name = name في الدالة البانية الخاصية name قبل تهيئتها. لكن التصريح عمومًا عن الخاصياتن يجعل الشيفرة أسهل قراءة، ويوّضح تمامًا الخاصيات التي يمتلكها الصنف. كما تستطيع أيضًا تهيئة الخاصية بقيمة افتراضية عند التصريح عنها على الشكل ; =name. عّرفت الدالة البانية باستخدام الكلمة المحجوزة constructor، وكما هو الحال عند تعريف الدالة البانية خارج الصنف،سيكون لها المهام التالية: إنشاء كائن جديد. ربط التعليمة this بالكائن الجديد كي تتمكن من استخدام هذه التعليمة في اﻹشارة إلى الكائن ضمن شيفرتها. تنفيذ شيفرة الدالة البانية. إعادة الكائن الجديد. وبالعودة إلى شيفرة التصريح عن الكائن السابق، تستطيع إنشاء واستخدام نسخة جديدة من الكائن Person كالتالي: const giles = new Person("Giles"); giles.introduceSelf(); // Hi! I'm Giles لاحظ كيف نستدعي الدالة البانية باستخدام اسم الصنف، وهو Personفي مثالنا. حذف الدالة البانية إن لم تكن هناك حاجة لتهيئة قيم الخاصيات، تستطيع إهمال الدالة البانية، وعندها ستُولَّد دالة بانية افتراضية: class Animal { sleep() { console.log("zzzzzzz"); } } const spot = new Animal(); spot.sleep(); // 'zzzzzzz' الوراثة Inheritance تسمح الوراثة بإنشاء علاقة تسلسلية بين الكائنات، حيث يمكن للكائنات الفرعية أو الأبناء sub classes وراثة الخاصيات والتوابع من الكائنات الأساسية أو الآباء base classes، وفي نفس الوقت يمكنها تعديلها أو إضافة خصائص جديدة. لنتعرف على كيفية تحقيق مفهوم الوراثة في جافا سكريبت، لذا دعونا نستخدم الصنفPerson السابق في تعريف كائن فرعي أو كائن ابن له باسمprofessor: class Professor extends Person { teaches; constructor(name, teaches) { super(name); this.teaches = teaches; } introduceSelf() { console.log( `My name is ${this.name}, and I will be your ${this.teaches} professor.`, ); } grade(paper) { const grade = Math.floor(Math.random() * (5 - 1) + 1); console.log(grade); } } نستخدم الكلمة المحجوزة extends للدالة إلى أن الصنف الجديد يرث صنفًا آخر. ويضيف الصنف Professor خاصية جديدة هي teaches لهذا نُصرّح عنها. وطالما أننا نريد تهيئة قيمة تلك الخاصية عندما ننشئ كائنًا جديدًا من الصنف Professor، لا بد من تعريف دالة بانية تأخذ معاملين هما name و teaches. وما تفعله الدالة البانية هنا، هو استدعاء الدالة البانية للصنف اﻷب باستخدام التابع ()super ممررة له المعامل name وستتكفل الدالة البانية للصنف اﻷب بضبط قيمة الخاصية name. وبعدها تهيئ الدالة البانية للصنف Professor قيمة الخاصية teaches. ملاحظة: إن كان على الدالة البانية للصنف الابن تهيئة أية قيم خاصة به، عليها أولًا التأكد من تهيئة قيم الصنف الأب لهذا الصنف من خلال استدعاء الدالة ()superوتمرير قيم أية معاملات تحتاجها. كما يتجاوز الكائن الابن التابع ()introduceSelf الخاص بالصنف اﻷب ويقدّم نسخته الخاصة، ويضيف التابع ()grade لتصحيح اﻷوراق (طبعًا في مثالنا يوزّع المدرّس علامات عشوائية على الأوراق). وهكذا سنتمكن اﻵن من إنشاء مدرسين جديد: const walsh = new Professor("Walsh", "Psychology"); walsh.introduceSelf(); // 'My name is Walsh, and I will be your Psychology professor' walsh.grade("my paper"); // some random grade التغليف Encapsulation لنرى أخيرًا كيف ننجز مفهوم التغليف في جافاسكريبت. فقد ناقشنا في مقال سابق كيف أردنا أن تكون الخاصية year للكائن Studentخاصّة، كي نتمكن من تغيير شروط التسجيل في دروس الرماية دون اﻹخلال بالشيفرة التي تستخدم الكائن Student. class Student extends Person { #year; constructor(name, year) { super(name); this.#year = year; } introduceSelf() { console.log(`Hi! I'm ${this.name}, and I'm in year ${this.#year}.`); } canStudyArchery() { return this.#year > 1; } } وما فعلناه في التصريح السابق عن الصنف، أننا جعلنا الخاصية year# خاصّة بالصنف، وهكذا سنتمكن من بناء كائن Student يستخدم الخاصية year# داخليًا، وسيعطي المتصفح رسالة خطأ إن حاولت شيفرة خارج الكائن الوصول إليها. const summers = new Student("Summers", 2); summers.introduceSelf(); // Hi! I'm Summers, and I'm in year 2. summers.canStudyArchery(); // true summers.#year; // SyntaxError ملاحظة: يمكن للشيفرة المكتوبة ضمن طرفية جافا سكريبت في المتصفح الوصول إلى الخاصيات الخاصة حتى لو كانت خارج الصنف. وهذا أمر خاص لتحرير أدوات مطوري ويب فقط من قيود الصياغة اللغوية لجافا سكريبت. إذًا ينبغي أن يبدأ اسم الخاصيات الخاصة بالمحرف # ويجب أن يُصرّح عنها داخل الصنف. التوابع الخاصة Private methods بإمكانك تحديد توابع أيضًا لتكون توابع خاصة بالصنف كما في الخاصيات، بحيث تكون تابعة للكائن نفسه ولا يمكن الوصول إليها من خارجه الدالة خاصة بالكائن نفسه، كما في الخاصيات ولا بد في هذه الحالة أن يبدأ اسم التابع بالمحرف # أيضًا، وعندها ستُستدعى فقط من قبل توابع هذا الصنف كما في المثال التالي: class Example { somePublicMethod() { this.#somePrivateMethod(); } #somePrivateMethod() { console.log("You called me?"); } } const myExample = new Example(); myExample.somePublicMethod(); // 'You called me?' myExample.#somePrivateMethod(); // SyntaxError الخلاصة ناقشنا في هذا المقال الأدوات التي تقدمها جافا سكريبت لكتابة الكائنات والتعامل معها في البرامج كائنية التوجه، وتجدر الإشارة إلى أننا لم نغطي كل النقاط المتعلقة بالموضوع في هذا المقال، لكن ما قدمناه سيكون كافيًا في البداية. ترجمة -وبتصرف- لمقال Classes in JavaScript اقرأ أيضًا المقال السابق: مفاهيم أساسية في البرمجة كائنية التوجه وتحقيقها في جافاسكريبت مدخل إلى جافاسكريبت كائنية التوجه (Object-Oriented JavaScript) لغة البرمجة بالكائنات Object-Oriented Programming برمجة الكائنات Objects في جافاسكريبت مختصر البرمجة كائنية التوجه OOP وتطبيقها في بايثون
  2. البرمجة كائنية التوجه Object-Oriented programming واختصارًا OOP هي مصطلح برمجي أساسي في الكثير من لغات البرمجة مثل جافا و ++C. ونحاول في هذا المقال تزويدك بإحاطة شاملة عن أساسيات مفهوم البرمجة كائنية التوجه، ونشرح مفاهيمها اﻷساسية، ونوضح مفهوم اﻷصناف classes والنسخ instances والوراثة inheritance والتغليف encapsulation. ولن نخص في شرحنا لهذه المفاهيم لغة جافا سكريبت حاليًا، وستكون اﻷمثلة جميعها مكتوبة بلغة معممة (أو بالشيفرة الوهمية pseudo-code). ملاحظة: لتوخي الدقة، عليك معرفة أن ما سنشرحه هو نمط مخصص من البرمجة كائنية التوجه هو البرمجة كائنية التوجه التقليدية. وهو المصطلح المقصود عندما يجري الحديث عمومًا عن البرمجة كائنية التوجه. بعد ذلك، سنوضح العلاقة بين الدوال البانية constructor وكائنات prototype في جافا سكريبت وبين مفاهيم البرمجة كائنية التوجه التي أشرنا إليها سابقًا، وأبرز الاختلافات بينها. وسنركز في مقالات لاحقة على بعض الميزات اﻹضافية في جافا سكريبت التي تساعد على تنفيذ برامج كائنية التوجه. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات جافا سكريبت كما شرحناها في سلسلة المقالات السابقة. أساسيات البرمجة كائنية التوجه في جافا سكريبت، كما شرحناها في مقال أساسيات العمل مع الكائنات في جافا سكريبت، ومقال استخدام كائنات Prototype في جافا سكريبت. ما هي البرمجة كائنية التوجه تشير البرمجة كائنية التوجه إلى طريقة لنمذجة نظام على شكل مجموعة من الكائنات، يمثّل كل كائن بعض ميزات النظام. وتضم الكائنات دوال functions أو (توابع methods) وبيانات data، كما يقدّم الكائن واجهة عمومية تستخدمها كائنات أو شيفرات أخرى للتعامل معه وواجهة خاصة تحتفظ بمعلومات وبيانات عن الحالة الداخلية للكائن ولا يمكن التعامل معها من خارج الكائن أي أنها تكون غير مرئية لما هو خارج الكائن، وبالتالي لن تُضطر بقية أجزاء المنظومة إلى معرفة ما يجري داخليًا ضمن أجزاء أو كائنات أخرى. اﻷصناف Classes والنُّسَخ Instances عندما نريد نمذجة مسألة وفقًا لمبدأ البرمجة كائنية التوجه OOP، ننشئ تعريفات عامة تمثّل أنواع الكائنات التي نريدها في المنظومة. فلو أدرنا مثلًا نمذجة مدرسة، قد نرغب بإنشاء كائنات تمثل المدرّسين، ويكون لهؤلاء المدرسين ميزات أو سمات مشتركة كأن يكون لهم أسماء ومواد يدرّسونها. وإضافة إلى ذلك، يمكن لأي مدرس تنفيذ أعمال محددة، مثل تصحيح اﻷوراق أو تقديم أنفسهم إلى الطلاب في بداية العام الدراسي مثلًا. لهذا يمكن أن يكون المدرّس ضنفًا في المنظومة باسم Professor، ويٌعرّف الصنف بداخله مجموعة من البيانات والتوابع التي يمتلكها كل مدرّس في المدرسة. على سبيل المثال يمكن أن يُعرّف الصنف Professor بالشيفرة الوهمية التالية: class Professor properties name teaches methods grade(paper) introduceSelf() تُعرّف الشيفرة السابقة الصنف Professor كالتالي: خاصيتين أو سمتين تحملان بيانات المدرس وهما name التي تمثل اسم المدرس و teaches التي تمثل المواد التي يقوم بتدريسها. تابعين هما ()grade لتصحيح ورقة، و ()introduceSelf للتعريف عن أنفسهم. كما تلاحظ ليس للصنف وظيفة قائمة بحد ذاتها، بل هو أقرب إلى قالب ﻹنشاء كائنات objects من هذا النوع أو الصنف. فكل مدرّس ننشئه وفق القالب Professor يُدعى نسخة instance عن هذا القالب. تُنشأ نسخة عن صنف باستخدام نوع خاص من الدوال تُدعى بالدوال البانية constructors. إذ نمرر قيمًا إلى الدوال البانية لتهيئة النسخة بقيم تضبط حالتها الداخلية اﻷساسية. تُكتب الدوال البانية عمومًا كجزء من تعريف الصنف، ولها عادة نفس اسم الصنف، لاحظ الشيفرة التالية: class Professor properties name teaches constructor Professor(name, teaches) methods grade(paper) introduceSelf() تأخذ الدالة البانية في الشيفرة السابقة معاملين أو وسيطين، لهذا بإمكاننا تهيئة الخاصيتين name و teaches عند إنشاء نسخة جديدة أو كائن عن صنف المدرّس. وطالما أن لدينا دالة بانية اﻵن، سنتمكن من إنشاء بعض المدرسين، وتستخدم بعض لغات البرمجة عادة الكلمة المحجوزة new للإشارة إلى استدعاء الدالة البانية: walsh = new Professor("Ahmad", "Psychology"); lillian = new Professor("Lyla", "Poetry"); walsh.teaches; // 'Psychology' walsh.introduceSelf(); // 'My name is Professor Ahmad and I will be your Psychology professor.' lillian.teaches; // 'Poetry' lillian.introduceSelf(); // 'My name is Professor Lyla and I will be your Poetry professor.' تنشئ شيفرة جافا سكريبت السابقة كائنين، وكلاهما نسخة عن الصنف Professor. الوراثة Inheritance لنفترض أننا نريد إنشاء طلاب في منظومة المدرسة السابقة، لكن لا يمكن للطلاب تصحيح اﻷوراق ولا يمكنهم تدريس مواد، وينتمون إلى سنوات دراسية محددة. لكن سيحمل الطلاب أسماءً، وقد يرغبون بتقديم أنفسهم، لهذا يمكن صياغة صنف خاص الطلاب من خلال الشيفرة الوهمية التالية: class Student properties name year constructor Student(name, year) methods introduceSelf() ومن المفيد أن نشير إلى اشتراك الطلاب والمدرسين ببعض الخاصيات، أو بشكل أدق اﻹشارة إلى أنهما ينتميان إلى نوع واحد عند مستوى ما، وهذا ما تسمح به الوراثة inheritance في البرمجة كائنية التوجه. فقد ننظر إلى المدرسين والطلاب بداية على أنهم أشخاص، وللأشخاص أسماء ويقدموّن أنفسهم عند الحاجة. ولنمذجة هذه الفكرة، ننشئ صنفًا جديدًا هو Person، نعرّف فيه جميع الخصائص المشتركة للأشخاص، ثم باﻹمكان اشتقاق الصنفين Professor و Student من الصنف Person ومن ثم إضافة الخاصيات المميزة لكل صنف: class Person properties name constructor Person(name) methods introduceSelf() class Professor : extends Person properties teaches constructor Professor(name, teaches) methods grade(paper) introduceSelf() class Student : extends Person properties year constructor Student(name, year) methods introduceSelf() وهكذا يمكن القول أن الصنف Person هو صنف أعلى super class أو صنف أب parent class لكل من الصنف Professor والصنف Student اللذان يُدعيان في هذه الحالة بأصناف فرعية sub classes أو أصناف أبناء child class. ولاحظ كيف عُرِّف التابع ()introduceSelf في جميع الأصناف الثلاث، والسبب هو اختلاف الطريقة التي يُقدّم فيها كل صنف نفسه: ahmad = new Professor("Ahmad", "Psychology"); ahmad.introduceSelf(); // 'My name is Professor Ahmad and I will be your Psychology professor.' summers = new Student("Summers", 1); summers.introduceSelf(); // 'My name is Summers and I'm in the first year.' وباﻹمكان أيضًا كتابة تابع افتراضي ()introduceSelf للتعريف عن أشخاص ليسوا طلابًا ولا مدرسين: passam = new Person("Passam"); passam.introduceSelf(); // 'My name is Passam.' تُدعى فكرة وجود تابع بنفس الاسم في عدة أصناف، لكنه ينفّذ وظيفة خاصة في كل صنف بتعدد الأشكال polymorphism، حيث يمكن تعريف تابع بنفس الاسم في الصنف الأب والابن وفي هذه الحالة يحل التابع المعرف في الصنف الابن محل التابع المعرف في الصنف الابن، ونقول في هذه الحالة أننا تجاوزنا overrides نسخة التابع الموجودة في الصنف اﻷب. التغليف Encapsulation تقدّم الكائنات واجهة عامة لبقية الشيفرة كي تتخاطب معها، لكنها تحتفظ بحالتها الداخلية (قيم مخصصة تساعدها على تنفيذ وظائفها). ونقول أن الحالة الداخلية للصنف تبقى خاصة private، أي يمكن الوصول إليها من قبل التوابع الخاصة بالصنف فقط، وليس من قبل أية كائنات أخرى. تُدعى عملية إبقاء الحالة الداخلية للصنف خاصة أو الفصل الواضح بين الواجهة العامة للصنف وأعضاءه الداخليين عمومًا بالتغليف encapsulation. تأتي أهمية هذه الميزة بأنها تسمح للمبرمج بتغيير الواجهة الداخلية للكائن دون الحاجة إلى البحث عن الشيفرة التي تستخدمه وتعديلها. فهي تقدم شكلًا من أشكال جدران الحماية بين الكائن وبقية مكونات المنظومة. فلو سمُح لطلاب السنة الثانية وما فوق دراسة الرماية، باﻹمكان تنفيذ اﻷمر بالاستفادة من الخاصية year، وستتمكن بقية الشيفرة من تحديد إمكانية تسجيل الطالب في صف الرماية أو لا: if (student.year > 1) { // allow the student into the class } لكن المشكلة ستقع إذا غيرنا معيار السماح للطلاب بالتسجيل في درس الرماية، كأن يحتاج إلى موافقة ولي أمره، عندها علينا تغيير الشيفرة التي تتحقق من إمكانية تسجيل الطالب في كل مكان. لهذا من اﻷفضل إنشاء تابع ()canStudyArchery في الكائنات Student لتنفيذ منطق العملية. class Student : extends Person properties year constructor Student(name, year) methods introduceSelf() canStudyArchery() { return this.year > 1 } if (student.canStudyArchery()) { // allow the student into the class } وهكذا، سيكون علينا تغيير الصنف Student فقط إذا أردنا تغيير شروط دراسة الرماية، وستعمل بقية الشيفرة في كل مكان كما يجب. وبامكاننا في الكثير من لغات البرمجة كائنية التوجه منع بقية الشيفرة من الوصول إلى الحالة الداخلية للكائن بجعل خاصياتها private، وسينتج خطأ إن حاولت الشيفرة خارج الصنف الوصول إلى الخاصية: class Student : extends Person properties private year constructor Student(name, year) methods introduceSelf() canStudyArchery() { return this.year > 1 } student = new Student('Wael', 1) student.year // error: 'year' is a private property of Student أما في اللغات التي لا تفرض قيودًا كهذه على الوصول، يستخدم المبرمجون أسلوبًا في التسمية، يميزّون فيه الخاصيات ذات الوصول الخاص، كأن تبدأ التسمية بشرطة سفلية _. تحقيق البرمجة كائنية التوجه في لغة جافا سكريبت ناقشنا حتى اللحظة في مقالنا الميزات اﻷساسية للبرمجة كائنية التوجه بالعموم والتي تعتمدها لغات برمجة عديدة مثل ++C وجافا، وكنا قد ألقينا النظرة في مقالي أساسيات الكائنات في جافا سكريبت وكائنات prototype عن مفهومي الدوال البانية والكائنات prototype. وترتبط هاتان الميزتان بالتأكيد مع ميزات البرمجة كائنية التوجه إلى حد ما. حيث تزوّدنا الدوال البانية في جافا سكريبت بما يشبه تعريف الصنف، مما يساعد على تحديد شكل الكائن، بما في ذلك التوابع التي قد يتضمنها في مكان واحد من الشيفرة. كما يمكن استخدام كائنات prototype أيضًا، فلو عُرِّف تابع مثلًا ضمن الخاصية prototype لدالة بانية، فإن جميع الكائنات التي ننشأها باستخدام الدالة البانية ستمتلك هذا التابع الذي مرر إليها من خلال الكائن prototype، ولا حاجة لتعريفه ضمن الدالة البانية نفسها. كما تبدي سلسلة prototype chain سلوكًا يشبه سلوك الوراثة، فلو كان لدينا كائن من الصنف Student يمتلك الكائن Person ككائن prototype، فسيرث الخاصية name والتابع ()introduceSelf. لكن من المهم أيضًا فهم الاختلاف بين تلك الميزات ومفاهيم البرمجة كائنية التوجه التقليدية. وهذا ما سنناقشه بشيء من التفصيل. بداية، هناك اختلاف واضح في البرمجة كائنية التوجه بين الكائنات واﻷصناف، فالكائنات هي دائمًا نسخ عن اﻷصناف، وهنالك اختلاف واضح بين طريقة تعريف الصنف (الصياغة القواعدية بحد ذاتها) وطريقة إنشاء نسخ عن هذا الكائن (الدالة البانية). بينما نتمكن في جافا سكريبت من إنشاء الكائنات دون الحاجة إلى وجود تعريف مستقل للصنف، سواء عند استخدام الدالة البانية أو بإنشاء الكائن حرفيًا. وهذا ما يجعل العمل مع الكائنات في جافا سكريبت أسرع مقارنة مع البرمجة كائنية التوجه. ثانيًا، على الرغم من أن سلسلة كائنات prototype قريبة من مفهوم الوراثة، وتسلك السلوك نفسه بشكل أو بآخر، لكنهما مفهومان مختلفان. فعند إنشاء كائنات من صنف ابن subclass سنحصل على كائن واحد يجمع بين الخاصيات المعرفة في الصنف الابن والخاصيات المعرّفة في الصنف اﻷب. بينما يتميز نموذج prototyping بأن كل مستوى من مستويات الوراثة الهرمية في سلسلة يمثل بكائن مستقل، وسترتبط هذه الكائنات ببعضها عبر الخاصية _proto_. فالكائنات في سلسلسة prototype chain هي أقرب إلى مفهوم التفويض delegation من مفهوم الوراثة. والتفويض هو نمط برمجي يعطي الكائن القدرة على تنفيذ مهمة ما توكل إليه بنفسه أو تفويض كائن آخر لتنفيذ هذه المهمة. وفي الكثير من اﻷحيان، نرى أن التفويض أكثر مرونة في ربط الكائنات مع بعضها مقارنة بالوراثة (لسبب مهم وهو إمكانية تغيير أو استبدال الكائن المفوَّض كليًا أثناء تنفيذ البرنامج). وهكذا نرى أن الدوال البانية وكائنات prototype هي ميزات تمكننا من تنفيذ أو مقاربة بعض مفاهيم الوراثة كائنية التوجه في لغة جافا سكريبت، لكن استخدامها المباشر في تنفيذ ميزات مثل الوراثة أمر على قدر من الصعوبة. لهذا تقدم جافا سكريبت ميزات مبنية على نموذج كائنات prototype ترتبط بشكل أوضح بمفاهيم الوراثة كائنية التوجه، وهذا ما سنراه بتفصيل أكبر في مقالات لاحقة. الخاتمة تحدثنا في هذا المقال عن الميزات اﻷساسية للبرمجة كائنية التوجه OOP وطريقة تحقيقها في جافا سكريبت، كما ألقينا نظرة سريعة على مواطن الشبه بين الدوال البانية وكائنات prototype في جافا سكريبت وبين مميزات البرمجة بالكائنات. ترجمة-وبتصرف- للمقال Object-Oriented programming اقرأ أيضًا المقال السابق: استخدام كائنات Prototype في جافا سكريبت مدخل إلى جافاسكريبت كائنية التوجه (Object-Oriented JavaScript) لغة البرمجة بالكائنات Object-Oriented Programming برمجة الكائنات Objects في جافاسكريبت
  3. من الضروري لأي مصمم مواقع ويب أن يتم بتصميم موقعه بشكل يتوافق مع احتياجات وتفضيلات الجمهور المستهدف. وفي مقال اليوم نناقش كيفية تحقيق ذلك وإظهار الموقع بشكل مقبول على مجموعة متنوعة من المتصفحات، بما في ذلك الإصدارات القديمة، مما يضمن تجربة مرضية لجميع الزوار. عليك قبل البدء في قراءة سلسلة المقالات هذه أن: تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات مدخل إلى HTML. تتفهم أساسيات CSS كما شرحناها في سلسلة المقالات خطواتك الأولى في CSS. فقد يزور صفحتك بعض الزوار الذين يستخدمون متصفحات أقدم او لا تدعم أساليب تخطيط صفحات الويب الحديثة. وهذا ما يحدث في عالم الويب، فلا تدعم كل المتصفحات جميع الميزات الجديدة مباشرة بل يعتمد مطورها أولويات مختلفة في دعم ما يستجد. وسنشرح في هذا المقال كيف نستخدم تقنيات الويب المعاصرة دون أن نستثني من يستخدم التقنيات الأقدم. ما المتصفحات التي ستعرض عليها موقعك؟ تختلف مواقع الويب عن بعضها وفقًا للجمهور المستهدف، لهذا لا بد من معرفة عدد متابعي موقعك الذين يستخدمون متصفحات قديمة بل أن تقرر النهج الذي تتبعه في تصميم الموقع. وهذه العملية واضحة ومباشرة إن كان لديك موقع ويب ترغب في تعديله أو استبداله بآخر، فمن المحتمل أن تمتلك مجموعة من الأدوات التحليلية القادرة على تحديد التقنية التي يستخدمها الزائرون.أما إن لم تكن تمتلك هذه الأدوات، أو كان موقعك جديد كليًا، فستجد مواقع مثل Statcounter تستطيع تزويدك بإحصائيات وفقًا لموقعك الجغرافي. لا بد أن تأخذ أيضًا بعين الاعتبار نوع الجهاز الذي يستخدمه الزائر لتصفح موقعك. فقد تجد أن نسبة مستخدمي الهواتف المحمولة تزيد عن المتوسط. كما أن مراعاة شمولية الوصول أمر ضروري، فمعرفة عدد الزائرين الذين يستخدمون تقنيات حساسة أمر ضروري وقد يغدو حيويًا في بعض المواقع. إذ يلاحظ أن مطوري الويب مهتمون جدًا بتعزيز تجربة مستخدمي متصفحات إنترنت إكسبلورر القديمة الذين لا تتجاوز نسبتهم 1%، ولا يهتمون إطلاقًا بالزائرين ذوي الاحتياجات الخاصة الذين يشكلون نسبة أعلى بكثير. ما هو الدعم المقدم للميزات التي تريد استخدامها؟ بعد أن تطلع على أنواع المتصفحات التي قد تستعرض موقعك، يمكنك تقييم التقنيات التي عليك استخدامها بناء على دعم المتصفحات لها وسهولة تقديم بديل للزائرين الذين لا تدعم متصفحاتهم هذه التقنيات. سنحاول في هذا المقال تسهيل الأمر عليك ستجد في موسوعة حسوب شرحًا لكل خاصية وأدنى إصدار للمتصفحات التي تدعمها. كما ستجد معلومات مماثلة ضمن موقع شبكة مطوري موزيلا. ومن الطرق الشعبية الأخرى لمعرفة دعم المتصفحات لخاصية ما نجد مواقع مساعدة مثل Can I Use. إذ يعرض هذا الموقع قوائم لتقنيات الويب و منصاتها الرئيسية ومعلومات عن دعم المتصفحات المختلفة لها. كما ستتمكن من معرفة إحصائيات عن استخدام ميزة ما في نطاق موقعك الجغرافي أو أي منطقة تريد من العالم. وبإمكانك أيضًا ربط حساب جوجل أنيلتيكس Google Analytics كي تحصل على تحليل للمعلومات وفقًا لمعلومات مستخدميك. ستزيد معرفة التقنيات التي يستخدمها زائرو موقعك ودعم المتصفحات لها من قدرتك على اتخاذ القرار المناسب والطريقة الأنسب لدعمهم جميعًا. لا يعني الدعم رؤية ما تتوقع أن تراه دائمًا! لا يمكن أن ترى موقعك بنفس الشكل دائمًا على جميع المتصفحات، فقد يستعرض بعض الزائرين موقعك على هاتف محمول وآخرين على شاشة حاسوب مكتبي عريضة. وكذلك ستجد أن بعضهم يستخدم متصفحات أقدم وآخرين يستخدمون أحدث إصدارات متصفح معين. كما يقد يستمع بعض الزائرين لموقعك عن طريق قارئات الشاشة أو قد يكبرون أو يصغرون الشاشة للحصول على تجربة قراءة أوضح. ويعني تمامًا دعم جميع هؤلاء المستخدمين تقديم نسخة دفاعية من المحتوى، إذ تبدو هذه النسخة رائعة في المتصفحات الحديثة وتبقى صالحة من ناحية تقديم الوظيفة الأساسية للموقع ضمن المتصفحات الأقدم. يأتي المستوى الأول من دعم المتصفحات عن طريق هيكلة محتوى الصفحة جيدًا كي تتوافق مع تخطيط الانسياب الأساسي الاعتيادي normal flow. فقد لا يستفيد زائر يستخدم هاتفًا محدود الميزات من ميزات التنسيق، لكن انسياب المحتوى بشكل صحيح سيسهّل عليه التتبع والقراءة. وبالتالي نجعل الهيكلة الجيدة لصفحة HTML نقطة الانطلاق دائمًا، وينبغي ألا يتأثر محتوى الصفحة من ناحية البنية والتتابع إن حذفت تنسيقات CSS. ويرى البعض أن تُبقي على هذه الطريقة الأساسية في عرض الصفحة كي يتراجع إليها مستخدمو المتصفحات القديمة أو المحدودة الإمكانيات. فإن كان عدد المستخدمين هؤلاء ممن يزورون صفحتك قليلًا جدًا، قد لا يكون مفيدًا من ناحية تجارية أن تهدر وقتك في منحهم تجربة تماثل تجربة مستخدمي المتصفحات الحديثة. ومن الأجدى أن تستغل هذا الوقت في جعل موقعك أفضل من ناحية شمولية الوصول (الوصول السهل Accessibility) فقد يخدم ذلك عددًا أكبر من الزائرين. إذًا هنالك دائمًا خيار وسط بين صفحة HTML الأساسية وكل الزخارف التي تقدمها تنسيقات CSS، وقد ساهمت CSS بالفعل في جعل التراجع إلى النسخة الأساسية مباشرًا وسهلًا. إنشاء خطة تراجع في CSS تتضمن مواصفات CSS معلومات تشرح ما يفعله المتصفح عندما يُطبّق تخطيطين مختلفين في الوقت ذاته. أي يوجد تعريف محدد لما سيحصل إن كان العنصر معومًا و عنصر شبكة في نفس الوقت على سبيل المثال. وبالاستفادة من فكرة أن المتصفح يتجاهل قواعد التنسيق التي لا يفهمها ومعرفتك بطريقة إنشاء تخطيطات وفق التقنيات القديمة التي غطيناها في مقال سابق والتي تهمل مقابل تخطيط الشبكة سيُعرض الموقع بالشكل الأمثل ضمن المتصفحات الحديثة التي تفهمها وبشكل مقبول وظيفيًا في المتصفحات الأقدم. التراجع من تخطيط الشبكة إلى تخطيط التعويم لدينا في مثالنا التالي ثلاث حاويات <div> معروضة في نفس السطر تراها المتصفحات التي لا تدعم تخطيط الشبكة كثلاث صناديق معوّمة. وبما أن العنصر المعوّم الذي يصبح عنصر شبكة سيفقد سلوك التعويم، ستصبح هذه العناصر عناصر شبكة. وبالتالي ستُعرض على شكل عناصر شبكة في المتصفحات الحديثة وستتتجاهل المتصفحات الأقدم الخاصية display: grid وما يتعلق بها وتستخدم تخطيط التعويم. * { box-sizing: border-box; } .wrapper { background-color: palegoldenrod; padding: 10px; max-width: 400px; display: grid; grid-template-columns: repeat(3, 1fr); } .item { border-radius: 5px; background-color: rgb(207 232 220); } @supports (grid-template-rows: subgrid) { .wrapper { grid-template-rows: subgrid; gap: 10px; background-color: lightblue; text-align: center; } } <div class="wrapper"> <div class="item">Item One</div> <div class="item">Item Two</div> <div class="item">Item Three</div> <div class="item">Item Four</div> <div class="item">Item Five</div> <div class="item">Item Six</div> </div> See the Pen Untitled by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: لا تأثير للخاصية clear أيضًا عندما تصبح العناصر عناصر شبكة، لهذا تستطيع إنشاء تخطيط له تذييل footer معزول باستخدام clear والذي يتحول عندها إلى تخطيط شبكة. أساليب التراجع هنالك العديد من التخطيطات التي يمكن استخدامها بطريقة تشابه مثالنا السابق، وبإمكانك اختيار الطريقة التي تراها أنسب لما يحتاجه موقعك: أسلوب التعويم والعزل: كما رأينا في المثال السابق، تؤثر الخاصيتين float و clear على التخطيط إن تحوّلت العناصر المعوّمة أو المعزولة إلى عناصر مرنة flex أو عناصر شبكة grid. استخدام القاعدة display: inline-block: تُستخدم هذه الطريقة لإنشاء تخطيط أعمدة. فإن تحوّل عنصر يمتلك الخاصية display: inline-block إلى عنصر مرن أو عنصر شبكة سيتجاهل المتصفح القاعدة display: inline-block. القاعدة display:table: يُستخدم هذا الأسلوب في إنشاء تخطيط جدول. فإن امتلك العنصر خواص مثل display: table و display: table-cell ثم تحوّل إلى عنصر شبكة أو عنصر مرن، يتجاهل المتصفح سلوك الخاصية display. التخطيط متعدد الأعمدة: يمكنك في حالات محددة استخدام التخطيط متعدد الأعمدة كتخطيط تراجع إذا امتلكت الحاوية إحدى الخاصيات -column ومن ثم تحوّلت إلى حاوية شبكة، سيتجاهل حينها المتصفح سلوك تعدد الأعمدة. التراجع من تخطيط الشبكة إلى تخطيط الصندوق المرن: يتمتع تخطيط الصندوق المرن بدعم أوسع من قبل المتصفحات موازنة بتخطيط الشبكة كونه مدعوم من متصفح إنترنت إكسبلورر بنسختيه 10 و 11. فإن حوّلت تخطيط الصندوق المرن إلى تخطيط شبكة سيتجاهل المتصفح أي خاصية من خواص flex التي تُطبق على العناصر الأبناء. باستخدام حيل CSS كالتي استعرضناها ستكون قادرًا على منح مستخدمي المتصفحات الأقدم تجربة لائقة. إذ يمكننا إضافة تخطيط أبسط مبني على تقنيات قديمة مدعومة جيدًا ثم نستخدم خاصيات تنسيق أحدث لإنشاء تخطيط عصري يراه أكثر من 90% من المستخدمين. لكن لا بد في بعض الحالات كتابة شيفرة تراجع تتضمن تقنيات تفهمها المتصفحات الحديثة أيضًا، ومن الأمثلة عليها استخدام نسبة مئوية لتقدير اتساع العناصر المعوّمة كي تبدو الأعمدة أقرب إلى شكل الشبكة وتتمدد لتملأ الحاوية. يُحسب اتساع العنصر المعوّم ليكون 33.333% من اتساع الحاوية، أي ثلث الاتساع، بينما تحسب نسبة 33.333% في الشبكة من المساحة التي يقع ضمنها في الشبكة وستصبح ثلث القياس الذي نريد بمجرد تحوّل التخطيط إلى تخطيط شبكة. * { box-sizing: border-box; } .wrapper { background-color: rgb(79, 185, 227); padding: 10px; max-width: 400px; display: grid; grid-template-columns: 1fr 1fr 1fr; } .item { float: left; border-radius: 5px; background-color: rgb(207, 232, 220); padding: 1em; width: 33.333%; } <div class="wrapper"> <div class="item">Item One</div> <div class="item">Item Two</div> <div class="item">Item Three</div> </div> See the Pen Untitled by Hsoub Academy (@HsoubAcademy) on CodePen. للتعامل مع هذه الحالة لابد من طريقة لمعرفة إن كان تخطيط الشبكة مدعومًا وبالتالي ستتجاوز اتساع التخطيط الأقدم ،وهنا تقدم لنا CSS حلًا. الاستعلام عن الميزات يساعدك الاستعلام عن الميزات في اختبار دعم المتصفح لأي ميزة من ميزات CSS، أي بإمكانك كتابة تنسيقات خاصة بالمتصفحات التي لا تدعم ميزات معينة، ثم التحقق لترى إن كان المتصفح يدعم ما تختبره من ميزات فإن كان كذلك سيعرض التخطيط العصري. إن أضفنا استعلام عن ميزة إلى مثالنا السابق، سنتمكن من إعادة ضبط الاتساعات العناصر على القيمة auto إن علمنا أن المتصفح يدعم تخطيط الشبكة: * { box-sizing: border-box; } .wrapper { background-color: rgb(79, 185, 227); padding: 10px; max-width: 400px; display: grid; grid-template-columns: 1fr 1fr 1fr; } .item { float: left; border-radius: 5px; background-color: rgb(207, 232, 220); padding: 1em; width: 33.333%; } @supports (display: grid) { .item { width: auto; } } <div class="wrapper"> <div class="item">Item One</div> <div class="item">Item Two</div> <div class="item">Item Three</div> </div> See the Pen Untitled by Hsoub Academy (@HsoubAcademy) on CodePen. تدعم المتصفحات الحديثة استعلامات الميزات جيدًا، لكن عليك الانتباه إلى أن المتصفحات التي لا تدعم تخطيط الشبكة قد لا تدعم استعلامات الميزات. ويعني ذلك أن المقاربة التي فصلناها في المثال السابق ستعمل أيضًا جيدًا في تلك المتصفحات. فما نفعله هو كتابة تنسيقات CSS القديمة أولًا خارج إطار الاستعلام. فالمتصفحات التي لا تدعم الشبكة ولا تدعم استعلام الميزات ستستعمل معلومات التنسيق القديم التي تفهمها وتتجاهل كليًا أي شيء آخر. أما المتصفحات التي تدعم استعلام الميزات وتدعم تخطيط الشبكة ستنفذ تنسيقات الشبكة الموجودة ضمن استعلام الميزات وتتجاهل كل شيء آخر. تتضمن توصيفات استعلام الميزات القدرة على اختبار عدم قدرة المتصفح على دعم ميزة وهذا مفيد فقط إن دعم المتصفح استعلام الوسائط. سنرى مستقبلًا مقاربة مبنية على التحقق من عدم دعم المتصفح للميزات، إذ ستختفي المتصفحات التي لا تدعم استعلامات الميزات. أما الآن، فعليك استخدام مقاربة التنسيقات القديمة أولًا ثم تجاوزها إذا دعم المتصفح الميزات الأحدث. نسخ من خاصيات الشبكة الخاصة بمتصفح إنترنت إكسبلورر 11 و10 ضمّت توصيفات شبكة CSS دعمًا أوليًا للمتصفح إنرتنت إكسبلور 10. وطالما أن إنترنت إكسبلورر بنسختيه 10 و 11 لا يقدم دعمًا للشبكات العصرية، فهو لا يمتلك نسخة جيدة من تخطيط الشبكة يمكن استخدامها. ولدعم تخطيط الشبكة في هذين المتصفحين توضع البادئة -ms- قبل اسم الخاصية، ويعني ذلك إمكانية استخدام هذه الخاصية لدعم إنترنت إكسبلورر 10 و11 وستتجاهل بقية المتصفحات هذه الخاصيات. مع ذلك لا يزال مايكروسوفت إيدج قادرًا على فهم الصياغة القديمة، لهذا لا بد من الانتباه جيدًا والتأكد من تجاوز الخاصيات القديمة بأمان إن كنت تعمل على تخطيط شبكة عصري. وعمومًا إن لم يكن عدد مستخدمي إنترنت إكسبلورر من زائري موقعك كبيرًا، قد يكون من الأفضل التركيز على إنجاز نسخة تراجع تعمل جيدًا مع جميع المتصفحات التي لا تدعم تخطيط الشبكة العصري. اختبار المتصفحات الأقدم تدعم معظم المتصفحات الحديثة تخطيطي الصندوق المرن وتخطيط الشبكة، وسيكون من الصعب أن تختبر المتصفحات القديمة. من الطرق التي قد تنفع في هذا الحالة استخدام أدوات اختبار مثل Sauce Labs. بإمكانك أيضًا تنزيل وتثبيت محاكيات افتراضية ومن ثم تشغيل نسخ أقدم من المتصفحات ضمن بيئة معزولة. إن كنت ترى أن دعم إنترنت إكسبلورر ضروري، ستجد مجموعة من المحاكيات التي تقدمها مايكروسوفت مجانًا، وهي متاحة لأنظمة تشغيل ويندوز وماك ولينكس وهي بالفعل طريقة ممتازة لاختبار متصفحات ويندوز القديمة والحديثة حتى لو لم تكن تستخدم حاسوبًا يعمل على هذا النظام. الخلاصة لديك الآن كل ما تحتاجه من المعلومات لاستخدام تقنيات مثل تخطيط الشبكة وإنشاء نسخ تراجع خاصة بالمتصفحات الأقدم، ولاستخدام أية تقنيات جديدة قد تظهر في المستقبل. ترجمة -وبتصرف لمقال Supporting older browsers اقرأ أيضًا المقال السابق: الأساليب القديمة في تخطيط صفحات الويب كيف تتحقّق من الخصائص المدعومة في المتصفحات سهولة الوصول كيفية تصميم جسم صفحة موقع إلكتروني باستخدام CSS أساسيّات التَمَوْضُع على صفحات الويب (CSS Positioning 101)
  4. يُعد استخدام الشبكة Grid ميزة من ميزات تخطيط الصفحات اعتمادًا على CSS. لكن ما جرى قبل ظهور هذه الخاصية هو اعتماد أسلوب تعويم العناصر float أو استخدام ميزات أخرى. إذًا كان عليك أن تتخيل مثلًا صفحتك بعدة أعمدة ( 4 أو 6 أو 12 وهكذا) ومن ثم العمل على ترتيب المحتوى ضمن هذه الأعمدة التخيلية. ما ستكتشفه في هذا المقال هي الأساليب القديمة في تخطيط الصفحات كي تفهم طريقة عملها إن اضطررت للعمل على مشروع قديم. عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML. تفهم أساسيات عمل CSS. تخطيط الصفحات والشبكات قبل ظهور تخطيط شبكات CSS قد يتفاجأ القادمون الجدد الذين لديهم خلفية في التصميم أن شبكات CSS لم تظهر إلا مؤخرًا، وقد استخدمت قبلها أساليب متنوعة ليست مثالية في إنشاء تصميمات شبيهة بالشبكات، ندعوها الآن بالأساليب القديمة أو الموروثة legacy. تعتمد المشاريع الحديثة على تخطيط شبكات CSS برفقة واحدة أو أكثر من أساليب التخطيط الحديثة لإنشاء أساس لأي تخطيط. لكن، قد تصادف تخطيط شبكة يعتمد على الأساليب القديمة بين الفينة والأخرى، لهذا من المفيد أن تتعرف على طريقة عملها ولماذا تختلف هذه الشبكات عن شبكات CSS. سنشرح في هذا المقال كيف تعمل منظومات الشبكات القديمة وإطارات عمل الشبكات grid frameworks اعتمادًا على التعويم ومبدأ الصندوق المرن. وقد تتفاجأ إن درست شبكات CSS بتعقيد هذه المنظومات، لكن معرفتك ستساعدك على كتابة شيفرة تراجع أو شيفرة آمنة من أجل المتصفحات التي لا تدعم الأساليب الحديثة في التخطيط. إضافة إلى ذلك، ستكون قادرًا على العمل على مشاريع سابقة تعتمد هذه الطرق القديمة في التخطيط. ومن المهم أن تتذكر دائمًا أن منظومات الشبكات القديمة لا تعمل إطلاقًا بالطريقة التي يعمل بها تخطيط شبكات CSS، بل تعتمد فكرة إعطاء العناصر أبعادًا محددةً ثم دفعها بطريقة تجعلها تبدو أنها شبكة. تخطيط من عمودين لنبدأ بأكثر الأمثلة بساطة وهو تخطيط مكون من عمودين. بإمكانك متابعة العمل معنا بإنشاء ملف باسم index.html على حاسوبك ثم نقل قالب HTML الخاص بالمثال إليه ومن ثم وضع الشيفرة التالية في أماكنها المناسبة ضمن القالب. سترى في نهاية المثال كيف ستبدو نتيجة العمل مباشرة. أولًا لابد من توفير محتوىً ما لوضعه ضمن الأعمدة، لهذا، استبدل كل ما هو موجود ضمن جسم الصفحة بالشيفرة التالية: <h1>2 column layout example</h1> <div> <h2>First column</h2> <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> </div> <div> <h2>Second column</h2> <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. 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> يحتاج كل عمود إلى عنصر خارجي كي يضم محتواه لهذا سنحاول فعل ذلك. اخترنا في مثالنا العنصر الحاوي <div>، وبإمكانك أيضًا اختيار عنصر آخر أكثر ملائمة من ناحية الدلالة مثل <article> و <section> و <aside>. أما بالنسبة لتنسيق CSS، فعليك أولًا تطبيق ما يلي على صفحتك لضبطها مبدئيًا: body { width: 90%; max-width: 900px; margin: 0 auto; } سيشكل جسم الصفحة 90% من نافذة العرض لكن دون أن يزيد عن 900 بكسل وعندها سيحافظ على هذا الاتساع ويتمركز في وسط الصفحة. ستمتد العناصر الأبناء له (العنصر <h1> والعنصران <div>) حتى يشغلا كامل اتساع الجسم. ولو أردنا أن يعوم العنصران <div> إلى جوار بعضهما، لا بد عندها من ضبط اتساعهما ليشغلا 100% من اتساع العنصر الأب أو أقل. لهذا أضف الشيفرة التالية إلى نهاية تنسيقات CSS: div:nth-of-type(1) { width: 48%; } div:nth-of-type(2) { width: 48%; } ضبطنا اتساع كلا العنصرين ليشغل 48% من مساحة العنصر الأب وبالتالي سيشغلان معًا 96% من المساحة الكلية له، وتركنا 4% لتمثل قناة تفصل بينهما وتعطي المحتوى مجالًا للتنفس. علينا الآن تعويم الأعمدة كالتالي: div:nth-of-type(1) { width: 48%; float: left; } div:nth-of-type(2) { width: 48%; float: right; } تكون النتيجة النهائية للمثال كالتالي: See the Pen Legacy layout by Hsoub Academy (@HsoubAcademy) on CodePen. ستلاحظ أننا استخدمنا نسب مئوية لتقدير الاتساعات، وهي استراتيجية جيدة كونها تنشئ تخطيطًا مرنًا أو "سائلًا" يمكن ضبطه ليلائم مختلف أبعاد الشاشات ويحافظ على نفس نسبة الاتساع الذي يأخذه كل عمود ضمن الشاشات الأصغر. حاول أن تغيّر أبعاد نافذة المتصفح وراقب ما يحدث! ملاحظة: يمكنك أن تتابع طريقة عمل هذا المثال على جيت-هاب (كما يمكنك الاطلاع على شيفرته المصدرية) إنشاء إطار عمل لشبكة وفق الأسلوب القديم تستخدم معظم أطر العمل القديمة سلوك التعويم float كي تعوّم أحد الأعمدة إلى جانب الآخر والحصول على تخطيط يشبه الشبكة. ويساعدك العمل على إنشاء شبكة من هذا النوع في معرفة طريقة عملها ويقدم لك بعض المفاهيم المتقدمة كي تبني على الأساسيات التي تعلمناها في مقال تعويم العناصر في CSS. يُعد إطار العمل الأسهل لهذا النوع من الشبكات هو الإطار ذو الاتساع الثابت. وكل ما نحتاجه هو معرفة مقدار الاتساع المطلوب لتصميمنا، وكم عدد الأعمدة وما هو اتساعها واتساع الأقنية الفاصلة بينها. بينما إن أردنا أن نبني شبكتنا على شكل أعمدة تتسع وتتقلص وفقًا لاتساع نافذة المتصفح، لا بد حينها من حساب اتساع الأعمدة والأقنية كنسب مئوية. سنلقي نظرة في القسم التالي على كيفية إنشاء التخطيطين السابقين، وسننشئ شبكة من 12 عمودًا وهو خيار شائع جدًا وواسع الاستخدام لحالات كثيرة كونه عدد قابل للقسمة على 2 و 4 و 6. شبكة بسيطة ثابتة الاتساع لننشئ بداية شبكة ذات أعمدة ثابتة الاتساع، وعليك أن تبدأ بإنشاء نسخة عن ملف المثال على حاسوبك والتي تضم الشيفرة التالية لجسم الصفحة: <div class="wrapper"> <div class="row"> <div class="col">1</div> <div class="col">2</div> <div class="col">3</div> <div class="col">4</div> <div class="col">5</div> <div class="col">6</div> <div class="col">7</div> <div class="col">8</div> <div class="col">9</div> <div class="col">10</div> <div class="col">11</div> <div class="col">12</div> </div> <div class="row"> <div class="col span1">13</div> <div class="col span6">14</div> <div class="col span3">15</div> <div class="col span2">16</div> </div> </div> إن الغاية من ذلك الحصول على شبكة من سطرين و 12 عمودًا، إذ يظهر السطر الأول قياس كل عمود بينما يعرض السطر الثاني مناطق مختلفة القياسات من الشبكة. أضف ضمن العنصر <style> الشيفرة التالية والتي تعطي لحاوية التغليف اتساعًا مقداره 980 بكسل مع حشوة إلى جهة اليمين مقدارها 20 بكسل. يترك لنا ذلك اتساعًا مقداره 960 بكسل للأعمدة والأقنية الفاصلة بينها، والسبب في أن الحشوة قد استهلكت مساحة من الاتساع الكلي هو أننا ضبطنا قيمة الخاصية box-sizing على القيمة border-box: * { box-sizing: border-box; } body { width: 980px; margin: 0 auto; } .wrapper { padding-right: 20px; } استخدم الآن حاوية السطر التي تغلف كل سطر من أسطر الشبكة لتمييز كل سطر عن الآخر وذلك من خلال إضافة شيفرة التنسيق التالية بعد شيفرة التنسيق السابقة: .row { clear: both; } ويعني تطبيق هذا التباعد أنه لا ضرورة لملئ السطر بالعناصر حتى نحصل على 12 عمودًا، بل ستبقى الأسطر منفصلة وغير متداخلة مع بعضها. أما بالنسبة للأقنية الفاصلة فقط جعلنا اتساعها 20 بكسل وأنشأناها على شكل هامش إلى يسار العمود بما في ذلك العمود الأول وذلك لموازنة الحشوة الموجودة إلى يمين الحاوية والتي كان اتساعها 20 بكسل. وبالتالي لدينا الآن 12 قناة تفصل بين الأعمدة اتساعها 12x20=240. لا بد من طرح المساحة السابقة من 960 بكسل وتكون النتيجة 720 بكسل للأعمدة جميعها وبتقسيم هذه القيمة على 12 وهو عدد الأعمدة سيكون اتساع العمود 60 بكسل. ننشئ كخطوة ثانية الصنف col. الذي يضبط العمود ويعوّمه إلى اليسار، إذ ضبطنا فيه الخاصية margin-left على القيمة 20px لإنشاء قناة فصل، ومنحناه اتساعًا width مقداره 60 بكسل، إليك قواعد التنسيق اللازمة: .col { float: left; margin-left: 20px; width: 60px; background: rgb(255, 150, 150); } سيظهر السطر الأول بأعمدة مفردة وكأنه شبكة. ملاحظة: منحنا كل عمود لونًا أحمر فاتح لكي ترى تمامًا الاتساع الذي يشغله. أما تخطيط الحاوية التي نريدها أن تمتد إلى عدة أعمدة فيتطلب أصنافًا خاصة لضبط قيم الاتساع للأعمدة المطلوبة إضافة إلى اتساع الأقنية بينها. نحتاج في مثالنا إلى صنف جديد ليسمح للحاويات أن تمتد بين العمودين 2 و 12. وينتج كل اتساع عن إضافة اتساع كل عمود من تلك الأعمدة إلى اتساع أقنية الفصل والتي عددها أقل دائمًا بواحد من عدد الأعمدة. أضف الشيفرة التالية إلى آخر شيفرة CSS: /* Two column widths (120px) plus one gutter width (20px) */ .col.span2 { width: 140px; } /* Three column widths (180px) plus two gutter widths (40px) */ .col.span3 { width: 220px; } /* And so on… */ .col.span4 { width: 300px; } .col.span5 { width: 380px; } .col.span6 { width: 460px; } .col.span7 { width: 540px; } .col.span8 { width: 620px; } .col.span9 { width: 700px; } .col.span10 { width: 780px; } .col.span11 { width: 860px; } .col.span12 { width: 940px; } بهذه الأصناف التي أنشأناها سنتمكن من وضع أعمدة مختلفة الاتساع ضمن الشبكة. حاول أن تحفظ التغييرات وتعيد تحميل الصفحة لترى تأثيرها. ملاحظة: إن وجدت صعوبة في تطبيق المثال السابق، الق نظرة عليه بشكله النهائي على جت-هاب (تستطيع أيضًا تنفيذه مباشرة هناك). حاول أن تعدّل الأصناف التي تطبقها على عناصرك أو حتى إضافة أو إزالة بعض الحاويات، لترى كيف يمكن أن يتغير التخطيط. إذ يمكنك مثلًا أن تجعل السطر الثاني يبدو كالتالي: <div class="row"> <div class="col span8">13</div> <div class="col span4">14</div> </div> لقد حصلنا الآن على شبكة يمكنك فيها تعريف عدد الأسطر والأعمدة في كل منها، ومن ثم وضع المحتوى الذي تريد في كل حاوية. إنشاء شبكة مرنة أو "سائلة" تعمل كما رأينا الشبكة السابقة جيدًا، لكن مشكلتها هو اتساعها الثابت. لكن ما نحتاجه حقًا هو شبكة مرنة تنمو وتتقلص وفقًا لاتساع نافذة عرض المتصفح. ولإنجاز الأمر، يمكن تحويل وحدات الاتساع من البكسل إلى نسب مئوية. تُعطى المعادلة التي تحوّل الاتساع الثابت إلى نسبة مئوية مرنة التالي: target / context = result بالنسبة إلى مثالنا سيكون الاتساع المستهدف للعمود target هو 60 بكسل والسياق أو الاتساع الكلي context هو 960 بكسل. سنستخدم هذه المعادلة لحساب النسب المئوية: 60 / 960 = 0.0625 بإزاحة الفاصلة العشرية مرتبتين إلى اليمين نحصل على النسبة المئوية 6.25%، ونستطيع الآن استبدال اتساع العمود المقدّر 60 بكسل بالنسبة المئوية 6.25%. سنفعل الشيء نفسه لضبط اتساع الأقنية الفاصلة: 20 / 960 = 0.02083333333 لهذا نستبدل قيمة الخاصية margin-left في الصنف col. وقيمة الخاصية padding-right في الصنف wrapper. لتصبحان 2.083%. تحديث الشبكة في مثالنا لتبدأ العمل في هذا القسم أنشئ نسخة جديدة عن المثال السابق على جهازك أو نسخة جديدة عن ملف المثال لتستخدمه كنقطة انطلاق. غيّر قاعدة التنسيق الثانية في المحدد wrapper. كالتالي: body { width: 90%; max-width: 980px; margin: 0 auto; } .wrapper { padding-right: 2.08333333%; } لم نغير قيمة الاتساع width لتصبح كنسبة مئوية فقط، بل أضفنا الخاصية max-width كي لايصبح التخطيط واسعًا أكثر مما هو مطلوب. عدّل بعد ذلك القاعدة الرابعة في المحدد col. كالتالي: .col { float: left; margin-left: 2.08333333%; width: 6.25%; background: rgb(255, 150, 150); } سنبدأ الآن الجزء الذي يتطلب مزيدًا من العمل، ونحتاج إلى تحديث كل قواعد المحددات col.span. كي تستخدم النسب المئوية بدلًا من البكسل. يستغرق الأمر وقتًا لإنجاز الحسابات، لكن لتوفير الوقت أجريت هذه الحسابات مسبقًا. حدّث الكتلة السفلية من قواعد CSS كالتالي: /* Two column widths (12.5%) plus one gutter width (2.08333333%) */ .col.span2 { width: 14.58333333%; } /* Three column widths (18.75%) plus two gutter widths (4.1666666) */ .col.span3 { width: 22.91666666%; } /* And so on… */ .col.span4 { width: 31.24999999%; } .col.span5 { width: 39.58333332%; } .col.span6 { width: 47.91666665%; } .col.span7 { width: 56.24999998%; } .col.span8 { width: 64.58333331%; } .col.span9 { width: 72.91666664%; } .col.span10 { width: 81.24999997%; } .col.span11 { width: 89.5833333%; } .col.span12 { width: 97.91666663%; } احفظ التغييرات التي أجريتها على الشيفرة وأعد تحميل الصفحة ثم حاول تغيير اتساع نافذة المتصفح. من المفترض أن ترى كيف يتغير اتساع الأعمدة بما يلائم اتساع نافذة المتصفح. ملاحظة: إن وجدت صعوبة في تطبيق المثال السابق، الق نظرة عليه بشكله النهائي على جت-هاب (تستطيع أيضًا تنفيذه مباشرة). إجراءات حسابات أسهل باستخدام الدالة ()calc بإمكاننا استخدام الدالة ()calc لتنفيذ العمليات الحسابية ضمن شيفرة CSS، إذ تسمح لك بإدخال معادلات رياضية بسيطة لحساب قيمة CSS. هذه الدالة مفيدة خصوصًا عندما تضطر لتنفيذ علاقات رياضية معقدة، كما تساعد في تنفيذ حسابات باستخدام وحدات مختلفة كان تريد مثلًا أن يكون ارتفاع العنصر 100% من ارتفاع العنصر الأب دائمًا بينما يكون ارتفاعه 50 بكسل. بالعودة إلى شبكتنا، نجد أن أي عمود يمتد ليغطي أكثر من عمود في شبكتنا له الاتساع 6.25% مضروبًا بعدد الأعمدة التي يمتد عليها ومضافًا إليه الاتساع 2.083% مضروبًا بعدد الأقنية الفاصلة (وهو عدد الأعمدة ناقصًا واحد). تسمح لنا الدالة ()calc بإجراء الحسابات السابقة ضمن قيمة الخاصية width، فمن أجل أي عمود يمتد على أربعة أعمدة مثلًا يمكن تنفيذ الشيفرة التالية: .col.span4 { width: calc((6.25% * 4) + (2.08333333% * 3)); } جرّب أن تستبدل الشيفرة في كتلة CSS الأخيرة بالقواعد التالية، ثم أعد تحميل الصفحة لترى النتيجة: .col.span2 { width: calc((6.25% * 2) + 2.08333333%); } .col.span3 { width: calc((6.25% * 3) + (2.08333333% * 2)); } .col.span4 { width: calc((6.25% * 4) + (2.08333333% * 3)); } .col.span5 { width: calc((6.25% * 5) + (2.08333333% * 4)); } .col.span6 { width: calc((6.25% * 6) + (2.08333333% * 5)); } .col.span7 { width: calc((6.25% * 7) + (2.08333333% * 6)); } .col.span8 { width: calc((6.25% * 8) + (2.08333333% * 7)); } .col.span9 { width: calc((6.25% * 9) + (2.08333333% * 8)); } .col.span10 { width: calc((6.25% * 10) + (2.08333333% * 9)); } .col.span11 { width: calc((6.25% * 11) + (2.08333333% * 10)); } .col.span12 { width: calc((6.25% * 12) + (2.08333333% * 11)); } ملاحظة1: إن وجدت صعوبة في تطبيق المثال السابق، الق نظرة عليه بشكله النهائي على جت-هاب (تستطيع أيضًا تنفيذه مباشرة). ملاحظة2: قد لا تتمكن من تطبيق المثال لأن المتصفح لا يدعم الدالة ()calc مع أنها مدعومة جيدًا في أغلب المتصفحات وصولًا إلى مايكروسوفت إنترنت إكسبلورر IE9. منظومات الشبكات الدلالية وغير الدلالية إن إضافة أصناف التنسيق إلى شيفرة HTML لتعريف التخطيطات يعني أن شيفرتك ومحتوى صفحتك مرهونان تمامًا بمنظورك الشخصي، لهذا قد تسمع أن هذه الطريقة في استخدام أصناف CSS هي طريقة غير دلالية أي لا تصف تمامًا كيف ينظَّم المحتوى بدلًا من استخدام الأصناف لتوصيف المحتوى، وهذا هو الأمر في حالتنا مع الأصناف span. لا يُعد النهج الذي استخدمناه هو المنهج الوحيد، بل يمكنك أن تقرر ما هي أبعاد شبكتك ثم تضيف معلومات عن هذه الأبعاد إلى قواعد الصفوف الدلالية الموجودة. فلو كان لديك العنصر <div> الذي يمتلك الصنف content وأردته أن يمتد على 8 أعمدة، يمكن إضافة القاعدة التالية إلى المحدد content.: .content { width: calc((6.25% * 8) + (2.08333333% * 7)); } ملاحظة: إن كنت تستخدم معالج أولي للتنسيق مثل Sass تكون قادرًا على إنشاء توجيه mixin@ لإدراج تلك القيم في أي مكان تريده من الصفحة. تمكين حاويات الإزاحة في الشبكة تعمل شبكتنا جيدًا طالما أننا نريد أن تتدفق الحاويات ابتداءً من الطرف اليميني للشبكة. لكن إن أردنا ترك فراغ بحجم عمود قبل أول حاوية أو بين حاويتين، لا بد عندها من إيجاد نوع من الإزاحة باستخدام صنف تنسيق يضيف هامشًا إلى اليسار كي يدفع الموقع عبر الشبكة. لنحاول أن نفعل ذلك، ولنبدأ بالشيفرة السابقة أو استخدم ملف المثال كنقطة انطلاق. شننشئ أيضًا صنفًا يعمل على إزاحة الحاوية بمقدار اتساع عمود واحد. أضف الشيفرة التالية في شيفرة CSS: .offset-by-one { margin-left: calc(6.25% + (2.08333333% * 2)); } أو إن أردت حساب النسب المئوية لاتساع الأعمدة بنفسك، استخدم الشيفرة التالية: .offset-by-one { margin-left: 10.41666666%; } بإمكانك الآن إضافة هذا الصنف إلى أية حاوية تريد كي تترك مساحة فارغة باتساع عمود من ناحية اليسار. فلو كان لدينا مثلًا شيفرة HTML التالية: <div class="col span6">14</div> استبدلها بهذه الشيفرة: <div class="col span5 offset-by-one">14</div> ملاحظة: انتبه إلى ضرورة تقليل عدد الأعمدة التي تريدها أن تمتد كي تترك مساحة للإزاحة. جرّب تحميل وتحديث الصفحة لترى الفرق، وبإمكانك أن ترى المثال على جيت-هاب وأن تجربته هنالك مباشرة. سيظهر المثال بشكله النهائي كالتالي: تمرين: هل بإمكانك كتابة صنف offset-by-two لإزاحة عمود بمقدار عمودين؟ محدودية الشبكة المعوّمة عند استخدام شبكة كهذه لا بد من حساب مجموع الاتساعات الإجمالي بشكل صحيح، وأن لا تضع عناصر في صف تجعله يمتد إلى أعمدة أكثر مما يمكنه أن يضم. ونظرًا للطريقة التي يعمل وفقها مبدأ التعويم، إن أصبح عدد أعمدة الشبكة أكثر مما تستوعبه الشبكة ستنتقل العناصر في نهاية السطر إلى سطر جديد وتخرّب الشبكة. تذكر أيضًا، إن ازداد اتساع محتوى عنصر أكثر من قدرة السطر على استيعابه سيطفح المحتوى خارج العمود ويبدو الأمر كارثيًا. وتبقى المحدودية الأهم لهذا الشبكة هي أنها وحيد البعد، فما نفعله هو التعامل مع أعمدة وجعل العناصر تتمدد على عدد منها ولا نتعامل مع أسطر فعليًا. من الصعب إذًا التعامل مع هذه التخطيطات القديمة للتحكم بارتفاع العناصر دون أن تحدد هذه الإرتفاع صراحة وهذه مقاربة لا تتمتع بالمرونة إطلاقًا، فلا يمكن أن تضمن النتائج ما لم تضمن أنّ المحتوى سيكون دائمًا بارتفاع محدد. شبكات الصندوق المرن إن اطلعت على مقالنا السابق حول [تخطيط الصندوق المرن]() فقد ترى أن هذا التخطيط هو الحل المثالي لإنشاء شبكة عناصر. إذ توجد بالفعل الكثير من تخطيطات الشبكات المعتمدة على الصندوق المرن وتخطيطات صندوق مرن التي تحل الكثير من المشاكل التي شرحناها في هذا المقال. لكن لم يُصمم الصندوق المرن لإنشاء منظومة شبكة لهذا ستظهر مشاكل عدة عند استخدامه. وكمثال بسيط يشرح ما نقصده، سنعود إلى نفس شيفرة المثال السابق ومن ثم نستخدم شيفرة CSS التالية لتنسيق الأصناف wrapper و raw و col: body { width: 90%; max-width: 980px; margin: 0 auto; } .wrapper { padding-right: 2.08333333%; } .row { display: flex; } .col { margin-left: 2.08333333%; margin-bottom: 1em; width: 6.25%; flex: 1 1 auto; background: rgb(255, 150, 150); } حاول أن تجري التعديلات السابقة على نسختك من المثال أو الق نظرة على نسخته على جت-هاب ( كما يمكنك الاطلاع على طريقة عمله أيضًا) ما فعلناه هنا هو تحويل كل سطر إلى حاوية مرنة فلا زلنا نحتاج إلى الأسطر في شبكات الصندوق المرن كي نستطيع استخدام العناصر التي يصل مجموع اتساعاتها إلى أقل من 100%. لهذا ضبطنا الخاصية display للحاوية على القيمة flex. ضبطنا أيضًا قيمة الخاصية flex في الصنف col. على القيمة 1 لتجعل العنصر يتمدد، وكذلك الخاصية flex-shrink على القيمة 1 كي يتقلص العنصر. أما قيمة الخاصية flex-basis فكانت auto لأن العنصر له اتساع محدد، وتستخدم تلك القيمة هذا الاتساع كقيمة للخاصية flex-basis. لدينا 12 صندوق في السطر الأول ضمن شبكة تنمو وتتقلص بشكل متماثل مع تغيّر اتساع نافذة العرض. وفي السطر الثاني لدينا فقط 4 أعمدة تتمدد وتتقلص أيضًا على أساس الاتساع المحدد 60 بكسل. وهكذا ستتمكن الأعمدة الأربعة في السطر الثاني من التمدد أكثر من الأعمدة في السطر الأول وستشغل هذه الأعمدة مساحة السطر الثاني بأكملها: ولإصلاح الأمر لابد من استخدام الأصناف span لتزويدنا باتساع يستبدل تلك القيمة التي تستخدمها الخاصية flex-basis للعنصر المحدد. وهذه الأصناف لا تحترم أيضًا تخطيط الشبكة الذي تنتظم وفقه العناصر لأنها لا تعرف شيئًا عن الشبكة. فالصندوق المرن تخطيط وحيد البعد ويتعامل مع أسطر أو أعمدة وليس الاثنان معًا. لهذا السبب لا يمكن إنشاء شبكة صريحة من أعمدة وأسطر، وهذا يعني أننا لا زلنا بحاجة إلى حساب نسب مئوية للأبعاد كما في عملية تعويم العناصر floating. مع ذلك قد تقرر استخدام الشبكة المرنة بدلًا من التعويم لأنها تقدم ميزات أفضل من ناحية المحاذاة وتوزيع المساحات الفارغة. لكن لا بد من الانتباه أنك تستخدم أداة لم تخصص لهذا الغرض، وقد تشعر أنك تدور في حلقات إضافية حتى تصل إلى النتيجة المطلوبة. شبكات تؤمنها أطراف خارجية أخرى بعد أن تعرفنا على طريقة حساب الأبعاد في شبكتنا، بإمكاننا الآن استيعاب بعض المنظومات التي صممتها أطراف أخرى للاستخدامات العامة. فإن بحثت في الويب عن " أطر عمل شبكات CSS" أو "CSS Grid framework" ستجد عددًا كبيرًا من الخيارات منها Bootstrap و Foundation والتي تضم تخطيط شبكة خاصة بها. كما ستجد منظومات شبكات مستقلة جرى تطويرها باستخدام CSS أو باستخدام معالجات أولية. لنلق نظرة على إحدى هذه المنظومات المستقلة لنعرض التقنيات الشائعة في إطارات عمل الشبكات. سنستخدم شبكة تمثلًا جزءًا من إطار العمل Skeleton وهو إطار عمل CSS بسيط. لهذا انتقل إلى موقع الويب الخاص بإطار العمل ثم نزّل الملف المضغوط الخاص بإطار العمل واستخرج ملفاته إلى حاسوبك ثم انسخ الملفين skeleton.css و normalize.css إلى مجلد جديد. انسخ أيضًا الملف html-skeleton.html إلى نفس المجلد السابق. اربط الملفين Normalize و skeleton بصفحة الويب بإضافة ما يلي إلى ترويسة الصفحة: <link href="normalize.css" rel="stylesheet" /> <link href="skeleton.css" rel="stylesheet" /> يتضمن الملف أكثر من نظام شبكة كما يضم شيفرة لتنسيق خط الكتابة وتنسيق عناصر أخرى يمكنك الاستفادة منها كنقطة انطلاق. سنترك كل شيء كما هو افتراضيًا، فالشبكة الافتراضية هي التي نحتاجها حاليًا. ملاحظة: تُعد المكتبة Normalize مكتبة تنسيقات CSS مفيدة حقًا كتبها نيكولاس غالفر، وتجري بعض الإصلاحات الأساسية على التخطيط ليظهر التنسيق الافتراضي للعناصر أكثر اتساقًا ضمن المتصفح. نستخدم تاليًا نفس شيفرة HTML للمثال السابق، لهذا أضف ما يلي إلى جسم ملف HTML: <div class="container"> <div class="row"> <div class="col">1</div> <div class="col">2</div> <div class="col">3</div> <div class="col">4</div> <div class="col">5</div> <div class="col">6</div> <div class="col">7</div> <div class="col">8</div> <div class="col">9</div> <div class="col">10</div> <div class="col">11</div> <div class="col">12</div> </div> <div class="row"> <div class="col">13</div> <div class="col">14</div> <div class="col">15</div> <div class="col">16</div> </div> </div> كي نبدأ باستخدام Skeleton لا بد أن يمتلك عنصر التغليف <div> الصنف container وهو موجود أصلًا في ملف HTML. ويجمّع هذا الصنف المحتوى في مركز الحاوية التي تأخذ اتساع أعظمي مقداره 960 بكسل. بإمكانك أن ترى كيف لن يتجاوز اتساع الصناديق 960 بكسل. إن ألقيت نظرة على الملف skeleton.css سترى التنسيقات التي تُطبّق عند استخدام ذلك الصنف. إذ سترى كيف يتوضع المحتوى في مركز الحاوية باستخدام هوامش يمينية ويسارية بقيمة auto، كما تُطبّق حشوات يسارية ويمينية مقدارها 20 بكسل.وتُضبط أيضًا قيمة الخاصية box-sizing على border-box كما فعلنا سابقًا كي يجري تضمين اتساع الحشوات والإطار ضمن اتساع الحاوية الكلي. .container { position: relative; width: 100%; max-width: 960px; margin: 0 auto; padding: 0 20px; box-sizing: border-box; } يكون العنصر جزءًا من الشبكة إن كان ضمن سطر row، لهذا سنحتاج -كما هو الحال في مثالنا السابق- إلى عنصر <div> آخر يمتلك الصنف ويأتي بين العنصر <div> الخاص بالمحتوى والعنصر <div> الخاص بالحاوية. لنرتب الآن الصناديق ضمن الحاوية، فالإطار Skeleton مكوّن من شبكة ذات 12 عمودًا. وتحتاج صناديق السطر الأول إلى أصناف one column كي تجعلها تمتد على عمود واحد. أضف الشيفرة التالية: <div class="container"> <div class="row"> <div class="one column">1</div> <div class="one column">2</div> <div class="one column">3</div> /* and so on */ </div> </div> اعط الآن حاويات السطر الثاني أصنافًا تشرح عدد الأعمدة التي ستمتد عليها هذه الحاويات كالتالي: <div class="row"> <div class="one column">13</div> <div class="six columns">14</div> <div class="three columns">15</div> <div class="two columns">16</div> </div> احفظ التغييرات على ملف HTML وحمّل الصفحة من جديد في المتصفح لترى ما يحدث! ملاحظة: إن واجهت مشكلة في تشغيل هذا المثال، حاول أن توسّع نافذة العرض التي تستخدمها فلن تُعرض الشبكة كما وصفنا إن كانت نافذة العرض ضيّقة. لم ينفع الأمر، حاول أن توازن ما فعلت بالملف html-skeleton-finished.html (أو اطّلع على طريقة تنفيذه مباشرة). إن ألقيت نظرة على الملف skeleton.css بإمكانك معرفة كيف يعمل المثال. فالملف يتضمن الأصناف "three columns" التالية لتنسيق العناصر بثلاثة أعمدة: .three.columns { width: 22%; } إن إطار العمل Skeleton وغيره من إطارات عمل الشبكات يُعرّف مسبقًا أصناقًا يمكنك استخدامها في صفحاتك. وتعمل كما لو أجريت الحسابات بالنسبة المئوية بنفسك. وكما ترى، لن تحتاج إلى كتابة الكثير من التنسيقات باستخدام Skeleton، فهي تتعامل مع العناصر المعوّمة نيابة عنا بمجرد إضافة أصناف التنسيق الملائمة إلى شيفرة HTML. إن هذه القدرة على التحكم بالتخطيط هو ما يجعل إطارات العمل لبناء شبكات خيارًا جذّابًا. لكن، ومع ظهور تخطيط شبكة CSS، يبتعد المطوّرون عن استخدام أطر العمل لاستخدام تلك التقنية المدمجة التي تقدمها CSS بشكل أصيل. الخلاصة تعرّفنا في هذا المقال كيف تعمل مختلف منظومات الشبكات، وهذا أمر مهم عند العمل مع مواقع قديمة، ولفهم الفرق بين شبكة CSS الأصيلة وهذه الأنظمة الأقدم للشبكات. ترجمة -وبتصرف- للمقال: Legacy layout methods اقرأ أيضًا المقال السابق: دليلك إلى استعلامات الوسائط Media Queries في CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS مدخل إلى تخطيط صفحات الويب باستخدام CSS التخطيط متعدد الأعمدة باستخدام CSS
  5. يشير مصطلح prototype إلى اﻵلية التي ترث فيها الكائنات ميزات من بعضها في جافا سكريبت، ويختلف عملها عن الوراثة في غيرها من اللغات كائنية التوجه، وهذا ما سنشرحه في هذا المقال. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات HTML. أساسيات عمل CSS أساسيات جافا سكريبت أساسيات البرمجة كائنية التوجه في جافا سكربت كما شرحناه في مقال أساسيات العمل مع الكائنات في جافا سكريبت. سلسلة من اﻷنماط المجرّدة حاول أن تنشئ في طرفية جافا سكريبت في متصفحك الكائن التالي: const myObject = { city: "Madrid", greet() { console.log(`Greetings from ${this.city}`); }, }; myObject.greet(); // Greetings from Madrid يمتلك الكائن خاصية واحدة لتخزين البيانات هي city وتابعًا واحدًا هو ()greet. فإن كتبت اسم الكائن تليه نقطة في الطرفية مثل .myobject، ستعرض الطرفية قائمة بجميع الخاصيات التي يمتلكها العنصر. وسترى إضافة إلى الخاصية city والتابع ()greet، الكثير من الخاصيات اﻷخرى! __defineGetter__ __defineSetter__ __lookupGetter__ __lookupSetter__ __proto__ city constructor greet hasOwnProperty isPrototypeOf propertyIsEnumerable toLocaleString toString valueOf جرّب الوصول إلى إحداها: myObject.toString(); // "[object Object]" لقد نجح اﻷمر (حتى لو لم يكن واضحًا لك بالضبط ما الذي يفعله التابع ()toString هنا). فما قصة هذه الخاصيات اﻹضافية، ومن أين أتت؟ في الواقع يملك كل كائن في جافا سكريبت خاصية مضمنة تُدعى prototype وهي بحد ذاتها كائن أيضًا ويضم بدوره خاصية أو كائن مجرّد إن صح التعبير، مما يوّلد ما يُدعى سلسلة prototype chain. تنتهي السلسلة عند الوصول إلى كائن قيمة الخاصية prototype له تساوي null. ملاحظة:لا تُدعى الخاصية التي تشير إلى prototype بالاسم prototype، إذ ليس لها اسم معياري لكنها تُكتب بالممارسة العملية بالشكل _proto_. وتُعد الطريقة المعيارية للوصول إلى الخاصية prototype هي استخدام التابع ()Object.getPrototypeOf. عندما تحاول الوصول إلى إحدى خاصيات كائن، ولم يُعثر على الخاصية في الكائن نفسه، يجري البحث عنها ضمن الكائن prototype، وإن لم يُعثر عليها يجري البحث مجددًا ضمن الكائن prototype للكائن prototype حتى نهاية السلسلة، فإن لم يجدها، سيُعيد القيمة undefined. فعندما تُنفّذ التعليمة ()myObject.toString: يبحث المتصفح عن التابع toString ضمن الكائن myObject. إن لم يجده، سيبحث عنه في الكائن prototype للكائن myObject. يجده هناك ويستدعيه. لكن ما هو prototype للكائن myObject؟ لمعرفة ذلك، يمكننا استخدام الأمر: Object.getPrototypeOf(myObject); // Object { } سنجد أن prototype هو كائن يُدعى Object.prototype، وهو أبسط الكائنات prototype، وتمتلكه جميع الكائنات افتراضيًا، والكائن prototype الخاص به هو null. لذا يقع هذا الكائن في نهاية سلسلة كائنات prototype. لكن لا يمثل Object.prototype دائمًا prototype لكل كائن، جرّب ما يلي لترى: const myDate = new Date(); let object = myDate; do { object = Object.getPrototypeOf(object); console.log(object); } while (object); // Date.prototype // Object { } // null تنشئ الشيفرة السابقة كائن من النوع Date، ثم تنتقل ضمن سلسلة كائنات prototype الخاصة به وتسجل أسماء هذه الكائنات. وتظهر أن النوع المجرد للكائن myDate هو Date.prototype وprototype الخاص بهذا اﻷخير هو Object.prototype. وعندما تستدعي توابع مثل ()mydate2.getMonth، فأنت تستدعي في واقع اﻷمر توابع معرّفة ضمن النوع Date.prototype. إخفاء الخاصيات ما الذي يحدث إن عرّفت خاصية في كائن وكانت هناك خاصية معرّفة بنفس الاسم ضمن الكائن prototype له؟ ألق نظرة على الشيفرة التالية: const myDate = new Date(1995, 11, 17); console.log(myDate.getYear()); // 95 myDate.getYear = function () { console.log("something else!"); }; myDate.getYear(); // 'something else!' لا بد أن تكون النتيجة التي حصلت عليها متوقعة. ووفقًا لوصف سلسلة الكائنات المجردة، سيبحث المتصفح عن الخاصية ()getYear ضمن خاصيات myDate التي تحمل هذا الاسم، ولا يتحقق من خاصيات الكائن المجرد إلا في الحالة التي لم نعرّف فيها هذه الخاصية. لهذا عندما أضفنا التابع ()getYear حرفيًا إلى الكائن myDate تُستدعى هذه النسخة مباشرة ويُعرف هذا اﻷمر بإخفاء الخاصية shadowing. إعداد كائنات prototype هناك طرق مختلفة ﻹعداد وضبط هذه الكائنات في جافا سكريبت، و سنناقش هنا طريقتان: الأولى باستخدام ()Object.create والثانية استخدام الدوال البانية constructors. استخدام التابع ()Object.create يُنشئ التابع ()Object.create كائنًا جديدًا ويسمح لك بتخصيص كائن ليصبح prototype الجديد الخاص به، إليك مثالًا: const personPrototype = { greet() { console.log("hello!"); }, }; const carl = Object.create(personPrototype); carl.greet(); // hello! أنشأنا في الشيفرة السابقة كائنًا باسم personPrototype، يمتلك التابع ()greet، ثم أنشأنا كائنًا جديدًا باستخدام التابع ()Object.create وجعلنا personPrototype كائن prototype له. وبالتالي نستطيع اﻵن استدعاء التابع ()greet من خلال الكائن الجديد، لأن كائن prototype قد زوّده به. استخدام الدالة البانية تمتلك جميع الدوال في جافا سكريبت خاصية تًدعى prototype. وعندما تستدعي الدالة على شكل دالة بانية، تُضبط تلك الخاصية لتكون prototype للكائن المبني حديثًا (داخل الخاصية التي تُدعى _proto_ تقليديًا). لهذا، وعندما نضبط القيمة prototype للدالة البانية، نضمن أن الكائنات التي تُنشئها هذه الدالة تمتلك كائن prototype: const personPrototype = { greet() { console.log(`hello, my name is ${this.name}!`); }, }; function Person(name) { this.name = name; } Object.assign(Person.prototype, personPrototype); // or // Person.prototype.greet = personPrototype.greet; لقد أنشأنا هنا: كائنًا بالاسم personPrototype يمتلك التابع ()greet. دالة بانية ()Person تهيئ اسم الشخص الذي نحييه. وضعنا بعد ذلك التوابع المعرّفة ضمن الكائن personPrototype ضمن الكائن prototype للدالة البانية باستخدام التابع Object.assign. وهكذا ستمتلك الكائنات المبنية باستخدام الدالة ()Person prototype Person.prototype الذي يضم تلقائيًا التابع greet. const reuben = new Person("Reuben"); reuben.greet(); // hello, my name is Reuben! يشرح هذا أيضًا ما قلناه سابقًا بأن الكائن prototype للكائن myDate هو Date.prototype، إذ يمثّل الخاصية للبانية Date. الخاصيات المملوكة Own properties يمتلك الكائن الذي أنشأناه باستخدام البانية ()Person خاصيتين: الخاصية name التي ضبطنا قيمتها باستخدام الدالة البانية، لهذا تظهر مباشرة ضم الكائن Person. التابع ()greetالذي ضبُط من خلال الكائن prototype. من الشائع أن تشاهد هذا الأسلوب الذي تُعرّف فيه التوابع ضمن كائنات prototype، وتُعرّف فيه خاصيات البيانات ضمن الدوال البانية. ذلك أن التوابع تبقى نفسها عادة لجميع الكائنات التي ننشئها، لكننا غالبا ما نريد أن يأخذ كل كائن قيم مخصصة لخاصيات البيانات (كأن يكون لكل شخص اسم خاص). تُدعى الخاصيات التي تُعرّف مباشرة ضمن الكائن مثل الخاصية name بالخاصيات المملوكة Own Property، وبإمكانك التحقق من كون الخاصية مملوكة باستخدام التابع الساكن ()Object.hasOwn: const irma = new Person("Irma"); console.log(Object.hasOwn(irma, "name")); // true console.log(Object.hasOwn(irma, "greet")); // false ملاحظة: كما تستطيع استخدام التابع غير الساكن ()Object.hasOwnProperty في هذه الحالة لكننا ننصح باستخدام التابع الساكن ما أمكن. الكائنات prototype والوراثة تُعد كائنات prototype ميزة قوية ومرنة في جافا سكريبت، تسمح لك بإعادة استخدام الشيفرة ودمج الكائنات. وهي بالتحديد تدعم نوعًا من الوراثة inheritance، والتي هي ميزة من ميزات البرمجة كائنية التوجه OOP. إذ تسمح الوراثة للمبرمج التعبير عن فكرة مفادها أن بعض الكائنات هي نسخ أكثر تخصيصًا من كائنات أخرى. فلو كنا نبني نموذجًا عن مدرسة، فقد ننشئ كائنات مثل مدرّس أو طالب وكلاهما أشخاص ويمتلكان بعض الميزات المتشابهة كاﻷسماء مثلًا، لكن قد يكون لكل منهما ميزات إضافية تميّزه عن اﻵخر (كأن يكون للمدرس موّاد يدرّسها)، وقد تنجز نفس الميزات بطريقة مختلفة لكل منهما. لهذا نقول في البرمجة كائنية التوجه OOP بأن الطالب والمدرس كائنان يرثان من كائن آخر يُدعى شخص. أما في جافا سكريبت فيمكن للكائنين Professor و Student أن يمتلكا نفس الكائن المجرد Person، ويرثا الخاصيات التي يمتلكها كائن prototype كما يمكن أن نعرّف خاصيات وتوابع جديدة تناسب كل منهما. سنناقش في مقالات لاحقة مفهوم الوراثة إضافة إلى الميزات الأخرى للبرمجة كائنية التوجه وطريقة دعم جافا سكريبت لها. الخلاصة غطينا في مقالنا كائنات prototype في جافا سكريبت، وآلية تكوين سلاسل كائنات prototype التي تسمح للكائنات أن ترث ميزات من بعضها، كما ناقشنا الخاصية prototype وكيفية استخدامها في إضافة توابع إلى الدوال البانية وغيرها من النقاط التي تتعلق بكائنات prototype. ترجمة -وبتصرف- للمقال Object prototpes اقرأ أيضًا المقال السابق: أساسيات العمل مع الكائنات في جافا سكريبت وراثة الأصناف (Class inheritance) في جافاسكربت كيف أتعلم لغة جافا سكريبت من الصفر حتى الاحتراف لغة البرمجة بالكائنات Object-Oriented Programming
  6. نلقي نظرة في هذا المقال على المصفوفات وهي إحدى الطرق الأنيقة لتخزين قوائم من العناصر تحت اسم متغير واحد. سنتعلم فائدة المصفوفات ونكتشف بعدها كيف نكوّن المصفوفة ونضيف العناصر إليها أو نزيلها أو نستعيدها، إضافة إلى بعض النقاط المفيدة الأخرى. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS ما هي المصفوفات؟ توصف المصفوفات Array عمومًا أنها كائنات تشبه القوائم، فهي في مبدأها كائنات مفردة تتضمن قيمًا مخزّمة ضمنها على شكل قائمة. يمكن تخزين المصفوفات ضمن المتغيرات ويجري التعامل معها كغيرها من القيم، لكن الفرق الوحيد هو إمكانية الوصول إلى كل قيمة ضمن المصفوفة بشكل منفصل عن غيرها مما يتيح إمكانيات كبيرة وفعّالة في التعامل مع القوائم والتنقل بين عناصرها ضمن حلقات لتنفيذ نفس التغييرات على كل قيمة أو على قيم منتقاة. فقد تتضمن المصفوفة مثلًا قائمة من أسعار منتجات وتريد طباعة هذه الأسعار ضمن فاتورة ثم جمع السعر الكلي وطباعته أسفل القائمة. ودون وجود مصفوفات كان علينا تخزين كل عنصر في متغير منفصل ومن ثم استدعاء الشيفرة التي تنفذ عملية الطباعة والجمع لكل عنصر وبشكل منفصل. ستكون الشيفرة عندها طويلة وغير فعّالة وموّلدة لأخطاء أكثر. فتخيل إن كان عدد العناصر 10 مثلًا، سيكون إضافتها إلى الفاتورة مزعجًا، فكيف هو الحال إن كان هناك 1000 عنصر؟ وكما فعلنا في مقالات أخرى سنتدرب على أساسيات التعامل مع المصفوفات باستخدام طرفية جافا سكريبت في المتصفح والتي يمكنك الوصول لها من خلال النقر على مفاتيح (Ctrl + Shift+ K في فايرفكس). إنشاء المصفوفات تتكون المصفوفة من قوسين مربعين وعناصر تفصل بينها فاصلة ,. افترض أنك تريد تخزين لائحة التسوق التالية في مصفوفة، أنشأ هذه المصفوفة بنسخ ولصق الشيفرة التالية في الطرفية: const shopping = ["bread", "milk", "cheese", "hummus", "noodles"]; console.log(shopping); إن كل عناصر المصفوفة السابقة هي عناصر نصية، لكن بإمكانك تخزين أنواع مختلفة من البيانات مثل الأعداد والسلاسل النصية والكائنات وحتى مصفوفات أخرى. كما يمكن استخدام أنواع مختلفة في المصفوفة نفسها، فلا ضرورة لإلزام أنفسنا بإنشاء مصفوفة لتخزين الأعداد وأخرى للنصوص. إليك مثالًا: const sequence = [1, 1, 2, 3, 5, 8, 13]; const random = ["tree", 795, [0, 1, 2]]; جرب إنشاء بعض المصفوفات قبل المتابعة. إيجاد طول مصفوفة بإمكانك إيجاد طول مصفوفة (عدد العناصر التي تضمها) بنفس طريقة إيجاد عدد المحارف في سلسلة نصية باستخدام الخاصية ()length: const shopping = ["bread", "milk", "cheese", "hummus", "noodles"]; console.log(shopping.length); // 5 الوصول إلى عناصر مصفوفة وتعديلها تُرقم عناصر المصفوفة ابتداءً من الصفر ويُدعى هذا الرقم دليل العنصر item index. وهكذا سيكون دليل العنصر الأول هو 0 والثاني 1 وهكذا. وللوصول إلى عنصر معين في مصفوفة ضع اسم المصفوفة يليها قوسين مربعين ضمنهما دليل العنصر أي بنفس الطريقة التي تصل فيها إلى محرف في سلسلة نصية: const shopping = ["bread", "milk", "cheese", "hummus", "noodles"]; console.log(shopping[0]); // returns "bread" بالإمكان أيضًا تعديل عنصر في المصفوفة بإسناد قيمة جديدة إلى العنصر المطلوب: const shopping = ["bread", "milk", "cheese", "hummus", "noodles"]; shopping[0] = "tahini"; console.log(shopping); // shopping will now return [ "tahini", "milk", "cheese", "hummus", "noodles" ] ملاحظة: تذكر أن العد أو الفهرسة في جافا سكريبت تبدأ من 0 وليس من 1. تُدعى المصفوفة ضمن مصفوفة بالمصفوفة متعددة الأبعاد، ويمكن الوصول إلى عنصر في مصفوفة موجودة ضمن مصفوفة أخرى بكتاب اسم المصفوفة الخارجية يليها زوجين من الأقواس المربعة يضم الأول دليل المصفوفة الداخلية ضمن المصفوفة الخارجية وفي الثاني دليل العنصر المطلوب في المصفوفة الداخلية. فلو أردت الوصول إلى أحد عناصر المصفوفة التي دليلها 2 (العنصر الثالث) ضمن المصفوفة random يمكنك إنجاز الأمر كالتالي: const random = ["tree", 795, [0, 1, 2]]; random[2][2]; جرّب أن تعدّل على عناصر المصفوفات التي أنشأتها قبل المتابعة. إيجاد دليل العناصر في مصفوفة إن لم تكن تعرف دليل العنصر، استخدم التابع ()indexOf الذي يأخذ العنصر وسيطًا له ويعيد دليله إن كان موجودًا أو 1- إن لم يجده. const birds = ["Parrot", "Falcon", "Owl"]; console.log(birds.indexOf("Owl")); // 2 console.log(birds.indexOf("Rabbit")); // -1 إضافة عنصر إلى مصفوفة ﻹضافة عنصر أو أكثر إلى نهاية المصفوفة، نستخدم التابع ()push وعليك حينها التأكد من إضافة عنصر أو آخر إلى نهاية المصفوفة. const cities = ["Manchester", "Liverpool"]; cities.push("Cardiff"); console.log(cities); // [ "Manchester", "Liverpool", "Cardiff" ] cities.push("Bradford", "Brighton"); console.log(cities); // [ "Manchester", "Liverpool", "Cardiff", "Bradford", "Brighton" ] يعيد التابع طول المصفوفة الجديد عند نجاح العملية، وبإمكانك أيضًا تخزين طول المصفوفة الجديد بإسناد التابع إلى متغير كالتالي: const cities = ["Manchester", "Liverpool"]; const newLength = cities.push("Bristol"); console.log(cities); // [ "Manchester", "Liverpool", "Bristol" ] console.log(newLength); // 3 وﻹضافة عناصر إلى بداية مصفوفة، استخدم التايع ()unshift: const cities = ["Manchester", "Liverpool"]; cities.unshift("Edinburgh"); console.log(cities); // [ "Edinburgh", "Manchester", "Liverpool" ] إزالة عناصر من مصفوفة ﻹزالة العنصر اﻷخير من مصفوفة، استخدم التابع ()pop: const cities = ["Manchester", "Liverpool"]; cities.pop(); console.log(cities); // [ "Manchester" ] يعيد هذا التابع العنصر الذي أزيل من المصفوفة، ولكي تخزن هذا العنصر في متغيّر، يمكنك اتباع الطريقة التالية: const cities = ["Manchester", "Liverpool"]; const removedCity = cities.pop(); console.log(removedCity); // "Liverpool" وﻹزالة العنصر اﻷول من مصفوفة استخدم التابع ()shift: const cities = ["Manchester", "Liverpool"]; cities.shift(); console.log(cities); // [ "Liverpool" ] وإن كنت تعلم دليل العنصر بإمكانك إزالته من المصفوفة باستخدام التابع ()splice: const cities = ["Manchester", "Liverpool", "Edinburgh", "Carlisle"]; const index = cities.indexOf("Liverpool"); if (index !== -1) { cities.splice(index, 1); } console.log(cities); // [ "Manchester", "Edinburgh", "Carlisle" ] يحدد الوسيط الأول للتابع ()splice دليل العنصر الذي تبدأ عنده إزالة العناصر، ويحدد الوسيط الثاني عدد العناصر التي يجب إزالتها، وبالتالي بإمكانك استخدامه ﻹزالة عدة عناصر: const cities = ["Manchester", "Liverpool", "Edinburgh", "Carlisle"]; const index = cities.indexOf("Liverpool"); if (index !== -1) { cities.splice(index, 2); } console.log(cities); // [ "Manchester", "Carlisle" ] الوصول إلى كل العناصر قد تحتاج أحيانًا الوصول إلى كل عنصر من عناصر مصفوفة، عندها يمكنك استخدام الحلقة for...of: const birds = ["Parrot", "Falcon", "Owl"]; for (const bird of birds) { console.log(bird); } وقد تضطر أحيانًا إلى تنفيذ عملية ما على كل عنصر من عناصر مصفوفة للحصول على مصفوفة جديدة مختلفة عن اﻷصل. استخدم لهذه الغاية التابع ()map. يوضح المثال التالي كيفية مضاعفة جميع أعداد مصفوفة عددية: function double(number) { return number * 2; } const numbers = [5, 2, 7, 6]; const doubled = numbers.map(double); console.log(doubled); // [ 10, 4, 14, 12 ] مررنا إلى التابع ()map وسيًا هو الدالة double التي يستدعيها لمضاعفة كل عنصر ثم يضيف ناتج كل استدعاء إلى المصفوفة الجديدة ويعيد هذه المصفوفة في النهاية. وقد تحتاج في بعض اﻷحيان إلى تشكل مصفوفة جديدة تضم عناصر من مصفوفة قديمة إذا حققت هذه العناصر شرطًا ما، استخدم لهذه الغاية التابع ()filter. لاحظ كيف نستخدم هذا التابع في المثال التالي الذي يأخذ مصفوفة ويعيد مصفوفة تضم فقط العناصر التي طولها أكبر من 8: function isLong(city) { return city.length > 8; } const cities = ["London", "Liverpool", "Totnes", "Edinburgh"]; const longer = cities.filter(isLong); console.log(longer); // [ "Liverpool", "Edinburgh" ] وكذلك يستدعي التابع ()filterدالة لاختبار كل عنصر من عناصر المصفوفة فإن أعادت القيمة true يُضاف العنصر إلى المصفوفة الجديدة ثم يعيد هذه المصفوفة في النهاية. التحويل بين المصفوفات والسلاسل النصية قد يُعرض عليك كم كبير من البيانات الخام التي تتضكن سلاسل نصية طويلة، وتجد ضرورة لتقسيم هذه البيانات إلى عناصر أكثر فائدة يمكن معالجتها لاحقًا كأن تعرضها في جدول. يمكنك في هذه الحالة استخدام التابع ()split، وهو تابع يأخذ في أبسط أشكاله معاملًا واحدًا هو المحرف الذي تحدث عنده عملية تقسيم السلسلة. ملاحظة: هذا التابع هو تابع لمعالجة السلاسل النصية لكنه يعيد مصفوفة لذلك أشرنا إليه في هذا المقال. ولمعرفة طريقة عمل ()split اتبع الخطوات التالية: أنشئ السلسلة النصية التالية في طرفية جافا سكربت ;"const data = "Manchester,London,Liverpool,Birmingham,Leeds,Carlisle افصل السلسلة عند المحرف ,: const cities = data.split(","); cities; حاول حساب طول المصفوفة الجديدة واستخلص بعض عناصرها: cities.length; cities[0]; // the first item in the array cities[1]; // the second item in the array cities[cities.length - 1]; // the last item in the array بإمكانك عكس العملية باستخدام التابع ()join: const commaSeparated = cities.join(","); commaSeparated; كما يمكن تحويل المصفوفة إلى سلسلة نصية باستخدام التابع ()toString الذي يعده البعض أبسط من ()join لأنه يأخذ معاملًا وحيدًا، لكنه أكثر محدودية فلا يمكنه الفصل سوى عند المحرف , على عكس ()join الذي يمكن أن تحدد فيه أكثر من محرف لفصل السلاسل. const dogNames = ["Rocket", "Flash", "Bella", "Slugger"]; dogNames.toString(); // Rocket,Flash,Bella,Slugger تطبيق عملي: طباعة قائمة منتجات بالعودة إلى مثالنا السابق عن قائمة المنتجات وأسعارها التي نريد إدراجها في فاتورة ثم نحسب إجمالي الفاتورة ونطبعها في اﻷسفل. ستجد في المحرر التفاعلي التالي مجموعة من التعليقات المرقمة، ويحدد كل تعليق مكانًا لكتابة شيفرة معينة: تحت التعليق number 1// ستجد عددًا من السلاسل النصية التي تضم كلا منها اسم المنتج وسعره ويفصل بينهما فاصلة. والمطلوب منك تحويلها إلى مصفوفة وتخزينها ضمن المتغيّر products أنشئ حلقة for...of تحت التعليق number 2// كي تمر على جميع عناصر المصفوفة السابقة. اكتب تحت التعليق number 3// شيفرة لفصل عناصر المصفوفة السابقة () إلى عنصرين يضم اﻷول الاسم والثاني السعر. إن لم تكن متأكدًا من طريقة تنفيذ اﻷمر راجع مقال [توابع جافا سكريبت اﻷصلية للتعامل مع النصوص]()، أو عد إلى فقرة التحويل بين المصفوفات والسلاسل النصية التي عرضناها قبل قليل. ويجب عليك أيضًا أن تحوّل السعر في السطر السابق من نص إلى عدد. يمكنك العودة إلى مقال التعامل مع النصوص في جافا سكريبت كي تتذكر آلية تنفيذ اﻷمر. ستجد متغرًا باسم totalوقد أسندت إليه القيمة 0. نطلب إليك أن تضيف سطرًا ضمن الحلقلة الموجودة أسفل التعليق number 4// ﻹضاف سعر العنصر الحالي إلى قيمة المتغير total عند كل تكرار للحلقة حتى نحصل في النهاية على إجمالي السعر أسفل الفاتورة، وقد تحتاج إلى استخدام عامل إسناد مناسب. غيّر السطر أسفل التعليق number 5// لتصبح قيمة المتغيّر مطابقة للسلسلة "العنصر الحالي — سعر العنصر الحالي$" مثل "Shoes — $23.99" عند كل تكرار كي تُطبع المعلومات الصحيحة لكل منتج ضمن الفاتورة. وتُنفّضذ العملية بضم بسيط لسلسلتين نصيتين. أضف القوس { أسفل التعليق number 6// ﻹنهاء حلقة for...of. See the Pen js-array-1 by Hsoub Academy (@HsoubAcademy) on CodePen. تطبيق عملي: نتائج البحث الخمسة الأولى يظهر استخدام مهم للتابعين ()push و ()pop عندما تريد أن تحدّث سجلًا لعناصر نشطة في تطبيق ويب، كأن يكون لديك تطبيق يعرض رسومًا متحركة ويضم عددًا كبيرًا من الكائنات مثل الخلفية وعناصر اخرى وتريد لسبب أو ﻵخر عرض 50 كائنًا فقط معًا. فعند إضافة عناصر جديدة لمصفوفة الكائنات تّحذف عناصر أقدم ليبقى عدد الكائنات المعروضة 50. في تطبيقنا هذا سنبسط اﻷمر أكثر، إذ سنفترض وجود محرك بحث وهمي يضم صندوق بحث، ومن المفترض عرض قائمة بآخر خمس عمليات بحث عند إدخال أي شيء في صندوق البحث. وعند تجاوز عمليات البحث 5 عمليات تُحذف العملية اﻷقدم وتضاف العملية الجديدة كي يبقى عدد عناصر القائمة 5. ملاحظة: قد تكون قادرًا في تطبيقات البحث الفعلية على النقر على زر ما لاستعادة جمع عمليات البحث التي جرت وطريقة لعرض كل النتائج. ﻹنجاز اﻷمر: أضف سطرًا تحت التعليق number 1// الذي يضيف عملية البحث المدخلة مؤخرًا إلى بداية المصفوفة، ويمكن الحصول على هذه القيمة من خلال اﻷمر searchInput.vlue. أضف سطرًا تحت التعليق number 2// كي يزيل العنصر الموجود في آخر المصفوفة حاليًا. See the Pen js-array-2 by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة لا بد وأنك استنتجت أهمية المصفوفات بعد إكمال قراءة هذا المقال، وستجدها في كل مكان في شيفرة جافا سكريبت وعادة بالتشارك مع حلقات كي تكرر نفس العمليات على كل عنصر من عناصرها. أما اﻵن وقد انتهيت من هذه السلسلة من التعليمات التي شرحنا فيها أساسيات جافا سكريبت، خذ قسطًا من الراحة واستعد للسلسلة القادمة التي سنوضح فيها مواضيع أكثر تقدمًا في لغة جافا سكريبت مثل العبارات الشرطية واتخاذ القرار في جافا سكريبت والتعامل مع الدوال البرمجية وغيرها من المفاهيم الضرورية لأي مطور. ترجمة -وبتصرف- للمقال Arrays اقرأ أيضًا المقال السابق: توابع التعامل مع النصوص في جافا سكريبت فهم المصفوفات في الجافاسكربت البحث والترتيب في المصفوفات Array في جافا كيفية استخدام وظائف المصفوفات في الجافا سكريبت – توابع التعديل تعلم جافا سكريبت
  7. سنوضح في مقال اليوم طريقة التعامل مع استعلام الوسائط Media Query في CSS والتي توفر طريقة لتطبيق تنسيقات معينة على عناصر HTML عندما تحقق بيئة العرض في جهاز أو متصفح معايير أوشروط محددة، كأن يكون اتساع نافذة العرض أكبر من 480 بكسل. إن هذا النمط من الاستعلام هو المفتاح لتصميم الويب المتجاوب Responsive web design، إذ يساعد فى بناء تخطيطات مختلفة للصفحات وفقًا لاتساع نافذة العرض. كما يمكن استخدام هذه الاستعلامات في معرفة بعض ميزات البيئة التي يعمل ضمنها موقعك كأن تعرف إن كان المستخدم يستعمل شاشة لمس بدلًا من الفأرة. لهذا سنتعلم أولًا طريقة صياغة استعلامات الوسائط، ثم سنتعلم استخدامها عمليًا من خلال مثال تفاعلي يشرح كيفية تحويل تخطيط بسيط إلى تخطيط متجاوب. عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات مدخل إلى HTML. تفهم أساسيات عمل CSS. أساسيات استعلامات الوسائط تبدو شيفرة استعلام الوسائط بشكلها الأبسط كالتالي: @media media-type and (media-feature-rule) { /* CSS rules go here */ } وهي تتكون من الأجزاء التالية: نوع واسطة العرض media type والذي يخبر المتصفح بطبيعة واسطة العرض التي كُتبت هذه الشيفرة من أجلها (طابعة، شاشة، ...إلخ.). شرط تطبيق الاستعلام media expression وهي قاعدة أو اختبار لا بد من تحققه حتى تُطبق شيفرة CSS المطلوبة. مجموعة قواعد تنسيق CSS التي تُطبق عند تحقق شرط تطبيق الاستعلام. أنواع وسائط العرض هناك ثلاث قيم لواسطة العرض: all print screen يضبط الاستعلام التالي مثلًا حجم الخط في جسم الصفحة على 12pt عند طباعة الصفحة، لكن هذه القاعدة لن تطبق عند عرض هذه الصفحة ضمن المتصفح: @media print { body { font-size: 12pt; } } ملاحظة1: إن نوع الوسائط في الاستعلامات مفهوم مختلف عن ما يُدعى نوع الوسائط المتعددة أو نوع المحتوى MIME-type وهو سلسلة نصية تُرسل مع الملف المرسل عبر الانترنت لتحديد نوعه أو وصف تنسيقه، على سبيل المثال، يمكن تسمية ملف صوتي audio/ogg، أو ملف صورة image/png). ملاحظة2: توجد أنواع أخرى من وسائط العرض أصّيفت في مواصفات المستوى الثالث من استعلامات الوسائط، لكنها أهملت ويجب تحاشيها. ملاحظة3: نوع الوسائط media type قيمة اختيارية، فإن لم ترغب بتحديد نوع واسطة العرض فلا تفعل وستكون القيمة الافتراضية all أي جميع الوسائط. قواعد تطبيق ميزات استعلام الوسائط بعد تخصيص نوع واسطة العرض يمكنك استهداف إحدى ميزات هذه الواسطة كي تحقق شرطًا أو اختبارًا ما: الاتساع والارتفاع أكثر الميزات استهدافًا للحصول على تصميم متجاوب وأكثرها دعمًا من قبل مختلف المتصفحات هي اتساع نافذة العرض viewport width. وهكذا يمكننا تطبيق مجموعة من قواعد التنسيق إن كان اتساع نافذة العرض أعلى أو أدنى أو يعادل قيمة محددة باستخدام ميزات استعلام الوسائط التالية: min-width و max-width و width. تُستخدم الميزات السابقة في إنشاء تخطيطات تتجاوب مع مختلف أبعاد الشاشات. فلو أردنا مثلًا تغيير لون خط الكتابة في جسم المستند إلى اللون الأحمر عندما يكون اتساع نافذة العرض 600 بكسل تمامًا، سنستخدم الاستعلام التالي: @media screen and (width: 600px) { body { color: red; } } ألق نظرة على هذا المثال على جيت-هاب أو اطلع على الشيفرة المصدرية. يمكن استخدام ميزتي الاتساع والارتفاع كمجالات وعندها تسبقان بالبادئة -min أو -max للإشارة إلى أن القيمة المعطاة هي أدنى أو أعلى قيمة. فإن أردنا في مثالنا السابق أن يكون لون الخط أحمر إن كان اتساع نافذة العرض 600 بكسل أو أضيق فنستخدم الميزة max-width: @media screen and (max-width: 600px) { body { color: blue; } } يمكنك إلقاء نظرة على هذا المثال على جيت-هاب أو الاطلاع على الشيفرة المصدرية. إن استخدام القيم العظمى والصغرى أكثر فائدة عمليًا في التصميم المتجاوب، لهذا قلما تُستخدم الميزتان width أو height وحدهما. ستجد العديد من ميزات وسائط الاستعلام التي يمكن استهدافها على الرغم من محدودية دعم المتصفحات للميزات الأحدث الموضوعة في مواصفات المستويين 4 و 5 من استعلامات الوسائط. ويمكنك الاطلاع على كل ميزة ومدى دعم المتصفحات لها من خلال شبكة مطوري موزيللا. جهة انسياب المحتوى من الميزات المدعومة جيدًا لاستعلامات الوساط نجد الميزة orientation التي تسمح باختيار نمط عرض الصورة إما كصورة عمودية portrait أو أفقية landscape. ولكي نغير لون خط كتابة جسم الصفحة إن كان نمط عرض الجهاز أفقيًا، نستخدم الاستعلام التالي: @media (orientation: landscape) { body { color: rebeccapurple; } } يمكنك إلقاء نظرة على هذا المثال على جيت-هاب أو الاطلاع على الشيفرة المصدرية. تعتمد شاشات حواسيب سطح المكتب نمط العرض الأفقي، وما يعمل جيدًا وفق نمط العرض هذا قد لا يعمل جيدًا على الهاتف المحمول أو الجهاز اللوحي الذي يعمل على النمط العمودي. وبالتالي سيساعدك الاستعلام عن نمط العرض في تبني تخطيط محسّن يخدم نمط العرض في الجهاز المستهدف. استخدام جهاز تأشير pointing device قدّمت مواصفات المستوى 4 لاستعلامات الوسائط الميزة hover التي تساعدك على اختبار قدرة المستخدم على إحداث أثر عند المرور فوق عنصر مما يدل على استخدامه نمطًا من أجهزة التأشير كالفأرة، فلا يمكن إحداث أثر عند المرور فوق عنصر في شاشات اللمس أو عند استخدام لوحة المفاتيح في التنقل بين العناصر. @media (hover: hover) { body { color: rebeccapurple; } } ألق نظرة على هذا المثال على جت-هاب. فإن عرفت أن المستخدم لا يعتمد على جهاز تأشير، بإمكانك عندها تقديم بعض الميزات التفاعلية للصفحة افتراضيًا، بينما يمكن تقديم هذه الميزات لمستخدمي أجهزة التأشير عند مرور المؤشر فوق العنصر. كما تضم مواصفات المستوى الرابع الميزة pointer التي تأخذ واحدة من ثلاث قيم هي none و fine و coarse. تُستخدم القيمة fine لأجهزة تأشير مثل الفأرة أو لوحة التتبع، وتساعد المستخدم على استهداف مساحة ضيقة من الصفحة. أما القيمة coarse فتدل على أن المستخدم يستعمل أصابعه أو يستعمل شاشة لمس. وأخيرًا تشير القيمة none إلى عدم استخدام أجهزة تأشير كحالة استخدام لوحة مفاتيح أو استخدام الأوامر الصوتية. يساعدك استخدام الميزة السابقة في تصميم واجهات مستخدم متجاوبة مع طريقة تفاعل المستخدم مع الشاشة. فبإمكانك مثلًا إنشاء مساحة لمس أوسع لمستخدم يستعمل شاشة لمس. استعلامات وسائط أكثر تعقيدًا قد ترغب أحيانًا بضم أكثر من استعلام أو إنشاء قائمة استعلامات قد يتحقق أيًا منها استخدام عامل الربط المنطقي and يُستخدم العامل and بنفس الطريقة التي استخدمناها سابقًا لربط نوع واسطة العرض مع الميزة. فقد نرغب مثلًا أن نختبر الميزتين min-width و orientation معًا. إذ نريد مثلًا أن يكون لون خط الكتابة أزرق إن كان اتساع نافذة العرض اكبر من 600 بكسل وكان الجهاز يعتمد طريقة العرض الأفقية: @media screen and (min-width: 600px) and (orientation: landscape) { body { color: blue; } } ألق نظرة على هذا المثال على جيت-هاب أو اطلع على الشيفرة المصدرية. استخدام عامل الربط المنطقي "or" ويُستخدم لتطبيق تنسيق معين عند تحقق واحدة من عدة استعلامات على الأقل وعندها نستخدم الفاصلة , للفصل بين هذه الاستعلامات. إذ يعرض المثال التالي خط الكتابة باللون الأزرق إن كانت اتساع نافذة العرض 600 بكسل على الأقل واعتمد الجهاز المستهدف طريقة العرض الأفقية. @media screen and (min-width: 600px), screen and (orientation: landscape) { body { color: blue; } } ألق نظرة على هذا المثال على جيت-هاب أو اطلع على الشيفرة المصدرية. استخدام عامل النفي المنطقي not بإمكانك نفي الاستعلام بالكامل باستخدام العامل not، إذ يعكس هذا العامل معنى الاستعلام تمامًا. لاحظ كيف يكون النص في مثالنا التالي أزرق اللون إن كان نمط العرض عموديًا: @media not all and (orientation: landscape) { body { color: blue; } } ألق نظرة على هذا المثال على جيت-هاب أو اطلع على الشيفرة المصدرية. كيفية اختيار النقاط الحدِّية breakpoints حاول المصممون في بدايات التصميم المتجاوب استهداف شاشات بقياسات محددة، بالاستفادة من قوائم تضم أبعاد شاشات أكثر الهواتف المحمولة والأجهزة اللوحية شعبية، وبالتالي سيكون التصميم ملائمًا تمامًا لنافذة العرض المستهدفة. أما الآن، وبوجود كم هائل من الأجهزة مختلفة الأبعاد، فلا جدوى من هذا النهج. وبدلًا من استهداف قياسات بعينها، ظهرت مقاربة تعتمد على تغيير التصميم أو التخطيط عندما لا يعود هذا التصميم ملائمًا لأبعاد الشاشة التي تعرضه. فقد يغدو السطر في نص ما طويلًا جدًا أو أن يضغط وتظهر أشرطة تمرير تصعب معها القراءة. في هذه الحالات، تساعدك استعلامات الوسائط في تغيير التصميم إلى آخر أفضل يلائم المساحة المتاحة للعرض. وهذا يعني أنك لن تحتاج إلى معرفة القياسات الدقيقة لأبعاد الشاشة المستخدمة، بل يتغير التصميم ضمن مجالات محددة لأبعاد نافذة العرض. تُدعى النقاط التي تُعرّف عندها استعلام الوسائط بنقاط الانتقال أو النقاط الحدية Breakpoints التي تسمح بالانتقال من تخطيط لآخر أو من تنسيق لآخر). يُساعدك نمط التصميم المتجاوب في أدوات مطوري ويب لمتصفح فايرفوكس في تفقد عمل نقاط الانتقال. إذ يمكنك بسهولة تصغير نافشة العرض أو تكبيرها لتتفحص كيفية تحسين التصميم إن أضفت استعلامات وسائط. تطبيق عملي: التصميم المتجاوب وقاعدة "الهاتف المحمول أولًا" يمكنك عمومًا اختيار أحد نهجين في التصميم المتجاوب. فإما أن تبدأ التصميم للحواسيب المكتبية أو الشاشات العريضة ثم تضيف نقاط انتقال يتغير عندها التصميم عند الانتقال إلى شاشات أضيق. أو أن تبدأ تصميمك لأصغر نوافذ العرض ثم تغير التخطيط مع ازدياد اتساع نافذة العرض. يُدعى النهج الأخير بنهج الهاتف المحمول أولًا وهو غالبًا ما يكون النهج الأفضل عمليًا. يُعرض المحتوى في الشاشات الصغيرة عادة ضمن تخطيط عمود واحد بسيط، كما هو الحال في تخطيط الانسياب الاعتيادي normal flow. أي أنك لن تحتاج غالبًا إلى تخطيطات معقدة للأجهزة الصغيرة، وكل ما عليك فعله هو ترتيب الشيفرة المصدرية جيدًا لتحصل على تخطيط واضح مقروء افتراضيًا. سنعمل في التطبيق التالي على توضيح هذا النهج من خلال تخطيط بسيط جدًا، وتذكّر أنه في المواقع الفعلية قد تواجه أشياء أكثر تعقيدًا تحتاج إلى ضبطها من خلال استعلامات الوسائط، لكن النهج سيبقى ذاته. تخطيط بسيط على نهج "الهاتف المحمول أولًا" سننطلق من مستند HTML مع بعض تنسيقات CSS التي تضيف ألونًا لخلفيات الأقسام المختلفة للتخطيط كما يلي. * { box-sizing: border-box; } body { width: 90%; margin: 2em auto; font: 1em/1.3 Arial, Helvetica, sans-serif; } a:link, a:visited { color: #333; } nav ul, aside ul { list-style: none; padding: 0; } nav a:link, nav a:visited { background-color: rgba(207, 232, 220, 0.2); border: 2px solid rgb(79, 185, 227); text-decoration: none; display: block; padding: 10px; color: #333; font-weight: bold; } nav a:hover { background-color: rgba(207, 232, 220, 0.7); } .related { background-color: rgba(79, 185, 227, 0.3); border: 1px solid rgb(79, 185, 227); padding: 10px; } .sidebar { background-color: rgba(207, 232, 220, 0.5); padding: 10px; } article { margin-bottom: 1em; } لم ندخل أية تغييرات على التخطيط من خلال شيفرة التنسيق السابقة، لكننا رتبنا الشيفرة المصدرية بطريقة تجعل المحتوى واضحًا. هذه الخطوة أساسية ومهمة من جهة، وتضمن سهولة قراءة المحتوى من قبل قارئات الشاشة من ناحية أخرى. <body> <div class="wrapper"> <header> <nav> <ul> <li><a href="">About</a></li> <li><a href="">Contact</a></li> <li><a href="">Meet the team</a></li> <li><a href="">Blog</a></li> </ul> </nav> </header> <main> <article> <div class="content"> <h1>Veggies!</h1> <p>…</p> </div> <aside class="related"> <p>…</p> </aside> </article> <aside class="sidebar"> <h2>External vegetable-based links</h2> <ul> <li>…</li> </ul> </aside> </main> <footer><p>&copy;2019</p></footer> </div> </body> يعمل هذا التخطيط البسيط جيدًا على الهاتف المحمول. وبإمكانك استخدام نمط التصميم المتجاوب في أدوات مطوري الويب لترى كيف يعمل بشكل واضح ومرضٍ على شاشة الهاتف المحمول. اطلع على الخطوة الأولى ضمن متصفحك أو ألق نظرة على الشيفرة المصدرية. وإن أردت أن تتابع العمل معنا، نزّل نسخة من الملف step1.html على حاسوبك. ابتداءً من هذه الخطوة، اسحب نافذة العرض في وضع التصميم المتجاوب لتصبح أوسع حتى اللحظة التي ترى فيها أن طول سطر الكتابة أصبح طويلًا، ولدينا متسع من المساحة لعرض المحتوى أفقيًا، هنا سنضع أول استعلام وسائط. سنستخدم واحدة em وتعني أنه إذا زاد المستخدم حجم الخط فإن نقطة الانتقال ستحدث عند طول السطر ذاته لكن ضمن نافذة عرض أوسع. أضف الشيفرة التالية إلى آخر الملف step1.html: @media screen and (min-width: 40em) { article { display: grid; grid-template-columns: 3fr 1fr; column-gap: 20px; } nav ul { display: flex; } nav li { flex: 1; } } يعطينا تنسيق CSS تخطيطًا من عمودين ضمن العنصر <article> الأول يضم محتوى المقال الأساسي والآخر لمعلومات متعلقة بالمحتوى إلى الجانب. كما استخدمنا الصندوق المرن لوضع قائمة التنقل ضمن صف واحد. اطلع على الخطوة الثانية ضمن متصفحك أو الق نظرة على الشيفرة المصدرية. نتابع الآن العمل ونزيد الاتساع بالمقدار الذي نرى أنه مناسب كي يشكل الشريط الجانبي عمودًا جديدًا. وسنضع ضمن استعلام الوسائط شيفرة تحوّل العنصر الأساسي إلى شبكة من عمودين، وعلينا عندها إزالة margin-bottom من العنصر كي يتحاذى العمودان، كما سنضيف حدًا border أعلى التذييل. إن ما فعلناه عمليًا هو الشيء الذي نحتاجه ليبدو التصميم جيدًا عند كل نقطة انتقال. @media screen and (min-width: 70em) { main { display: grid; grid-template-columns: 3fr 1fr; column-gap: 20px; } article { margin-bottom: 0; } footer { border-top: 1px solid #ccc; margin-top: 2em; } } اطلع على الخطوة الثالثة ضمن متصفحك أو الق نظرة على الشيفرة المصدرية. لو نظرت إلى المثال الأخير سترى كيف يتجاوب التصميم مع الاتساعات المختلفة للشاشة ابتداءًا من عمود واحد ثم عمودين وثلاثة أعمدة وفقًا للاتساع المتاح. وهذا بالطبع مثال بسيط عن التصميم وفق مبدأ "الهاتف المحمول أولًا". الوسم <meta> الخاص بنافذة العرض إن ألقيت نظرة على الشيفرة المصدرية لصفحة متجاوبة سترى عادة الوسم <meta> ضمن الترويسة كالتالي: <meta name="viewport" content="width=device-width,initial-scale=1" /> وهي طريقة للتحكم بكيفية تصيير متصفحات الهاتف المحمول للمحتوى، لأن متصفحات الهواتف المحمولة لا تكون صادقة تمامًا فيما يخص اتساع نافذة العرض.ولا تُعرض معظم المواقع غير المتجاوية بالشكل الأفضل ضمن نوافذ العرض الضيقة. لهذا تصيير الهواتف الذكية المحتوى وفق نافذة العرض أوسع من نافذة العرض الفعلية للجهاز (عادة 980 بكسل) ومن ثم تقلّص الصفحة بعد تصييرها لتلائم شاشة الجهاز. ويعني هذا أن المواقع المتجاوبة لن تعمل كما هو متوقع إن كان اتساع نافذة العرض التي يتعامل معها الجهاز هي 980 بكسل. فالتخطيط الذي تريده عند النقطة الحدِّية{}media screen and (max-width: 600px)@ مثلًا لن يُصيّر كما هو متوقع. يأتي الحل لهذه المشكلة باستخدام الوسم <meta> الذي يعرف نافذة العرض كما في الشيفرة السابقة والذي يمنع متصفح الهاتف من تصيير المحتوى على أساس اتساع 980 بكسل، بل وفقًا لنافذة العرض الفعلية للجهاز، ويضبط المقياس افتراضيًا ليكون كمقياس الصفحة الأصلي. عندها ستعمل استعلامات الوسائط كما هو متوقع. هل نحتاج فعلًا استعلامات الوسائط؟ تقدم لك تقنيات مثل الصندوق المرنflexbox وتخطيط الشبكة grid والتخطيط متعدد الأعمدة multicol وسيلة لإنشاء صفحات ويب مرنة ومتجاوبة دون الحاجة إلى استعلامات الوسائط. ومن الأفضل التفكير في تصميمك إن كان يحتاج فعلًا إلى هذه الاستعلامات أو لا، فقد ترغب مثلًا بعرض مجموعة من البطاقات اتساعها على الأقل 200 بكسل بقدر ما تتسع له الحاوية، هذا الأمر سهل الإنجاز باستخدام تخطيط الشبكة دون استعلامات وسائط كما يلي: <ul class="grid"> <li> <h2>Card 1</h2> <p>…</p> </li> <li> <h2>Card 2</h2> <p>…</p> </li> <li> <h2>Card 3</h2> <p>…</p> </li> <li> <h2>Card 4</h2> <p>…</p> </li> <li> <h2>Card 5</h2> <p>…</p> </li> </ul> .grid { list-style: none; margin: 0; padding: 0; display: grid; gap: 20px; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); } .grid li { border: 1px solid #666; padding: 10px; } افتح هذا المثال باستخدام المتصفح أو اطلع على الشيفرة المصدرية. إن فتحت المثال في متصفحك، حاول أن تغيير اتساع نافذة المتصفح لترى كيف يتغير عدد الأعمدة في الصفحة. والمثير في هذه الطريقة عدم اعتماد الشبكة على اتساع نافذة العرض بل على مقدار المساحة المتاحة للعنصر أو الحاوية. قد تجد كتابة مقال عن استعلامات الوسائط ثم التوصية باستخدام تقنيات أخرى أمرًا غريبًا، لكن ما ستراه في الواقع التطبيقي هو تخطيطات ويب حديثة مدعومة باستعلامات وسائط للحصول على أفضل النتائج. الخلاصة تعلمنا في هذا المقال مبادئ استعلامات الوسائط وكيفية استخدامها عمليًا في إنشاء تصميمات تعتمد على قاعدة "الهاتف المحمول أولًا". بإمكانك استخدام الأمثلة والشيفرات المصدرية التي عرضناها كنقطة انطلاق وتتمرن بعدها على تطبيق استعلامات الوسائط المختلفة كأن تغير مثلًا حجم قائمة التنقل إن اكتشفت أن الزائر يستخدم جهاز تأشير خشن (غير دقيق) بالاستفادة من الميزة pointer. يمكنك اختبار الاستعلامات أيضًا بإضافة مكونات مختلفة وتحري إن كان إضافة استعلامات وسائط أو استخدام أساليب التخطيط المختلفة كالصندوق المرن أو الشبكات هو الأفضل في جعل تلك المكونات متجاوبة. إذًا لا توجدغالبًا طريقة صحيحة وأخرى خاطئة، وما عليك فعله هو التجريب لتعرف ما هو الأنسب لتصميمك. ترجمة -وبتصرف- للمقال: Beginners guide to media queries اقرأ أيضًا المقال السابق: التصميم المتجاوب لصفحات الويب Responsive Web Design استعلامات الوسائط (Media Queries) في CSS مدخل إلى التصميم المتجاوب والتصميم المتكيف عرض محتوى صفحات الويب بتجاوب على الأجهزة المتعددة
  8. تُعرَّف اﻷحداث أنها أفعال أو ظواهر تحدث في النظام الذي تبرمجه، ويخبرك بها هذا النظام كي تستجيب لها بطريقة مناسبة إن أردت. فلو نقر مثلًا مستخدم زرًا في صفحة ويب، قد ترغب في الاستجابة لهذا الحدث بعرض رسالة معينة. وما نناقشه في هذا المقال هي بعض المفاهيم المهمة المتعلقة باﻷحداث وكيفية عملها في المتصفح والتعامل معها في جافا سكريبت. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافاسكريبت كما شرحناها في سلسلة المقالات السابقة. ما هي اﻷحداث؟ تُعرَّف اﻷحداث events أنها أفعال أو ظواهر تحدث في النظام الذي تبرمجه، ينتج عن النظام (أو يحرّض) إشارة من نوع ما عند وقوع هذا الفعل أو الظاهرة، ويزوّدك بآلية لتحديد اﻹجراءات التي تتُخذ تلقائيًا (تشغيل شيفرة معينة) عند وقوعها. تقع اﻷحداث ضمن نافذة المتصفح وُربط أغلب اﻷحيان بعنصر محدد ضمن النافذة، قد يكون العنصر مفردًا أو مجموعة من العناصر أو صفحة HTML حُمِّلت ضمن النافذة الحالية أو نافذة المتصفح بأكملها. وستجد أنواعًا مختلفة من اﻷحداث التي تقع مثل: اختيار المستخدم عنصرًًا أو النقر عليه أو تحريك مؤشر الفأرة فوقه. اختيار المستخدم مفتاحًا من لوحة المفاتيح. تغيير المستخدم حجم نافذة المتصفح أو إغلاقها. إنهاء تحميل صفحة ويب. تشغيل فيديو أو إيقافه أو انتهائه. وقوع خطأ. إذًا هناك الكثير من اﻷحداث التي يمكن ترصدها. ولكي تستجيب لحدث ما، نربطه بما يُدعى معالج حدث event handler، وهو كتلة من الشيفرة (دالة جافاسكريبت عادة) تُنفَّذ عندما يقع الحدث. وعندما تُعرف كتلة برمجية كهذه كي تعمل استجابة لوقوع حدث ما، نقول أننا سجلنا معالج حدث. وتجدر الملاحظة أن معالج الحدث يُسمى أحيانًا مترصد حدث event listener، وسنتستخدم المصطلحين معًا ويعملان معًا. فالمترصد هو من يكتشف وقوع الحدث والمعالج هو الشيفرة التي تُنفّذ استجابة له. ملاحظة: لا تُعد أحداث الويب جزءًا من بنية جافاسكريبت، بل كجزء من الواجهة البرمجية المدمجة مع المتصفح. مثال: التعامل مع حدث النقر على عنصر لدينا في المثال التالي صفحة HTML تضم زرًا <button> واحدًا: <button>Change color</button> سنعود إليه لاحقًا بعد كتابة شيفرة جافا سكريبت في القسم التالي، لكن ما يهمنا حاليًا هو إضافة معالج حدث "النقر click" إلى الزر وسيتفاعل المتصفح مع هذا الحدث بانتقاء لون عشوائي للصفحة: const btn = document.querySelector("button"); function random(number) { return Math.floor(Math.random() * (number + 1)); } btn.addEventListener("click", () => { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; document.body.style.backgroundColor = rndCol; }); سيكون خرج المثال كالتالي: See the Pen js-events-1 by Hsoub Academy (@HsoubAcademy) on CodePen. استخدام التابع ()addEventListener رأينا في المثال السابق أن العناصر التي يمكنها إطلاق حدث، تمتلك التابع ()addEventListener وهو اﻵلية التي ننصح بها ﻹضافة معالج حدث. لنلق نظرة أقرب إلى شيفرة المثال السابق: const btn = document.querySelector("button"); function random(number) { return Math.floor(Math.random() * (number + 1)); } btn.addEventListener("click", () => { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; document.body.style.backgroundColor = rndCol; }); سيحرّض المستخدم عندما ينقر على الزر <button> حدثًا، لذلك عرّفنا دالة ()addEventListener نستدعيها عند وقوع الحدث ونمرر لها معاملين هما: القيمة النصية "click"ونحدد فيها أن الحدث الذي نترصده هو حدث النقر. ويمكن للأزرار أن تطلق العديد من اﻷحداث مثل mouseover عندما يحرّك المستخدم الفأرة فوق الزر أو keydown عندما يضغط على مفتاحًا ويكون تركيز الدخل على الزر. دالة نستدعيها عند وقوع الحدث، وفي حالتنا تولّد هذه الدالة لون RGB عشوائي وتضبط قيمة الخاصية background-color للعنصر <body> على قيمة اللون تلك. ولا بأس أن تفصل دالة الحدث باسم خاص بها كالتالي: const btn = document.querySelector("button"); function random(number) { return Math.floor(Math.random() * (number + 1)); } function changeBackground() { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; document.body.style.backgroundColor = rndCol; } btn.addEventListener("click", changeBackground); ترصّد اﻷحداث ستجد العديد من اﻷحداث التي يمكن للزر أن يطلقها، ولكي نختبر ذلك، انسخ الملف إلى جهازك وافتحه ضمن المتصفح. يمثل الملف نسخة عن مثال اللون العشوائي السابق. حاول اﻵن تغيير الحدث click إلى القيم المختلفة التالية على التوالي وراقب النتيجة: focus و blur: يتغير لون خلفية الصفحة عندما يتلقى الزر تركيز الدخل أو يفقد. حاول أن تنقل تركيز الدخل إلى الزر باستخدام المفتاح Tab ثم اضغط على المفتاح مجددًا ﻹبعاد تركيز الدخل. تُستخدم هذه اﻷحداث لعرض معلومات عن ملء نموذج بالبيانات عندما ينتقل التركيز إلى حقل نصي أو زر، أو لعرض رسالة خطأ عند ملئ أحد حقول النموذج بقيمة خاطئة. dblclick: يتغير اللون عندما تنقر الزر نقرًا مزدوجًا. mouseover أو mouseout: يتغير لون الخلفة عندما تمرر مؤشر الفأرة فوق الزر أو عندما يبتعد مؤشر الفأرة عن الزر. وبعض اﻷحداث مثل متاح تقريبًا لجميع العناصر، بينما يكون بعضها مخصصًا لحالات محددة. فالحدث play متاح مثلًا لبعض العناصر مثل العنصر <video. إزالة مترصد حدث عندما تضيف مترصد حدث باستخدام الدالة ()addEventListener، بإمكانك إزالته باستخدام التابع removeEventHandler. ستزيل الشيفرة التالية معالج الحدث ()changeBackgroud: btn.removeEventListener("click", changeBackground); كما يزال المعالج بتمرير إشارة إيقاف AbortSignal إلى الدالة ()addEventListener ومن ثم استدعاء التابع ()abort العائد للمتحكم controller الذي يمتلك إشارة اﻹيقاف لاحقًا. const controller = new AbortController(); btn.addEventListener("click", () => { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; document.body.style.backgroundColor = rndCol; }, { signal: controller.signal } // تمرير إشارة إيقاف إلى المعالج ); عندها يمكن إزالة معالج الحدث الذي تنتجه الشيفرة السابقة كالتالي: controller.abort(); // يزيل أي أو كل معالج حدث مرتبط بهذا المتحكم لا حاجة في البرامج الصغيرة والقديمة لإزالة معالجات اﻷحداث غير المستخدمة، لكن في البرامج اﻷضخم واﻷعقد سيزيد ذلك فعالية البرنامج. كما يمكن الاستفادة من فكرة إزالة معالج الحدث في استخدام الزر نفسه مثلًا لأداء إجراءات مختلفة في ظروف مختلفة، وكل ما عليك فعله هو إضافة وإزافى معالج الحدث المطلوب. إضافة أكثر من مترصد لنفس الحدث إن أردت استدعاء الدالة ()addEventListener أكثر من مرة وتزويدها بأكثر من معالج حدث لنفس الحدث، يمكنك استخدام أكثر من معالج كالتالي: myElement.addEventListener("click", functionA); myElement.addEventListener("click", functionB); وهكذا ستُنفَّذ كلا الدالتين في المعالجين السابقين عندما يُنقر الزر. عُد إلى موسوعة حسوب إن أردت الاطلاع على ميزات وخيرات أخرى مفيدة للدالة ()addEventListener. آليات اخرى لاستخدام مترصد الحدث ننصح دومًا باستخدام ()addEventListener لتسجيل معالجات اﻷحداث، فهي الطريقة اﻷقوى وتتلائم جيدًا مع البرامج المعقدة. مع ذلك، هنالك طريقتان إضافيتين لتسجيل معالج الحدث قد تصادفهما وهما استعمال الخاصية الموافقة للحدث أو معالج الحدث السطري inline event handler. خاصيات معالج الحدث للكائنات التي تطلق أحداثًا مثل اﻷزرار خاصيات تبدأ أسماؤها بالسابقة on يليها اسم معالج الحدث، مثل الخاصية onclick. تُدعى هذه الخاصيات بخاصيات معالج الحدث. ولكي تترصد حدثًا، أسند دالة معالج الحدث إلى هذه الخاصية كما في المثال التالي: const btn = document.querySelector("button"); function random(number) { return Math.floor(Math.random() * (number + 1)); } btn.onclick = () => { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; document.body.style.backgroundColor = rndCol; }; وتستطيع أيضًا إسناد اسم الدالة إلى الخاصية كالتالي إن كانت دالة المعالج مسماة: const btn = document.querySelector("button"); function random(number) { return Math.floor(Math.random() * (number + 1)); } function bgChange() { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; document.body.style.backgroundColor = rndCol; } btn.onclick = bgChange; ولا يمكن بالطبع إسناد أكثر من معالج حدث إلى حدث ما باستخدام الخاصيات، إذ يمكنك مثلًا استدعاء الدالة (click',handler')addEventListener من قبل عنصر عدة مرات وبتمرير دوال معالجة مختلفة كوسيط ثانِ: element.addEventListener("click", function1); element.addEventListener("click", function2); لكن ذلك مستحيل التنفيذ باستخدام الخاصيات لأن الإسناد الثاني سيلغي اﻷول: element.onclick = function1; element.onclick = function2; معالجات الأحداث السطرية لا تستخدم هذه الطريقة، لكنك قد تصادفها: <button onclick="bgChange()">Press me</button> إليك شيفرة جافاسكريبت function bgChange() { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; document.body.style.backgroundColor = rndCol; } ظهرت هذه الطريقة في مراحل مبكرة وتضمنت استخدام سمات HTML التي تحدد معالج الحدث (المعالجات السطرية inline event handler) كما تعرضه الشيفرة السابقة. وتحمل هذه السمة حرفيًا شيفرة جافاسكريبت التي تريد تنفيذها عند وقوع الحدث. نستدعي في المثال السابق دالة معرّفة ضمن العنصر <script> في نفس الصفحة، كما يمكنك إدراج شيفرة جافاسكريبت مباشرة ضمن السمة كالتالي: <button onclick="alert('Hello, this is my old-fashioned event handler!');"> Press me </button> قد تجد سمات HTML تكافئ العديد من خاصيات معالجات اﻷحداث، لكن لا ينبغي استخدامها لأنها ممارسة عملية سيئة. وقد يبدو استخدام معالج أحداث ضمن السمة سهلًا عند تنفيذ أمر سريع، لكنها ستغدو صعبة اﻹدارة وغير فعّالة. والسبب في ذلك من ناحية أولى أن دمج توصيف الملف HTML مع جافاسكريبت أمر غير عملي لما يسببه صعوبة في قراءة وفهم الصفحات، لهذا ينبغي فصل شيفرة جافاسكريبت في ملف خاص بها وهذا عمليًا أمر جيد ﻹمكانية إدراج نفس الملف في صفحات مختلفة. وحتى لو استخدمت ملفًا واحدًا، لا يُعد استخدام معالجات الأحداث السطرية فكرة جيدة، فقد ينفع الأمر إن كان هناك زر واحد، لكن ماذا لو ضمت الصفحة 100 زر؟ عليك حينها إضافة 100 سمة إلى الملف، وستكون إدارتها وصيانتها كابوسًا. أما عند استخدام جافاسكريبت، ستتمكن من إضافة دالة معالج الحدث إلى كل اﻷزرار في الصفحة مهما كان عددها وباستخدام شيفرة كهذه: const buttons = document.querySelectorAll("button"); for (const button of buttons) { button.addEventListener("click", bgChange); } ومن ناحية أخرى لا تسمح الكثير من إعدادات الخوادم باستخدام شيفرة جافاسكريبت سطرية، لأسباب تتعلق باﻷمان. لهذا لا يجب استخدام سمات HTML المتعلقة بمعالجات اﻷحداث، فهي قديمة واستخدامها عادة سيئة. كائنات الحدث قد تجد ضمن دالة الحث معاملات خاصة تحمل أسماء مثل event أو evt أو e، تُدعى هذه المعاملات كائنات حدث event object، وتمرر تلقائيًا إلى المعالجات لتزويدها بميزات ومعلومات إضافية. لنُعِد على سبيل المثال كتابة مثال اﻷلون العشوائية السابق مع بعض الاختلاف: const btn = document.querySelector("button"); function random(number) { return Math.floor(Math.random() * (number + 1)); } function bgChange(e) { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; e.target.style.backgroundColor = rndCol; console.log(e); } btn.addEventListener("click", bgChange); ملاحظة: يمكنك إيجاد الشيفرة الكاملة لهذا المثال على جت-هب (جرّب الشيفرة مباشرة أيضًا). لاحظ كيف أضفنا هنا كائن أحداث e في الدالة، ثم استخدمنا ضمن الدالة الخاصية target للكائن كالتالي e.target لضبط لون خلفية الصفحة، حيث تشير هذه الخاصية دائمًا إلى العنصر الذي يقع عليه الحدث. لذا نضبط في هذا المثال خلفية لونية عشوائية للزر وليس للصفحة لأن target تستهدف الزر فقط. ملاحظة: بإمكانك استخدام أي اسم تريده لكائن الحدث، لكن عليك استخدام اسم تتذكره عند استخدامه ضمن دالة الحدث. يستخدم المطورون عادة أسماء مثل event أو evt أو e لأنها قصيرة وسهلة التذكر. والأهم أن تحافظ على تناسق التسميات سواء في مشاريعك الشخصية أو المشتركة. خاصيات إضافية لكائن اﻷحداث لبعض كائنات الأحداث خاصيات إضافية تتعلق بالطبيعة الخاصة لكل حدث. إذ يقع الحدث keydown مثلًا عندما يضغط المستخدم على مفتاح، لهذا يضم كائن اﻷحداث الموافق له keyboardEvent الخاصية key التي تعطيك الزر الذي ضغطه المستخدم: const textBox = document.querySelector("#textBox"); const output = document.querySelector("#output"); textBox.addEventListener("keydown", (event) => { output.textContent = `You pressed "${event.key}".`; }); حاول استخدام لوحة المفاتيح لكتابة شيء ما وراقب ما يحدث: See the Pen js-events-2 by Hsoub Academy (@HsoubAcademy) on CodePen. إيقاف السلوك الافتراضي تضطر في بعض المواقف إلى منع الحدث من تنفيذ وظيفته الافتراضية. ومن اﻷمثلة الشائعة عن ذلك حالة نموذج ويب web form مثل نموذج تسجيل مستخدم جديد. فعندما تملأ الحقول وتنقر زر "تسليم submit"، سيكون السلوك الافتراضي هو رفع البيانات إلى صفحة محددة على الخادم لمعالجتها، ويُعاد توجيه المتصفح لعرض رسالة تشير إلى نجاح التسليم ضمن صفحة جديدة أو في نفس الصفحة. وتكمن المشكلة إن لم تُسلم البيانات بشكل صحيح، لهذا عليك كمطوّر منع تسليم البيانات إلى الخادم وإعطاء رسالة خطأ تشير إلى طبيعة الخطأ وما عليك تصحيحه. تتيح بعض المتصفحات ميزة التدقيق التلقائي للبيانات، وطالما أن هذا اﻷمر قد لا يحدث، ننصحك ألا تعتمد عليه بل تنفيذ آلية تحقق خاصة بك. لنلق نظرة على المثال التالي: إليك أولًا ملف htmL بسيط تحتاجه لإدخال الاسم اﻷول واﻷخير للمستخدم: <form> <div> <label for="fname">First name: </label> <input id="fname" type="text" /> </div> <div> <label for="lname">Last name: </label> <input id="lname" type="text" /> </div> <div> <input id="submit" type="submit" /> </div> </form> <p></p> إليك ثانيًا شيفرة جافاسكريبت التي نتحقق فيها من الحدث submit بشكل مبسط (يُطلق هذا الحدث ضمن النموذج <form> عندما يُرسل إلى الخادم) وذلك إن كانت المربعات النصية في النموذج فارغة. فإن كانت كذلك، نستدعي الدالة ()preventDefault العائدة لكائن الحدث ﻹيقاف عملية اﻹرسال ومن ثم نعرض رسالة خطأ في الفقرة النصية أسفل النموذج ﻹبلاغ المستخدم بالخطأ: const form = document.querySelector("form"); const fname = document.getElementById("fname"); const lname = document.getElementById("lname"); const para = document.querySelector("p"); form.addEventListener("submit", (e) => { if (fname.value === "" || lname.value === "") { e.preventDefault(); para.textContent = "You need to fill in both names!"; } }); من الواضح أنها طريقة ضعيفة في التحقق، فهي لن تمنع المستخدم من إدخال فراغات أو أرقام في حقول النموذج، لكنها مناسبة لمثالنا. سيكون الخرج كالتالي: See the Pen js-events-3 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: يمكنك إيجاد الشيفرة الكاملة لهذا المثال على جت-هب (جرّب الشيفرة مباشرة أيضًا). رفع اﻷحداث يُقصد برفع اﻷحداث bubbling كيفية تعامل المتصفح مع اﻷحداث المستهدفة لعناصر متداخلة. إعداد مترصد لعنصر أب تأمل صفحة ويب لها الهيكلية التالية: <div id="container"> <button>Click me!</button> </div> <pre id="output"></pre> إن الزر هنا داخل العنصر <div> الذي يمثل العنصر اﻷب له. ما الذي سيحدث إن أضفنا معالج حدث نقر إلى العنصر اﻷب ثم نقرنا الزر؟ const output = document.querySelector("#output"); function handleClick(e) { output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`; } const container = document.querySelector("#container"); container.addEventListener("click", handleClick); لاحظ كيف يطلق العنصر اﻷب الحدث عندما ينقر المستخدم الزر: You clicked on a DIV element إن اﻷمر منطقي، فالزر ضمن العنصر <div> وعندما تنقر هذا العنصر فانت تنقر على الزر ضمنًا. مثال عن رفع الحدث ما الذي قد يحدث إن أضفنا مترصد الحدث إلى الزر وليس إلى العنصر اﻷب؟ <body> <div id="container"> <button>Click me!</button> </div> <pre id="output"></pre> </body> لنجرّب إضافة حدث نقر إلى الزر (الموجود ضمن <div> وكلاهما ضمن العنصر <body>? const output = document.querySelector("#output"); function handleClick(e) { output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`; } const container = document.querySelector("#container"); const button = document.querySelector("button"); document.body.addEventListener("click", handleClick); container.addEventListener("click", handleClick); button.addEventListener("click", handleClick); See the Pen js-events-3 by Hsoub Academy (@HsoubAcademy) on CodePen. سترى أن العناصر الثلاث جميعها ستطلق الحدث عندما ينقر المستخدم على الزر: You clicked on a BUTTON element You clicked on a DIV element You clicked on a BODY element في هذه الحالة: يُطلق حدث النقر على الزر أولًا. يليه حدث النقر على العنصر اﻷب (العنصر <div>). يليه حدث النقر على العنصر اﻷب للعنصر <div> (العنصر <div>). نصف ذلك برفع الحدث من العنصر الأعمق إلى الأب اﻷول. يمكن ان يكون هذا السلوك مفيدًا وقد يسبب مشاكل غير متوقعة، سنرى مثالًا عن هذه المشاكل في فقرات لاحقة وسنجد لها حلًا. مثال عن مشغّل فيديو تضم صفحتنا في هذا المثال فيديو أُخفي عمدًا وزر عنوانه "Display vedio". ونريد ان نظهر السلوك التفاعلي التالي: عندما ينقر المستخدم على الزر، يُعرض الصندوق الذي يضم الفيديو، لكنه لا يُشغّل المقطع. عندما ينقر المستخدم على الفيديو، يبدأ العرض. عندما ينقر المستخدم في أي مكان في الصندوق خارج الفيديو يختفي الصندوق. إليك شيفرة HTML: <button>Display video</button> <div class="hidden"> <video> <source src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm" type="video/webm" /> <p> Your browser doesn't support HTML video. Here is a <a href="rabbit320.mp4">link to the video</a> instead. </p> </video> </div> تتضمن الشيفرة: زر <button>. عنصر حاوية <div> له السمة "class="hidden. عنصر <video> ضمن العنصر <div>. أما شيفرة جافاسكريبت فهي كالتالي: const btn = document.querySelector("button"); const box = document.querySelector("div"); const video = document.querySelector("video"); btn.addEventListener("click", () => box.classList.remove("hidden")); video.addEventListener("click", () => video.play()); box.addEventListener("click", () => box.classList.add("hidden")); تضيف الشيفرة ثلاثة مترصدين للحدث 'click': اﻷول للزر <button> الذي يعرض الحاوية التي تضم الفيديو. والثاني للفيديو <video> لكي يُشغّله. والثالث للحاوية <div> لكي يخفي الفيديو. راقب كيف يجري اﻷمر: See the Pen js-events-5 by Hsoub Academy (@HsoubAcademy) on CodePen. سترى عند النقر على الزر كيف يظهر الصندوق والفيديو، لكن عند النقر على الفيديو سيعمل ويختفي الصندوق مجددًا. ولأن الفيديو داخل الحاوية، سيسبب نقره تنفيذ كلا معالجي اﻷحداث وظهور السلوك السابق. إصلاح المشكلة باستخدام الدالة ()stoppropogation قد يسبب رفع اﻷحداث كما رأينا سابقًا بعض المشاكل، لكن بالطبع توجد طريقة لمنع اﻷمر. إذ يُقدّم كائن اﻷحداث Event دالة تُدعى ()stopPropogation والتي تمنع عند استدعائها من داخل معالج الحدث من رفع الحدث إلى عناصر أعلى. ولكي نصلح المشكلة التي ظهرت معنا في المثال السابق، عدّل شيفرة جافاسكريبت كالتالي: const btn = document.querySelector("button"); const box = document.querySelector("div"); const video = document.querySelector("video"); btn.addEventListener("click", () => box.classList.remove("hidden")); video.addEventListener("click", (event) => { event.stopPropagation(); video.play(); }); box.addEventListener("click", () => box.classList.add("hidden")); كل ما فعلناه هنا هو استدعاء الدالة ()stopPropogation العائدة لكائن اﻷحداث داخل معالج الحدث الذي يتعامل مع الحدث 'click' الخاص بالعنصر <video>. سيمنع ذلك الحدث من الارتفاع إلى الصندوق. جرّب أن تنقر على الزر ثم على الفيديو: See the Pen js-events-6 by Hsoub Academy (@HsoubAcademy) on CodePen. التقاط الحدث إن الشكل اﻵخر لانتقال اﻷحداث هو التقاط الحدث event capture. واﻷمر مشابه لرفع الحدث لكن بترتيب معكوس. فبدلًا من اطلاق الحدث أولًا من قبل العنصر اﻷدنى في التسلسل الهرمي ثم ينتقل الحدث صعودًا إلى العنصر اﻷعلى مستوىً، يحدث العكس ويطلق الحدث العنصر اﻷعلى مستوى ويهبط وصولًا إلى العنصر اﻷدنى مستوىً. هذا الخيار معطّل افتراضيًا وعليك أن تمرر الخيار capture إلى الدالة ()addEventListener. يعرض المثال التالي نفس المثال السابق عن رفع اﻷحداث لكن سنستخدم فيه الخيار capture. إليك شيفرة HTML: <body> <div id="container"> <button>Click me!</button> </div> <pre id="output"></pre> </body> وشيفرة جافاسكريت: const output = document.querySelector("#output"); function handleClick(e) { output.textContent += `You clicked on a ${e.currentTarget.tagName} element\n`; } const container = document.querySelector("#container"); const button = document.querySelector("button"); document.body.addEventListener("click", handleClick, { capture: true }); container.addEventListener("click", handleClick, { capture: true }); button.addEventListener("click", handleClick); See the Pen js-events-7 by Hsoub Academy (@HsoubAcademy) on CodePen. تُعكس في هذه الحالة الرسائل التي تُعرض عند تنفيذ الشيفرة، حيث يطلق العنصر <body> الحدث أولًا يليه العنصر <div> من ثم الزر <button>: You clicked on a BODY element You clicked on a DIV element You clicked on a BUTTON element لكن لماذا نربك أنفسنا برفع الحدث أو التقاطه؟ يعود اﻷمر إلى الزمن الذي كانت فيه المتصفحات أقل توافقية بكثير مع بعضها، فاستخدم المتصفح نيتسكيب التقاط اﻷحداث واستخدم إنترنت إكسبلورر رفع اﻷحداث. وعندما حاولت منظمة W3C وضع معيار لهذا السلوك لكنها انتهت بإضافتها معًا، وهذا ما تتبناه المتصفحات اﻷحدث. وتستخدم معظم المتصفحات حاليًا رفع الأحداث افتراضيًا، وهذا أمر منطقي في معظم اﻷوقات. تفويض اﻷحداث لقد رأينا في الفقرات السابقة المشكلة التي ولدها رفع الحدث وكيفية إصلاحه. ومع أن رفع اﻷحداث مزعج أحيانًا لكنها قد تكون شديدة الفائدة. فهي تمكّن من استخدام تفويض اﻷحداث event delegation تحديدًا. فلو أردنا تنفيذ شيفرة محددة عندما يتفاعل المستخدم مع أي عنصر ابن ضمن عدد كبير من اﻷبناء، نهيئ مترصد أحداث للعنصر اﻷب ثم نرفع اﻷحداث التي تقع إلى العنصر اﻷب بدلًا من ضبط مترصد حدث لكل عنصر ابن على حدى. لنعد إلى مثالنا الأول التي نغيّر فيها لون خلفية الصفحة عند النقر على الزر، ولنفترض أن الصفحة مقسمة إلى 16 قسمًا ونريد إعطاء كل قسم لونًا عشوائيًا: إليك شيفرة HTML: <div id="container"> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> <div class="tile"></div> </div> إليك شيفرة CSS بسيطة لتحديد موضع وأبعاد اﻷقسام: .tile { height: 100px; width: 25%; float: left; } يمكننا اﻵن باستخدام جافاسكريبت إضافة معالج أحداث لكل قسم، لكن الخيار اﻷبسط والأكثر فعالية هو إعداد معالج حدث نقر للعنصر اﻷب ومن ثم الاعتماد على رفع الحدث للتأكد من تنفيذ معالج الحدث عندما ينقر المستخدم على قسم ما: function random(number) { return Math.floor(Math.random() * number); } function bgChange() { const rndCol = `rgb(${random(255)}, ${random(255)}, ${random(255)})`; return rndCol; } const container = document.querySelector("#container"); container.addEventListener("click", (event) => { event.target.style.backgroundColor = bgChange(); }); سيكون الخرج كالتالي: See the Pen js-events-8 by Hsoub Academy (@HsoubAcademy) on CodePen. ملاحظة: نستخدم في هذا المثال الخاصية event.target للحصول على العنصر الذي كان هدفًا للحدث. لكن إذا أردنا الوصول إلى العنصر الذي يعالج الحدث (وهو في حالتنا العنصر اﻷدنى مستوى) يمكن استخدام الخاصية event.currentTarget. ملاحظة: يمكنك إيجاد الشيفرة الكاملة لهذا المثال على جت-هب (جرّب الشيفرة مباشرة أيضًا). اﻷحداث ليست فقط لصفحات الويب لا تخص اﻷحداث صفحات الويب فقط، فلكل لغات البرمجة نموذجًا من نوع ما للتعامل مع اﻷحداث ويختلف تعامل كل نموذج مع هذه النماذج بشكل مختلف عن أسلوب جافاسكريبت. وفي واقع اﻷمر يختلف نموذج جافا سكربت عند التعامل مع أحداث صفحات الويب مع نموذجها عند التعامل مع اﻷحداث في بيئة أخرى. ونجد مثلًا بيئة Node.js وهي بيئة تنفيذية شعبية لجافاسكريبت تساعد المطوّرين على بناء تطبيقات للشبكات وتطبيقات الخادم، أن نموذجها الخاص بالأحداث يعتمد على مترصدي اﻷحداث listeners لترقب وقوع الحدث ومصدري اﻷحداث emitters لبث اﻷحداث دوريًا. قد لا يبدو اﻷمر مختلفًا جدًا، لكن الشيفرة مختلفة تمامًا. إذ تُستخدم دوال مثل ()on لتسجيل مترصد حدث، و ()once لتسجيل مترصد أحداث مرة واحدة ثم إلغاء تسجيله بعد تنفيذه مباشرة. يمكنك الاطلاع على توثيق أحداث الاتصال وفق بروتوكول HTTP لأمثلة أوضح. بإمكانك استخدام جافاسكريبت لبناء إضافات قابلة للعمل في بيئات مختلفة باستخدام تقنية تُدعى موّسعات الويب WebExtension. إن نموذج اﻷحداث مشابه لنموذج أحداث الويب مع اختلاف بسيط، هو أن خاصيات مترصد اﻷحداث تُكتب باستخدام أسلوب سنام الجمل (مثل onMessage بدلًا من onmessage) والحاجة إلى دمجه مع الدالة addListener. لا حاجة حاليًا لاستيعاب أي شيء عن البيئات اﻷخرى، لكننا أردنا توضيح فكرة أن اﻷحداث قد تختلف من بيئة برمجة إلى أخرى. الخلاصة لقد تعلمّت اﻻن ما يجب عليك تعلّمه عن أحداث الويب في هذه المرحلة المبكرة من مسيرتك، وكما ذكرنا سابقًا، لا تُعد اﻷحداث جزءًا بنيويًا من جافاسكريبت بل تُعرّف ضمن الواجهة البرمجية للمتصفح. كما ينبغي أن تدرك أن سياق استخدام جافا سكريبت يتطلب نموذجًا مختلفًا لمعالجة اﻷحداث ابتداءًا بالواجهات البرمجية للويب إلى تقنيات أخرى مثل موسّعات ويب الخاصة بالمتصفحات وجافاسكريبت المخصصة للعمل على الخوادم مثل Node.js. لا نتوقع منك أن تستوعب كل هذه النواحي حاليًا، لكنها تساعدك بالتأكيد على فهم أساسيات اﻷحداث مع تقدّمك في مسيرة تطوير الويب. و إن استعصى عليك شيء لا تتردد في طرح أية أسئلة ضمن نقاش الصفحة أو في قسم الأسئلة والأجوبة في أكاديمية حسوب. ترجمة -وبتصرف- للمقال Introduction to events اقرأ أيضًا المقال السابق: الدوال التي تعيد قيمًا في جافا سكريبت فهم الأحداث في جافاسكربت معالجة الأحداث في جافا سكريبت الأحداث المتعلقة بدورة حياة صفحة HTML وكيفية التحكم بها عبر جافاسكربت
  9. نلقي نظرة في هذا المقال على الصياغة اﻷساسية لكائن جافا سكريبت كما نستذكر بعض الميزات التي ناقشناها سابقًا لنشدد على حقيقة أن العديد من الميزات التي اطلعنا عليها وتعاملنا معها في لغة جافا سكريبت هي في الواقع كائنات، ابتداءً من المميزات البنيوية للغة مثل المصفوفات إلى الواجهات البرمجية للمتصفحات APIs المبنية على أساس جافا سكريبت. وبإمكانك أيضًا بناء كائنات خاصة بك لتغليف مجموعة من الدوال والمتغيرات ضمن حزمة أكثر فعالية تعمل كحاوية بيانات. ولا بد أن تتفهم طبيعة جافا سكريبت القائمة على الكائنات إن أردت التعمق أكثر في تعلم هذه اللغة، لهذا نزوّدك ضمن هذا المقال والمقالات التالية بمعلومات مهمة عن الكائنات البرمجية وصياغتها بشيء من التفصيل ثم ننتقل إلى شرح طريقة بناء كائنات خاصة بك. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات HTML أساسيات عمل CSS أساسيات جافا سكريبت أساسيات الكائنات الكائن object هو مجموعة مترابطة من البيانات أو الوظائف، فهو يتألف عادة من من عدة متغيرات ودوال (تُدعى المتغيرات ضمن الكائن خاصيات properties والدوال methods). وحتى نستوعب مفهوم الكائنات سنعمل على المثال التالي. أنشئ أولًا نسخة من الملف oojs.html على حاسوبك. ويتضمن هذا الملف العنصر <script> الذي سنكتب ضمنه الشيفرة. سنستخدم هذه الصفحة كأساس لاستكشاف الصياغة اﻷساسية للكائن في جافا سكريبت. وعليك أن تفتح طرفية جافا سكريبت الخاصة بأدوات مطوري ويب وتجعلها جاهزة لكتابة التعليمات. وكما هو الحال في الكثير من اﻷشياء في جافا سكريبت، يبدأ إنشاء الكائن بتعريف وتهيئة بعض المتغيرات. لهذا جرّب إدخال السطر التالي تحت السطر الموجود أصلًا في ملف التمرين ثم احفظ التغييرات وأعد تحميل الصفحة: const person = {}; افتح اﻵن طرفية جافا سكريبت ثم اكتب person ضمنها ثم اضغط المفتاح Enter. من المفترض أن تحصل على نتيجة مشابهة للتالي: [object Object] Object { } { } تهانينا! لقد أنشأت للتو كائنًا في جافا سكريبت. لقد أنجز العمل بالفعل، لكن الكائن فارغ كما تلاحظ ولا يمكن أن يفيدنا كثيرًا. لهذا سنعدّل الكائن ضمن شيفرة جافا سكريبت في ملف التمرين كالتالي: const person = { name: ["Bob", "Smith"], age: 32, bio: function () { console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`); }, introduceSelf: function () { console.log(`Hi! I'm ${this.name[0]}.`); }, }; جرّب إدخال بعض التعليمات كما يلي في طرفية جافا سكريبت وذلك بعد حفظ التغييرات وإعادة تحميل الصفحة: person.name; person.name[0]; person.age; person.bio(); // "Bob Smith is 32 years old." person.introduceSelf(); // "Hi! I'm Bob." يضم الكائن بعض البيانات اﻵن كما يحتوي على بعض الوظائف التي يمكن الوصول إليها من خلال صياغة بسيطة واضحة. ما الذي يحدث فعلًا في مثالنا؟ لقد أنشأنا كائنًا مكوّنًا من عدة أعضاء members لكل منها اسم ( مثال name و age) وقيمًا مثل ['Bob, 'Smiths] و 32. يفصل بين كل زوج (اسم/قيمة) فاصلة ,، وبين كل اسم وقيمة نجد نقطتين متعامدتين :. وتتبع صياغة الكائن الشكل التالي دائمًا: const objectName = { member1Name: member1Value, member2Name: member2Value, member3Name: member3Value, }; قد تكون قيمة أعضاء الكائن أي شيء، وفي مثالنا السابق ضم الكائن person أعدادًا ومصفوفة ودالتين. إن أول عضوين كما رأينا يضمان بيانات عن الكائن ويُشار إليهما كخاصيات properties، كما يُشار إلى الدالتين اللتان تسمحان للكائن بتنفيذ بعض العمليات على البيانات بالتوابع أو الدوال التابعة للكائن methods. وعندما يكون عضو الكائن دالة، باﻹمكان استخدام صياغة أبسط من bio:function، ويكفي أن نكتب ()bio كالتالي: const person = { name: ["Bob", "Smith"], age: 32, bio() { console.log(`${this.name[0]} ${this.name[1]} is ${this.age} years old.`); }, introduceSelf() { console.log(`Hi! I'm ${this.name[0]}.`); }, }; وسنستخدم من اﻵن وصاعدًا الصيغة المختصرة في تعريف الدوال. ندعو الكائنات التي تُبنى كما شرحنا سابقًا بالكائنات الحرفية literal object لأننا كتبنا حرفيًا كل أعضائها عند إنشائها. وهي كائنات مختلفة عن تلك التي تُنسخ عن اﻷصناف، والتي سنلقي عليها نظرة لاحقًا. من الشائع أيضًا إنشاء الكائنات كنسخ عن الكائنات الحرفية، وخاصة عندما نريد نقل سلسلة من البيانات المهيكلة المترابطة مثل إرسال طلب إلى خادم لوضع البيانات ضمن قاعدة بيانات. فإرسال كائن واحد أكثر فعالية من إرسال البيانات بشكل مستقل ومن اﻷسهل التعامل معها موازنة بالتعامل مع مصفوفة عندما تريد تحديد كل عنصر باسمه بشكل مستقل. طريقة النقطة Dot notation للوصول إلى أعضاء الكائن لقد وصلنا في مثالنا السابق إلى خاصيات وتوابع الكائن باستخدام العامل . ويسمى هذا الأسلوبالتدوين النقطي أو الاستدعاء النقطي dot notation. حيث يسلك اسم الكائن person سلوك فضاء الأسماء namespace. ولا بد من كتاب اسم الكائن أولًا للوصول إلى أي عضو من أعضاءه ومن ثم تكتب النقطة ومن ثم العضو الذي تريد الوصول إليه، وقد يكون العضو عنصرًا أو مصفوفة أو استدعاء أحد توابع الكائن. person.age; person.bio(); الكائنات والخاصيات على شكل كائنات يمكن للخاصية بحد ذاتها أن تكون كائنًا، جرّب مثلًا أن تغيّر العضو name من الشكل التالي: const person = { name: ["Bob", "Smith"], }; ليصبح بالشكل: const person = { name: { first: "Bob", last: "Smith", }, // … }; وللوصول إلى هذه العناصر لا بد من ربطها بالكائن الرئيسي بكتابة نقطة أخرى. جرّب كتابة الشيفرة التالية في طرفية جافا سكريبت: person.name.first; person.name.last; فإن فعلت ذلك، عليك العودة إلى الشيفرة وتغيير كل شيفرة من الشكل: name[0]; name[1]; إلى name.first; name.last; وإلا لن تعمل التوابع. استخدام طريقة اﻷقواس في الوصول إلى أعضاء الكائن باﻹمكان الوصول إلى أعضاء الكائن باستخدام طريقة اﻷقواس المربعة Bracket notation فبدلًا من استخدام النقطة كما في المثال التالي: person.age; person.name.first; يمكننا استخدام الأقواس كما في الكود التالي: person["age"]; person["name"]["first"]; يبدو اﻷمر مشابهًا للوصول إلى عناصر مصفوفة، هو من ناحية المبدأ اﻷمر ذاته، فبدلًا من الوصول باستخدام دليل المصفوفة index، تستخدم الاسم المرتبط بقيمة العنصر. لهذا السبب تًدعى الكائنات أحيانًا مصفوفات ترابطية associative arrays، فهي ترتبط بالقيم النصية بنفس الطريقة التي ترتبط فيها المصفوفة بدليلها. يُفضّل استخدام طريقة النقطة على طريقة اﻷقواس عمومًا لأنها أكثر وضوحًا وأسهل قراءة. لكن ستجد بعض الحالات التي تستخدم فيها اﻷقواس المربعة مثل إسناد قيمة خاصية على شكل كائن إلى متغيّر، فلن تستطيع في هذه الحالة استخدام النقطة للوصول إلى القيمة، لكن باﻹمكان استخدام الأقواس المربعة. في المثال التالي، يمكن للدالة أن تستخدم الطريقة person[propertyName] للوصول إلى القيمة المخزنة في الخاصية proertyName: const person = { name: ["Bob", "Smith"], age: 32, }; function logProperty(propertyName) { console.log(person[propertyName]); } logProperty("name"); // ["Bob", "Smith"] logProperty("age"); // 32 ضبط قيم أعضاء الكائن كل ما تعلمناه حتى اللحظة هو الحصول على قيمة أعضاء الكائن object members، لكن باﻹمكان أيضًا ضبط قيمة هذه الأعضاء بالتصريح عن العضو الذي تريد ضبطه (باستخدام طريقة النقطة أو اﻷقواس المربعة) كالتالي: person.age = 45; person["name"]["last"] = "Cratchit"; جرّب إدخال التعليمتين التاليتين ضمن طرفية جافا سكريبت، ولاحظ كيف تغيرت القيمة: person.age; person["name"]["last"]; لا يقتصر ضبط قيم أعضاء الكائن على تغيير القيمة الموجودة للخاصيات أو التوابع، بل يمكنك إنشاء عناصر جديدة كليًا. جرّب اﻵن ما يلي في الطرفية: person["eyes"] = "hazel"; person.farewell = function () { console.log("Bye everybody!"); }; اختبر اﻵن العناصر الجديدة: person["eyes"]; person.farewell(); // "Bye everybody!" من إيجابيات استخدام طريقة اﻷقواس المربعة أنه باﻹمكان استخدامها في ضبط قيمة اﻷعضاء ديناميكيًا إضافة إلى تغيير أسماء الأعضاء أيضًا. لنفترض أننا نريد منح المستخدم القدرة على تخزين أنواع مخصصة من القيم في بيانات اﻷشخاص وذلك بكتابة اسم العضو وقيمته ضمن مربعي إدخال نصي منفصلين. باﻹمكان إنجاز اﻷمر كالتالي: const myDataName = nameInput.value; const myDataValue = nameValue.value; نضيف بعدها هذا العضو الجديد إلى الكائن person كالتالي: person[myDataName] = myDataValue; ولاختبار اﻷمر، جرّب إضافة الأسطر التالية إلى شيفرتك، أسفل قوس إغلاق الكائنن person: const myDataName = "height"; const myDataValue = "1.75m"; person[myDataName] = myDataValue; جرّب حفظ التغييرات وإعادة تحميل الصفحة ثم إدخال التالي ضمن مربع اﻹدخال النصي: person.height; إن إضافة خاصية إلى الكائن باستخدام اﻷسلوب السابق لن يكون ممكنًا باستخدام طريقة النقطة والتي تقبل فقط الاسم الحرفي للعضو ولا تقبل قيمة متغير يشير إلى الاسم. لماذا استخدمت الكلمة المحجوزة this لربما قد لاحظت شيئًا غريبًا في أسلوبنا في الشيفرة السابقة، ألق نظرة اﻵن على هذا المثال: introduceSelf() { console.log(`Hi! I'm ${this.name[0]}.`); } لربما تتساءل ماذا فعلت الكلمة this؟ تشير هذه الكلمة المحجوزة إلى الكائن الحالي الذي كُتبت الشيفرة ضمنه، فهي في مثالنا تكافئ الكائن person. لماذا إذًا لا نستخدم person وحسب؟ في الواقع، لا فائدة من استخدام الكلمة this كثيرًا في حال أنشأت كائن حرفي وحيد، لكن إن أنشأت أكثر من كائن حرفي، يساعدك ذلك في استخدام نفس تعريف التابع لكل كائن أنشأته. لنوضح ذلك من خلال المثال التالي: const person1 = { name: "Chris", introduceSelf() { console.log(`Hi! I'm ${this.name}.`); }, }; const person2 = { name: "Deepti", introduceSelf() { console.log(`Hi! I'm ${this.name}.`); }, }; في هذه الحالة، ينتج عن تنفيذ التعليمة ()person1.introduceSelf الخرج التالي "Hi! I'm Chris."، بينما ينتج عن تطبيق نفس التابع على كائن آخر ()person2.introduceSelf من نفس النوع خرج آخر مناسب للكائن "Hi! I'm Deepti". وعلى الرغم من أن الشيفرة نفسها في الحالتين، لكن أهميتها لن تظهر عند كتابة كائنات حرفية يدويًا، بل عندما تبدأ باستخدام الدوال البانية constructors ﻹنشاء أكثر من كائن من تعريف واحد له وهذا ما نناقشه تاليًا. مقدمة إلى الدوال البانية Constructors لا بأس باستخدام الكائنات الحرفية عندما تريد استخدام كائن واحد، لكن إن كان عليك إنشاء أكثر من عنصر سيغدو استخدام العناصر الحرفية غير ملائم. إذ علينا كتابة نفس الشيفرة لكل كائن على حدى، وإن كان علينا تغيير بعض الخاصيات كإضافة الخاصية height، لا بد من تذكرّ تغيير كل الكائنات التي أنشأتها. من الأفضل استخدام شكل أو نموذج للكائن يضم كافة التوابع والخاصيات اللازمة، ومن ثم إنشاء العدد الذي يلزمنا من هذا الكائن وفق هذا الشكل بتعديل قيم الخواص المختلفة. إليك نسخة أولى من الشكل الذي نتحدث عنه وهو على شكل دالة: function createPerson(name) { const obj = {}; obj.name = name; obj.introduceSelf = function () { console.log(`Hi! I'm ${this.name}.`); }; return obj; } تُنشئ الدالة وتعيد كائنًا جديدًا في كل مرة نستدعيها، ويضم هذا الكائن عضوين هما: الخاصية: name. التابع: ()introduceSelf. تأخذ الدالة ()createPersonمعاملًا وحيدًا هو name لضبط قيمة الخاصية name، لكن تبقى قيمة التابع ()introduceSelf نفسها لجميع الكائنات التي تنشأها الدالة. ويُعد هذا اﻷسلوب من أكثر الطرق شيوعًا في إنشاء الكائنات. بإمكانك اﻵن إنشاء العدد الذي تريده من الكائنات، بإعادة استخدام الدالة: const salva = createPerson("Salva"); salva.name; salva.introduceSelf(); // "Hi! I'm Salva." const frankie = createPerson("Frankie"); frankie.name; frankie.introduceSelf(); // "Hi! I'm Frankie." ستعمل الشيفرة السابقة جيدًا لكنها طويلة بعض الشيء. إذ أنشأنا كائنًا فارغًا ثم هيأناه وأعدناه، لكن الطريقة اﻷفضل تقضي باستخدام دالة بانية، وهي دالة نستدعيها باستخدام الكلمة المحجوزة new. وعندما نستدعي دالة بانية فإنها: تنشئ كائنًا جديدًا. تربط this بالكائن الجديد، وستتمكن عندها من اﻹشارة إلى الكائن الجديد من خلالها ضمن شيفرة الدالة البانية. تعيد الكائن الجديد. تبدأ أسماء الدوال البانية عادة بحرف كبير وتسمى باسم نوع الكائن الذي تنشؤه، لهذا سنعيد كتابة مثالنا ليصبح بالشكل التالي: function Person(name) { this.name = name; this.introduceSelf = function () { console.log(`Hi! I'm ${this.name}.`); }; } ولكي نستدعي الدالة ()Person كدالة بانية نستخدم الكلمة new: const salva = new Person("Salva"); salva.name; salva.introduceSelf(); // "Hi! I'm Salva." const frankie = new Person("Frankie"); frankie.name; frankie.introduceSelf(); // "Hi! I'm Frankie." لقد استخدمت الكائنات كثيرًا فيما مضى لربما تساءلت أثناء العمل على الأمثلة السابقة بأن استخدام النقطة كان مألوفًا، والسبب أنك استخدمتها طوال فترة تعلم جافا سكريبت عبر سلسلة مقالتنا. فكل مرة عملنا فيها مع مثال يستخدم واجهة المتصفح البرمجية أو كائنات جافا سكريبت، استخدمنا فيها الكائنات لأنها ميزات بنيت باستخدام الطريقة نفسها التي بنينا فيها الكائنات المخصصة في مثالنا اﻷخير، لكنها بالطبع أكثر تعقيدًا. فعندما تستخدم التابع النص كما في المثال التالي: myString.split(","); فأنت تستخدم التوابع التي يوفرّها الكائن String. وفي كل مرة تنشئ فيها سلسلة نصية في شيفرتك، سيتولد هذا النص تلقائيًا كنسخة عن الكائن String، ويشترك معه بالعدبد من الخاصيات والتوابع. وعندما تحاول الوصول إلى كائن المستند باستخدام شيفرة كالتالي: const myDiv = document.createElement("div"); const myVideo = document.querySelector("video"); فأنت تستخدم في الواقع التوابع التي يقدّمها الكائن أو الواجهة البرمجية Document، وعند كل تحميل لصفحة الويب تُنشئ نسخة جديدة عن هذا الكائن تُدعى document تمثّل هيكلية الصفحة بأكملها ومحتواها وغير ذلك من الميزات مثل عناوين URL. أي باختصار، سيمتلك الكائن الذي أنشأته عدة توابع وخاصيات يشترك فيها مع الكائن الأساسي Document. وهذا الأمر مشابه للكثير من الكائنات والواجهات البرمجية المضمنة في لغة جافا سكريبت مثل المصفوفات Array والمكتبة Math. لاحظ أن الكائنات والواجهات البرمجية الأصلية أو المدمجة في جافا سكريبت لا تنشأ من تلقاء نفسها بل عليك إنشاؤها بنفسك في كل مرة تحتاجها، مثل الواجهة البرمجية للتنبيهات Notifications API التي تسمح للمتصفحات الحديثة بإطلاق تنبيهات، حيث تتطلب هذه الواجهة منك أن تنشئ نسخة جديدة باستخدام الدالة البانية لكل تنبيه تريد إطلاقه. جرّب إدخال السطر التالي إلى طرفية جافا سكريبت: const myNotification = new Notification("Hello!"); الخلاصة لقد أنهينا هذه المقال التي يمهّد لاستخدام الكائنات في جافا سكريبت، ولا بد أنك امتلكت بقراءته فكرة عن عمل الكائنات، بما في ذلك إنشاء كائنات بسيطة. ومن المهم إدراك أهمية الكائنات كبنى لتخزين البيانات والوظائف المترابطة. فلو حاولت تتبع جميع الخاصيات والتوابع في الكائن person الذي بنيناه في مثالنا شكل متغيرات ودوال منفصلة ستجد أنها طريقة غير مجدية ومحبطة، وستزيد مخاطر التضارب بين المتغيرات والدوال التي تمتلك نفس اﻷسماء. إذ تساعد الكائنات في حفظ المعلومات ضمن حاويات خاصة بها لتقليل مثل هذه المخاطر. ترجمة -وبتصرف- للمقال JavaScript object basics اقرأ أيضًا المقال السابق: مدخل إلى اﻷحداث في جافاسكريبت مدخل إلى جافاسكريبت كائنية التوجه (Object-Oriented JavaScript) لغة البرمجة بالكائنات Object-Oriented Programming برمجة الكائنات Objects في جافاسكريبت مختصر البرمجة كائنية التوجه OOP وتطبيقها في بايثون
  10. بعد أن اطلعنا في مقال سابق على أبسط أساسيات النصوص في جافا سكريبت، سننتقل في هذا المقال إلى مناقشة العمليات المهمة التي يمكن تنفيذها على النصوص والتوابع الأصلية التي توفرها جافا سكريبت لتحقيق ذلك مثل إيجاد طول سلسلة نصية وضم أو فصل سلسلة نصية وتبديل محرف بآخر وغيرها. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS السلاسل النصية والكائنات إن معظم اﻷشياء في جافا سكربت هي كائنات objects، فعندما ننشئ سلسلة نصية كما في المثال التالي: const string = "This is my string"; يصبح المتغير قالبًا لكائن نصي وكنتيجة يمكن تطبيق عدد كبير من الخاصيات والتوابع عليه. وللاطلاع على هذه الخاصيات والتوابع يمكنك مراجعة توثيق موسوعة حسوب للكائن String. لا حاجة طبعًا أن تربك نفسك بهذه التوابع في بداية رحلتك في تعلم البرمجة، لكنك ستستخدم بعضها مرارًا لهذا من الجيد أن تلقي نظرة عليها. إيجاد طول سلسلة نصية بإمكانك إنجاز اﻷمر باستخدام الخاصية length، إليك مثالًا: const browserType = "mozilla"; browserType.length; يُفترض أن يعيد المتصفح القيمة 7 لأن عدد محارف السلسلة "mozilla" هو 7. ولهذا اﻷمر فائدته، وكمثال على ذلك، تصوّر أنك ترد ترتيب سلسلة من اﻷسماء وفقًا لطول هذه اﻷسماء، أو أنك تريد إعلام المستخدم أن النص الذي أدخله في الحقل أطول مما هو مسموح. استخلاص محرف محدد من سلسلة نصية بإمكانك عمومًا إعادة محرف محدد من سلسلة نصية باستدعاء المتغير الذي يضمها وبعده قوسان مربعان []يحويان رقم المحرف الذي تريده، فلو أردت مثلًا إعادة المحرف الأول من سلسلة نصية يمكنك كتابة التالي: browserType[0]; وتذكر أن الفهرسة تبدأ من الصفر وليس من 1. ولكي تعيد آخر محرف من أي سلسلة نصية يمكن استخدام الخاصية length وفق التقنية التالية: browserType[browserType.length - 1]; وبما أن طول السلسة "mozilla" هو 7، سيكون رقم المحرف اﻷخير منها هو 6 لأن العد يبدأ من 0. لهذا استخدمنا في الكود أعلاه الأمر length-1. التحقق من وجود سلسلة نصية ضمن أخرى قد ترغب أحيانًا في إيجاد سلسلة نصية ضمن أخرى أطول، لهذا يمكن أن تستخدم التابع ()includes الذي يأخذ معاملًا واحدًا وهو السلسلة النصية التي تبحث عنها. ويعيد التابع القيمة true إن وجد هذه السلسلة و false إن لم يجدها. const browserType = "mozilla"; if (browserType.includes("zilla")) { console.log("Found zilla!"); } else { console.log("No zilla here!"); } وقد ترغب أيضًا بمعرفة إذا ما بدأت سلسلة نصية أو انتهت بسلسلة محددة، لهذا ستحتاج إلى التابعين ()startsWith و ()endsWith ويأخذ كلا التابعين معاملًا واحدًا هو السلسلة التي تبحث عنها في بداية او نهاية سلسلة أطول: const browserType = "mozilla"; if (browserType.startsWith("zilla")) { console.log("Found zilla!"); } else { console.log("No zilla here!"); } const browserType = "mozilla"; if (browserType.endsWith("zilla")) { console.log("Found zilla!"); } else { console.log("No zilla here!"); } إيجاد موقع سلسلة فرعية ضمن سلسلة أطول نستخدم لهذا الغرض التابع ()indexOf الذي يأخذ معاملين اﻷول هو السلسلة التي تبحث عنها والثاني اختياري ويشير إلى موقع بداية البحث. يعيد التابع موقع أول ظهور للسلسلة النصية، لكن إن لم يجدها يعيد القيمة 1-. const tagline = "MDN - Resources for developers, by developers"; console.log(tagline.indexOf("developers")); // 20 في مثالنا السابق يبدأ البحث من الموقع 0، ولو عددت المحارف بما فيها المسافات الفارغة من البداية فسيكون أول ظهور للسلسلة النصية "developers" في الموقع 20. console.log(tagline.indexOf("x")); // -1 يعيد البحث السابق القيمة 1- لأن المحرف x غير موجود ضمن السلسلة. طالما أنك تعلمت كيفية إيجاد الظهور اﻷول لسلسلة فرعية ضمن سلسلة أطول، فكيف ستجد ظهورات أخرى؟ يمكنك ذلك بتمرير قيمة أعلى من قيمة الظهور السابق للوسيط الثاني للتابع. const firstOccurrence = tagline.indexOf("developers"); const secondOccurrence = tagline.indexOf("developers", firstOccurrence + 1); console.log(firstOccurrence); // 20 console.log(secondOccurrence); // 35 نبلغ التابع في هذه الحالة أن يبدأ البحث عن النص بدءًا من الموقع 21 (firstOccurrence + 1) وأعاد القيمة 35 وهو موقع الظهور الثاني. استخلاص سلسلة نصية فرعية من سلسلة أطول وذلك باستخدام التابع ()slice الذي يُمرر له معاملان هما: موقع بداية الاستخلاص. موقع نهاية الاستخلاص ولا يؤخذ المحرف في آخر موقع للاستخلاص. إليك مثالًا: const browserType = "mozilla"; console.log(browserType.slice(1, 4)); // "ozi" إن المحرف الموجود في الموقع 1 هو "o" وفي الموقع 4 هو "l" لهذا يكون النتيجة هي السلسلة "ozi" التي تبدأ بالمحرف "o" وتنتهي قبل المحرف "l". أما إذا أردت استخلاص جميع المحارف التي تأتي بعد محرف معين، فلا تحدد قيمة للمعامل الثاني، بل مرر فقط موقع المحرف الذي تريد أن تبدأ الاستخلاص عنده. جرّب اﻵن ما يلي: browserType.slice(2); // "zilla" تُعيد الشيفرة السابقة النص "zilla" لأن المحرف في الموقع 2 هو "z" ولم نحدد قيمة للمعامل الثاني فستكون النتيجة جميع المحارف من نقطة بداية الاستخلاص حتى نهاية السلسلة. **ملاحظة للتابع ()slice خيارات أخرى يمكنك الاطلاع عليها في توثيق التابع ضمن موسوعة حسوب. تغيير حالة الحروف يغيّر التابع ()toLowerCase جميع حروف السلسلة إلى حروف صغيرة والتابع ()toUpperCase إلى حروف كبيرة، وهذا مفيد مثلًا إن أردت تسوية حالة جميع الحروف التي يدخلها مستخدم قبل تخزينها في قاعدة بيانات. جرّب أن تدخل اﻷسطر التالية لترى ما يحدث: const radData = "My NaMe Is MuD"; console.log(radData.toLowerCase()); console.log(radData.toUpperCase()); تحديث أجزاء من النص يمكن استبدال سلسلة فرعية من نص بسلسلة أخرى باستخدام التابع ()replace. ولتجريب الأمر نحاول أن نستبدل في المثال التالي السلسلة التي مررناها كمعامل أول للتابع بالسلسلة التي تمثل المعامل الثاني: const browserType = "mozilla"; const updated = browserType.replace("moz", "van"); console.log(updated); // "vanilla" console.log(browserType); // "mozilla" وتجدر الملاحظة أن التابع ()replace مثل العديد من توابع الكائن النصي لا يغير الكائن الذي استدعاه بل يعيد سلسلة نصية جديدة، لكن إن أردت تحديث المتغير اﻷصلي browserType، فعليك تنفيذ الشيفرة التالية مثلًا: let browserType = "mozilla"; browserType = browserType.replace("moz", "van"); console.log(browserType); // "vanilla" كما عليك أن تصرح عن browserType في هذه الحالة باستخدام letوليس const ﻷن الثابت لا يمكن تحديث قيمته. وانتبه إلى أن التابع ()replace يستبدل السلسلة عند أول ظهور فقط، لكن إن أردت استبدال كل حالات الظهور، عليك استخدام التابع ()replaceAll let quote = "To be or not to be"; quote = quote.replaceAll("be", "code"); console.log(quote); // "To code or not to code" تطبيقات عملية سندفعك في هذا القسم إلى بذل جهدك في العمل مع السلاسل النصية. ففي كل تمرين مصفوفة من السلاسل النصية وحلقة تعالج قيم كل سلسلة ومن ثم تعرضها على شكل قائمة نقطية. لا حاجة لفهم المصفوفات والحلقات حاليًا لأننا سنفصلها لاحقًا، وكل ما عليك فعله هو كتابة شيفرة برمجية تعرض السلاسل النصية بالشكل الذي نريده. لكل تطبيق زر إعادة ضبط "Reset" يمكنك استخدامه إن ارتكبت خطأً ليعيد كل شيء إلى ما كان عليه، وزر "Show solution" للاطلاع على الحل إن لم تتمكن من إيجاده. فلترة رسائل المعايدة عليك في هذا التمرين وضع رسائل المعايدة بعيد الميلاد على شكل قائمة، لها ضع الاختبار المناسب في البنية الشرطية ()if واختبر كل سلسلة نصية واطبعها ضمن القائمة إن كانت رسالة عيد ميلاد. See the Pen string -methods1 by Hsoub Academy (@HsoubAcademy) on CodePen. تصحيح حالة بداية الكلمات نقدم في هذا التمرين أسماء مدن في المملكة المتحدة لكن حالة بداية حروف بدايات الكلمات غير صحيحة في بعضها. لهذا عليك تغيير حالة الحروف في كل كلمة إلى حروف صغيرة ما عدا الحروف اﻷولى. إليك أحد الطرق لتنفيذ اﻷمر: حول حالة جميع الحروف في السلسلة النصية الموجودة في المتغير إلى حروف صغيرة وخزنها في متغير جديد. استخلص الحرف اﻷول من السلسلة وخزّنه في متغير آخر. استبدل الحرف اﻷول من المتغير الذي أنشأته في الخطوة 2 بقيمة المتحول الذي أنشأته في الخطوة 2 بعد تحويل قيمته إلى حروف كبيرة. غيّر قيمة المتغير result كي تساوي نتيجة الخطوة 3. ملاحظة وتلميح: ليس من الضروري أن يكون معامل توابع السلاسل النصية نصًا بل قد يكون عددًا أو متغيرًا أو تابعًا آخر. See the Pen string-methods2 by Hsoub Academy (@HsoubAcademy) on CodePen. إنشاء نص جديد من أجزاء قديمة تتكون المصفوفة في هذا التمرين من عدة سلاسل نصية تتضمن معلومات عن محطات القطار شمال إنجلترا. وتمثل السلاسل النصية بيانات تضم رمز المحطة المكوّن من ثلاثة حروف متبوعًا بمجموعة بيانات تفهمها اﻵلة يليها فاصلة منقوطة يليها اسم المحطة المقروء بالنسبة للبشر مثلMAN675847583748sjt567654;Manchester Piccadilly لهذا نريد استخلاص رمز المحطة واسمها ووضعهما في سلسلة جديدة لها الشكل التالي: MAN: Manchester Piccadilly. ننصحك بتجريب الخطوات التالية: استخلص رمز المحطة المكون من ثلاثة حروف وخزنه في متغير جديد. جد موقع الفاصلة المنقوطة. استخدم موقع الفاصلة المنقوطة في استخلاص اسم المحطة المقروء بالنسبة للبشر وخزّنه في متغير آخر. ضم قيمتي آخر متغيرين مع سلسلة من حروف مناسبة لتحصل على الصيغة النهائية. غيّر قيمة المتغير result كي تساوي النتيجة النهائية. See the Pen Untitled by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة لا يمكن أن نتغاضى عن أهمية التعامل مع النصوص في البرمجة بشكل عام وفي جافا سكريبت بشكل خاص لأن النصوص تتعامل مع الويب المبني على التخاطب مع البشر. وقد قدمنا في هذا المقال أهم توابع التعامل مع النصوص التي تلزمك حاليًا، والتي ستكون ركيزة تساعدك على فهم المواضيع اﻷكثر تعقيدًا في المستقبل. ترجمة -وبتصرف- للمقال Useful string methods اقرأ أيضًا المقال السابق: أساسيات التعامل مع النصوص في جافا سكريبت الدليل الشامل لتعلم جافا سكريبت السلاسل النصية (strings) في جافاسكربت تصميم النّصوص البرمجيّة: تنظيم JavaScript توابع الأنواع الأولية (primitives methods) في جافاسكربت
  11. يُعد مفهوم التصميم المتجاوب للويب responsive web design واختصارًا RWD نهجًا يسمح للصفحة أن تغيّر تخطيطها ومظهرها لتلائم الاتساعات المختلفة لشاشات الأجهزة والدقات المختلفة لها، وضمان الاستخدام الأمثل للمحتوى. لهذا سنساعدك في هذا المقال على فهم بعض التقنيات التي تحتاجها لتتقن التصميم المتجاوب. ننصحك قبل المتابعة في قراءة هذا المقال أن: تطلع على أساسيات HTML. تفهم أساسيات عمل CSS. تُعد عناصر HTML متجاوبة أساسًا وقابلة للانسياب وفقًا لتوجه نافذة العرض. فلو أنشأت صفحة ويب باستخدام شيفرة HTML فقط دون تنسيقات CSS وغيّرت أبعاد المتصفح، سيغيّر المتصفح عندها انسياب العناصر لتلائم نافذة العرض. قد يبدو الأمر للوهلة الأولى بأن التجاوب الأساسي الذي يقدمه المتصفح هو كل ما نحتاجه، إلا أن النصوص ذات الأسطر الطويلة مثلًا والتي تعرض بأكملها ضمن الشاشات الواسعة قد تكون صعبة القراءة. فإن قللنا اتساع السطر باستخدام تنسيقات CSS بإنشاء أعمدة أو إضافة حشوات حول المحتوى، قد يبدو حينها الموقع منكمشًا بالنسبة لمستخدمي الأجهزة أو المتصفحات قليلة الاتساع. لن تعمل أيضًا فكرة صفحة ويب ثابتة الحجم بتثبيت اتساع الصفحة لأنه سيؤدي إلى ظهور أشرطة التمرير ضمن نافذة عرض الأجهزة قليلة الاتساع ومساحة فارغة مبالغ فيها في الأجهزة التي تتمتع بشاشات واسعة. لهذا ظهر مفهوم التصميم المتجاوب للويب كنهج موجّه للتعامل مع مجموعة واسعة من الأجهزة بمختلف الأبعاد، إذ يسمح هذا النهج بالتكيف التلقائي مع الشاشات سواء عّرض محتوى الصفحة ضمن جهاز لوحي أو جوّال أو تلفاز أو ساعة. والتصميم المتجاوب كما أشرنا هو نهج وليس تقانة منفصلة، يصف مجموعة من الممارسات التطبيقية المستخدمة في بناء تخطيطات لمواقع الويب يسمح لها بالتجاوب مع أي جهاز يعرض محتواها. استخدم مصطلح الويب المتجاوب أول مرة من قبل إيثان ماركوتي عام 2010 في وصف الشبكات الانسيابية fluid grid والصور الانسيابية fluid image واستعلامات الوسائط media query التي تُستخدم في بناء محتوى متجاوب. لقد اقتُرح حينها استخدام الخاصية float للتخطيطات واستخدام استعلامات الوسائط لتحري اتساع شاشة الجهاز ووضع نقاط محددة (تتعلق بالأبعاد المفترضة للأجهزة) يتغير عندها التخطيط. ضُبطت أيضًا الصور الانسيابية كي لا يتجاوز اتساعها اتساع الحاوية باستخدام القاعدة ;max-width: 100%. وهكذا تصغر الصورة عندما يضيق العمود الذي يضمها لكن حجمها لايزيد عن الحجم الأصلي إن زاد اتساع العمود. وبهذا الطريقة تتغير أبعاد الصورة لتتسع إلى المحتوى دون طفحان ودون أن يزداد حجمها عن الحجم الأصلي وتتشوه عند ازدياد حجم الحاوية عن حجم الصورة. إن طرق تخطيط صفحات الويب العصرية باستخدام CSS متجاوبة بطبيعتها، ومنذ بدأت فكرة الويب المتجاوب ظهرت ميزات وأدوات لتسهيل تصميم مواقع الويب المتجاوبة. لهذا سنشير في بقية هذا المقال إلى الميزات المختلفة التي قد ترغب في معرفتها لإنشاء مواقع ويب متجاوبة. استعلامات الوسائط تسمح لك هذه الاستعلامات بتنفيذ اختبارات (كأن تكون شاشة المستخدم أكثر اتساعًا من قيمة محددة أو دقة محددة) وانتقاء تنسيق CSS مناسب للصفحة بما يتلائم مع حاجة المستخدم. يختبر الاستعلام التالي مثلًا إن كانت صفحة الويب معروضة كشاشة وسائط screen media (أي لا يمكن طباعتها كمستند) وإن كان اتساع نافذة العرض على الأقل 80rem. إن قواعد التنسيق الموجودة ضمن المحدد container. ستُطبق فقط إن تحقق الشرطين السابقين: @media screen and (min-width: 80rem) { .container { margin: 1em 2em; } } بإمكانك إضافة عدة استعلامات وسائط في صفحة التنسيقات لتعدّل تخطيطك وفقًا لنتائج تلك الاستعلامات، وبما يتلائم مع المقاسات المختلفة للشاشات. تُدعى النقاط التي تُطبق فيها استعلامات الوسائط ويتغير تخطيط الصفحة بنقاط الانتقال breakpoints. من أكثر المقاربات شيوعًا في استخدام استعلامات الوسائط هي استخدام تخطيط عمود وحيد للشاشات الضيقة (مثل شاشات الجوّال) ثم تبني تخطيط من عدة أعمدة عندما تتحقق من خلال الاستعلامات أن اتساع الشاشة أصبح ملائمًا لذلك. يُعرف تصميم الصفحات لتلائم أجهزة الجوال بمصطلح "الجوّال أولًا mobile first". من الممارسات المفضلة عند استخدام نقاط الانتقال تعريف الأبعاد بصفتها وحدات نسبية relative unites بدلًا من الوحدات المطلقة لكل جهاز. سترى أيضًا عدة مقاربات تنسيق ضمن كتلة الاستعلام منها ربط ملفات تنسيق بصفحة الويب من خلال العنصر <link> وفقًا لأبعاد مختلفة للمتصفح أو استخدم متغيرات مخصصة لضبط بعض الخاصيات وتخزين القيم الملائمة لها عند كل نقطة انتقال. تساعد استعلامات الوسائط في التصميم المتجاوب لكنها ليست ضرورية، إذ يمكن استخدام الشبكات المرنة والوحدات النسبية والقيم العظمى والصغرى للخاصيات دون الحاجة إلى استعلامات الوسائط. تقنيات التخطيطات المتجاوبة تُبنى المواقع المتجاوبة باستخدام الشبكات المرنة، بمعنى أنك لن تحتاج استهداف كل الاتساعات المحتملة للأجهزة بتخطيطات منفصلة لضمان دقة العرض. إذ تسمح لك الشبكات المرنة بتغيير ميزة أو إضافة نقطة انتقال لتغيير التصميم في الحالات التي يُعرض فيها المحتوى بشكل سيء. فلكي تضمن مثلًا أن طول الأسطر في نص لن يزيد إلى الحد الذي يعيق القراءة وذلك عندما تكون الشاشة واسعة، بإمكانك استخدام الأعمدة columns؛ وإن انكمش صندوق النص ليعرض مثلًا كلمتين فقط في السطر عندما تضيق الشاشة بإمكانك وضع نقطة انتقال لتغيير التخطيط. تتمتع عدة تخطيطات مثل التخطيط المتعدد الأعمدة وتخطيط الصندوق المرن وتخطيط الشبكة بقدرتها على التجاوب افتراضيًا. إذ تفترض جميعها أنك تحاول بناء شبكة مرنة وتساعدك على تنفيذ الأمر بأبسط الطرق. تخطيط متعدد الأعمدة تستطيع في هذه الحالة تحديد قيمة للخاصية column-count لتشير إلى العدد الأعظمي للأعمدة التي تريد توزيع المحتوى ضمنها. وعندها يحسب المتصفح عندها الاتساع المناسب لكل عمود وبما يناسب اتساع شاشة العرض. .container { column-count: 3; } بينما لو خصصت قيمة للخاصية column-width ستخصص في هذه الحالة أدنى اتساع للعمود، وسينشئ حينها المتصفح أكبر قدر ممكن من الأعمدة بهذا الاتساع بما يلائم اتساع الحاوية، وإن بقيت مساحة فارغة (بقية مساحة أقل من مساحة عمود) سيقسمها بالتساوي بين تلك الأعمدة. وهكذا سيتغير عدد الأعمدة بتغير المساحة المتوفرة. .container { column-width: 10em; } وكذلك الأمر، بإمكانك استخدام الخاصية المختصرة columns لتزويد المتصفح بأعلى عدد من الأعمدة وأدنى اتساع لكل منها. وهذا ما يضمن مثلًا ألا يصبح طول سطر الكتابة أكبر مما يسمح بتجربة قراءة مقبولة -وذلك عندما يزداد اتساع الشاشة- أو قصيرًا جدًا عندما تضيق الشاشة. الصندوق المرن تتقلص العناصر المرنة في تخطيط الصندوق المرن وتتمدد مغيرة مقدار المساحات المتاحة بين العناصر وفقًا للمساحة الكلية للحاوية المرنة. وبتغيير قيم الخاصيتين flex-grow و flex-shrink بإمكانك تحديد سلوك العناصر عندما تزيد المساحة الفارغة المحيطة بها أو تقل. سيأخذ كل عنصر مرن في مثالنا التالي نفس المساحة ضمن الحاوية المرنة باستخدام القاعدة flex: 1 وقد أشرنا إلى هذا الموضوع بتفصيل أوفى في المقال الذي يتحدث عن تخطيط الصندوق المرن. .container { display: flex; } .item { flex: 1; } ملاحظة: في مثال عن استخدام الصندوق المرن (اطلع على الشيفرة المصدرية)، بنينا تخطيطًا متجاوبًا بسيطًا يتضمن نقاط انتقال إلى الوضع متعدد الأعمدة عندما يزداد شاشة العرض وتقييدًا لأبعاد المحتوى الرئيسي باستخدام الخاصية max-width، إليك هذا المثال (اطلع على الشيفرة المصدرية أيضًا). شبكة CSS تسمح لك الواحدة fr في تخطيط الشبكة توزيع المساحة الفارغة المتاحة بين مسارات الشبكة. سنرى في مثالنا التالي كيف ننشئ شبكة حاوية بثلاث مسارات قياس كل منها 1fr وستكون النتيجة ثلاثة أعمدة يشغل كل منها جزءًا من المساحة المتاحة في الحاوية. يمكنك الاطلاع على تفاصيل أوفى عن استخدام الشبكة في مقال تخطيط صفحات ويب باستخدام خاصيات الشبكة في CSS .container { display: grid; grid-template-columns: 1fr 1fr 1fr; } ملاحظة: إن النسخة المتجاوبة المبنية على تخطيط الشبكة أبسط لأنه بالإمكان تعريف الأعمدة ضمن الصنف wrapper. ألق نظرة على المثال على github (كما يمكنك الاطلاع على الشيفرة المصدرية أيضًا) الصور المتجاوبة لكي تضمن أن حجم الصورة لن يزيد عن حجم الحاوية، يمكنك استخدام المقاربة التالية: img, picture, video { max-width: 100%; } تُطبق هذه القاعدة على الوسائط المتعددة كي لا تطفح خارج الحاوية. ولا بد من إدراك أن استخدام صورة كبيرة الحجم ثم تصغيرها لتلائم الشاشات الضيقة سيهدر حزمة البيانات نظرًا لتزيل صورة أكبر من المطلوب. يساعد استخدام العنصر <picture> مع السمتين srcset و sizes للعنصر <img> في تقديم صور تتلائم مع نوافذ عرض مختلفة ودقات مختلفة. إذ يمكنك مثلًا إضافة صورة مربعة الشكل لأجهزة الجوال ونفس الصورة لكن على شكل مستطيل للحواسب المكتبية. ستتمكن باستخدام العنصر <picture> من تقديم قياسات مختلفة مع تلميحات (على شكل بيانات وصفية توضّح حجم الشاشة ودقة الصورة و الطريقة الأمثل لعرضها) يختار بعدها المتصفح الصورة الأكثر ملائمة لكل جهاز ليضمن المستخدم تنزيل الصورة ذات الحجم الأنسب للجهاز الذي يستخدمه. ومع استخدام مع الخاصية max-width لا حاجة بعد ذلك لاستخدام استعلامات الوسائط لتغيير أبعاد الشاشة، وستتمكن من استهداف نوافذ عرض مختلفة بصور تختلف في نسب أبعادها. كما يمكنك توجيه الصورة فنيًا (وضع رؤية فنية) باستخدام أحجام مختلفة للصورة ذاتها، باقتصاص الصورة أو استخدام أخرى بأبعاد مختلفة. يمكنك الاطلاع على مشكلة الرؤية الفنية ضمن مقال "استخدام الصور المتجاوبة في صفحات الويب" على أكاديمية حسوب. خطوط الكتابة المتجاوبة يُقصد بتجاوب خطوط الكتابة هو تغيير حجم الخط من خلال استعلامات الوسائط أو باستخدام وحدات قياس مرتبطة بنافذة العرض. استخدام استعلامات الوسائط للحصول على خطوط كتابة متجاوبة سنرى في مثالنا التالي عنوانًا من المستوى الأول ضُبط حجم الخط فيه على 4rem، ويعني ذلك أن الحجم سيكون أربعة أضعاف الحجم الأساسي للخط المستخدم. وهذا في الواقع حجم كبير لعنوان من المستوى الأول، ولا نحتاجه سوى في شاشة العرض الواسعة، لهذا أنشأنا أولًا عنوانًا بحجم أصغر، وبعدها استخدمنا استعلامًا يلغي الحجم الصغير ويستبدله بالحجم الأكبر إن كان اتساع الشاشة أكبر من 1200px html { font-size: 1em; } h1 { font-size: 2rem; } @media (min-width: 1200px) { h1 { font-size: 4rem; } } عدّلنا مثال الشبكة المتجاوبة السابق كي يتضمن أيضًا نوعًا متجاوبًا باستخدام الطريقة الموضّحة سابقًا. بإمكانك ملاحظة تبدّل حجم الخط في العنوان عندما يتغير التخطيط إلى تخطيط عمودين. في الهواتف المحمولة سيكون العنوان أصغر حجمًا: في الحواسب المكتبية سنرى الحجم الأكبر للعنوان: وكما رأينا في هذه المقاربة، لم نستخدم استعلامات الوسائط لتغيير تخطيط الصفحة، ويمكن استخدام الأسلوب ذاته في تغيير أي عنصر ليكون أسهل استخدامًا أو جاذبيةّ ضمن شاشات مختلفة الأبعاد. استخدام وحدات قياس نوافذ العرض للحصول على خطوط كتابة متجاوبة يمكن استخدام وحدات نافذة العرض vw في التصميم المتجاوب لخطوط الكتابة دون الحاجة إلى ضبط نقاط انتقال في استعلامات الوسائط. فالقيمة 1vw هي واحد في المئة من اتساع نافذة العرض، وبالتالي أيًا كانت شاشة العرض فستكون أبعاد حجم الكتابة مضبوطةً بالنسبة لها. h1 { font-size: 6vw; } تظهر المشكلة عند تكبير وتصغير النصوص فلن يتمكن من ذلك طالما أن حجم خط الكتابة منسوب إلى حجم نافذة العرض. وبالتالي لا يجب استخدام هذه الوحدات بمفردها. من الحلول المتاحة للمشكلة استخدام الدالة ()calc. إذ يمكنك إضافة القيمة المقدرة بواحدة vw إلى قيمة ثابتة مثل وعندها تحسب الدالة السابقة القيمة الجديدة مع بقاء النص قابلًا للتصغير والتكبير. فالقيمة vw تضاف إلى القيمة الناتجة عن التكبير أو التصغير. h1 { font-size: calc(1.5rem + 3vw); } وهكذا لا نحتاج سوى تحديد حجم الخط سوى مرة واحدة بدلًا من ضبط قيم لكل شاشة عرض باستخدام استعلامات الوسائط. سيزداد حجم النص الآن أو يقل وفقًا لاتساع شاشة العرض. الوسم <meta> الخاص بنافذة العرض إن ألقيت نظرة على الشيفرة المصدرية لصفحة متجاوية سترى عادة الوسم <meta> ضمن الترويسة كالتالي: <meta name="viewport" content="width=device-width,initial-scale=1" /> يجبر هذا الوسم متصفحات الهاتف المحمول أن تضبط اتساع نافذة العرض لتكون نفسها اتساع الجهاز، ومن ثم ضبط حجم الصفحة لتكون 100% من الحجم المحدد لها، وهكذا ستُعرض الصفحة بالشكل الأمثل على هذه الأجهزة. لماذا نحتاج إلى هذا الوسم؟ لأن متصفحات الهواتف المحمولة لا تكون صادقة تمامًا فيما يخص اتساع نافذة العرض. عندما ظهرت الهواتف الذكية، لم تُعرض معظم المواقع بالشكل الأفضل ضمنها. لهذا ضبطت الهواتف الذكية اتساع نافذة العرض على 980 بكسل وصيّرت الصفحة وفقًا لهذا القيمة ومن ثم عرضتها كنسخة مصغّرة عن نسخة الحواسب. وهكذا كان على المستخدمين تكبير وتصغير الصفحة للوصول إلى النقطة التي يريدون، وكان المظهر بالطبع سيئًا. وباستخدام السمة width=device-width تلغى القيمة الافتراضية للهاتف المحمول وتستبدل بالقيمة الحقيقية لنافذة العرض. ودون هذه السمة، قد لا يعمل التصميم المتجاوب الذي يعتمد على استعلامات الوسائط ونقاط الانتقال كما ينبغي ضمن أجهزة الهاتف المحمول، فإن كنت تستهدف مثلًا نقطة انتقال عند الاتساع 480 بكسل أو أقل واعتبر الهاتف أن استاع نافذة عرضه هي 980 بكسل فلن يرى المستخدم أبدًا تأثير نقاط الانتقال. الخلاصة يشير مصطلح التصميم المتجاوب إلى تصميم موقع أو تطبيق يتجاوب مع البيئة التي يُعرض فيها. وتُستخدم في ذلك مجموعة من ميزات CSS و HTML وبعض التقنيات الأخرى التي أصبحت أساسية الآن عند بناء مواقع الويب. فلن تجد حاليًا على الأغلب موقعًا يُعرض على هاتف محمول بنفس تخطيط الحواسب المكتبية لكن بشكل مصغّر أو أن تستخدم أشرطة التمرير الجانبية للوصول إلى مكان ما في الصفحة، لأن تصميم الويب المعاصر قد انتقل كليًا إلى نهج التجاوب. وقد أضحى من السهل أيضًا إنجاز تصاميم متجاوبة بالاستفادة من طرق تخطيط الصفحات التي تعلمناها في مقالات سابقة. فلو كنت حديث العهد بتطوير الويب، ستجد الكثير من الأدوات التي لم تكن موجودة في البدايات. لهذا من الأفضل أن تتحقق من تاريخ أي مادة تعليمية تستخدمها. وعلى الرغم من أهمية بعض المواد القديمة، إلا أن الميزات الجديدة في CSS و HTML تزوّدك بتصاميم أنيقة ومفيدة، أيًا كان الجهاز الذي يستعرض موقعك. ترجمة -وبتصرف- للمقال: Responsive design اقرأ أيضًا المقال السابق: التخطيط متعدد الأعمدة باستخدام CSS أساسيات تصميم الويب المتجاوب فلسفة تصميم الويب المتجاوب استخدام الصور المتجاوبة في صفحات الويب عرض محتوى صفحات الويب بتجاوب على الأجهزة المتعددة مدخل إلى التصميم المتجاوب والتصميم المتكيف
  12. بدأنا في المقال السابق شرح طريقة إنشاء دوال خاصة بك في جافا سكريبت وسنتعلم في مقال اليوم أحد المفاهيم الأساسية حول الدوال وهي إعادة قيم من الدالة. إذ تعيد بعض الدول قيمًا هامة عند اكتمال تنفيذها، بينما لا تعيد دوال أخرى أي شيء. لذلك من المهم فهم القيم التي تعيدها الدوال وكيفية استغلالها وكيفية إعداد الدوال المخصصة التي بنيتها كي تعيد قيمًا مفيدة. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافا سكريبت ما هي القيم المُعادة Return values القيمة المعادة هي ما يحمله معناها تمامًا، أي القيم التي تُعيدها الدوال عندما يكتمل تنفيذها. ومن المؤكد أنك واجهت هذا النوع من القيم مرات عدة لكنك لم تفكر بها صراحة. سنعيد طرح مثال من المقال السابق: const myText = "The weather is cold"; const newString = myText.replace("cold", "warm"); console.log(newString); // Should print "The weather is warm" // the replace() string function takes a string, // replaces one substring with another, and returns // a new string with the replacement made تُستدعى الدالة ()replace للتعامل مع النص myText وتُمرر لها قيمتان: النص الذي نريد إيجاده ('cold'). النص الذي نستبدله به ('warm'). عندما ينتهي تنفيذ الدالة تعيد قيمة جديدة هي نص جديد بعد تنفيذ عملية الاستبدال. وتُخزّن النتيجة في الشيفرة السابقة والتي تتضمن هذه القيمة المعادة في المتغير newString. إن عُدت إلى توثيق الدالة ()replace في موسوعة حسوب ستجد معلومات كافية ومفيدة من المعلومات حول القيم المعادة وسنحاول اﻹشارة عليها ما أمكن في هذا المقال. ولا تعيد بعض الدوال أية قيم (يُيشار في توثيق هذه الدوال عادة إلى القيمة المعادة على الشكل غير محدد undefined أو لا توجد قيمة void). فلا تعيد مثلًا الدالة displayMessage التي بنيناها في المقال السابق أية قيمة عند تنفيذها، بل تعرض فقط صندوق الرسائل على الشاشة. تُستخدم القيم المعادة عمومًا عندما تكون الدالة خطوة متوسطة لحساب شء ما. إذ لا بد من استخدام الدالة لحساب قيمة ما تُستخدم لحساب القيمة النهائية. فبعد أن تحسب الدالة القيمة المطلوبة تعيدها ثم تُخزّن في متغيّر. بعدا يمكنك حساب هذا المتغير في حسابات أخرى. استخدام القيمة المعادة في دوال بنيتها كي تعيد قيمة من دالة مخصصة، استخدم التعليمة return. لقد رأينا ذلك سابقًا في المثال. إذ ترسم الدالة 100 دائرة عشوائية ضمن العنصر <canvas> في ملف HTML: function draw() { ctx.clearRect(0, 0, WIDTH, HEIGHT); for (let i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); ctx.fill(); } } وضمن كل تكرار للحلقة، تُستدعى الدالة ()random لتوليد قيم عشوائية لإحداثيات الدائرة x و y ولنصف القطر على التتالي. تأخذ الدالة ()random معاملًا واحدًا (عدد صحيح) وتعيد عددًا عشوائيًا صحيحًا بين 0 وهذا العدد. وسيبدو اﻷمر كالتالي: function random(number) { return Math.floor(Math.random() * number); } كما يمكن كتابة الدالة كالتالي: function random(number) { const result = Math.floor(Math.random() * number); return result; } لكن بالطبع النسخة اﻷولى أسرع وأصغر. نعيد نتيجة الحسابات Math.floor(Math.random() * number) كل مرة نستدعي فيها الدالة، وتظهر هذه القيمة في مكان استدعاء الدالة ومن ثم تتابع الشيفرة. ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); فلو أعادت الاستدعاءات الثلاث للدالة ()random القيم بالترتيب 500 و 200 و 35 سيكون تنفيذ السطر في الواقع كما لو كان على الشكل: ctx.arc(500, 200, 35, 0, 2 * Math.PI); تُستدعى الدوال ()random في سطر الشيفرة السابق أولًا ومن ثم توضع نتيجة تنفيذها (القيم المعادة) مكانها ثم يُنفّذ السطر. تطبيق عملي: تابع خاص يعيد قيمة نحاول في هذا التطبيق بناء دالة خاصة بنا تعيد قيمة، لهذا اتبع الخطوات التالية: انسخ الملف إلى حاسوبك، وهي صفحة بسيطة تضم عنصر إدخال نصي <input> وفقرة نصية <p>. كما يوجد عنصر <script> لتخزين مرجع إلى كل من العنصرين السابقين في متغيرين. أضف دالة مفيدة ما إلى العنصر <script> تحت ما هو موجود: function squared(num) { return num * num; } function cubed(num) { return num * num * num; } function factorial(num) { if (num < 0) return undefined; if (num === 0) return 1; let x = num - 1; while (x > 1) { num *= x; x--; } return num; } لا غموض في شيفرة الدالتين ()squared و ()cubed اللتان تعيدان مربع ومكعب العدد الذي يُمرر إليهما، أما الدالة ()factorial فتعيد قيمة العاملي ! للعدد الذي يُمرر إليها (العاملي هو جداء تناقصي للعدد حتى الواحد). أضف طريقة لطباعة معلومات عن العدد الذي تدخله في مربع اﻹدخال النصي من خلال معالج الحدث التالي تحت الدوال الموجودة: input.addEventListener("change", () => { const num = parseFloat(input.value); if (isNaN(num)) { para.textContent = "You need to enter a number!"; } else { para.textContent = `${num} squared is ${squared(num)}. `; para.textContent += `${num} cubed is ${cubed(num)}. `; para.textContent += `${num} factorial is ${factorial(num)}. `; } }); احفظ التغييرات على الشيفرة وحمّل الصفحة من جديد في المتصفح. إليك بعض اﻹيضاحات حول معالج الحدث addEventListener في الخطوة الثالثة: بإضافة مترصد للحدث change ستُنفَّذ الدالة غير المسماة (المعامل الثاني لمترصد الحدث) في كل مرة يحدث فيها تغيير على عنصر اﻹدخال النصي، أي في كل مرة نكتب فيه قيمة جديدةً وتُرسل (مثل إدخال قيمة وإبعاد التركيز عن المربع النصي بالضغط على الزر Tab أو Return). تُخزّن القيمة الموجودة في المربع النصيinput عند تنفيذ الدالة غير المسماة في الثابت num. تطبع عبارة if رسالة خطأ إن لم تكن القيمة المدخلة عددًا. إذ تتحقق العبارة الشرطية إذا ما أعادت الدالة isNaN(num) القيمة true. وما تفعله هذه الدالة هو التحقق فيما لو كانت القيمة التي تمرر إليها عددًا أم لا، وتعيد true إن كانت بالفعل عددًا و false إن لم يكن كذلك. إن أعاد الشرط القيمة false، ستكون القيمة عددًا، وتطبع الدالة جملة ضمن الفقرة النصية توضح قيمة مربع ومكعب وعاملي العدد المدخل. إذ تستدعي العبارة الدوال ()squared و ()cubed و ()factorial لحساب القيم الموافقة. ملاحظة: إن كانت هناك أية مشاكل في تنفيذ هذا التمرين، وازن بين ما كتبته وبين النسخة الجاهزة من هذا التمرين على جت-هب (نفّذه مباشرة إن أردت!)، أو اطلب المساعدة. حان دورك اﻵن لكتابة الدالة الخاصة بك في جافا سكريبت نطلب منك اﻵن كتابة دالتين من تأليفك وأضفهما إلى المكتبة. نقترح عليك مثلًا دالة تحسب الجذر التربيعي لعدد وأخرى تحسب الجذر التكعيبي! أو محيط دائرة لها قطر معين! إليك بعض التلميحات المفيدة اﻷخرى عن الدوال: ألق نظرة على أمثلة أخرى لمعالجة اﻷخطاء في دالة. ومن الجيد عمومًا التحقق من قيمة أي معامل ضروري للدالة والتحقق من وجود نوع من القيم الافتراضية التي يأخذها هذا المعامل في حال كانت قيمة هذا المعامل خاطئة فهذا سيقلل احتمال ظهور اﻷخطاء. فكرّ بتأليف مكتبة من الدوال. فعندما تتقدم في مسيرتك البرمجية، سترى أنك تكرر الكثير من اﻷشياء مرات ومرات. لهذا سيفيدك كثيرًا وجود مكتبة خاصة بك من الدوال التي تنفّذ وظائف متنوعة تحتاجها باستمرار. وهكذا ستتمكن من نسخها ولصقها في الشيفرة الجديدة أو تدرجها في صفحة HTML عندما تحتاجها. الخلاصة وهكذا نكون قد أنهينا موضوع الدوال في جافا سكريبت وهي وكما لاحظت أمر شديدة الفائدة لأي مبرمج، وعلى الرغم من وجود الكثير من النقاط الأخرى التي يمكن التحدث عنها بخصوص الداوال لكنها يفترض أن تكون قد أصبحت مفهومة بالنسبة لك. إن وجدت أي شيء غير مفهوم، حاول مراجعة المقال مرة أخرى، ونرحب بأي تساؤل تطرحه في قسم التعليقات أسفل الصفحة. ترجمة -وبتصرف- للمقال Function return values اقرأ أيضًا المقال السابق: إنشاء دوال خاصة بك في جافا سكريبت الدوال Functions في جافاسكريبت الدوال وإعادة استخدام الشيفرة في جافا سكريبت نظرة تفصيلية على الدوال السهمية Arrow functions في جافاسكربت الميزات الجديدة في ES6: نطاق المتغيرات، الدوال السهمية والمعاملات المبدئية
  13. التخطيط متعدد الأعمدة هو أسلوب لترتيب العناصر في صفحات الويب ضمن أعمدة كما تُرتب أعمدة الصحف، وهذا ما سنشرحه في هذا المقال. عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML. تتفهم أساسيات عمل CSS. مثال تمهيدي نختبر في هذا المثال التخطيط المتعدد الأعمدة والذي يشار إليه في الإنجليزية اختصارًا multicol. بإمكانك متابعة العمل معنا بعد تنزيل نسختك من الملف المخصص للتمرين وإضافة شيفرة CSS في الأماكن المناسبة. سترى في نهاية القسم مثالًا عما ينبغي أن تكونه الشيفرة النهائية. تخطيط من ثلاث أعمدة يضم الملف شيفرة HTML بسيطة جدًا مؤلفة من عنصر تغليف <div> يمتلك صنف التنسيق container وضمن عنصر التغليف ستجد عنوانًا وبعض الفقرات. سنحوّل عنصر التنسيق السابق إلى حاوية من ثلاث أعمدة باستخدام واحدة من الخاصيتين column-count أو column-width. إذ تأخذ الخاصية الأولى قيمتها على شكل أعداد وتنشئ أعمدة بمقدار هذه القيمة. فإن أضفت الشفرة التالية إلى تنسيق الصفحة وأعدت تحميلها ستحصل على ثلاثة أعمدة. .container { column-count: 3; } للأعمدة التي تنشئها اتساع مرن وسيعمل المتصفح على تقدير الاتساع الذي يأخذه كل عمود. See the Pen Multiple-column-1 by Hsoub Academy (@HsoubAcademy) on CodePen. ضبط اتسع العمود باستخدام column-width عدّل شيفرة CSS كي تستخدم column-width كما يلي: .container { column-width: 200px; } سيعمل المتصفح في هذه الحالة على إنشاء أكبر عدد من الأعمدة بما يتناسب مع اتساع الحاوية، ثم يقسم المساحة المتبقية على الأعمدة. ويعني ذلك أنك لن تحصل بالضرورة على الاتساع الذي تريده ما لم يكن اتساع الحاوية قابلًا للقسمة على هذا الاتساع. See the Pen Multiple-column-2 by Hsoub Academy (@HsoubAcademy) on CodePen. تنسيق الأعمدة لا يمكن تنسيق الأعمدة التي تنشئها باستخدام خاصيات تعدد الأعمدة بشكل منفصل، فلن تجد طريقة لجعل عمود أكثر اتساع من آخر أو أن تغير لون أو خلفية أحد الأعمدة فقط دون البقية. وعمومًا، لديك طريقتين لتغيير طريقة عرض الأعمدة: column-gap: لتغيير حجم المسافة الفارغة بين عمودين. column-rule: لإضافة فاصل بين عمودين. جرّب أن تغيّر في مثالنا حجم المسافة الفارغة بين الأعمدة باستخدام column-width وإسناد أية قيم تراها مناسبة إذ تقبل هذه الخاصية أية قيم لها واحدة الطول. أضف أيضًا فاصلًا بين الأعمدة باستخدام الخاصية column-rule التي تمثل خاصية مختصرة مشابهة للخاصية border وتضم ثلاثة خاصيات هي column-rule-color و column-rule-style و column-rule-width وتقبل نفس القيم التي تأخذها خاصيات border. .container { column-count: 3; column-gap: 20px; column-rule: 4px dotted rgb(79, 185, 227); } حاول أن تضيف أعمدة بتنسيقات وألوان مختلفة: See the Pen Multiple-column-3 by Hsoub Academy (@HsoubAcademy) on CodePen. من الجدير بالملاحظة هنا أن الفواصل لا تملك اتساعًا مخصصًا بها، بل تستقر في المسافة الفارغة بين الأعمدة التي حددتها باستخدام column-gap. لهذا ستحتاج إلى زيادة أبعاد المسافة الفارغة بين الأعمدة حتى تزيد اتساع الفواصل. الأعمدة المتمددة بالإمكان أن تجعل عنصرًا يمتد على جميع الأعمدة، وفي هذه الحالة ينتقل المحتوى إلى سطر جديد في النقطة التي يظهر فيها العمود الممتد ثم يكمل تحته ضمن مجموعة جديدة من الأعمدة. وحتى يمتد العنصر على جميع الأعمدة يجب أن تضبط قيمة الخاصية column-span على all. ملاحظة: من غير الممكن أن تجعل عنصرًا يمتد على عدد معين من الأعمدة، فإما أن يتمدد على كل الأعمدة all أو أن لا يتمدد أبدًا none. See the Pen Multiple-column-4 by Hsoub Academy (@HsoubAcademy) on CodePen. الأعمدة وتجزئة المحتوى يُجزّأ محتوى التخطيط متعدد الأعمدة، ويسلك أساسًا سلوك محتوى صفحات متعددة الوسائط كما يُلاحظ عند طباعة صفحة ويب. فعندما تحوّل المحتوى إلى حاوية متعددة الأعمدة فأنت تجزئ هذا المحتوى إلى أعمدة، ولكي يحدث ذلك لا بد للمحتوى من الانتقال إلى أسطر جديدة. الصناديق المجزأة يحدث انتقال المحتوى إلى أسطر جديدة أحيانًا في أماكن قد تسيء إلى تجربة القراءة. ولتوضيح الأمر، استخدمنا في المثال التالي تخطيطًا متعدد الأعمدة لتوزيع سلسلة من الصناديق لكلٍ منها عنوان ونص. سيظهر العنوان منفصلًا عن النص إذا حدث التجزئة بينهما. <div class="container"> <div class="card"> <h2>I am the heading</h2> <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. </p> </div> <div class="card"> <h2>I am the heading</h2> <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. </p> </div> <div class="card"> <h2>I am the heading</h2> <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. </p> </div> <div class="card"> <h2>I am the heading</h2> <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. </p> </div> <div class="card"> <h2>I am the heading</h2> <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. </p> </div> <div class="card"> <h2>I am the heading</h2> <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. </p> </div> <div class="card"> <h2>I am the heading</h2> <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. </p> </div> </div> .container { column-width: 250px; column-gap: 20px; } .card { background-color: rgb(207, 232, 220); border: 2px solid rgb(79, 185, 227); padding: 10px; margin: 0 0 1em 0; } See the Pen Multiple-column-5 by Hsoub Academy (@HsoubAcademy) on CodePen. ضبط الانتقال إلى سطر جديد مع الخاصية break-inside للتحكم بهذا الأمر، يمكننا استخدام خاصيات التجزئة التي تتحكم بانتقال المحتوى إلى سطر جديد في التخطيط متعدد الأعمدة. فلو استخدمنا مثلًا الخاصية break-inside بعد إسناد القيمة avoid لها ضمن المحدد card. الذي ينسّق الحاوية التي تضم العنوان والنص، سيمنع هذا من تجزئة المحتوى ضمن الحاوية. .card { break-inside: avoid; background-color: rgb(207, 232, 220); border: 2px solid rgb(79, 185, 227); padding: 10px; margin: 0 0 1em 0; } إن إضافة هذه الخاصية تُبقي الصندوق كما هو جزءًا واحدًا ولن يتجزأ المحتوى ضمنه بين الأعمدة. See the Pen Multiple-column-6 by Hsoub Academy (@HsoubAcademy) on CodePen. الخلاصة إلى هنا تكون قد وصلت لنهاية هذا المقال الذي تعلمت من خلاله طريقة استخدام الميزات الأساسية للتخطيط متعدد الأعمدة في CSS، وهي أداة مفيدة أخرى يمكنك استعمالها في تنفيذ تصاميم صفحات موقعك الإلكتروني عند اختيار طريقة التخطيط الملائمة لعملك. ترجمة -وبتصرف- للمقال: Multi-column layout اقرأ أيضًا المقال السابق: ضبط تموضع العناصر في صفحات ويب باستخدام CSS مدخل إلى تخطيط صفحات الويب باستخدام CSS مفاهيم أساسية في التعامل مع تخطيط الشبكة Grid Layout نبذة عن الطرق الجديدة في تصميم صفحات CSS تخطيط الصندوق المرن Flexbox في صفحات الويب
  14. نلقي الضوء في هذا المقال على طرق التعامل مع السلاسل النصية Strings، التي تمثل في لغة جافا سكريبت وفي لغات البرمجة عمومً الأجزاء المقتطفة من نص ما، ونناقش ما عليك معرفته بشكل أساسي حول السلاسل النصية مثل إنشائها وتجاوز إشارات التنصيص (استخدامها كما هي دون أية دلالات) وضم أو دمج عدة سلاسل نصية مع بعضها البعض. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS. قوة الكلمات! الكلمات هي أساس التواصل البشري والويب وسط معرفي مبني أساسًا على النصوص وقد صممت لتعزز التواصل بين البشر وتبادل المعلومات، لهذا من المهم جدًا التحكم بطريقة عرض تلك النصوص. تتيح لك لغة التوصيف HTML بناء هيكل المحتوى النصي وإعطاءه دلالاته بينما تنسّق CSS هذا المحتوى وتعززه ويتمثل دور لغة جافا سكريبت في تقديمها لطرق مختلفة للتعامل مع النصوص واﻷعداد وتقديم ميزات مهمة مثل القدرة على عرض رسائل وعرض نوافذ لإدخال البيانات وترتيب العناصر كما نريد وغيرها الكثير. وما عرضناه من برامج حتى اﻵن في سلسلة مقالاتنا التي بدأناها بالتعرف على لغة جافا سكريبت تتضمن شكلًا من أشكال التعامل مع النصوص. التصريح عن السلاسل النصية قد تجد بداية أن طريقة التعامل مع النصوص في جافا سكريبت تتشابه مع طريقة التعامل مع اﻷعداد تقريبًا، لكن عندما تتعمق أكثر ستلاحظ بعض الاختلافات الواضحة. سنبدأ إذًا بإدخال بعض اﻷسطر في طرفية جافا سكريبت في متصفحك (اضغط "Ctrl" + "Shift" + "k" في فايرفكس): const string = "The revolution will not be televised."; console.log(string); عرّفنا في الأسطر السابقة متغيّرًا وهيأناه بإسناد قيمة نصية له ثم أعدنا هذه القيمة. لاحظ أن الاختلاف الوحيد عند تهيئة متغير نصي هو إحاطة القيمة بإشارتي تنصيص، وإن لم تفعل ذلك سترى رسالة خطأ. جرّب ما يلي: const badString1 = This is a test; const badString2 = 'This is a test; const badString3 = This is a test'; لن تعمل الأسطر السابقة، لأن أي قيمة نصية دون إشارتي تنصيص حولها سيفترضها المتصفح أسماءً لمتغيرات أو خاصيات أو كلمات محجوزة وما شابه. وعندما لا يجد المتصفح ما افترضه سيعرض رسالة خطأ (مثل "missing; before statement" بمعنى "إغفال ; قبل التصريح"). وعندما يرى المتصفح بداية السلسلة النصية لكنه لا يرى نهايتها التي تشير إليها علامة التنصيص الثانية سيعرض رسالة خطأ تشير إلى أنك لم تُنهي السلسلة النصية كما يجب ("unterminated string literal"). فإن واجهتك رسائل خطأ مشابهة لما ذكرناه فعد إلى شيفرتك البرمجية وتحقق من إشارات التنصيص حول السلاسل النصية. const badString = string; console.log(badString); سيعمل هذا المثال فقط إن صرحت بداية عن المتغير string، وسيكون للثابت badString قيمة string نفسها. علامة التنصيص المفردة والمزدوجة والمائلة بإمكانك أن تختار علامة التنصيص المفردة (') أو المزدوجة (") أو المائلة (```) لإحاطة السلسلة النصية. إليك مثالًا: const single = 'Single quotes'; const double = "Double quotes"; const backtick = `Backtick`; console.log(single); console.log(double); console.log(backtick); ولا بد من استخدام نفس إشارة التنصيص في بداية ونهاية السلسلة وإلا سترى رسالة خطأ: const badQuotes = 'This is not allowed!"; وبإمكانك عمومًا استخدام العلامات المزدوجة أو المفردة وفقًا لرغبتك الشخصية فلكلاهما العمل نفسه ومن اﻷفضل من الناحية العملية اختيار أحد اﻷسلوبين واتباعه في كامل مشروعك. وتُدعى السلاسل النصية المعرّفة بعلامة التنصيص المائلة بالقوالب المفسّرة template literals وهي مشابه من الناحية العامة للسلاسل النصية العادية مع بعض الميزات الخاصة: بإمكانك إدراج شيفرة جافا سكريبت ضمنها. بإمكانك التصريح عن القوالب المفسّرة على عدة أسطر. إدراج شيفرة جافا سكريبت ضمن نص بإمكانك تغليف شيفرة جافا سكريبت لعرض نتيجتها ضمن سلسلة نصية ضمن القالب {}$. إليك مثالًا: const name = "Chris"; const greeting = `Hello, ${name}`; console.log(greeting); // "Hello, Chris" استخدم نفس التقنية أيضًا لضم متغيرين معًا: const one = "Hello, "; const two = "how are you?"; const joined = `${one}${two}`; console.log(joined); // "Hello, how are you?" تُدعى عملية ضم السلاسل النصية بهذه الطريقة بتجاوز أو دمج النصوص concatenation سياق عملية التجاور لنلق نظرة على عملية التجاور من خلال المثال التالي: <button>Press me</button> <div id="greeting"></div> const button = document.querySelector("button"); function greet() { const name = prompt("What is your name?"); const greeting = document.querySelector("#greeting"); greeting.textContent = `Hello ${name}, nice to see you!`; } button.addEventListener("click", greet); See the Pen str-in-js by Hsoub Academy (@HsoubAcademy) on CodePen. استخدمنا في الشيفرة السابقة الدالة ()window.prompt التي تطلب من المستخدم اﻹجابة عن سؤال ضمن نافذة منبثقة يعرضها المتصفح، ويُخزّن النص الذي يكتبه ضمن متغيّر وهو name في حالتنا. ونعرض بعد ذلك نصًا يُظهر الاسم وقد أُدرج ضمن رسالة ترحيبية. دمج السلاسل النصية باستخدام "+" يمكن استخدام اﻷسلوب مع القوالب المفسّرة وليس مع السلاسل النصية النمطية، لكن باﻹمكان ضم سلسلتين نمطيتين باستخدام العامل +: const greeting = "Hello"; const name = "Chris"; console.log(greeting + ", " + name); // "Hello, Chris" لكن القوالب المفسرة تسهّل قراءة الشيفرة: const greeting = "Hello"; const name = "Chris"; console.log(`${greeting}, ${name}`); // "Hello, Chris" تضمين عبارات برمجية ضمن السلاسل النصية وذلك باستخدام القوالب المفسّرة كما هو حال المتغيرات وستدرج نتيجة تنفيذ العبارة البرمجية ضمن السلسلة النصية: const song = "Fight the Youth"; const score = 9; const highestScore = 10; const output = `I like the song ${song}. I gave it a score of ${ (score / highestScore) * 100 }%.`; console.log(output); // "I like the song Fight the Youth. I gave it a score of 90%." النص متعدد اﻷسطر تفسّر القوالب المفسّرة محرف الانتقال إلى سطر جديد وبالتالي يمكن استخدامها لكتابة السلسلة النصية على أكثر من سطر واحد لتسهيل قرائتها ولن يُعتبر الانتقال إلى سطر جديد نهاية عبارة برمجية ولن يولد ذلك أية أخطاء: const newline = `One day you finally knew what you had to do, and began,`; console.log(newline); /* One day you finally knew what you had to do, and began, */ ولتنفيذ الفكرة نفسها باستخدام السلاسل النصية النمطية لا بد من استخدام محرف الانتقال إلى سطر جديد (n\) ضمن السلسلة النصية وفي المكان الذي ينبغي الانتقال يه إلى السطر الجديد. إليك مثالًا: const newline = "One day you finally knew\nwhat you had to do, and began,"; console.log(newline); /* One day you finally knew what you had to do, and began, */ تضمين إشارة التنصيص ضمن سلسلة نصية طالما أننا نستخدم إشارات التنصيص لتحديد بداية ونهاية سلسلة نصية، فما الذي سنفعله إن احتجنا إلى وضع إشارة التنصيص ضمن نص أي أن تكون جزءًا من السلسلة النصية؟ نعرف طبعًا أنه أمر غير ممكن بالطريقة النمطية التالية: const badQuotes = "She said "I think so!""; ومن إحدى الطرق المتبعة لحل المشكلة استخدام نوع من علامات التنصيص ضمن السلسلة وآخر لتحديد بدايتها ونهايتها كالتالي: const goodQuotes1 = 'She said "I think so!"'; const goodQuotes2 = `She said "I'm not going in there!"`; أما الخيار اﻵخر فهو تجاوز escape إشارة التنصيص ضمن النص ويقصد بذلك إهمال وظيفتها كبداية أو نهاية السلسلة واعتبارها محرفًا عاديًا أي اعتبارها كنص وليس جزءًا من الشيفرة. لتنفيذ ذلك في جافا سكريبت نضع شرطة مائلة عكسية \ قبل المحرف الذي نريد تجاوزه. إليك مثالًا: const bigmouth = 'I\'ve got no right to take my place…'; console.log(bigmouth); بإمكانك استخدام نفس التقنية ﻹضافة محارف خاصة أخرى إلى النص. اﻷعداد مقابل السلاسل النصية ما الذي سيحدث إن حاولت ضم عدد إلى سلسلة نصية؟ لنجرّب ذلك: const name = "Front "; const number = 242; console.log(`${name}${number}`); // "Front 242" قد نتوقع ظهور رسالة خطأ لكن ذلك لا يحدث! في هذه الحالة يحوّل المتصفح اﻷعداد إلى نص تلقائيًا ومن ثم يضمها إلى النص. فإن كانت لديك متغير عددي تريد تحويل قيمته إلى نص أو نصي تريد تحويل قيمته إلى عدد، ستتمكن من ذلك باستخدام الدالتين String و Number. تحوّل الدالة ()Number القيمة التي تمرر إليها إلى عدد إن كان ذلك ممكنًا، جرّب ذلك: const myString = "123"; const myNum = Number(myString); console.log(typeof myNum); // number تحوّل الدالة ()Stringالقيمة التي تمرر إليها إلى نص، جرّب ذلك: const myNum2 = 123; const myString2 = String(myNum2); console.log(typeof myString2); // string ستجد أن هاتان الدالتان غاية في الأهمية في بعض المواضع، فعندما يُدخل المستخدم عددًا في حقل نصي على سبيل المثال، سيعامل العدد معاملة السلسلة النصية. لكن إن أردت استخدامه كعدد ﻹضافته إلى عدد آخر، لا بد من تمريره إلى الدالة ()Number لتتولى أمر تحويله إلى عدد. الخلاصة قدمنا في هذا المقال الأساسيات البسيطة للتعامل مع النصوص في جافا سكريبت وسنتابع في مقال قادم العمل شرح المزيد من التوابع الأصلية المضمنة في لغة جافا سكريبت والمخصصة للتعامل مع النصوص. ترجمة -وبتصرف- للمقال Handling text-strings in JavaScript اقرأ أيضًا المقال السابق: العمليات الرياضية على اﻷعداد في جافا سكريبت السلاسل النصية (strings) في جافاسكربت تصميم النّصوص البرمجيّة: تنظيم JavaScript توابع الأنواع الأولية (primitives methods) في جافاسكربت
  15. نقدم في هذا المقال مقاربة تطبيقية مبنية على المفاهيم اﻷساسية التي قدمتها المقالات السابقة. وستتعلم كيف تبني دوال مخصصة بنفسك وتطلع على بعض التفاصيل المفيدة عند التعامل مع الدوال أثناء دراستك لها المقال. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافا سكريبت كما شرحناها في سلسلة المقالات السابقة. تطبيق عملي: بناء دالة سنبني في هذا التمرين دالة باسم ()displaymessage مهمتها عرض مربع رسالة على صفحة ويب، وستعمل كبديل خاص بك عن الدالة المدمجة مع المتصفح ()alert.تحدثنا عن عمل هذه الدالة سابقًا لكننا سنعيدها لتتذكر بعض التفاصيل. لهذا اكتب الشيفرة التالية في طرفية جافا سكريبت في متصفحك: alert("This is a message"); تقبل الدالة alert وسيطًا واحدًا هو النص الذي تريد عرضه ضمن صندوق الرسالة، وبتغييرك هذا النص تتغير الرسالة ضمن الصندوق. لكن هذه الدالة محدودة القدرات، إذ ستتمكن من تغيير النص المعروض بسهولة لكن من الصعب تغيير أشياء أخرى كاللون واﻷيقونة وغيرها من التفاصيل. لهذا سنبني دالة أكثر متعة. ملاحظة: ينبغي أن تعمل الشيفرة ضمن جميع المتصفحات الحديثة، لكن قد يبدو التنسيق غريبًا قليلًا في المتصفحات اﻷقدم. لهذا ننصحك بتنفيذ هذا التمرين في متصفحات حديثة مثل فايرفوكس وأوبرا وكروم. الدالة اﻷساسية لنبدأ بتشكيل دالة أساسية مع التقيد بنفس قواعد التسمية المتبعة عند تسمية المتغيرات، فلا مشكلة في ذلك طالما أن الدوال يتبعها قوسان وبالتالي يمكن تمييزها عن المتغيرات. أنشئ نسخة خاصة بك من ملف HTM الخاص بالتمرين وستجده بسيطًا يحتوي جسمه على زر واحد ويضم أيضًا تنسيقات CSS أساسية لتنسيق صندوق الرسالة، كما ستجد العنصر <script> الذي يضم شيفرة جافا سكريبت الخاصة بالتمرين أضف الشيفرة التالية ضمن العنصر <script>: function displayMessage() { ... } تبدأ الشيفرة بالكلمة المفتاحية function والتي تعني أننا نعرّف تابعًا يليها الاسم الذي نريد تسمية الدالة به يليه زوج من الأقواس وزوج من اﻷقواس المعقوصة. ونضع أية معاملات نريد نعطيها للدالة داخل القوسين العاديين، بينما نضع الشيفرة التي تنفذها الدالة بين القوسين المعقوصين. أضف أخيرًا الشيفرة التالية ضمن القوسين المعقوصين: const body = document.body; const panel = document.createElement("div"); panel.setAttribute("class", "msgBox"); body.appendChild(panel); const msg = document.createElement("p"); msg.textContent = "This is a message box"; panel.appendChild(msg); const closeBtn = document.createElement("button"); closeBtn.textContent = "x"; panel.appendChild(closeBtn); closeBtn.addEventListener("click", () => panel.parentNode.removeChild(panel), ); يختار السطر الأول من الشيفرة العنصر <body> باستخدام الواجهة البرمجية لشجرة DOM للحصول على الخاصية body للكائن document الذي يمثل مستند HTML بالكامل ثم يسند قيمته لثابت يُدعى body: const body = document.body; يستخدم القسم الثاني أحد دوال الواجهة البرمجية ()document.createElement ﻹنشاء عنصر <div> ثم يخزّن مرجعًا إليه ضمن ثابت يُدعى panel. سيمثّل هذا العنصر الحاوية الخارجية لصندوق الرسائل. نستخدم بعد ذلك دالة أخرى ()Element.setAtrtribute لضبط السمة class العائدة للوحة صندوق الرسائل على msgbox كي يسهل تنسيق العنصر. فلو ألقيت نظرة على تنسيقات الصفحة ستجد أنها تحتوي على الصنف msgbox. لتنسيق صندوق الرسائل ومحتواه. نستدعي أخيرًا الدالة ()Node.appendChild للثابت body الذي عرَفناه سابقًا والتي تضع عنصرًا ضمن آخر كابن له. وخصصنا العنصر <div> ليكون ابنًا للعنصر <body> والسبب في ذلك ألا يظهر العنصر الذي ننشئه في مكانه الافتراضي في الصفحة بل نريد وضعه في مكان نخصصه له. const panel = document.createElement("div"); panel.setAttribute("class", "msgBox"); body.appendChild(panel); نستخدم تاليًا الدالتين ()createElement و ()appendChilde اللتان رأينا عملهما سابقًا في إنشاء عنصري فقرة نصية <p> والحاقهما في الصفحة كابنين للعنصر <div>. وبعدها نستخدم الخاصية Node.textContent والتي تمثل المحتوى النصي للفقرة لوضع الرسالة المطلوبة ضمن الفقرة النصية وإشارة "x" كعنوان للزر لتعكس وظيفته وهي إغلاق صندوق الرسائل. const msg = document.createElement("p"); msg.textContent = "This is a message box"; panel.appendChild(msg); const closeBtn = document.createElement("button"); closeBtn.textContent = "x"; panel.appendChild(closeBtn); ثم نستدعي في النهاية الدالة ()addEveentListener ﻹضافة دالة (دالة سهمية غير مسماة) تُستدعى عندما ينقر المستخدم على الزر ومهمتها حذف العنصر الذي يمثل صندوق الرسائل بأكمله من الصفحة. يُمرر للتابع ()addEveentListener الذي يمكن أن يستخدمه أي عنصر في الصفحة دالة أخرى واسم الحدث الذي ينبغي ترصده وهو في حالتنا "click"، أي ستُنفّذ الدالة عندما ينقر المستخدم على الزر ( سنتحدث بتفصيل أكثر عن اﻷحداث في مقال "مدخل إلى اﻹحداث في جافا سكريبت. أما الدالة التي نستخدمها عند وقوع الحدث فتضم دالة أخرى من دوال الواجهة البرمجية لشجرة المستند DOM وهي ()Node.removeChild التي تزيل ابنًا محددًا من أبناء العنصر وهو في حالتنا العنصر <div>: closeBtn.addEventListener("click", () => panel.parentNode.removeChild(panel)); تعرض الشيفرة السابقة طريقة إنشاء عناصر HTML برمجيًا وستضيف شيفرتنا السابقة إلى الصفحة مايلي: <div class="msgBox"> <p>This is a message box</p> <button>x</button> </div> لا تقلق إن لم تتذكر تمامًا كيف تعمل الشيفرة السابقة، فكل ما نهتم له اﻵن هو هيكلية الدالة التي أنشأناها واستخدامها. استدعاء الدالة عرفّنا اﻵن الدالة التي نريدها ضمن العنصر <script> بالشكل الصحيح، لكنها لن تفعل شيئًا بنفسها. جرّب أن تضيف السطر التالي تحت الدالة في الشيفرة: displayMessage(); يستدعي هذا السطر الدالة وينفذها مباشرة. فعندما تحفظ التغييرات وتعيد تحميل الصفحة، يعرض المتصفح الشيفرة في الصفحة مباشرة ولمرة واحدة، لأننا استدعيناها مرة واحدة. افتح أدوات مطوري ويب في نتصفح وانتقل إلى طرفية جافا سكريبت واكتب السطر السابق مجددًا وسترى كيف تظهر الرسالة مجددًا! حققنا إذا ما نريده وهي دالة يمكن استخدامها بشكل متكرر في أي وقت نشاء. لكن لربما من اﻷفضل استخدامها كاستجابة لحدث ما أو إجراء ما، وهذا ما يحدث في التطبيقات الواقعية، فصندوق الرسائل يظهر مثلًا كاستجابة لوجود بيانات جديدة أو وقوع خطأ ما، أو تنبيه لفعل ما كأن يحاول المستخدم حذف ملفه مثلًا فتكون الرسالة على الشكل "هل أنت متأكد من ذلك؟"، أو عندما يضيف المستخدم بنجاح جهة اتصال جديدة وهكذا. أما في مثالنا، سنعرض الرسالة عندما ينقر المستخدم على الزر. احذف السطر اﻷخير الذي أضفته إلى الشيفرة. ما سنفعله تاليًا هو اختيار الزر ثم حفظ مرجع إليه ضمن ثابت، لهذا أَضف الشيفرة التالية أعلى تعريف الدالة: const btn = document.querySelector("button"); أضف السطر التالي بعد السطر السابق: btn.addEventListener("click", displayMessage); وعلى غرار ما فعلناه للتعامل مع حدث النقر على زر اﻹغلاق closeBtn نستدعي في هذا السطر الشيفرة كاستجابة لحدث النقر على الزر، لكن بدلًا من استدعاء دالة غير مسماة سنستدعي الدالة التي أنشأناها ()displayMessage باسمها. احفظ التغييرات وأعد تحميل الصفحة، سترى اﻵن الرسالة فقط عندما تنقر على الزر. قد تتسائل لماذا لم نضع القوسين بعد اسم الدالة عندما نراها كمعامل لدالة ترصد الأحداث؟ السبب هو أننا لا نريد تنفيذ الدالة دون النقر على الزر. فلو غيرت الشيفرة لتصبح على الشكل: btn.addEventListener("click", displayMessage()); ثم حفظت التغيرات وأعدت تحميل الصفحة، ستظهر الرسالة مباشرة دون النقر على الزر. تُدعى اﻷقواس أحيانًا وفق هذا السياق "عامل تنفيذ الدالة function invocation operator"، وتسُستخدم عندما تريد تنفيذ الدالة مباشرة ضمن نطاق العمل الحالي. بالمقابل، لا تُنفّذ الشيفرة داخل الدالة غير المسماة مباشرة، لأنها ضمن نطاق الدالة التي تترصد الحدث. تراجع عن التغيرات السابقة إن جربت الفكرة السابقة قبل المتابعة. تحسين الدوال باستخدام المعاملات لا زالت الدالة بشكلها الحالي غير مفيدة، فلا نريد عرض نفس الرسالة دائمًا، لهذا سنحاول تحسين الدالة بإضافة معاملات تسمح لنا باستدعائها وفق عدة خيارات: عدّل بداية السطر اﻷول من الدالة ليصبح كالتالي: function displayMessage(msgText, msgType) { عندما نستدعي الدالة اﻵن، يمكننا تزويدها بمتغيرين ضمن القوسين لتحديد الرسالة التي تعرضها في صندوق الرسائل ونوع هذه الرسالة. للاستفادة من المعامل اﻷول، عدّل السطر التالي: msg.textContent = "This is a message box"; ليصبح بالشكل: msg.textContent = msgText; عليك اﻵن تعديل استدعاء الدالة لتضم نص الرسالة الجديد، لهذا عدّل السطر التالي: btn.addEventListener("click", displayMessage); ليصبح بالشكل: btn.addEventListener("click", () => displayMessage("Woo, this is a different message!"), ); إن أردنا أن نخصص معاملات ضمن دالة نستدعيها ضمن قوسي دالة أخرى، لا يمكننا استدعائها مباشرة، ولا بد من وضعها ضمن دالة غير مسماة، وبالتالي لن تكون ضمن مجال رؤية الدالة المستدعية مباشرة زلن تُستدعى مباشرة. وهكذا لن تُستدعى الدالة حتى ينقر المستخدم على الزر. أعد تحميل الصفحة مجددًا وسترى أنها لا تزال تعمل جيدًا، ما عدا أنك تستطيع تغيير الرسالة الموجودة ضمن المعامل لتعرض رسالة مختلفة في صندوق الرسائل. معامل أكثر تعقيدًا بالنسبة للمعامل اﻵخر، سيتطلب اﻷمر عملًا أكثر. إذ سنجعل صندوق الرسائل يعرض أيقونة مختلفة وخلفية ذات لون مختلف وفقًا لقيمة هذا المعامل (msgType). حمّل اولاً اﻷيقونات اللازمة لهذا التمرين (أيقونة التحذير وأيقونة المحادثة) ثم خزنهما في المجلد "icons". ابحث عن تنسيقات CSS داخل الملف لأننا سنجري بعض التغييرات لعرض اﻷيقونات، ثم عدّل اتساع صندوق الرسائل من width: 200px إلى width: 242px. أضف الشيفرة التالية داخل القاعدة {} msgBox p.: padding-left: 82px; background-position: 25px center; background-repeat: no-repeat; سنضيف اﻵن بعض الشيفرة إلى الدالة ()displayMessage لعرض اﻷيقونات بالشكل المطلوب. لهذا ضع الشيفرة التالية داخل القوسين المعقوصين للدالة: if (msgType === "warning") { msg.style.backgroundImage = "url(icons/warning.png)"; panel.style.backgroundColor = "red"; } else if (msgType === "chat") { msg.style.backgroundImage = "url(icons/chat.png)"; panel.style.backgroundColor = "aqua"; } else { msg.style.paddingLeft = "20px"; } إن كانت قيمة العامل msgBox هي warning ستُعرض أيقونة التحذير وتصبح لون خلفية صندوق الرسائل أحمر، وإن كانت قيمته chat ستُعرض أيقونة المحادثة ويصبح لون الخلفية أزرق مائي. إما إن لم تُضبط قيمة المعامل msgType أو أسندت إليه قيمة غير محددة، ستُنفَّذ الشيفرة داخل {}else وستأخذ الفقرة النصية الحاشية الافتراضية ولن تظهر اﻷيقونة ولن يُضبط لون الخلفية، وبهذا يكون هذا المعامل اختياريًا. لنجرّب اﻵن الشكل المعدّل للدالة ()displayMessage ونغيّر طريقة الاستدعاء من الشكل: displayMessage("Woo, this is a different message!"); لتصبح كالتالي: displayMessage("Your inbox is almost full — delete some mails", "warning"); displayMessage("Brian: Hi there, how are you today?", "chat"); لاحظ فائدة المعاملات في تحسين طريقة عمل الدوال. ملاحظة: إن لم تعمل الشيفرة أو واجهتك المشاكل، بإمكانك موازنة شيفرتك مع الشيفرة الجاهزة على جت-هب (أو تجريها مباشرة) أو طرح أية أسئلة في قسم التعليقات أسفل المقال أو في قسم الأسئلة والأجوبة في أكاديمية حسوب. الخلاصة حاولنا في هذا المقال السير بك تدريجيًا في بناء دالة مخصصة يمكن بقليل من العمل اﻹضافي أن تكون جاهزة للاستخدام في تطبيقات واقعية، ويبقى علينا مناقشة موضوع أساسي آخر يتعلق بالقيم التي يمكن للدوال أن تعيدها، وسنرى ذلك في مقالات قادمة. ترجمة -وبتصرف- للمقال: Build your own function اقرأ أيضًا المقال السابق:الدوال وإعادة استخدام الشيفرة في جافا سكريبت الدوال Functions في جافاسكريبت الدوال العليا في جافاسكريبت تعلم لغة جافا سكريبت من الصفر حتى الاحتراف
  16. تسمح لنا فكرة ضبط موقع العناصر بلغة CSS بإخراج عنصر من التخطيط الاعتيادي للمستند وتغيير سلوكه، كأن نقرر بأن يظهر فوق عنصر آخر أو يبقى دائمًا في نفس المكان ضمن نافذة عرض المتصفح. لهذا نشرح في هذا المقال القيم المختلفة للخاصية position وكيفية استخدامها. ملاحظة: يمكنك أن تنجز تمارين المقال على حاسوبك الشخصي، لهذا حاول أن تحضر نسخة من الملف 0_basic-flow.html من مستودع جيت-هاب (حمّل الشيفرة المصدرية أيضًا) واستخدامه كنقطة انطلاق. مدخل إلى تموضع العناصر يسمح لك تحديد مواضع العناصر في صفحة الويب في الحصول على نتائج مثيرة بتجاوز تنسيق الانسياب الاعتيادي. فماذا لو أردت أن تغيّر قليلًا موقع بعض الصناديق عن موقعها الافتراضي في الانسياب الاعتيادي لتمنح المستخدم تجربة خاصة مثلًا؟ سيكون تموضع العناصر الأداة المثالية لك في هذه الحالة. أو تخيّل أنك تريد أن تنشئ كائنًا في واجهة صفحتك يعوم فوق أجزاء أخرى أو أن يبقى دائمًا في مكانه ضمن نافذة المتصفح بصرف النظر عن تمرير المحتوى. سيساعدك توضيع العناصر أيضًا في إنجاز الأمر. توجد أنواع مختلفة لضبط مواضع العناصر، ولتحديد طريقة الضبط التي نحتاجها، سنستخدم الخاصية position ونوضح لك تاليًا خيارات مختلفة لاستخدامها. التموضع الساكن Static وهو التموضع الافتراضي لأي عنصر، ويعني "ضع العنصر في موقعه الطبيعي ضمن الانسياب الاعتيادي". ولكي ترى ذلك (وتحضّر الملف الذي ستتمرن عليه في الأقسام اللاحقة). أضف أولًا الصنف positioned إلى الفقرة النصية <p> الثانية: <p class="positioned">…</p> أضف الآن القاعدة التالية في نهاية شيفرة CSS: .positioned { position: static; background: yellow; } لن تجد عندما تحفظ التغيّرات وتعيد تحميل الصفحة أي اختلاف سوى لون الخلفية الجديد للفقرة النصية الثانية. وهذا بالفعل ما سيحدث فالتموضع الساكن هو التموضع الافتراضي للعنصر. التموضع النسبي Relative وهو أول التموضعات التي سنناقشها، ويشبه كثيرًا التموضع الساكن باستثناء أنه بمجرد أنك وضّعت العنصر في مكانه ضمن الانسياب الاعتيادي، بإمكانك عندها تعديل موقعه النهائي بما في ذلك وضعه فوق عناصر أخرى في الصفحة. لتوضيح الأمر، غيّر التصريح positioned في شيفرتك إلى: position: relative; لن تجد أي شيئ قد تغيّر أيضًا في هذه المرحلة إن حفظت التغيّرات وأعدت تحميل الصفحة، لكن كيف سنغير الموقع النهائي للعنصر؟ سنحتاج إلى استخدام الخاصيات top و bottom و left و right والتي سنشرحها تاليًا. الخاصيات top و bottom و left و right تستخدم هذه الخاصيات مع الخاصية position لتحديد المكان الذي تريد وضع العنصر فيه بدقة. لتجريب الأمر أضف إلى التصريح positioned. الشيفرة التالية: top: 30px; left: 30px; ملاحظة: يمكن أن تأخذ هذه الخاصيات قيمًا بأية واحدات مثل البكسل و mm و rem و %. إن حفظت لتغيرات الآن وأعدت تحميل الصفحة ستكون النتيجة كالتالي: See the Pen positioning-css1 by Hsoub Academy (@HsoubAcademy) on CodePen. لقد توضح الأمر قليلًا، أليس كذلك؟ لكن لا أعتقد أنك توقعت النتيجة. لماذا تحرّكت الفقرة نحو الأسفل واليمين مع أننا حددنا قيم الخاصيتين top و left. عليك أن تتخيل الموضوع كما لو كانت هناك قوة تدفع الفقرة من الجانبين السابقين (أعلى ويسار) وبالتالي ستنقل الفقرة نحو اليمين والأسفل. فلو كان ;top: 30px فيبدو الأمر كقوة تدفع صندوق الفقرة النصية من الأعلى نحو الأسفل وتحركه مقدار 30 بكسل. التموضع المطلق Absolute يأتي التموضع المطلق بنتائج مختلفة جدًا. ضبط الوضع المطلق غيّر قيمة الخاصية position كالتالي: position: absolute; لو حاولت حفظ التغيير وإعادة تحميل الصفحة ستجد النتيجة التالية: See the Pen positioning-css2 by Hsoub Academy (@HsoubAcademy) on CodePen. لاحظ بداية أن المكان المحجوز سابقًا للفقرة النصية الثانية في الانسياب الاعتيادي لم يعد موجودًا، وظهرت الفقرة النصية الثالثة بعد الأولى مباشرة. هذا الأمر صحيح نوعًا ما، فالعنصر الذي يأخذ توضعًا مطلقًا لن يظهر ضمن الانسياب الاعتيادي للعناصر، بل يتوضع ضمن طبقة خاصة به منفصلة عن الطبقة التي تضم عناصر الانسياب الاعتيادي. ولهذا الأمر فائدته، إذ يعني أنه بإمكاننا إنشاء واجهة مستخدم مستقلة لا تتداخل مع التخطيط الذي يضم بقية عناصر الصفحة. وكمثال عن هذه الواجهات نجد الصناديق المنبثقة ولوحات التحكم واللوحات القابلة للطي وعناصر واجهة المستخدم التي يمكن جرها وإفلاتها في أي مكان في الصفحة وغيرها. ونلاحظ ثانيًا أن موضع العنصر قد تغيّر، لأن سلوك الخاصيات top و bottom و left و right قد تغيّر مع التموضع المطلق. فبدلًا من توضيع العنصر بناء على موقعه بالنسبة إلى تخطيط الانسياب الاعتيادي، ستحدد تلك الخاصيات بعد صندوق العنصر عن حواف العنصر الحاوي. أي كأننا نقول في مثالنا أن صندوق الفقرة النصية ذات التموضع المطلق ستبتعد 30 بكسل عن الحافة العليا للعنصر الحاوي و 30 بكسل عن الحافة اليسرى له (إن العنصر الحاوي في حالتنا هي الكتلة الحاوية الأساسية initial containing block). ملاحظة: يمكنك استخدام top و bottom و left و right لإعادة تحديد أبعاد العناصر. جرّب مثلًا القيم التالية: ;top: 0 و ;bottom: 0 و;left: 0 و ; right: 0 و ;margin: 0 على العنصر الذي تحدد موضعه وراقب ما الذي سيحدث! أعد كل شيء إلى حاله عندما تنتهي. ملاحظة: تؤثر الهوامش على نوع العنصر، لكن لا تؤثر به خاصيات الهوامش المنقبضة Margin collapsing. سياق تموضع العناصر من هو العنصر الحاوي لعنصر ذو توضّع مطلق؟ يعتمد هذا الأمر كثيرًا على قيمة الخاصية position للعنصر الأب للعنصر المطلق. فإن لم يكن للعنصر عنصر أب قد حُددت قيمة الخاصية position له صراحةً فسيأخذ العنصر الأب التموضع الساكن. وتكون النتيجة أن يُحتوى العنصر المطلق ضمن الكتلة الحاوية الأساسية. للحاوية الأساسية أبعاد نافذة العرض وهي أيضًا الكتلة التي تحتوي العنصر <html> . وبعبارة أخرى، سيُعرض العنصر المطلق خارج حدود العنصر الأب ويتوضع بالنسبة إلى نافذة العرض. يقع العنصر المطلق ضمن العنصر <body> في شيفرة HTML المصدرية، لكن ستجده في التخطيط النهائي بعيدًا مسافة 30 بكسل عن الحد الأعلى للصفحة. بإمكاننا تغيير سياق توضع العنصر بمعنى كيفية توضعه المطلق وبالنسبة لأية عناصر. ننجز ذلك بضبط قيمة الخاصية position لأحد العناصر الآباء، أي العناصر التي يقع ضمنها العنصر ذو التموضع المطلق (إذ لا يمكن ضبط موقع عنصر بالنسبة إلى عنصر آخر إذا لم يكن ضمن هذا الأخير). لترى التأثير الذي نتحدث عنه، أضف التصريح التالي إلى القاعدة body: position: relative; من المفترض أن تحصل على النتيجة التالية: See the Pen positioning-css3 by Hsoub Academy (@HsoubAcademy) on CodePen. ضُبط موقع العنصر الآن بالنسبة إلى العنصر <body>. الخاصية z-index ما تقدمه فكرة التموضع المطلقة أمر ممتع، لكن هناك ميزة أخرى لم نذكرها بعد. فقد يبرز سؤال مهم عندما تتراكب العناصر فوق بعضها يتعلق بالعنصر الذي سيظهر فوق الجميع. ففي مثالنا حتى الآن، لدينا عنصر واحد قد غيّر موضعه وقد ظهر أعلى جميع العناصر الأخرى لأنها غير موضّعة، فما الذي سيحدث إذًا عندما نحدد موضع أكثر من عنصر؟ أضف شيفرة CSS التالية لتوضّع الفقرة النصية الأولى توضّعًا مطلقًا أيضًا: p:nth-of-type(1) { position: absolute; background: lime; top: 10px; right: 30px; } سترى الفقرة الأولى بعد تطبيق التغييرات الجديدة وقد ظهرت باللون الأخضر الفاتح وانتُزعت من الانسياب الاعتيادي للعناصر ثم وضعت في موقع أعلى من مكانها السابق. لاحظ أيضًا كيف حُشرت تحت الفقرة النصية التي وضّعناها سابقًا حيث تراكبت الفقرتين فوق بعضهما. ولأن الفقرة النصية الثانية (التي تمتلك الصنف positioned. ) قد ظهرت ثانيًا في ترتيب العناصر في الشيفرة المصدرية، فالعناصر الموّضعه التي ترتيبها متأخر عن العناصر الموّضعة الأخرى في الشيفرة المصدرية تربح أولوية الظهور في الأعلى. لكن هل يمكن تغيير الأمر؟ نعم باستخدام الخاصية z-index والتي تُعرف باسم "مؤشر العلو". وهي قيمة مرجعية للمحور الثالث Z الذي نفترض أنه يتجه نحو المستخدم عموديًا على الشاشة. وقد تتذكر من مقالات سابقة كيف استخدمنا المحور الأفقي (المحور X) والمحور العمودي (المحور Y) لتحديد إحداثيات أشياء مثل موقع صور الخلفية وتأثيرات الظل. فمن أجل اللغات التي تكتب من اليسار إلى اليمين، يمثل الإحداثي (0,0) الزاوية العليا اليسرى للصفحة أو العنصر ضمن حاويته. ويتحرك منها الإحداثي X نحو اليمين والإحداثي Y نحو الأسفل. للصفحات أيضًا محور ثالث هو المحور Z وهو كما ذكرنا محور تخيلي ينطلق من سطح الشاشة نحو المستخدم. وتؤثر قيمة محور العلو z-index على موقع العنصر الموّضع على هذا المحور، وكلما كانت قيمة هذه الخاصية أعلى لعنصر ظهر فوق العنصر ذو القيمة الأقل. ولكل العناصر القيمة الافتراضية للخاصية z-index وهي عمليًا القيمة 0. يسمح باستخدام القيم السالبة والتي تجعل العنصر ينزل نحو الأسفل. ولكي تغير ترتيب العناصر المتراكبة، أضف التصريح التالي إلى القاعدة p:nth-of-type(1) : z-index: 1; سترى الآن الفقرة ذات اللون الأخضر الفاتح في الأعلى: See the Pen positioning-css4 by Hsoub Academy (@HsoubAcademy) on CodePen. يجدر الانتباه إلى أن الخاصية z-index تقبل فقط قيمًا بلا وحدات، فلا يمكنك تخصيص قيمة مثل 23px. ودائمًا تأتي القيم الأكبر فوق القيم الأصغر، ولك حرية تخصيص هذه القيم بأي قيم تريدها، فاستخدام قيمتين مثل 2 و 3 يماثل استخدام القيمتين 300 و400. التموضع الثابت Fixed لنلق نظرة الآن على التموضع الثابت للعناصر، والذي يعمل تمامًا مثل التموضع المطلق مع اختلاف جوهري واحد. إذ يثبّت التموضع المطلق موقع العنصر بالنسبة إلى أقرب عنصر أب ضبطت خاصية التموضع له (والكتلة الحاوية الأساسية في حال لا يوجد عنصر سلف مضبوط الموقع)، بينما يثبت التموضع الثابت موقع العنصر بالنسبة إلى الجزء المرئي من نافذة العرض، باستثناء حالة واحدة تحدث إن كان أحد العناصر الأسلاف كتلة حاوية ثابتة نظرًا لتطبيق قيمة للخاصية transform مختلفة عن القيمة الافتراضية none. ويعني ذلك أنه بإمكانك إنشاء عناصر واجهة مستخدم ثابتة في مكانها مثل قوائم التنقل التي تُبقى مرئية دائمًا مهما قمت بتمرير محتوى الصفحة للأسفل. لنعمل سوية على المثال التالي كي تتوضح الصورة. احذف بداية القاعدتين (p:nth-of-type(1 و positioned. من شيفرة CSS. عدّل بعد ذلك القاعدة body واحذف التصريح ;position: relative ثم اجعل الارتفاع ثابتًا كالتالي: body { width: 500px; height: 1400px; margin: 0 auto; } سيُمنح الآن العنصر <h1> موضعًا ثابتًا ;position: fixed ونضبط هذا الموقع ليكون أعلى نافذة العرض بإضافة الشيفرة التالية: h1 { position: fixed; top: 0; width: 500px; margin-top: 0; background: white; padding: 10px; } لا بد من استخدام التصريح ;top: 0 ليبقى العنصر أعلى الشاشة، كما منحنا العنوان اتساعًا يماثل اتساع محتوى العمود وخلفية بيضاء وبعض الهوامش والحشوات كي يظهر المحتوى تحته. عند حفظ التغيّرات وإعادة تحميل الصفحة، سترى أن موقع العنوان سيقى ثابتًا، ويظهر المحتوى وكأنه يظهر ويختفي تحته عن تمريره. لكن تجدر الملاحظة أن بعض المحتوى قد اقتُطع تحت العنوان ولم يعد ظاهرًا، ذلك أن العنوان قد انتزع من الانسياب الاعتيادي وارتفع جزء من المحتوى ليحل مكانه. تُحل هذه المشكلة بدفع الفقرة النصية إلى الأسفل بضبط الهامش العلوي لها مثلًا: p:nth-of-type(1) { margin-top: 60px; } سترى النتيجة كالتالي: See the Pen positioning-css5 by Hsoub Academy (@HsoubAcademy) on CodePen. التموضع اللاصق Sticky هنالك قيمة أخرى للخاصية position هي position: sticky، وهي أحدث من القيم الأخرى نوعًا ما. وهي في الواقع خيار هجين بين الوضعين النسبي والثابت. تسمح هذه القيمة للعنصر بالتصرف وكأنه موضّع نسبيًا حتى تُمرر الصفحة إلى حد معين (مثل 10 بكسل عن أعلى نافذة العرض) ليصبح بعدها ثابتًا. مثال بسيط على Sticky يمكن استخدام الوضع اللاصق مثلًا في إنشاء قوائم تنقل يمكن تمريرها إلى حد معين ومن ثم تبقى في أعلى نافذة العرض. .positioned { position: sticky; top: 30px; left: 30px; } See the Pen positioning-css6 by Hsoub Academy (@HsoubAcademy) on CodePen. فهرس قابل للتمرير من الاستخدامات الشائعة والمهمة للوضع اللاصق هو إنشاء فهرس قابل للتمرير تبقى فيه العناوين ملتصقة بأعلى الصفحة عندما تصل إليها. ولكتابة شيفرة مثال كهذا جرّب ما يلي: <h1>Sticky positioning</h1> <dl> <dt>A</dt> <dd>Apple</dd> <dd>Ant</dd> <dd>Altimeter</dd> <dd>Airplane</dd> <dt>B</dt> <dd>Bird</dd> <dd>Buzzard</dd> <dd>Bee</dd> <dd>Banana</dd> <dd>Beanstalk</dd> <dt>C</dt> <dd>Calculator</dd> <dd>Cane</dd> <dd>Camera</dd> <dd>Camel</dd> <dt>D</dt> <dd>Duck</dd> <dd>Dime</dd> <dd>Dipstick</dd> <dd>Drone</dd> <dt>E</dt> <dd>Egg</dd> <dd>Elephant</dd> <dd>Egret</dd> </dl> تبدو شيفرة CSS قريبة من التالي: تتحرك العناصر <dt> في الانسياب الاعتيادي مع المحتوى عند تمريره، لكن بإضافة الخاصية position: sticky إلى هذه العناصر بالإضافة إلى الخاصية top ستعمل المتصفحات الحديثة على إبقاء العناوين في أعلى نافذة العرض عندما تبلغ هذا الموقع. سيستبدل كل عنوان لاحق العنوان السابق عندما يصل إليه وهكذا. dt { background-color: black; color: white; padding: 10px; position: sticky; top: 0; left: 0; margin: 1em 0; } See the Pen positioning-css7 by Hsoub Academy (@HsoubAcademy) on CodePen. لاحظ كيف تبقى العناصر اللاصقة لاصقةً بالنسبة إلى أقرب عنصر يمتلك آلية تحدد طريقة تمرير محتواه أي بعبارة أخرى له قيمة مخصصة للخاصية position. الخلاصة تعرّفنا في هذا المقال على الأوضاع المختلفة التي يمكن أن يأخذها العنصر وجرّبنا الكثير من الأوضاع، مع ذلك لا تعد هذه الطريقة مناسبًة لتخطيط الصفحات في الويب الحديث بل لها حالات استخدام مخصصة مفيدة كما رأينا. ترجمة -وبتصرف- للمقال: Positioning اقرأ أيضًا المقال السابق: الخاصية float: تعويم عناصر الصفحة في CSS التحكم في تخطيط الصفحة وضبط محاذاة العناصر في CSS أساسيّات التَمَوْضُع على صفحات الويب (CSS Positioning 101) مدخل إلى تَموضُع الخلفيّة Background Positioning في CSS مدخل إلى تخطيط صفحات الويب باستخدام CSS
  17. نناقش في هذا المقال العمليات الرياضية في جافا سكريبت وكيفية استخدام العوامل الرياضية وغيرها من اﻷفكار للتعامل مع اﻷعداد وصولًا إلى النتيجة المطلوبة. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS. الرياضيات للجميع بعضنا يحب إجراء الحسابات الرياضية، وبعضنا الآخر يكره الرياضيات منذ تلك اللحظة التي اضطر فيها تعلم جدول الضرب والقسمة المطوّلة في المدرسة والبعض اﻵخر ما بين بين. لكن الجميع متفق على أن الرياضيات أمر أساسي في حياتنا بشكل أو بآخر ولا يمكن الاستغناء عنها، وخاصة إن كنت مهتمًا بتعلم البرمجة. فالكثير من اﻷمور التي ننفذها في أكواد البرمجة تعتمد على معالجة بيانات عددية، أو حساب قيم جديدة. لهذا لا تتفاجأ إن علمت أن لغة جافا سكريبت تضم مجموعة متكاملة من الدوال الرياضية الجاهزة للاستخدام. وما نقدمه في هذا المقال مجرد تعريف بالميزات اﻷساسية التي لا بد من معرفتها. أنواع اﻷعداد في عالم البرمجة ستجد أن النظام العشري البسيط الذي خبرناه جميعًا أكثر تعقيدًا مما تعتقد، لهذا نستخدم مصطلحات مختلفة لوصف اﻷنواع المختلفة للأعداد في نظام العد هذا: الأعداد الصحيحة Integers: وهي أعداد ذات فاصلة عائمة لكن دون أجزاء عشرية (لا أرقام بعد الفاصلة) قد تكون موجبة أو سالبة مثل "34" أو "5-". اﻷعداد ذات الفاصلة العائمة Floating point numbers:ولها خانات عشرية وأجزاء عشرية (تضم أرقام بعد الفاصلة) مثل "12.5" و "23.3331". اﻷعداد المضاعفة Doubles: تمثل نمطًا خاصًا من اﻷعداد العائمة لكنها تمتلك دقة أكبر من اﻷعداد ذات الفاصلة العائمة المعيارية (أي أنها تمثل القيم ذات اﻷجزاء العشرية بدقة أكبر إذ تضم عددًا أكبر من اﻷرقام بعد الفاصلة). كما يوجد أكثر من نظام عد، فالنظام العشري هو نظام أساسه العدد 10 (أي يتكون أي عدد وفق النظام العشري من خانات مكوّنة من أرقام بين 0 إلى 9)، لكن هناك أنظمة أخرى مثل: النظام الثنائي Binary: يستخدم في لغات البرمجة منخفضة المستوى وتتكون أعداده من تتابع رقمين فقط 0 أو 1. النظام الثماني Octal: أساسه 8 أي يستخدم فقط اﻷرقام من 0 إلى 7. النظام الست عشري hexadecimal: أساسه 16 ويستخدم اﻷرقام من 0 إلى 9 والحروف من a إلى f (ربما اطلعت على هذا النظام عند ضبط اﻷلوان في CSS). لكننا كبداية سنلتزم بنظام العد العشري المألوف بالنسب لك خلال سلسلة مقالاتنا التمهيدية هذه، ولن تكون مضطرًا للتفكير ببقية اﻷنظمة. أما الخبر الجيد التالي فهو أن جافا سكريبت تعرّف نوعًا واحدًا للبيانات العددية هو number على عكس لغات برمجة أخرى فاﻷعداد الصحيحة والعائمة والمضاعفة هي مجرد أعداد في جافا سكريبت وتتعامل معها بنفس الطريقة تمامًا. ملاحظة: في الواقع هناك نوع آخر للأعداد في جافا سكريبت غير number وهو النوع BigInt وهو يستخدم للتعامل مع اﻷعداد الضخمة جدًا، لكننا لن نستخدمه في سلسلتنا هذه. كلها أعداد بالنسبة لجافا سكريبت سنجرّب العمل مع بعض اﻷعداد حتى نتعرف على الصياغة الصحيحة، لهذا ادخل إلى طرفية جافا سكريبت في متصفحك (اضغط "Ctrl" + "Shift" + "K" في متصفح فايرفوكس) واتبع الخطوات التالية: صرّح عن المتغيرين التاليين myInt و myFloat (ثابتين باﻷحرى) على الترتيب ثم اكتبهما في الطرفية من جديد للتأكد أن كل شيء على ما يرام: const myInt = 5; const myFloat = 6.667; myInt; myFloat; تُسند القيم العددية للمتغيرات دون أن تُحاط بإشارات تنصيص. تحقق اﻵن أن المتغيران لهما نفس النوع باستخدام العامل typeof يليها اسم المتغير كالتالي: typeof myInt; typeof myFloat; ومن المفترض أن تحصل على النتيجة numberفي الحالتين. هذا أفضل، كي نتعامل مع متغيرين لهما نفس النوع بدلًا من متغيرين من نوعين مختلفين لأن التعامل معهما سيكون مختلفًا. توابع رياضية مفيدة يمثل الكائن Number نموذجًا لجميع اﻷعداد التي تستخدمها في جافا سكريبت ويمتلك هذا الكائن مجموعة من التوابع المفيدة تساعدك على التعامل مع الأعداد. لن نستعرض جميع هذه التوابع في مقالنا ﻷننا نريده بسيطًا ويغطي فقط الأساسيات، لكن مع تقدمك في تعلم جافا سكريبت، لا بد من العودة مرارًا إلصفحة هذا الكائن لتطلع على التوابع اﻷخرى التي يقدّمها. فلكي تقرّب العدد ليضم عددًا محددًا من اﻷرقام بعد الفاصلة استخدم التابع ()toFixed. جرّب ذلك بكتابة السطر التالي في الطرفية: const lotsOfDecimal = 1.766584958675746364; lotsOfDecimal; const twoDecimalPlaces = lotsOfDecimal.toFixed(2); twoDecimalPlaces; التحويل إلى النوع number قد ينتهي بنا اﻷمر أحيانًا إلى مواجهة عدد مخزّن ضمن سلسلة نصية ومن الصعب حينها تنفيذ أية حسابات رياضية عليه. وعادة ما نواجه هذه الحالات عند استخلاص البيانات الموجودة في حقل إدخال نصي <input> ضمن نموذج <form>. وبالطبع هناك طريقة لحل هه المشكلة تتمثل في تمرير هذه القيمة إلى الكائن ()Number الذي يعيد نفس القيمة لكن كعدد وليس كسلسلة نصية. لفهم هذه الطريقة جرّب كتابة اﻷسطر التالية في الطرفية: let myNumber = "74"; myNumber += 3; ستلاحظ أن الجواب سيكون 743 بدلًا من 77، ذلك أن myNumber يعرف في الواقع سلسلة نصية. وللتأكد من ذلك جرّب ما يلي: typeof myNumber; لاحظ ماذا فعلنا لحل المشكلة: let myNumber = "74"; myNumber = Number(myNumber) + 3; سنحصل اﻻن على النتيجة المطلوبة 77. العوامل الحسابية في جافا سكريبت وهي العوامل الحسابية اﻷساسية التي تستخدم لتنفيذ الحسابات اﻷساسية كالجمع والطرح وغيرها: العامل اسمه الغاية منه مثال + الجمع إضافة عددين إلى بعضهما 6+9 - الطرح طرح العدد اﻷيمن من اﻷيسر 6-9 * الضرب ضرب عددين معًا 9*6 / القسمة قسمة العدد اﻷيسر على اﻷيمن 9/6 % باقي القسمة يعيد باقي قسمة العدد اﻷيسر على اﻷيمن 4%9 سيكون الناتج هو 1 ** الرفع إلى قوة يرفع العدد اﻷيمن إلى قوة هي العدد اﻷيسر 4**2 سيكون الناتج هو 16 ملاحظة1: تُدعى اﻷعداد التي تخضع للعمليات الحسابية بالمعاملات operands. ملاحظة2: قد ترى أحيانًا عملية رفع إلى قوة باستخدام التابع اﻷقدم ()Math.pow الذي يعمل بنفس الطريقة فالأمر (7,3)Math.pow يعطي نفس نتيجة الأمر 3**7. لن نعلّمك بالطبع كيف تجري الحسابات الرياضية لكن نريد اختبار فهمك لصياغة هذه العمليات باستخدام جافا سكريبت، لهذا افتح طرفية المتصفح واتبع الخطوات التالية: حاول بداية أن تنفذ بعض العمليات التي تختارها مثل: 10 + 7; 9 * 8; 60 % 3; بإمكانك أيضًا أن تجرب تصريح وتهيئة بعض المتغيرات بقيم عددية من اختيارك، ثم حاول استخدام هذه المتغيرات لتنفيذ عمليات حسابية، إذ ستسلك المتغيرات سلوك قيمها تمامًا. إليك بعض اﻷمثلة: const num1 = 10; const num2 = 50; 9 * num1; num1 ** 3; num2 / num1; حاول أخيرًا أن تختبر بعض العبارات اﻷكثر تعقيدًا مثل: 5 + 10 * 3; (num2 % 9) * num1; num2 + num1 / 8 + 2; قد لا تعطيك بعض الحسابات الأخيرة النتائج التي تتوقعها، وقد تجد جوابًا عن ذلك في القسم التالي. أفضلية العمليات الحسابية لنلق نظرة على المثال السابق ولنفترض أن المتغير num2 يضم القيمة 50 ويضم المتغير num1 القيمة 10 ولننفذ العملية التالية: num2 + num1 / 8 + 2; فقد يقرأ البعض هذه العملية بالشكل 50 زائد 10 يساوي 60 ثم * 8 زائد 2 يساوي 10 وأخيرًا 60 تقسيم 10 يساوي 6. لكن ما يفعله المتصفح هو التالي 10 تقسيم 8 يساوي 1.25 ثم 50 زائد 1.25 زائد 2 يساوي 53.25. السبب في ذلك أن المتصفح يحترم أفضلية العمليات الحسابية فبعض العمليات الحسابية تُتفّذ قبل أخرى. وأفضلية العمليات الحسابية في جافا سكريبت هي نفس اﻷفضلية التي تعلمناها في المدرسة أي الرفع إلى القوة أولًا ثم يأتي الضرب والقسمة بنفس اﻷهمية وأخيرًا الجمع والطرح مع الانتباه أن الحسابات تجري دائمًا من اليسار إلى اليمين. لإلغاء اﻷفضلية السابقة استخدم اﻷقواس فلها أعلى أفضلية، إذ يمكنك إحاطة العملية التي تريد أن تنفذها في البداية بين قوسين. فلكي تحصل على النتيجة 6 في المثال السابق يمكنك تغيير الشيفرة لتصبح كالتالي: (num2 + num1) / (8 + 2); جرب ذلك وسترى! عوامل الزيادة والنقصان قد تتطلب الحسابات أحيانًا إضافة نفس العدد أو طرحه بشكل مستمر من متغير عددي. يمكن أن ننجز ذلك مباشرة باستخدام العامل (++) للزيادة بواحد و (--) للإنقاص بواحد. وتذكر أننا استخدمنا العامل (++) في لعبة "خمّن الرقم" في مقال سابق. حيث زدنا قيمة المتغير بمقدار واحد لتتبع عدد المحاولات التي نفّذها اللاعب. guessCount++; جرّب هذا العوامل بنفسك وستلاحظ أنها لا تعمل مع عدد مباشرة وقد يبدو هذا غريبًا لكن ما يجري أننا نحدّث قيمة المتغيّر ولا ننفذ العملية على القيمة نفسها. سينتج خطأ عن التعليمة التالية: 3++; لهذا لا يمكن استخدام عامل الزيادة أو النقصان إلا مع المتغيرات: let num1 = 4; num1++; وهذا أمر غريب آخر يحدث، فالناتج سيبقى 4! والسبب أن المتصفح سيعيد القيمة الحالية ثم يضيف 1. تأكد من ذلك بطلب قيمة المتغير من جديد: num1; ينطبق اﻷمر أيضًا على عامل اﻹنقاص بواحد: let num2 = 6; num2--; num2; ملاحظة: بإمكانك إضافة أو إنقاص قيمة المتغير ثم عرض النتيجة بوضع عامل الزيادة أو النقصان قبل المتغير مثل num1++. جرّب ذلك! عوامل اﻹسناد وهي عوامل تسند قيمًا إلى المتغيرات وقد استخدمنا منها العامل اﻷساسي = كثيرًا حتى اﻵن، والذي يخزّن القيمة التي على يسار المساواة في المتغير الذي يقع إلى يسارها. let x = 3; //القيمة 3 x يضم المتغير let y = 4; //القيمة 4 y يضم المتغير x = y; //وهي 4 y قيمة المتغير x يتضمن المتغير لكن هناك أيضًا عوامل إسناد اخرى أكثر تعقيدًا وتزود المبرمج بطرق مختصرة لتبقى الشيفرة أكثر أناقة وأكثر فعالية. إليك قائمة بالعوامل اﻷكثر شيوعًا: العامل اسمه الغاية منه مثال اختصار لـ += إسناد وإضافة جمع قيمة المتغير إلى اليسار مع القيمة على اليمين وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x+=4; x=x+4 -= إسناد وطرح طرح القيمة إلى اليمين من قيمة المتغير على اليسار وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x-=4; x=x-4 *= إسناد وجداء جداء قيمة المتغير إلى اليسار مع القيمة على اليمين وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x*=4; x=x*4 /= إسناد وقسمة تقسيم قيمة المتغير إلى اليسار على القيمة على اليمين وإسناد الناتج إلى المتغير ويعيد قيمته الجديدة ;x/=4; x=x/4 جرّب استخدام بعض هذه العوامل في طرفية المتصفح كي تتعرف على طريقة عملها. حاول أن تخمّن في كل مرة القيمة التي ستنتج قبل أن تنتقل إلى سطر آخر. ولاحظ أنه باﻹمكان إسناد متغيرات إلى متغيرات أخرى بكل بساطة، إليك مثالًا" let x = 3; //القيمة 3 x يضم المتغير let y = 4; //القيمة 4 y يضم المتغير x *= y; // القيمة 12 x يتضمن المتغير تطبيق عملي: تحديد أبعاد صندوق رسم سنتعامل في هذا التطبيق مع عوامل حسابية ومعاملات مختلفة لتغيير أبعاد صندوق. يُرسم الصندوق باستخدام واجهة برمجية API تُدعى Canvas API. لا تقلق حاليًا حيال عمل الواجهة وركّز على الرياضيات. يُعرّف اتساع وارتفاع الصندوق عبر المتغيرين xو y وتقدر قيمتهما بالبكسل ويعطى كلاهما القيمة 50 في البداية. ستجد في صندوق الشيفرة القابلة للتعديل في الأعلى سطرين يطلب إليك تعديلهما كي تجعل الصندوق يتمدد ويتقلص إلى حجم محدد باستخدام عوامل محددة أو/و قيم في كل حالة، عليك أن تجرّب التالي: غير السطر الذي يحسب قيمة x كي يبقى اتساع الصندوق 50 بكسل، لكن يجب أن تُحسب القيمة 50 انطلاقًا من عددين هما 43 و 7 وباستخدام عملية حسابية. غير السطر الذي يحسب قيمة y كي يصبح ارتفاع الصندوق 75 بكسل، لكن يجب أن تُحسب القيمة 75 انطلاقًا من عددين هما 3 و 25 وباستخدام عملية حسابية. غير السطر الذي يحسب قيمة x كي يصبح اتساع الصندوق 250 بكسل، لكن يجب أن تُحسب القيمة 250 انطلاقًا من عددين وعملية باقي القسمة. غير السطر الذي يحسب قيمة y كي يصبح ارتفاع الصندوق 150 بكسل، لكن يجب أن تُحسب القيمة 150 انطلاقًا من ثلاثة أعداد وعمليتي طرح وقسمة. غير السطر الذي يحسب قيمة x كي يصبح اتساع الصندوق 200 بكسل، لكن يجب أن تُحسب القيمة 250 انطلاقًا من العدد 4 وعامل إسناد. غير السطر الذي يحسب قيمة y كي يصبح ارتفاع الصندوق 200 بكسل، لكن يجب أن تُحسب القيمة 250 انطلاقًا من العددين 50 و 3 وعمل الضرب وعامل اﻹسناد واﻹضافة. لا تقلق إن خرّبت الشيفرة، بل اضغط على الزر "Reset" في الأسفل ليعود كل شيء إلى ما كان عليه. حاول أن تجرّب أفكارًا أخرى مشابهة إن نجحت في اﻹجابة عن كل الأسئلة السابقة. عوامل الموازنة لا بد في بعض اﻷحيان من كتابة بعض الاختبارات التي تكون نتيجتها صحيح أو خاطئ كي ننفذ شيفرة ما وفقًا لنتيجة الاختبار. لهذا نستخدم عوامل الموازنة: العامل اسمه الغاية منه مثال === مساواة تامة يختبر إن كان القيمتين على يسار ويمين العامل متطابقتين تمامًا 4+3==5 ==! لا مساواة تامة يختبر إن كان القيمتين على يسار ويمين العامل غير متطابقتين تمامًا 2+3=!5 > أصغر من يختبر إن كانت القيمة على يمين العامل أصغر تمامًا من القيمة إلى يساره 10<6 < أكبر من يختبر إن كانت القيمة على يمين العامل أكبر تمامًا من القيمة إلى يساره 6>10 => أصغر من أو يساوي يختبر إن كانت القيمة على يمين العامل أصغر أو تساوي القيمة إلى يساره 3=>2 => أكبر من أو يساوي يختبر إن كانت القيمة على يمين العامل أكبر أو تساوي من القيمة إلى يساره 3=<2 ملاحظة: قد ترى أن البعض يستخدم في اختبارات التساوي == أو عدم التساوي =!، وهي عوامل صحيحة في جافا سكريبت لكنها مختلفة عن ===/==!. فالأولى تختبر إن كانت القيم متساوية لكنها لا تختبر إن كانت القيم من نفس النوع. أما النسخة الثانية اﻷكثر صرامة فتختبر كلًا من القيمة ونوعها وينتج عنها عادة أخطاء أقل لهذا ننصح باستخدامها. إن حاولت تجريب بعض القيم السابقة في الطرفية سيكون الناتج إما true أو false وهي القيم المنطقية booleans التي ذكرناها في المقال السابق. ولهذه القيم الكثير من الفوائد فهي تسمح باتخاذ القرارات في شيفرتنا، إذ تُستخدم في كل مرة تحتاج فيها إلى اتخاذ قرار ما. ويمكن استخدام القيم المنطقي على سبيل المثال في: إظهار العنوان المناسب على زر وذلك إن كانت ميزة ما مفعّلة أو معطّلة. إظهار رسالة "انتهت اللعبة" عندما تنتهي أو رسالة فوز إن ربح اللاعب. عرض التحية المناسبة وفقًا لموسم العطلات. تقريب الخريطة وفقًا لمستوى التقريب الذي تختاره. سنرى لاحقًا كيفية كتابة منطق الاستخدامات السابقة عند المرور على العبارات الشرطية لاحقًا، لكن لا بد أن نلق نظرة اﻵن على مثال سريع: <button>Start machine</button> <p>The machine is stopped.</p> const btn = document.querySelector("button"); const txt = document.querySelector("p"); btn.addEventListener("click", updateBtn); function updateBtn() { if (btn.textContent === "Start machine") { btn.textContent = "Stop machine"; txt.textContent = "The machine has started!"; } else { btn.textContent = "Start machine"; txt.textContent = "The machine is stopped."; } } لاحظ كيف استُخدم عامل المساواة ضمن الدالة ()updateBtn. لا نختبر في هذه الحالة تطابق قيمتي تعبيرين رياضيين، بل نختبر إذا ما كان محتوى عنوان زر يتضمن نصًا محددًا، لكن المبدأ يبقى ذاته. فإن كان عنوان الزر "Start machine" عند النقر عليه سنغيره إلى "Stop machine" ومن ثم نحدّث العنوان أسفله بما يناسب. بينما إن كان عنوان الزر "Stop machine" عند النقر عليه نستبدله مجددًا. ملاحظة: يُشار عادة إلى عملية التبديل تلك باسم "الانتقال toggle". الخلاصة غطينا في هذا المقال الأساسيات التي تحتاجها للتعامل مع الأعداد في جافا سكريبت. ولأنك ستتعامل مع الأعداد كثيرًا جدًا في رحلة تعلم جافا سكريبت، لا بد أن تتعلم هذه الأساسيات على بساطتها وتتقنها بشطكل جيد قبل المُضيّ قدمًا في رحلة تعلم واحتراف لغة جافا سكريبت. ترجمة -وبتصرف- لمقال Basic math in JavaScript-Numbers and Operators اقرأ أيضًا المقال السابق: المتغيرات وتخزين البيانات في جافا سكريبت تجربتك اﻷولى مع جافا سكريبت تعرّف على أساسيات لغة جافا سكريبت من منظور عام تعلم لغة جافا سكريبت من الصفر حتى الاحتراف
  18. استُخدمت الخاصية float بشكل أساسي لتعويم الصور ضمن كتل نصية، لكن شاع استخدامها كثيرًا في إنشاء تخطيطات متعددة الأعمدة لصفحات ويب. لكن مع ظهور الخاصيتين flex و Grid عاد استخدام تلك الخاصية إلى الغاية الأساسية منه كما سنوضح في هذا المقال: عليك قبل البدء في قراءة هذا المقال أن: تطلع على أساسيات HTML كما شرحناها في سلسلة المقالات مدخل إلى HTML. تتفهم أساسيات عمل CSS. فكرة عن العناصر المعوّمة وضعت الخاصية كي تسمح لمطوري الويب إنجاز تخطيطات تتضمن صورًا تعوم داخل عمود نصي، يلتف فيه النص إلى يمين أو يسار هذه الصورة، كما هو الحال في أعمدة الصحف. لكن سرعان ما أدرك مطوري الويب إمكانية تعويم أية عناصر وليس الصور فقط، لهذا توسّع استخدام هذه الخاصية لتشمل تأثيرات جمالية على التخطيط مثل كتابة أحرف استهلالية كبيرة. لقد شاع استخدام التعويم لإنشاء تخطيطات كاملة لمواقع الويب تعرض محتوى موزعًا على عدة أعمدة محاذية لبعضها (تظهر الأعمدة افتراضيًا تحت بعضها بنفس ترتيب ظهورها في الشيفرة المصدرية). لكن ظهرت حديثًا تقنيات أفضل لتخطيط الصفحات واعتبر استخدام التعويم لإنشاء هذا النوع من التخطيطات قديمًا. سنركز في مقالنا على الاستخدام الأنسب للعناصر العائمة. مثال عن التعويم نكتشف في هذا المقال استخدامات التعويم وسنبدأ بكتلة نصية عائمة حول عنصر. بإمكانك العمل على هذا المثال بإنشاء ملف يحمل الاسم index.html على جهازك ومن ثم وضع قالب HTML الخاص بمثالنا ضمنه، وبعدها ضع الشيفرة التالية في مكان مناسب ضمن الملف. ستجد في نهاية هذه الفقرة مثالًا حيًا عما تفعله الشيفرة. سنبدأ أولًا بشيفرة HTML، لذلك أضف الشيفرة التالية ضمن العنصر <body> بعد إزالة أية شيفرة موجودة ضمنه: <h1>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. </p> <p> 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> <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. 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> طبّق الآن شيفرة CSS التالية بوضعها ضمن العنصر <style> أو وضعها ضمن ملف css. واربطها بملف HTML من خلال العنصر <link> على النحو التالي: body { width: 90%; max-width: 900px; margin: 0 auto; font: 0.9em/1.2 Arial, Helvetica, sans-serif; } .box { width: 150px; height: 100px; border-radius: 5px; background-color: rgb(207, 232, 220); padding: 1em; } عندما تحفظ التغييرات التي أجريتها على الملفات ثم تعيد تحميل الصفحة، سترى نتيجة مطابقة لما هو متوقع، فالصندوق فوق النص مباشرة وهذا هو الأمر الطبيعي في تخطيط الانسياب الاعتيادي. تعويم الصندوق لتعويم الصندوق أضف الخاصيتين float و margin-right إلى القاعدة box.: .box { float: left; margin-right: 15px; width: 150px; height: 100px; border-radius: 5px; background-color: rgb(207, 232, 220); padding: 1em; } احفظ التغييرات التي أجريتها وأعد تحميل الصفحة وسترى النتيجة التالية: See the Pen float1 by Hsoub Academy (@HsoubAcademy) on CodePen. لنفكر قليلًا كيف تعمل فكرة التعويم. يأخذ العنصر الذي يحمل الخاصية float خارج الانسياب الاعتيادي (العنصر <div> في مثالنا) ويوضع في الناحية اليُسرى من الحاوية (العنصر <body> في حالتنا). ويحيط المحتوى الذي كان في تخطيط الانسياب الاعتيادي تحت العنصر العائم بهذا العنصر شاغلًا المساحة التي تقع على يمينه ابتداءً من أعلى نقطة من نقاط العنصر العائم. إن تعويم العنصر إلى اليمين له التأثير نفسه تمامًا، لكن بطريقة معكوسة، فسيتوضع العنصر العائم إلى يمين العنصر الحاوي ويلتف حوله المحتوى شاغلًا المساحة الواقعة إلى يساره. حاول أن تغيّر قيمة الخاصية float إلى right واستبدل الخاصية margin-right بالخاصية margin-left في آخر القاعدة السابقة وراقب ما الذي سيحدث إظهار التعويم بإمكانك أن تضيف الهوامش إلى العنصر العائم كي يدفع النص بعيدًا عنه قليلًا، لكن لا يمكنك إضافة هوامش إلى النص كي يبتعد عن العنصر العائم. يعود السبب في ذلك أن العنصر العائم يخرج من تخطيط الانسياب الاعتيادي وستُعرض العناصر التالية له خلفه مباشرة. لإظهار الأمر سنغيّر قليلًا في الشيفرة السابقة بإضافة صنف جديد special إلى الفقرة النصية الأولى من النص التي تأتي بعد الصندوق العائم مباشرة. أضف الشيفرة التالية إلى شيفرة CSS والتي تمنح الفقرة النصية المستهدفة لونًا للخلفية: .special { background-color: rgb(79, 185, 227); padding: 10px; color: #fff; } ولكي يبدو التأثير المطلوب أكثر وضوحًا، غير الخاصية margin-right للعنصر العائم لتصبح margin وبالتالي ستحصل على نفس الهوامش لكن من الجوانب الأربعة للعنصر. سترى أن خلفية الفقرة النصية تمتد خلف العنصر العائم كالتالي: See the Pen float2 by Hsoub Academy (@HsoubAcademy) on CodePen. اختُزلت مساحة صندوق النص للعنصر الذي يلي العنصر العائم حتى يلتف النص حوله، لكن صندوق العنصر ككل يحافظ على اتساعه كاملًا لأن العنصر العائم قد انتزع من الانسياب الاعتيادي. إخلاء محيط العناصر العائمة رأينا كيف يُنتزع العنصر العائم خارج تخطيط الانسياب الاعتيادي، وتُعرض العناصر التالية إلى جانبه. لكن إن أردنا تعويم عنصر دون أن يرتفع محتوى العنصر التالي إلى الأعلى، لا بد من إخلاء محيط العنصر العائم باستخدام الخاصية clear. أضف الصنف cleared إلى الفقرة النصية الثانية في ملف HTML السابق وضع ضمنه التصريح التالي: .cleared { clear: left; } See the Pen float3 by Hsoub Academy (@HsoubAcademy) on CodePen. سترى أن الفقرة النصية الثانية قد انتقلت إلى أسفل العنصر العائم مخلّية المساحة إلى يمينه. تقبل الخاصية القيم التالية: left: إخلاء محيط العنصر العائم إلى اليسار. right: إخلاء محيط العنصر العائم إلى اليمين. both: إخلاء محيط العنصر العائم سواءً إلى اليمين أو اليسار. إخلاء الصناديق الملتفة حول عنصر عائم تعلمنا في الفقرة السابقة كيفية إخلاء العناصر التي تلي العنصر العائم، لكن ما الذي سيحدث عندما يكون لدينا عنصر عائم طويل وفقرة نصية قصيرة وكلاهما ضمن نفس الصندوق؟ توضيح المشكلة غير ملف HTML كي يصبح العنصر العائم والفقرة النصية الأولى ضمن حاوية <div> واحدة تمتلك الصنف wrapper: <div class="wrapper"> <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. </p> </div> ضع القواعد التالية ضمن الصنف wrapper. ثم أعد تحميل الصفحة: .wrapper { background-color: rgb(79, 185, 227); padding: 10px; color: #fff; } أزل أيضًا الصنف cleared.: .cleared { clear: left; } لاحظ كيف عدنا إلى الحالة التي وضعنا فيها خلفية للفقرة النصية الأولى، إذ يمتد الصندوق خلف العنصر العائم: See the Pen float4 by Hsoub Academy (@HsoubAcademy) on CodePen. نذكر مجددًا أن هذا قد حدث نظرًا لانتزاع العنصر من انسيابه الاعتيادي. قد تتوقع أنك بتغليف العنصر العائم والفقرة النصية في حاوية واحدة سيُخلي العنصر التالي (الفقرة الثانية) المساحة حول العنصر العائم تلقائيًا، لكن هذا لم يحدث. وللتعامل مع الأمر عليك استخدام الأسلوب المعياري المُسمى سياق تنسيق الكتل block formatting context واختصارًا (BFC) بالاعتماد على الخاصية display. القاعدة display:flow-root تُستخدم هذه القاعدة فقط لإنشاء سياق لتنسيق الكتل دون أية مشاكل أو تبعات. لإنجاز الأمر، أزل القاعدة overflow: auto من الصنف wrapper. ثم أضف القاعدة display: flow-root وسترى كيف يخلي العنصر التالي المساحة إلى جوار الصندوق العائم: .wrapper { background-color: rgb(79, 185, 227); padding: 10px; color: #fff; display: flow-root; } See the Pen float6 by Hsoub Academy (@HsoubAcademy) on CodePen. خلاصة تعلمنا في هذا المقال كل ما يلزم معرفته حول تعويم العناصر في تصميمات الويب الحديثة، وسنتابع في مقال لاحق استخدامات قديمة أخرى من المهم أن تطلع عليها إن اضطررت للعمل على مشاريع قديمة. ترجمة -وبتصرف- للمقال: Floats اقرأ أيضًا المقال السابق: تخطيط صفحات ويب باستخدام تخطيط الشبكة Grid في CSS مدخل إلى تخطيط صفحات الويب باستخدام CSS تعرف على أساسيات CSS تقنيات كتابة شيفرات CSS احترافية وسهلة الصيانة التحكم في تموضع العناصر في CSS
  19. ربما كوّنت فكرة بعد قراءتك للمقالات السابقة حول أساسيات جافا سكريبت عما يمكن لهذه اللغة فعله، وكيفية استعمالها مع بقية تقنيات الويب، وكيف تبدو ميزاتها من منظور عام. لهذا نحاول في هذا المقال الاقتراب قليلًا من اﻷساسيات ونتعلم المزيد حول العمل مع المتغيرات وهي الكتل البرمجية اﻷبسط في جافا سكريبت. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسوب. أساسيات HTML. أساسيات عمل CSS. الأدوات التي تحتاجها سنطلب إليك مع تقدم مقالنا كتابة بعض أسطر الشيفرة لنختر فهمك لما شرحناه. فإن كنت تستخدم متصفح حاسوب مكتبي، ستجد أن أفضل مكان لكتابة الشيفرة هو طرفية جافا سكريبت في المتصفح Web Console التي تمكّنك من التفاعل مع صفحة الويب عن طريق تنفيذ تعبيرات جافا سكريبت في سياق الصفحة (اضغط اﻷزرار "Ctrl" + "Shift" + "K" معًا لفتحها في متصفح فايرفوكس). المتغيّرات في جافا سكريبت يُعرّف المتغير بأنه حاوية تضم قيمة قد تكون عددًا يمكن أن نستخدمه لاحقًا في عملية جمع، أو سلسلة نصية يمكن أن نستخدمها كجزء من جملة. أمثلة عن المتغيرات لنلق نظرة على هذا المثال البسيط: <button id="button_A">Press me</button> <h3 id="heading_A"></h3> const buttonA = document.querySelector("#button_A"); const headingA = document.querySelector("#heading_A"); buttonA.onclick = () => { const name = prompt("What is your name?"); alert(`Hello ${name}, nice to see you!`); headingA.textContent = `Welcome ${name}`; }; See the Pen js-variables 1 by Hsoub Academy (@HsoubAcademy) on CodePen. عند النقر على الزر في هذا المثال سينفذ المتصفح بعض الشيفرة. إذ يعرض السطر اﻷول صندوقًا على الشاشة يطلب من المستخدم إدخال اسمه ومن ثم يخزّن الاسم كقيمة ضمن متغيّر. ويعرض السطر الثاني رسالة ترحيب تتضمن اسم المستخدم وقد أُخذ من المتغيّر في السطر السابق. أما السطر الثالث فيعرض الاسم على الصفحة. كيف سيكون الوضع دون متغيّرات؟ لكي نفهم الفائدة الكبيرة من استخدام المتغيرات، دعونا نفكّر بطريقة لكتابة شيفرة المثال السابق دون استعمال المتغيرات. سينتهي بنا اﻷمر إلى شيفرة من هذا القبيل: <button id="button_B">Press me</button> <h3 id="heading_B"></h3> const buttonB = document.querySelector("#button_B"); const headingB = document.querySelector("#heading_B"); buttonB.onclick = () => { alert(`Hello ${prompt("What is your name?")}, nice to see you!`); headingB.textContent = `Welcome ${prompt("What is your name?")}`; }; See the Pen js-variables 2 by Hsoub Academy (@HsoubAcademy) on CodePen. ربما لن تدرك جيدًا الصياغة التي استخدمناها حاليًا، لكن لا بد وأن تكون قد استوعبت الفكرة. فإن لم يكن لديك متغيرات، ستسأل المستخدم كل مرة عن اسمه إن احتجته في الشيفرة. إذًا من المنطقي استخدام المتغيرات، وستألفها مع تقدمك في تعلم جافا سكريبت. ومن المهم أن تعرف أنك قادر على تخزين أي شيء تقريبًا في المتغيرات وليس فقط النصوص والأرقام. فقد تضم المتغيرات بنى مركّبة من البيانات وحتى دالة بأكملها، وهذا ما ستتعلمه خلال تعلم لغة جافا سكريبت. ملاحظة: لقد أشرنا أن المتغيرات تضم قيمًا أي أن المتغيرات ليست قيمًا بحد ذاتها بل حاويات للقيم. يمكن أن تشبهها بعلبة من الكرتون تضع اﻷغراض بداخلها. التصريح عن المتغيرات لا بد من إنشاء المتغير قبل استخدامه، ندعو هذه العملية "تصريحًا عن المتغيّر". وكي نفعل ذلك في جافا سكريبت، نكتب الكلمة let يليها الاسم الذي تريده للمتغير كما يلي: let myName; let myAge; أنشأنا في الشيفرة السابقة متغيرين وأسميناهما myName و myAge. جرّب أن تكتب هذين السطرين في طرفية المتصفح ثم صرّح عن متغير أو أكثر وسمِّهما بالاسم الذي تريده. ملاحظة: يجب أن تنتهي جميع التعليمات في جافا سكريبت بفاصلة منقوطة (;)، فقد تعمل شيفرتك إن أهملتها سطرًا واحدًا لكنها لن تعمل إن حاولت كتابة عدة أسطر من الشيفرة دونها. لهذا تعود على استخدامها في نهاية كل تعليمة. بإمكانك اختبار وجود المتغيّر في بيئة التنفيذ أم لا بكتابة اسمه: myName; myAge; لا قيم حاليًا للمتغيرين السابقين، بل يمثلان حاويتان فارغتان. وعندما تضغط الزر "Enter" في الطرفية ستحصل على النتيجة undefined، لكن إن لم يكن المتغير موجودًا فستحصل على رسالة خطأ. جرّب أن تكتب: name; ملاحظة: لا تخلط بين متغيّر مصرّح عنه ولم تُسند له قيمة، ومتغير غير موجود أصلًا فهما أمران مختلفان تمامًا. فلو عدنا لمثال الصندوق، سيكون عدم وجود المتغير عدم وجود الصندوق، أما التصريح عن المتغير وعدم إسناد قيمة له فيعني أن الصندوق موجود لكنه فارغ. تهيئة المتغيّر حالما تصرح عن المتغير يمكنك تهيئته بإسناد قيمة له. نفّذ ذلك بكتابة اسم المتغيّر تليه إشارة المساواة (=) ثم القيمة التي تريدها. إليك مثالًا: myName = "Chris"; myAge = 37; جرّب أن تعود إلى طرفية المتصفح واكتب الشيفرة السابقة، ومن المفترض أن ترى بعدها كيف تعيد الطرفية القيمة التي أسندتها إلى المتغير. ونذكرّك بإمكانية عرض قيمة المتغير بمجرد كتابة اسمه في الطرفية. جرّب مجددًا الشيفرة التالية: myName; myAge; باﻹمكان أيضًا التصريح عن المتغير وتهيئته في نفس الوقت كالتالي: let myDog = "Rover"; وهذا ما ستفعله غالبًا لأنها طريقة أسرع. ملاحظة حول المتغيرات قد تصادف أيضًا طريقة مختلفة في التصريح عن المتغيرات وذلك باستخدام التعليمة var كما يلي: var myName; var myAge; لقد كانت هذه الطريقة هي الطريقة الوحيدة للتصريح عن المتغيرات في بدايات جافا سكريبت، وقد وجدت أنها طريقة مربكة أثناء الممارسة لهذا استبدلت لاحقًا بالتعليمة letفي النسخ الأحدث من جافا سكريبت، وهي تعليمة ﻹنشاء المتغيرات بشكل مختلف نوعًا ما عن var وتحل بعض المشاكل التي نتجت عنها. سنشرح تاليًا بعض نقاط الاختلاف بين التعليمتين لكننا لن نخوض بها جميعًا في الوقت الراهن، بل ستكتشف ذلك أثناء تقدمك في تعلم جافا سكريبت. لو كتبنا برنامج جافا سكريبت مكوّن من عدة أسطر تصرّح وتهيئ متغيرًا، بإمكانك استخدام التعليمة var للتصريح عن المتغيّر حتى بعد تهيئته وسيعمل! إليك مثالًا: myName = "Chris"; //تصريح وتهيئة متحول function logName() { console.log(myName); } logName(); var myName; //التصريح عن نفس المتحول من جديد ملاحظة: لن يعمل هذا المثال إن كتبت اﻷسطر السابقة سطرًا سطرًا في الطرفية، بل فقط إن نفذتها معًا في صفحة ويب. تعمل الشيفرة السابقة بسبب عملية تقديم أو رفع الكائن hoisting، لكنها لن تنفع مع التعليمة let. جرّب تبديل var بالتعليمة let في الشيفرة السابقة وستخفق العملية مع رسالة خطأ، وهذا أمر جيد، لأن تصريح متغير بعد تهيئته سابقًا مربك كثيرًا ويولد شيفرة صعبة الفهم. ومن ناحية ثانية، يمكن التصريح عن المتحول نفسه عدة مرات باستخدام var لكنك لن تستطيع ذلك مع let.ستعمل مثلًا الشيفرة التالية: var myName = "Chris"; var myName = "Bob"; لكن الشيفرة التالية ستعطي خطأً في السطر الثاني: let myName = "Chris"; let myName = "Bob"; وعليك أن تعيد كتابة الشيفرة لتصبح بالشكل: let myName = "Chris"; myName = "Bob"; ونؤكد أن وجود letهو قرار لغوي معقول، فلا معنى لتعريف المتغيرات مرة أخرى لأنها تربك القارئ. لهذه اﻷسباب وغيرها ننصحك باستخدام let في الشيفرة إلا إن كنت ستكتب صراحة شيفرة تدعم المتصفحات القديمة لأن جميع المتصفحات الحديثة تدعمها منذ عام 2015. ملاحظة: إن كنت ستجرّب الشيفرة التالية ضمن طرفية المتصفح فانسخها والصقها ككتلة واحدة. وفي متصفح كروم هنالك ميزة تسمح لك بإعادة تصريح المتغيرات باستخدام let وconst. > let myName = "Chris"; let myName = "Bob"; // SyntaxError: Identifier 'myName' has already been declared إن أدخلت الشيفرة سطرًا سطرًا: ستحصل على الخطأ > let myName = "Chris"; > let myName = "Bob"; // As two inputs: both succeed إن أدخلتهما معًا ستنجح العملية تحديث متغيّر بمجرّد أن تهيئ المتغير يمكنك تغيير قيمته مجددًا (تحديثها) بإسناد قيمة أخرى له. جرّب إدخال الأسطر التالية في طرفية المتصفح: Name = "Bob"; myAge = 40; نظرة إلى قواعد تسمية المتغيرات يمكنك أن تسمي متغيرك بأي اسم تريد مع وجود بعض القيود. وعمومًا التزم باستخدام المحارف اللاتينية (0-9, a-z, A-Z) ومحرف الشرطة السفلية (_). لا تستخدم محارف أخرى لأنها قد تسبب أخطاءً أو تجعل الشيفرة صعبة الفهم للقارئين حول العالم. لا تستخدم الشرطة السفلية في بداية اسم المتغيّر لأنه أسلوب مستخدم في بعض بنى جافا سكريبت وله دلالة خاصة. لا تستخدم أرقامًا في بداية الاسم، فهذا غير مسموح ويسبب خطأ. من العادات الآمنة أن تلتزم بحرف صغير في بداية الاسم، وإن أردت ضم عدة كلمات لتعيين اسم المتغير اكتب أحرف الكلمة اﻷولى جمعها بأحرف صغيرة ثم اجعل بدايات الكلمات التالية بأحرف كبيرة. وهذا ما استخدمناه في مقالنا حتى اﻵن. اجعل أسماء المتغيرات واضحة وتصف البيانات التي تخزّنها، ولا تستخدم أحرفًا مفردة أو عبارات طويلة. انتبه إلى أن المتغيرات حساسة لحالة اﻷحرف فالمتغير myAge مختلف عن myage. نقطة أخيرة: تجنب استخدام كلمات جافا سكريبت المحجوزة (التعليمات) كأسماء متغيرات مثل var و function و letو for، فلن يميزها المتصفح كمتغيرات وستظهر اﻷخطاء مباشرة. ملاحظة: إليك قائمة بالكلمات المحجوزة التي لا ينبغي استخدامها كأسماء متغيرات. وإليك بعض اﻷمثلة عن التسمية الجيدة للمتغيرات: age myAge init initialColor finalOutputValue audio1 audio2 وأمثلة عن التسمية السيئة: 1 a _12 myage MYAGE var Document skjfndskjfnbdskjfb thisisareallylongvariablenameman جرّب أن تسمي بعض المتغيرات وفقًا للنقاط التي ناقشناها سابقًا. أنواع المتغيرات توجد عدة أنواع للبيانات التي يمكن أن نخزنها ضمن المتغيرات، وسنناقشها باختصار في هذا القسم ونتعمق بها في مقالات لاحقة. لهذا سنلقي نظرة على أول نوعين. اﻷعداد بإمكانك تخزين اﻷعداد ضمن المتغيرات كاﻷعداد الصحيحة مثل (30) أو الأعداد العشرية مثل (2.234) وتُدعى أيضًا (أعداد عائمة float أو أعداد ذات فاصلة عائمة). ولا حاجة للتصريح عن نوع المتغير في جافا سكريبت على عكس العديد من لغات البرمجة اﻷخرى. وعندما تُخزّن أعدادًا ضمن المتغير لا داعي لإحاطة العدد بعلامتي تنصيص. let myAge = 17; النصوص (السلاسل النصية) السلاسل النصية هي مقاطع من نصوص، وعندما تخزّنها ضمن متغيّر لا بد من إحاطتها بعلامتي تنصيص مفردتين '' أو مزدوجتين "". فإن لم تفعل ذلك، تحاول جافا سكريبت تفسيرها كأسماء لمتغيرات أخرى. let dolphinGoodbye = "So long and thanks for all the fish"; القيم المنطقية وهي إحدى القيمتين true أو false( صحيح أو خاطئ). وتستخدمان عادة في اختبار تحقق شرط ما، ثم تنفيذ شيفرة بناءً على نتيجة الشرط. إليك مثالًا: let iAmAlive = true; لكن ما ستراه في الواقع شيفرة كهذه: let test = 6 < 3; وقد استخدم العامل (<) لاختبار إن كان الرقم 6 أصغر من 3 ثم خُزنت النتيجة false في المتغيّر. سترى ذلك بتفاصيل أكثر لاحقًا. المصفوفات تُعرّف المصفوفة بأنها كائن في جافا سكريبت يضم عدة قيم تفصل بينها فاصلة , ضمن قوسين مرّبعين []. جرّب إدخال الأسطر التالية في الطرفية: let myNameArray = ["Chris", "Bob", "Jim"]; let myNumberArray = [10, 15, 40]; بمجرّد أن تعرّف المصفوفات، تستطيع الوصول إلى أي قيمة من القيم المخزنة ضمنها باﻹشارة إلى موقعها. جرّب ما يلي: myNameArray[0]; //'Chris' تعيد myNumberArray[2]; // 40 تعيد تحدد اﻷقواس المربعة بعد اسم المصفوفة ترتيب القيمة الموافق لموقعها في المصفوفة، وانتبه إلى أن المصفوفات في جافا سكريبت تبدأ من الصفر. الكائنات تمثل الكائنات في لغات البرمجة بنية من الشيفرة تنمذج شيئًا في الواقع. فقد تنمذج كائنًا بسيطًا مثل صندوق يتضمن معلومات مثل طوله وعرضه وارتفاعه، أو قد يمثل الكائن شخصًا ويتضمن اسمه وطوله ووزنه واللغات التي يتكلمها وكيف ستقول "مرحبًا" له وهكذا. جرّب الشيفرة التالية في الطرفية: let dog = { name: "Spot", breed: "Dalmatian" }; وكي تسترجع المعلومات المخزنة في كائن، يمكنك استخدام الصياغة التالية: dog.name; لن نشرح أكثر عن الكائنات هنا، بل سنترك اﻷمر لمقالات أخرى. التحديد التلقائي للنوع تُحدد جافا سكريبت النوع تلقائيًا dynamically typed language ويعني ذلك أنك لن تحتاج إلى تحديد نوع البيانات التي تخزنها (أعداد، سلاسل نصية، مصفوفات) في متغير على عكس لغات أخرى. فإن صرحت عن متغير وأسندت إليه قيمة ضمن إشارتي تنصيص سيفهم المتصفح أن المتغير هو سلسلة نصية. let myString = "Hello"; وحتى لو كان ما داخل إشارتي التنصيص مجرد أرقام سيبقى نوع المتغير نصيًا فانتبه إلى ذلك: let myNumber = "500"; // المتغير من النوع النصي هنا typeof myNumber; myNumber = 500; // اﻵن يصبح المتغير عددًا typeof myNumber; جرّب أن تدخل الأسطر اﻷربعة السابقة في الطرفية سطرًا تلو اﻵخر وراقب النتيجة. لاحظ أننا نستخدم عامل خاص هو typeofيعيد نوع المتغيّر الذي تذكره بعده. فعندما استُدعي لأول مرة أعاد القيمة string لأن المتغير myNumber حتى لحظة كتابته كان نصيًا، لكن عندما استدعيته في المرة الثانية إعادة القيمة number. الثوابت في جافا سكريبت يمكن التصريح عن الثوابت constants في جافا سكريبت كما تصرّح عن المتغيرات لكن باستخدام التعليمة const مع بعض الاستثناءات: عليك تهيئة الثابت عندما تعرّفه. لا يمكن أن تسند لها قيمة أخرى بعد تهيئتها. إذ يمكنك مثلًا التصريح عن المتغير باستخدام letدون أن تهيئه: let count; لكن إن حاولت أن تفعل ذلك باستخدام const سترى رسالة خطأ: const count; يمكنك بعد التصريح عن متغير باستخدام let أن تهيئه في خطوة منفصلة (تُدعى بعملية إعادة اﻹسناد re-assigning): let count = 1; count = 2; لكن إن حاولت ذلك عند استخدام const سترى رسالة خطأ أيضًا: const count = 1; count = 2; وعلى الرغم من أن الثوابت في جافا سكريبت تشير دائمًا إلى نفس القيمة إلا أنه يمكن تغيير محتوى هذه الثوابت في بعض اﻷنواع مثل الكائنات. إليك مثالًا: const bird = { species: "Kestrel" }; console.log(bird.species); // "Kestrel" بإمكانك تحديث أو إضافة أو إزالة خاصيات من كائن حتى لو كان مصرّحًا عنه باستخدام const، فحتى لو تغير المحتوى في هذه الحالة، فسوف يشير الثابت دائمًا إلى نفس الكائن. bird.species = "Striated Caracara"; console.log(bird.species); // "Striated Caracara" متى نستخدم الثوابت والمتغيرات؟ لماذا نستخدم const إن لم تكن تفعل الكثير موازنة بالتعليمة let؟ لأن الواقع يقول أنها ذات فائدة كبيرة. فلو اطلع أي قارئ على شيفرة عُرّف فيها ثابت باستخدام const سيعرف بالتأكيد أن هذه القيمة لن تتغير لاحقًا وسيرتبط اسم هذا الثابت بقيمة ثابتة دائمًا. نتبنى في سلسلة مقالاتنا القاعدة التالية بخصوص let و const وهي كالتالي: استخدم const عندما تستطيع و let عندما يجب عليك ذلك. أي إن كنت تستطيع إسناد قيمة للمتغير عند التصريح عنه ولن تحتاج لتغييرها فصرّح عنه كثابت وإلا صرّح عنه كمتغير. الخلاصة تعرفنا في هذا المقال على المتغيرات والثوابت في لغة جافا سكريبت وأصبحت تمتلك معرفة لا بأس بها حول إنشائها واستخدامها، وسنكمل في مقالات أخرى تفصيل أنواع هذه المتغيرات وطرق استخدامها والتعامل معها. ترجمة -وبتصرف- لمقال Storing the information you need-Variables اقرأ أيضًا المقال السابق: الدوال وإعادة استخدام الشيفرة في جافا سكريبت تجربتك اﻷولى مع جافا سكريبت تعلم لغة جافا سكريبت من الصفر حتى الاحتراف أساسيات لغة جافاسكربت
  20. تُعد الدوال Functions من المفاهيم اﻷساسية في كتابة الشيفرة. إذ تسمح لك الدوال بتخزين عدة أسطر أو تعليمات تنفذ مهمة معينة ضمن كتلة معرّفة مسبقًا، وعندما تحتاج هذه الشيفرة في أي مكان تستدعي هذه الكتلة عبر تعليمة واحدة مختصرة، بدلًا من كتابة هذه الشيفرة عدة مرات. نتعرف إذًا في هذا المقال على المفاهيم الأساسية التي تعتمد عليها الدوال مثل الصياغة وطريقة التعريف والاستدعاء ومجال الرؤية والمعاملات. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب. أساسيات HTML. أساسيات عمل CSS أساسيات جافا سكريبت كما شرحناها في سلسلة المقالات السابقة. أين تجد الدوال؟ ستجد الدوال أينما نظرت في جافا سكريبت، وقد استخدمنا في الواقع الدوال في جميع مقالاتنا السابقة، لكننا لم نتحدث عنها بالتفصيل. لهذا سنبدأ في هذا المقال حديثنا عن الدوال ونستكشف صياغتها. في كل مرة تستخدم بنية في جافا سكربت يليها قوسين () (باستثناء البنى الأصلية في اللغة مثل حلقة for أو حلقة while أو for...else) فأنت تستخدم دالة. الدوال اﻷصلية المدمجة في المتصفح استخدمنا سابقًا دوال كثيرة جاهزة في المتصفح، وذلك في كل مرة تعاملنا فيها مع السلاسل النصية: const myText = "I am a string"; const newString = myText.replace("string", "sausage"); console.log(newString); // سلسلة نصية مصدرية replace() تأخذ الدالة // وأخرى هدف وتستبدل السلسلة المصدرية //بالسلسلة الهدف وتعيد السلسلة النصية الجديدة أو في كل مرة تعاملنا فيها مع مصفوفات: const myArray = ["I", "love", "chocolate", "frogs"]; const madeAString = myArray.join(" "); console.log(madeAString); // مصفوفة وتضم جميع عناصرها join() تأخذ الدالة // في سلسلة نصية جديدة وتعيد هذه السلسلة أو عندما ولدنا أعدادًا عشوائية: const myNumber = Math.random(); // عددًا عشوائيًا بين random() تولد الدالة //الرقم 0 و الرقم 1 (ما عدا 1) ملاحظة: جرّب الشيفرة السابقة في متصفحك (في طرفية جافا سكريبت) كي تتعود على استخدامها إن اقتضى اﻷمر. تضم جافا سكريبت الكثير من الدوال الجاهزة المدمجة معها لتساعدك على إنجاز الكثير من المهام دون أن تفكر في كتابة شيفرتها بنفسك. وحقيقة اﻷمر أن الكثير من الدوال المدمجة التي تستخدمها لا يمكن كتابتها باستخدام جافا سكريبت، ويستدعى العديد منها شيفرة خلفية للمتصفح كتبت عمومًا بلغات منخفضة المستوى مثل ++C وليس باستخدام لغات الويب مثل جافا سكريبت. وتذكر دائمًا أن الكثير من الدوال المدمجة مع المتصفح ليست جزءًا من نواة جافا سكريبت. فبعضها معرّف كأجزاء من الواجهة البرمجية للمتصفح مبنية على لغات أخرى لتأمين مستوى معين من الوظائف. الدوال والتوابع تُدعى الدوال اﻷعضاء في كائن ما توابعًا methods. ولا حاجة بالطبع هنا إلى التعمق في عمل كائنات جافا سكريبت، لأننا سنتستعرضها لاحقًا في مقالات أخرى، وكل ما نريده هو إزالة الالتباس الذي قد يحصل بين الدوال والتوابع لأنك ستواجه كلا المصطلحين عندما تبحث في المصادر المختلفة على الويب. فالشيفرة المدمجة الجاهزة التي تعاملنا معها سابقًا تضم الدوال والتوابع، وبإمكانك الاطلاع على هذه الدوال الجاهزة والكائنات الجاهزة في جافا سكربت مع توابعها من خلال توثيق جافا سكريبت في موسوعة حسوب. كما رأيت أيضًا في مقالاتنا السابق العديد من الدوال المخصصة، وهي دوال عرفناها ضمن الشيفرة وليست مدمجة مع المتصفح. فعندما ترى اسمًا مخصصًا يليه قوسين ستكون أمام دالة مخصصة. وكمثال عليها تجد الدالة ()draw المخصصة التي استخدمناها ضمن الملف random-canvas-circles.html (انظر الشيفرة المصدرية) في مقال استخدام الحلقات في جافا سكريبت والذي يبدو كالتالي: function draw() { ctx.clearRect(0, 0, WIDTH, HEIGHT); for (let i = 0; i < 100; i++) { ctx.beginPath(); ctx.fillStyle = "rgba(255,0,0,0.5)"; ctx.arc(random(WIDTH), random(HEIGHT), random(50), 0, 2 * Math.PI); ctx.fill(); } } ترسم الدالة 100 دائرة عشوائية ضمن العنصر <canvas>، وفي كل مرة نريد تكرار اﻷمر، نستدعي هذه الدالة كالتالي: draw(); بدلًا من إعادة كتابة الشيفرة من جديد كل مرة. ويمكن أن تضم الدوال أية شيفرة تريدها كما يمكنها استدعاء أي دالة أخرى، فالدالة السابقة تستدعي الدالة ()random المعرّفة كالتالي ثلاث مرات: function random(number) { return Math.floor(Math.random() * number); } لقد احتجنا الدالة السابقة لأن الدالة اﻷصلية المدمجة ()Math.random مع المتصفح تولد أرقامًا عشوائية عشرية بين 0 و 1 فقط، لكننا نريد رقمًا عشوائيًا صحيحًا بين 0 وقيمة معينة. استدعاء الدالة قد يكون مفهوم الدالة واضحًا بالنسبة لك، لكن نذكرك أن استخدام الدالة فعليًا يكون من خلال استدعائها. ويُنفّذ اﻷمر بكتابة اسم الدالة في المكان الذي تريده ضمن الشيفرة يليه قوسين: function myFunction() { alert("hello"); } myFunction(); // يستدعي الدالة مرة واحدة ملاحظة: تُدعى هذه الطريقة في إنشاء الدوال "تصريحًا عن الدالة"، وبإمكانك استدعاء الدالة قبل أن تصرح عنها وستعمل الشيفرة جيدًا. معاملات الدالة تحتاج بعض الدوال إلى معاملات parameters عند استدعائها، وهي قيم ينبغي وضعها ضمن قوسي الدالة كي تعمل الدالة بالشكل المطلوب. ملاحظة: تُدعى المعاملات أحيانًا "وسائطًا arguments" أو "خاصيات properties" أو "سمات attributes". وكمثال على ذلك، نجد دالة المتصفح ()Math.random التي لا تأخذ أية معاملات وتعيد دومًا عددًا عشوائيًا بين 0 و1: const myNumber = Math.random(); بينما تأخذ الدالة ()replace معاملين هما النص الذي تريد إيجاده ضمن السلسلة الرئيسية والنص البديل: const myText = "I am a string"; const newString = myText.replace("string", "sausage"); ملاحظة: يُفصل بين المعاملات ما بين القوسين بفواصل من الشكل ,. المعاملات الاختيارية قد تكون المعاملات اختيارية في بعض اﻷحيان ولا حاجة لوضع قيم لها. فإن لم تفعل ذلك تتبنى الدالة نوعًا من القيم الافتراضية. وكمثال عن هذه الدوال نجد دالة المصفوفات ()join: const myArray = ["I", "love", "chocolate", "frogs"]; const madeAString = myArray.join(" "); console.log(madeAString); //'I love chocolate frogs' تعيد الدالة const madeAnotherString = myArray.join(); console.log(madeAnotherString); //'I,love,chocolate,frogs'تعيد الدالة فإن لم تخصص في الدالة المعامل الذي يمثل محرف الفصل أو الوصل، تستخدم الدالة الفاصلة افتراضيًا. المعاملات الافتراضية إن كنت في صدد إنشاء دالة وأردت أن تجعل لأحد المعاملات قيمة افتراضية، بإمكانك تخصيص هذه القيمة بإضافة اﻹشارة = بعد اسم المعامل تليها قيمة المعامل الافتراضية: function hello(name = "Chris") { console.log(`Hello ${name}!`); } hello("Ari"); // Hello Ari! hello(); // Hello Chris! الدوال غير المسماة والدوال السهمية ما تعلمناه حتى اﻵن هو دالة من الشكل: function myFunction() { alert("hello"); } لكن بإمكانك أيضًا إنشاء دالة بلا اسم: (function () { alert("hello"); }); تُدعى هذه الدوال بالدوال غير المسماة anonymous functions. وترى هذه الدوال عادة عندما تأخذ دالة ما دالة أخرى كمعامل لها، عندها يمرر المعامل كدالة غير مسماة. ملاحظة: يُدعى هذا الشكل من تعريف الدوال بالشكل التعبيري تمييزًا له عن الشكل التصريحي ولا يمكن استباق الدالة التعبيرية أي استخدامها في الشيفرة قبل أن تكتبها. مثال عن الدوال غير المسماة Anonymous Functions لنفرض أنك تريد تنفيذ شيفرة معينة عندما يطبع المستخدم بعض اﻷحرف في صندوق نصي. وﻹنجاز اﻷمر بإمكانك استدعاء الدالة ()addEventListener العائدة للصندوق النصي، والتي تتوقع أن تمرر لها على اﻷقل معاملين: اسم الحدث الذي تترصده وهو في حالتنا الضغط على المفتاح keydown. دالة كي تُنفَّذ عندما يقع الحدث. عندما يضغط المستخدم على مفتاح، يستدعي المتصفح الدالة التي استخدمتها كمعامل ويمرر لها على شكل معامل أيًضًا معلومات عن الحدث بما في ذلك المفتاح الي ضغطه المستخدم: function logKey(event) { console.log(`You pressed "${event.key}".`); } textBox.addEventListener("keydown", logKey); وبدلًا من استخدام دالة منفصلة مثل ()logkey، بإمكانك تمرير دالة غير مسماة إلى ()addEventListener على النحو التالي: textBox.addEventListener("keydown", function (event) { console.log(`You pressed "${event.key}".`); }); الدالة السهمية Arrow functions إن مررت دالة إلى دالة أخرى على شكل دالة غير مسماة، ستجد طريقة أخرى لذلك تُدعى الدالة السهمية <=() بدلًا من ()function: textBox.addEventListener("keydown", (event) => { console.log(`You pressed "${event.key}".`); }); فإن أخذت الدالة السهمية معاملًا واحدًا، تستطيع حينها حذف اﻷقواس المحيطة بالمعامل: textBox.addEventListener("keydown", event => { console.log(`You pressed "${event.key}".`); }); وأخيرًا إن احتوت الدالة على سطر واحد فقط يتضمن العبارة return، بإمكانك عندها حذف اﻷقواس المعقوصة للدالة والتعليمة return. لاحظ كيف استخدمنا تابع المصفوفة ()map لمضاعفة كل عدد في المصفوفة اﻷصلية: const originals = [1, 2, 3]; const doubled = originals.map(item => item * 2); console.log(doubled); // [2, 4, 6] يأخذ التابع ()map كل عنصر من عناصر المصفوفة بدوره ويمرر إلى دالة محددة ثم يعيد نتيجة تنفيذ هذه الدالة ويضيفها إلى المصفوفة الجديدة. فالعبارة البرمجية item => item * 2 إذًا هو شكل مختصر للدالة السهمية ويكافئ تمامًا الدالة التصريحية التالية: function doubleItem(item) { return item * 2; } بإمكانك استخدام اﻷسلوب السابق لإعادة كتابة الدالة addEventListener: textBox.addEventListener("keydown", (event) => console.log(`You pressed "${event.key}".`), ); تُعاد قيمة التابع ()console.log (والتي هي قيمة غير محددة undefined) ضمنًا كنتيجة لاستدعاء الدالة. ننصحك باستخدام الدوال السهمية لأنها تختصر الشيفرة وتسهّل قراءتها، بإمكانك العودة إلى توثيق جافا سكريبت في موسوعة حسوب للاطلاع اكثر على الدوال السهمية. مثال مباشر عن استخدام الدالة السهمية إلك مثالنا السابق عن التقاط ضغطة مفتاح: شيفرة HTML: <input id="textBox" type="text" /> <div id="output"></div> شيفرة جافا سكريبت: const textBox = document.querySelector("#textBox"); const output = document.querySelector("#output"); textBox.addEventListener("keydown", (event) => { output.textContent = `You pressed "${event.key}".`; }); إليك النتيجة: See the Pen functions by Hsoub Academy (@HsoubAcademy) on CodePen. نطاق الدوال والتعارضات عند تفسير الشيفرة لنلق نظرة أقرب إلى موضوع مجال أو نطاق دالة scope، وهو مفهوم هام جدًا عند التعامل مع الدوال. فعندما تنشئ دالة ستكون المتغيرات وغيرها من اﻷشياء المعرّفة داخل الدالة ضمن نطاق مخصص لها، أي أنها غير مرئية ولا معروفة من قبل الشيفرة الموجودة خارج الدالة. بينما يكون كل ما يُعرّف خارج نطاق الدالة ضمن النطاق العام global scope ويمكن الوصول إليها من أي مكان في الشيفرة. أُعدت جافا سكريبت بهذه الطريقة لعدة أسباب أهمها اﻷمان والتنظيم. فقد لا ترعب أحيانًا بالوصول إلى متغير من أي مكان في الشيفرة. فالسكربتات التي تستدعيها من مصادر خارجية، قد تعبث بشيفرتك وتسبب المشاكل لأنه قد يصدف وتحمل أسماء مشابهة لمتغيراتك لكن لأغراض أخرى مما يسبب تعارضًا في تفسير الشيفرة. وقد يحدث هذا اﻷمر عرضيًا أو بشكل مقصود. لنقل مثلًا أن لديك ملف HTML يستدعي ملفي جافا سكريبت خارجيين، ويضم كلا الملفين متغيرًا ودالة لهما الاسم ذاته: ملف HTML: <!-- Excerpt from my HTML --> <script src="first.js"></script> <script src="second.js"></script> <script> greeting(); </script> ملف جافا سكريبت الأول: // first.js const name = "Chris"; function greeting() { alert(`Hello ${name}: welcome to our company.`); } ملف جافا سكريبت الثاني: // second.js const name = "Zaptec"; function greeting() { alert(`Our company is called ${name}.`); } تحمل الدالتين اللتين تريد استدعاءهما الاسم ()greeting، لكنك لن تصل إلى إلى الدالة في الملف اﻷول (ستهمل الدالة في الثاني). إضافة إلى ذلك سيتوّلد خطأ إن حاولت (في الملف الثاني) إسناد قيمة جديدة إلى المتغير name لأنه عّرف مسبقًا على أنه ثابت const ولا يمكن إعادة إسناد قيمة له. وهكذا فإن الاحتفاظ بأجزاء من شيفرتك ضمن الدوال بعيدًا عن بقية الشيفرة يجنبك العديد من المشاكل، ويُعد من الممارسات العملية الجيدة. اﻷمر مشابهة قليلًا لحديقة حيوان فيها أسد ونمر وحمار وحشي وبطريق وكل منها في قفصه الخاص ويمكنه الوصول فقط إلى اﻷشياء الموجودة في أقفاصها بشكل يشابه نطاق الدوال. فلو أمكن وصول إلى منها إلى قفص اﻵخر ستقع المشاكل بالتأكيد. وعلى اﻷقل لن تشعر بعض الحيوانات بالراحة مع سلوك اﻷخرى، فلن يتكيف اﻷسد والنمر مع بيئة البطريق الرطبة والباردة، وقد بيحدث اﻷسوء فقد يحاول اﻷسد أو النمر افتراس البطريق. ومشرف الحديقة سيلعب دور النطاق العام، فبإمكانه الوصول إلى أي قفص ﻹيصال الطعام ومعالجة الحيوان المريض. تطبيق عملي: التعامل مع نطاقات الرؤية لنلق نظرة على مثال واقعي يوضح مفهوم المجالات. احفظ نسخة من ملف التمرين على حاسوبك. ويتضمن الملف دالتين هما ()a و ()b وثلاثة متغيرات x و y و z اثنان منهما معرّفان ضمن الدالتين واﻷخير متغير عام. كما يتضمن الملف دالة ثالثة تُدعى تأخذ معاملًا واحدًا وتطبعه ضمن فقرة نصية في الصفحة. افتح التمرين ضمن المتصفح وضمن المحرر النصي. افتح طرفية جافا سكريبت ضمن أدوات مطوري ويب الخاصة بالمتصفح، ثم اكتب في الطرفية اﻷمر التالي: output(x); ينبغي أن ترى قيمة المتغير x قد طُبعت على الشاشة. حاول اﻵن إدخال التالي في الطرفية: output(y); output(z); ينبغي أن ترمي كلا اﻷمرين خطأً (y غير معرّف y is not defined ). إن السبب في ذلك هو مجال الدالة. فكل من y و Z معرفان ضمن الدالة ()a و ()b فلا يمكن للدالة ()output الوصول إليهما من النطاق العام. لكن ما الذي سيحدث عندما نستدعيهما من داخل الدالة؟ حاول أن تغيّر الدالتين ()a و ()b كالتالي: function a() { const y = 2; output(y); } function b() { const z = 3; output(z); } احفظ الشيفرة وأعد تحميل الصفحة ضمن المتصفح وحاول بعدها استدعاء الدالتين ()a و ()b من الطرفية: a(); b(); من المفترض أن ترى قيمتي y و z قد طبعتا على شاشة المتصفح، وسيعمل اﻷمر طالما أن الدالة ()output قد استدعيت من داخل الدالتين ()a و ()b أي في نفس النطاق الذي عُرفت فيه المتغيرات وتبقى ()output متاحة فيأي مكان طالما أنها ضمن النطاق العام جرّب أن تغيّر الشيفرة لتصبح كالتالي: function a() { const y = 2; output(x); } function b() { const z = 3; output(x); } احفظ التغييرات وحاول أن تعيد تحميل الصفحة وجرّب ما يلي مجددًا في الطرفية: a(); b(); ستطبع الدالين الدالتين ()a و ()b قيمة x في المتصفح. سيعمل اﻷمر جيدًا لأنه وعلى الرغم من أن استدعائي الدالة لا ينتميان إلى نفس مجال x لكن x معرّف كمتغير عام، فهو متاح في أي مكان من الشيفرة جرّب أخيرًا تحديث الشيفرة لتصبح كالتالي: function a() { const y = 2; output(z); } function b() { const z = 3; output(y); } احفظ التغييرات وحاول أن تعيد تحميل الصفحة وجرّب ما يلي مجددًا في الطرفية: a(); b(); سيلقي اﻵن استدعاء الدالتين ()a و ()b نفس الخطأ (ReferenceError: variable name is not defined) في الطرفية لأن استدعاءات الدالة ()output والمتغيرات التي تريد طباعة قيمتها لا ينتميان إلى نفس مجال أو نطاق الرؤية للدالة فهما غير مرئيان للاستدعاءات. ملاحظة: لا تُطبق نفس قواعد النطاق على الحلقات (مثل {}()for) والجمل الشرطية (مثل {}()if) فقد تبدوان مشابهتين للدوال لكنهما أمران مختلفان. انتبه إلى ذلك. ملاحظة: الخطأ ReferenceError: "x" is not defined من أكثر اﻷخطاء شيوعًا. ففي حال واجهت هذا الخطأ وأنت متأكد من أنك عرّفت المتغير، عليك في هذه الحالة مراجعة مجالات الرؤية. الخلاصة تعرفنا في هذا المقال على المفاهيم اﻷساسية للدوال كي نمهد لك الطريق لمقالات قادمة في تعلم جافا سكريبت والتعرف على عملية إنشاء دوال مخصصة تلائم احتياجاتك. ترجمة -وبتصرف- للمقال Functions- reusable bloacks of code اقرأ أيضًا المقال السابق: الحلقات في جافا سكريبت الدوال في جافاسكربت الدوال Functions في جافا سكريبت كائنات الدوال Function object وتعابير الدوال المسماة NFE في جافاسكربت الدوال العليا في جافاسكريبت
  21. تخطيط الشبكة 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
  22. لربما صادفتك بعض المشاكل عندما بنيت في مقال سابق لعبة تخمين الرقم الصحيح، أو وجدت أن هذه اللعبة لا تعمل بالشكل المطلوب! لهذا سنفرد هذا المقال لمساعدتك في البحث عن المشكلات البرمجية التي قد تصادفك وإيجاد حل لها، من خلال تزويدك ببعض التلميحات والنصائح عن كيفية إيجاد وإصلاح اﻷخطاء. ننصحك قبل المتابعة في قراءة هذا المقال بالاطلاع على بعض المقالات السابقة مثل: أساسيات علوم الحاسوب أساسيات 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 اقرأ أيضًا المقال السابق: تجربتك اﻷولى مع جافا سكريبت مبادئ كتابة جافا سكريبت متسقة ومفهومة كيفية التعامل مع الأخطاء البرمجية الزلات البرمجية والأخطاء في جافاسكريبت
  23. تظهر أهمية لغات البرمجة في إنجاز مهام مكررة وبسرعة مثل الحسابات المتعددة، أو في الحالات التي ينبغي فيها إنجاز الكثير من اﻷعمال المتشابهة. لهذا سنخصص هذا المقال للحديث عن الحلقات في جافا سكريبت التي تعالج تلك اﻷمور. ننصحك قبل أن تبدأ العمل معنا في هذه السلسلة أن تطلع على: أساسيات علوم الحاسب أساسيات 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 اقرأ أيضًا المقال السابق: العبارات الشرطية واتخاذ القرار في جافا سكريبت تعلم جافا سكريبت من الصفر حتى الاحتراف هيكل البرنامج في جافاسكريبت ترميز النصوص والتعامل مع كائنات الملفات في جافاسكربت
  24. الصندوق المرن 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
  25. بعد أن اطلعنا في مقال سابق على مفهوم جافا سكريبت وما يمكنها فعله، سنتابع في مقالنا هذا فكرة إنشاء تطبيق بسيط باستخدام جافا سكربت من خلال جولة إرشادية عملية نبني من خلالها تطبيق "خمّن الرقم 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)
×
×
  • أضف...