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

ابراهيم الخضور

الأعضاء
  • المساهمات

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

  • تاريخ آخر زيارة

كل منشورات العضو ابراهيم الخضور

  1. قد يحصل المخترق على مفتاح التشفير، لكن لن يستطيع استخدامه لأسباب كثيرة.
  2. أحد المهام الرئيسية للغة HTML هي هيكلة النص لكي يتمكن المتصفح من عرض صفحات ويب بالطريقة التي يريدها المطوّر، إذ يصف هذا المقال طريقة استخدام لغة HTML لهيكلة صفحة نصية بإضافة عناوين وفقرات وإبراز بعض العبارات والكلمات وإنشاء قوائم وغير ذلك. لا بدّ قبل الشروع في القراءة من الاطلاع على أساسيات لغة HTML التي يغطيها المقال تعرَّف على لغة HTML التعامل مع الأساسيات: العناوين الرئيسية والفقرات تتكون معظم النصوص المهيكلة من عناوين وفقرات سواء أكان النص قصة أو صحيفة أو كتاب جامعي أو مجلة. تسهل النصوص المهيكلة تجربة القراءة وتزيد المتعة، وتُنظَّم النصوص ضمن فقرات في لغة HTML بتغليف النص داخل عنصر الفقرة النصية <p> كما يلي: <p>أكتب حاليًا فقرة نصية</p> بينما لا بد من استخدام عنصر عنوان مثل العنوان الرئيسي <h1> لإبراز النص على أساس عنوان للمحتوى الذي تعرضه الصفحة: <h1>هذا عنوان المقال</h1> تقدّم لغة HTML ستة مستويات للعناوين ابتداءً من الأعلى مستوى <h1> وانتهاءً بالأدنى<h6>، إذ يمثِّل العنصر <h1> العنوان الرئيسي ويمثِّل <h2> العناوين الفرعية للعنوان الرئيسي و<h3> العناوين الفرعية للعناوين الفرعية وهكذا، انظر توثيقها -العناصر h1-h6- في موسوعة حسوب. إنجاز الهيكلية المتدرجة لمحتوى صفحة HTML سنستخدِم في القصة التي نراها تاليًا العنصر <h1> ليمثل عنوان القصة و<h2> ليمثل عنوان كل فصل، بينما سيمثل العنصر <h3> الأقسام الفرعية لكل فصل. <h1>The Crushing Bore</h1> <p>By Chris Mills</p> <h2>Chapter 1: The dark night</h2> <p>It was a dark night. Somewhere, an owl hooted. The rain lashed down on the ...</p> <h2>Chapter 2: The eternal silence</h2> <p>Our protagonist could not so much as a whisper out of the shadowy figure ...</p> <h3>The specter speaks</h3> <p>Several more hours had passed, when all of a sudden the specter sat bolt upright and exclaimed, "Please have mercy on my soul!"</p> يعود الأمر إليك في طبيعة الحال لاختيار عناصر العناوين ودلالاتها طالما أنّ التسلسل الذي تتبعه في هيكلة المحتوى منطقي، لذلك لا بد أن تتذكر دائمًا بعض الممارسات الجيدة عند هيكلة العناوين: يفضَّل استخدام عنوان رئيسي <h1> واحد في كل صفحة، فهو العنوان ذو المستوى الأعلى وتتدرج تحته بقية العناصر. تأكد من استخدام عناصر العناوين بالترتيب الصحيح في الهيكلية المتدرجة للصفحة، فلا تستخدِم مثلًا <h3> ليشير إلى عنوان فرعي ثم تستخدم بعده <h2> ليمثل عنوانًا فرعيًا لعنوان فرعي، فهذا أمر غير منطقي وسيقود إلى نتائج غريبة. حاول عدم استخدام أكثر من ثلاثة مستويات في كل صفحة إلا إذا كان الأمر ضروريًا، فقد قَلَّ انتشار المستندات التي تعتمد هيكلية ذات مستويات عميقة نظرًا لصعوبة التنقل بين مستوياتها، ويفضل في حالات مثل هذه توزيع المحتوى ضمن صفحات عدة. ما فائدة إعطاء الصفحة هيكلية معينة؟ للإجابة على هذا السؤال، دعنا نلقي نظرةً على الملف text-start.html الذي سيكون نقطة انطلاق في تطبيقات هذا المقال (وصفة حمُّص جيدة)، لذلك انسخ هذا الملف وضعه على حاسوبك لأنك ستحتاجه لاحقًا، كما يحتوي جسم المستند محتوًى مكوّنًا من أجزاء مختلفة لم تُنظَّم بعد في هيكلية محددة، لكنها مفصولة عن بعضها بمحارف الأسطر الجديدة، فعندما تفتح الملف عبر المتصفح، فستجد أنّ النص قطعة واحدة كبيرة. السبب في ذلك هو عدم وجود أيّة عناصر لهيكلة هذا النص، فلن يميّز المتصفح بين العناوين أو الفقرات، بالإضافة إلى ذلك: يستعرض الزائر النص بسرعة بحثًا عن المحتوى الأساسي ويكتفي عادةً بقراءة العناوين ليختار نقطة البداية، إذ يقضي المستخدِم عادةً فترةً قصيرةً جدًا ضمن صفحة الويب، فإذا لم يستطع الزائر إيجاد أيّ شيء واضح أو مفيد خلال ثوان معدودة، فعلى الغالب سيتوقف عن القراءة ويغادر الصفحة. تفهرِس محركات البحث الصفحات آخذةً بالحسبان محتوى العناوين على أنها كلمات مفتاحية مهمة تؤثِّر على ترتيب نتائج البحث، فدون العناوين إذًا لن تُعَدّ صفحتك جيدةً وفق معايير السيو SEO أو تحسين محركات البحث Search Engine Optimization. لا يقرأ الزوار ذوو المشاكل البصرية الحادة صفحات ويب بأنفسهم، وإنما يستمعون إلى محتواها عن طريق برمجيات تُدعى قارئات الشاشة، إذ تتيح هذه البرمجيات الوصول السريع إلى محتوى نصي محدد، وتجري هذه العملية بطرق عدة منها تحديد خطوط عريضة للمحتوى بقراءة العناوين، مما يسمح للمستخدِم بإيجاد المعلومات التي يريدها بسرعة، فإذا لم تكن العناوين موجودةً، فسيضطر المستخدِم إلى الاستماع إلى النص بأكمله. إذا أردت تنسيق الصفحة باستخدام CSS أو جعلها تفاعليةً أكثر عن طريق جافاسكربت، فلا بد من تغليف المحتوى المطلوب بواسطة عناصر HTML لكي تستهدفه شيفرات CSS أو جافاسكربت بفعالية أكبر. لهذه الأسباب وغيرها سنحتاج إلى هيكلة محتوى الصفحة. تطبيق: هيكلة محتوى لننتقل مباشرةً إلى التطبيق العملي، إذ سنضيف عناصر إلى النص غير المنسق الموجود في حقل المدخلات Input field والذي يُعرض في حقل المخرجات Output field، فإذا ارتكبت خطأً، فيمكنك مسح ما كتبته بالنقر على زر "إعادة ضبط Reset"، في حين إذا وجدت نفسك تائهًا كليًا، فانقر على الزر"أظهر الحل Show solution" لترى الحل الصحيح. لماذا نحتاج إلى الدلالات؟ نعتمد على الدلالات في كل مكان حولنا، فنحن نعتمد على تجاربنا السابقة لتدلنا على وظيفة كل الأغراض التي حولنا، فعندما نرى هذا الغرض نعرف تمامًا عمله، فنحن نتوقع مثلًا أن تعني إشارة المرور الحمراء التوقف والخضراء الحركة، لكن سنرتبك بالفعل إذا تغيّرت الدلالات أو طبقناها بصورة خاطئة. لا بد في السياق عينه أن نتأكد من استخدام العناصر الصحيحة لكي نعطي المحتوى المعنى والوظيفة والمظهر الصحيح، فوفقًا لهذه المقاربة، فإن العنصر <h1> هو عنصر دلالي يعطي المحتوى الذي يغلفه صفة عنوان رئيسي لصفحتك بأعلى مستوى. <h1>This is a top level heading</h1> سيعطي المتصفح محتوى هذا العنوان حجمًا كبيرًا افتراضيًا لكي يظهر على أساس عنوان رئيسي (على الرغم من أنك قادر على تنسيقه بالطريقة التي تريد). والأهم من ذلك أن لدلالته استخدامات عدة في محركات البحث مثلًا أو في قارئات الشاشة، كما يمكنك من ناحية أخرى عرض أيّ عنصر وكأنه عنوان رئيسي. لاحظ الشيفرة التالية: <span style="font-size: 32px; margin: 21px 0; display: block;">Is this a top level heading?</span> ليس للعنصر <span> أية دلالات، ويستخدَم لتطبيق تنسيق لغة CSS على محتوى محدد أو جزء منه أو استهدافه بشيفرة جافاسكربت دون أن يدل ذلك على معنى جديد، وقد طبقنا في الشيفرة السابقة بعض التنسيقات على محتوى ليبدو تمامًا على أنه عنوان رئيسي، لكنك لن تجني أيّ فوائد أخرى من هذه العملية لأنّ هذا العنصر غير دلالي ولا يحمل معنًى محددًا، لذلك من الأفضل دائمًا استخدام عناصر HTML المخصصة لأداء وظيفة معينة. القوائم لنركِّز الآن على القوائم، فهي موجودة في كل مكان يحيط بنا ابتداءً من قائمة التسوق إلى قائمة الاتجاهات التي تسلكها يوميًا للوصول إلى المنزل، وستجد كذلك أنّ القوائم موجودة في كل مكان على الويب ويهمك منها ثلاثة أنواع سنناقشها بشيء من التفصيل. القوائم غير المرتبة تستخدَم القوائم غير المرتبة Unordered list لوصف مجموعة عناصر لا أولوية لترتيبها ضمن القائمة، ومن الأمثلة عليها قائمة التسوق: milk eggs bread hummus تبدأ القائمة غير المرتبة بالعنصر <ul> الذي يغلف جميع العناصر: <ul> milk eggs bread hummus </ul> وينبغي أن نغلف عناصر القائمة بالعنصر <li>: <ul> <li>milk</li> <li>eggs</li> <li>bread</li> <li>hummus</li> </ul> تطبيق: إنشاء قائمة غير مرتبة حاول استخدام محرر الشيفرة في الأسفل لإنشاء قائمة غير مرتبة خاصة بك: القوائم المرتبة تستخدَم القوائم المرتبة ordered list لوصف مجموعة عناصر ينبغي ترتيبها ضمن القائمة، ومن الأمثلة عليها الاتجاهات التي يجب أن تسلكها لتصل إلى مكان معيَّن: Drive to the end of the road Turn right Go straight across the first two roundabouts Turn left at the third roundabout The school is on your right, 300 meters up the road توصَّف القائمة المرتبة بأسلوب القوائم غير المرتبة نفسه باستثناء العنصر المستخدَم في تغليف العناصر الذي سيكون في هذه الحالة <ol> بدلًا من <ul>: <ol> <li>Drive to the end of the road</li> <li>Turn right</li> <li>Go straight across the first two roundabouts</li> <li>Turn left at the third roundabout</li> <li>The school is on your right, 300 meters up the road</li> </ol> تطبيق: إنشاء قائمة مرتبة حاول استخدام محرر الشيفرة في الأسفل لإنشاء قائمة مرتبة خاصة بك: تطبيق: هيكلة وصفة الطعام في مثالنا السابق أصبحت في هذه المرحلة قادرًا على هيكلة الصفحة التي تتحدث عن وصفة الحمُّص في مثالنا السابق، إذ يمكنك تخزين نسخة عن الملف text-start.html على جهازك ثم تجري التعديلات عليه، أو تنفيذ الأمر مستخدمًا محرر الشيفرة في الأسفل، وقد يكون تنفيذ التمرين على حاسوبك أفضل لأنك ستتمكن من حفظ عملك، في حين لن تتمكن من ذلك إذا استخدمت محرر الشيفرة في المقال لأن التغييرات التي أجريتها ستختفي عند إغلاق الصفحة. إذا وجدت نفسك تائهًا، فانقر على الزر "أظهر الحل Show solution" لترى الحل الصحيح، أو تحقق من الملف text-complete.html الذي يحتوي على العمل بأكمله. القوائم المتداخلة من الممكن جدًا أن تتداخل القوائم، فقد تحتاج إلى قائمة فرعية تحت أحد عناصر قائمة أخرى، ولنأخذ القائمة الثانية من مثال وصفة الطعام: <ol> <li>Remove the skin from the garlic, and chop coarsely.</li> <li>Remove all the seeds and stalk from the pepper, and chop coarsely.</li> <li>Add all the ingredients into a food processor.</li> <li>Process all the ingredients into a paste.</li> <li>If you want a coarse "chunky" hummus, process it for a short time.</li> <li>If you want a smooth hummus, process it for a longer time.</li> </ol> بما أن آخر عنصرين يتعلقان بالموضوع ذاته، فقد يبدوان مثل إرشادات فرعية تحت العنصر الذي يسبقهما، ومن المنطقي في هذه الحالة وضعهما ضمن قائمة غير مرتبة فرعية تحت العنصر الذي يسبقهما: <ol> <li>Remove the skin from the garlic, and chop coarsely.</li> <li>Remove all the seeds and stalk from the pepper, and chop coarsely.</li> <li>Add all the ingredients into a food processor.</li> <li>Process all the ingredients into a paste. <ul> <li>If you want a coarse "chunky" hummus, process it for a short time.</li> <li>If you want a smooth hummus, process it for a longer time.</li> </ul> </li> </ol> عُد إلى التطبيق السابق وحاول تعديل القائمة الثانية كما فعلنا هنا. إبراز النص والإشارة إلى أهميته نحاول أحيانًا إبراز بعض الكلمات عندما نتحدث للإيحاء بأهميتها أو لنعني أمرًا آخر، وقد نحاول إعطاء أهمية لكلمات أخرى أوتمييزها بطريقة أو بأخرى، وكذلك الأمر في HTML، إذ تضم اللغة عناصر دلالية لتمييز المحتوى النصي بأسلوب مماثل، وسنناقش تاليًا أكثر هذه العناصر شيوعًا. إبراز محتوى عندما نريد إبراز أو تفخيم عبارة في الكلام المحكي نشدِّد على كلمات محدَّدة لكي نغيّر عمدًا دلالة ما نقول، وهذا ما نفعله أيضًا أثناء الكتابة، إذ نميل إلى التشديد على الكلمات بكتابتها على نحو مائل، وتحمل الجملتين التاليتين على سبيل المثال دلالتين مختلفتين: سعيد لأنك لم تتأخر سعيد لأنك لم تتاخر تشير الجملة الأولى إلى راحة الشخص بأن الآخر لم يتأخر بالفعل، بينما تحمل الثانية نوعًا من الاستهزاء أو هجوم مبطن على الآخر للتعبير عن الانزعاج من تأخره. نستخدِم في HTML العنصر <em> لتوصيف حالات مثل هذه وزيادة المتعة أثناء قراءة النص، كما تُميِّز قارئات الشاشة هذه العناصر وتنطقها بنبرة صوتية مختلفة، وتعرض المتصفحات محتوى هذا العنصر بخط مائل افتراضيًا، لكن انتبه إلى استخدام العنصر لمجرد عرض النص مائلًا، وعليك عندها استخدام العنصر <i> أو أن تطبق تنسيق CSS مع عنصر بصنف محدد. <p>I am <em>glad</em> you weren't <em>late</em>.</p> إظهار أهمية محتوى لإبراز أهمية بعض الكلمات، فإننا نشدِّد عليها عند لفظها أو نكتبها بخط ثخين Bold مثل هذا السائل عالي السميّة. نستخدِم في HTML العنصر <strong> لإظهار أهمية الكلمات، إذ يعرض المتصفح محتوى هذا العنصر بخط ثخين افتراضيًا، كما تميزه قارئات الشاشة وتنطقها بنبرة صوتية مختلفة، ولا تحاول أيضًا استخدام العنصر لمجرد عرضه بخط ثخين، وعليك عندها استخدام العنصر <b> أو تطبيق تنسيق CSS مع عنصر بصنف محدد. <p>This liquid is <strong>highly toxic</strong>.</p> <p>I am counting on you. <strong>Do not</strong> be late!</p> يمكنك أيضًا المزج بين العنصرين <strong> و <em>. <p>This liquid is <strong>highly toxic</strong> — if you drink it, <strong>you may <em>die</em></strong>.</p> تطبيق: لنغير أهمية محتوى يوجد نص قابل للتعديل في محرِّر الشيفرة التالي، وحاول أن تبرز بعض الكلمات وتعطي أهمية لبعضها الآخر، كما ترى لكي تتمرن قليلًا: خط مائل أو ثخين أو تحته سطر تتميز العناصر التي تحدثنا عنها سابقًا بدلالات معنوية محددة، لكن حالة العناصر <b> و <i> و <u> أعقد قليلًا، فقد وضعت هذه العناصر كي يكتب الأشخاص كلمات محددة بخط ثخين أو مائل أو ليظهر تحتها سطر، وذلك في حقبة لم تكن تنسيقات CSS مدعومة أو كانت محدودة الدعم، إذ تؤثر هذه العناصر على طريقة العرض وليس على الدلالة، وتُعرف حاليًا بعناصر العرض presentational elements، كما لا ينبغي استخدام هذه العناصر حاليًا لأنها غير دلالية، وقد رأينا أهمية الدلالات على أصعدة مختلفة مثل سهولة الوصول accessibility وتحسين محركات البحث أو سيو SEO. أعادت HTML5 تعريف هذه العناصر مانحةً إياها دلالات قد تبدو مربكةً نوعًا ما، وإليك قاعدة مهمة جدًا: قد يكون استخدام العناصر <b> أو <i> أو <u> ملائمًا لإيصال معنى محدد يمكن إيصاله تقليديًا بإمالة الخط أو تثخينه أو وضع سطر تحته إذا لم توجد عناصر أخرى مناسبة، لكن عليك أن تبقي في حساباتك مفهوم سهولة الوصول أو الشمولية accessibility، فليس استخدام الخط المائل في غير سياقه مفيدًا لمستخدمِي قارئات الشاشة أو للأشخاص الذين يستخدِمون أبجديات مختلفة عن اللاتينية. <i>: يُستخدَم لإيصال معنى يمكن إيصاله تقليديًا بخط مائل مثل كلمات أجنبية، مرادفات، تصميم، مصطلحات تقنية، أفكار… <b>: يُستخدَم لإيصال معنى يمكن إيصاله تقليديًا بخط ثخين مثل كلمات مفتاحية، أسماء منتجات، جملة افتتاحية.. <u>: يُستخدَم لإيصال معنى يمكن إيصاله تقليديًا بخط تحته سطر مثل الأخطاء الإملائية والنحوية. ملاحظة: يربط الناس جدًا بين الكلمات التي تحتها سطر والروابط التشعبية، لذلك يفضَّل في صفحات الويب أن لا تضع سطرًا تحت محتوى ما لم يكن رابطًا، واستخدم <u> عندما تجد أنّ دلالته صحيحة، وفكّر دائمًا باستخدام CSS لتغيير المحتوى الذي تحته سطر إلى شكل أكثر ملاءمةً لويب، كما يشرح المثال التالي ما يمكن فعله: <!-- مصطلحات علمية --> <p> الطنان ياقوتي الحنجري (<i>Archilochus colubris</i>) هو الطنان الأكثر انتشارًا في أمريكا الشمالية. </p> <!-- كلمات أجنبية --> <p> كانت القائمة بحرًا من الكلمات الغريبة <i lang="uk-latn">vatrushka</i>, <i lang="id">nasi goreng</i> و <i lang="fr">soupe à l'oignon</i>. </p> <!-- أخطاء إملائية غير معروفة --> <p> Someday I'll learn how to <u style="text-decoration-line: underline; text-decoration-style: wavy;">spel</u> better. </p> <!-- تظليل كلمات عند عرض إرشادات --> <ol> <li> <b>شرِّح</b> قطعتين من الخبز </li> <li> <b>أدخل</b> شريحة طماطم وورقة خس بين شريحتي الخبز </li> </ol> خلاصة هذا كل شيء حتى الآن، ومن المفترض أن يكسبك هذا المقال فكرةً جيدةً عن توصيف النصوص وهيكلتها في HTML وأن يعرّفك على أهم العناصر المستخدمة لهذه الأغراض، كما هنالك الكثير من العناصر التي تحمل دلالات مختلفة وسنطَّلع على الكثير منها عند التطرق إلى التنسيق المتقدم للنصوص لاحقًا، وسنستعرض في المقال التالي تفاصيل إنشاء الروابط التشعبية في HTML والذي يُعَدّ العنصر الأهم تقريبًا في ويب. ترجمة -وبتصرف- للمقال HTML text fundamentals. اقرأ أيضًا هيكلة وتوزيع محتوى صفحات الويب HTML و CSS للمبتدئين: كيف تصمم أول صفحة ويب لك ترويسة الصفحة والبيانات الوصفية في HTML
  3. تُعَدّ الترويسة الجزء الذي لا تعرضه متصفحات ويب من الصفحة عند تحميلها، وتضم معلومات مثل عنوان الصفحة وروابط إلى ملفات تنسيق CSS إذا أردت تنسيق محتوى صفحتك، بالإضافة إلى روابط إلى أيقوناتك المفضلة وغيرها من البيانات الوصفية مثل المؤلف والكلمات المفتاحية الهامة التي تصف الصفحة، كما تستخدِم المتصفحات البيانات الموجودة في الترويسة لتصيير صفحة HTML بصورة صحيحة، وسنناقش في هذا المقال كل النقاط السابقة لتقف على ركيزة قوية عندما تعمل مع HTML. لا بد قبل متابعة القراءة أن تطلع على أساسيات HTML التي ذكرناها في مقال تعرّف على لغة HTML. ما هي ترويسة صفحة HTML لنراجع صفحة HTML البسيطة التي عرضناها في المقال السابق: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>My test page</title> </head> <body> <p>This is my page</p> </body> </html> الترويسة هي محتوى العنصر <head>، ولا تُعرض محتويات الترويسة على خلاف محتوى العنصر <body> الذي يعرضه المتصفح، لأن وظيفتها هي احتواء معلومات وصفية عن الصفحة، كما إنّ محتوى الترويسة في المثال السابق قليل كما نرى: <head> <meta charset="utf-8"> <title>My test page</title> </head> لكن في الصفحات الكبيرة قد يغدو محتوى الترويسة أكبر، وحاول فتح بعض الصفحات الكبيرة التي تفضلها ثم استخدام أدوات مطوري ويب لتطلع على محتويات الترويسة، كما أننا لا نهدف في هذا المقال إلى استعراض كل ما يمكن وضعه ضمن الترويسة، وإنما لتتعلم استخدام العناصر الرئيسية التي ستجد أن إدراجها ضمن الترويسة ضروري ولتعتاد على ذلك. لنبدأ إذًا. إضافة عنوان للصفحة رأينا سابقًا عمل العنصر <title> والذي يُستخدَم لإضافة عنوان إلى مستند HTML، لكنك قد تخلط بينه وبين العنوان الرئيسي <h1> الذي يُستخدَم في إضافة عنوان رئيسي لمحتوى الصفحة المرئي ضمن العنصر <body>، كما يُدعى العنصر <h1> أحيانًا باسم عنوان الصفحة، وتتلخص الاختلافات بما يلي: يظهر العنصر <h1> على صفحة الويب عند تحميلها ضمن المتصفح، ومن المفترض أن يُستخدَم مرةً واحدةً في الإشارة إلى العنوان الرئيسي لمحتوى الصفحة، أي عنوان قصة أو عنوان رئيسي لنشرة إخبارية وغير ذلك. يُعَدّ العنصر <title> عنصرًا وصفيًا يمثل عنوان مستند HTML برمته وليس عنوان المحتوى المرئي للصفحة. تطبيق: تفحص مثال بسيط لتبدأ العمل عليك تنزيل نسخة عن الملف "title-example.html" من المستودع المخصص على جيت-هاب بإحدى الطريقتين التاليتين: انسخ محتوى الملف ثم الصقه ضمن ملف نصي جديد باستخدام محرِّر النصوص المتوفر لديك، ثم احفظه في مكان مناسب. اضغط الزر "Raw" في صفحة جيت-هاب لتظهر الشيفرة (ربما في نافذة جديدة للمتصفح)، ثم اختر ملف File ثم حفظ الصفحة باسم Save Page As، واختر بعد ذلك مكانًا مناسبًا لتخزين الملف. افتح بعد ذلك الملف باستخدام المتصفح وسيبدو بالصورة التالية: من الواضح الآن أين سيظهر عنوان الصفحة <h1> وعنوان المستند <title>. جرب بعدها أيضًا فتح الشيفرة ضمن المحرِّر النصي وتعديل محتوى هذين العنصرين، ثم حدّث المحتوى المعروض ضمن المتصفح. يُستخدَم محتوى العنصر <title> بطرق أخرى، فإذا أردت مثلًا إضافة علامةً مرجعيةً إلى صفحة، فستجد أنّ هذا المحتوى سيكون الاسم المقترح لهذه العلامة، كما يُستخدَم في نتائج البحث كما سنرى لاحقًا. عنصر البيانات الوصفية تعتمد HTML طريقةً رسميةً في إضافة البيانات الوصفية metadata إلى المستند من خلال العنصر <meta>، كما يمكن أن تُعدَّ بقية الأمور التي نتحدث عنها في هذا المقال بيانات وصفية أيضًا، فهنالك أنواع عديدة للبيانات الوصفية تُضاف إلى جانب <meta> داخل ترويسة الصفحة <head>، لكننا لن نشرحها في هذه المرحلة لأنها ستربكك كثيرًا، وبدلًا من ذلك سنوضِّح عدة أمور قد تشاهدها كثيرًا وذلك لتأخذ فكرةً عنها. تحديد مجموعة المحارف المستخدمة في صفحتك ستجد في مثالنا السابق سطرًا يبدو كما يلي: <meta charset="utf-8"> يحدد هذا العنصر مجموعة المحارف المستخدَمة في ترميز الصفحة، وتُعَدّ المجموعة مجموعةً عالميةً من المحارف وتضم تقريبًا أيّ محرف يُستخدَم في اللغات البشرية المكتوبة، أي ستعرض صفحتك المحتوى المكتوب أيًا كانت لغته، فلهذا من الجيد دائمًا تحديد هذه المجموعة من المحارف في كل صفحة تبنيها، فقد تتعامل صفحتك مع اليابانية والإنجليزية في الوقت نفسه دون أية مشاكل تذكر: قد لا تُعرض الصفحة بالصورة الصحيحة إذا استخدمت مجموعة المحارف ISO-8859-1 مثل مجموعة المحارف الخاصة بالأبجدية اللاتينية: ملاحظة: تصحِّح بعض المتصفحات -مثل كروم- هذه الأخطاء تلقائيًا، لذلك قد لا ترى هذه المشكلة في بعض المتصفحات، ومع ذلك يُفضّل استخدام مجموعة المحارف utf-8 لتلافي أيّ مشاكل محتمَلة في المتصفحات المختلفة. تطبيق: اختبار مجموعات المحارف عُد إلى قالب HTML الذي عملنا عليه قبل قليل في فقرة "إضافة عنوان للصفحة"، وحاول تغيير قيمة السمة charset للعنصر <meta> إلى ISO-8859-1 وستدخِل المحارف اليابانية إلى صفحتك: <p>Japanese example: ご飯が熱い。</p> إضافة اسم المؤلف ووصف للصفحة قد يحتوي العنصر <meta> على سمات مثل name و content: name: يحدِّد نوع البيانات التي يعرضها العنصر <meta>. content: يحدِّد المحتوى الفعلي للعنصر. تساعدك الصفتين السابقتين على إضافة مؤلف الصفحة وتزودك بوصف مناسب لمحتواها، وإليك مثالًا كما يلي: <meta name="author" content="Chris Mills"> <meta name="description" content="The MDN Web Docs Learning Area aims to provide complete beginners to the Web with all they need to know to get started with developing web sites and applications."> يساعدك تحديد اسم المؤلف في نواح عدة منها الحصول على أجوبة عن نقاط في المحتوى أو التواصل معه، كما تتيح بعض أنظمة إدارة المحتوى إمكانية الاستخراج التلقائي لمعلومات مؤلف الصفحة لكي تستخِدمها لاحقًا للاستفسار عن المحتوى أو التواصل، ومن الجيد أيضًا أن تقدِّم وصفًا لصفحتك يضم كلمات مفتاحيةً تتعلق بطبيعة المحتوى الذي تقدمه، فقد يزيد ذلك من فرصة تصدُّر صفحتك لنتائج محركات البحث، إذ يُدعى هذا الأمر تحسين محركات البحث Search Engine Optimization أو SEO اختصارًا. تطبيق: استخدام وصف الصفحة في محركات البحث يُستخدَم وصف الصفحة أيضًا في نتائج محركات البحث، وسنوضِّح ذلك من خلال المثال التالي: انتقل إلى الصفحة الرئيسية لموقع حسوب. اعرض الشيفرة المصدرية للصفحة بالنقر عليها بالزر اليميني للفأرة ثم اختر "عرض الشيفرة المصدرية للصفحة View Page Source" من القائمة. ابحث عن البيانات الوصفية التي تحدِّد وصفًا للصفحة، وستبدو لك كما يلي: <meta name="description" content="في مهمة لتطوير العالم العربي. نعمل لنمكّن الشباب ونفتح مزيدًا من الفرص أمامهم. نحن حسوب."> ابحث الآن عن "حسوب" باستخدام محرك البحث جوجل وستلاحظ كيف استخدم عنوان المستند <title> والبيانات الوصفية <meta> في إظهار نتيجة البحث. ملاحظة: قد تلاحظ في جوجل صفحات فرعيةً مرتبطةً بنتيجة البحث مرتبةً في قائمة أسفل رابط الصفحة الرئيسية، إذ تُدعى هذه الصفحات باسم الروابط الداخلية siteLink ويمكن إدارتها من خلال الأداة Google's webmaster tools لتبدو نتائج البحث أفضل. ملاحظة: لم تعُد بعض البيانات الوصفية مستخدَمةً حاليًا مثل الكلمات المفتاحية "keywords": <meta name="keywords" content="fill, in, your, keywords, here"> التي يُفترض استخدامها من قبل محركات البحث للحكم على مطابقة صفحة ما لمعايير البحث، وتتجاهل محركات البحث حاليًا هذه الكلمات، إذ يضيف بعض المخترقين مئات من هذه الكلمات للحصول على نتائج منحازة إلى الصفحات التي يريدونها. أنواع أخرى من البيانات الوصفية قد تصادف في رحلتك ضمن عالم ويب أنواع أخرى من البيانات الوصفية، والكثير من الميزات التي ستراها في مواقع الويب هي ملكية إبداعية خاصة صُمِّمت لتزويد بعض المواقع بمعلومات محددة مثل مواقع التواصل الاجتماعي، فقد طوّرت الفيسبوك مثلًا بروتوكول بيانات وصفية يُدعى Open Graph Data لكي يقدّم بيانات وصفيةً غنيةً عن موقع ما، فإذا اطلعت على الشيفرة المصدرية لموقع "MDN Web Docs"، فستجد التالي: <meta property="og:image" content="https://developer.mozilla.org/static/img/opengraph-logo.png"> <meta property="og:description" content="The Mozilla Developer Network (MDN) provides information about Open Web technologies including HTML, CSS, and APIs for both Web sites and HTML5 Apps. It also documents Mozilla products, like Firefox OS."> <meta property="og:title" content="Mozilla Developer Network"> سيظهر الرابط مزوّدًا بصورة ووصف للموقع عندما تضع رابطًا إلى موقع "MDN Web Docs" على فيسبوك، مما يزيد غنى تجربة المستخدِم: يستخدِم تويتر بروتوكولًا وصفيًا مماثلًا يُدعى Twitter Cards يقدِّم محتوًى غنيًا عن عنوان URL محدد عندما يُعرض على twitter.com، وإليك مثالًا كما يلي: <meta name="twitter:title" content="Mozilla Developer Network"> إضافة أيقونات مخصصة إلى صفحتك تستطيع الإشارة إلى أيقونات مخصصة تُعرض في مناسبات محددة من خلال البيانات الوصفية لكي تُغني تصميم صفحتك، وأكثر هذه الأيقونات استخدامًا هي الأيقونة المفضلة favicon والتي تُستخدم لتدل على صفحتك عندما تنشئ علامةً مرجعيةً إليها في متصفحك أو عندما تضيفها إلى المفضلة، ولا يزال استخدام هذه الأيقونة شائعًا منذ سنوات عديدة، فهي عبارة عن أيقونة مربعة بعرض 16 بكسل تشاهدها في أماكن عدة مثل عناوين نوافذ المتصفح وإلى جانب العلامات المرجعية للصفحات، كما يمكنك إضافة الأيقونة المفضلة على صفحتك كما يلي: احفظ الأيقونة المطلوبة باللاحقة ico. في المجلد نفسه الذي يحتوي على الصفحة index.html، وقد تدعم بعض المتصفحات لواحق أخرى مثل gif. أو png.، لكن يضمن لك استخدام اللاحقة السابقة العمل على أيّ متصفح رجوعًا إلى إنترنت أكسبلورر 6. أضف السطر التالي إلى ترويسة ملف HTML: <link rel="icon" href="favicon.ico" type="image/x-icon"> إليك مثالًا عن أيقونة مفضلة إلى جانب علامة مرجعية: ستجد أيضًا الكثير من أنواع الأيقونات حاليًا، فإذا فتحت الشيفرة المصدرية لموقع "MDN Web Docs"، فستجد التالي: <!-- third-generation iPad with high-resolution Retina display: --> <link rel="apple-touch-icon-precomposed" sizes="144x144" href="https://developer.mozilla.org/static/img/favicon144.png"> <!-- iPhone with high-resolution Retina display: --> <link rel="apple-touch-icon-precomposed" sizes="114x114" href="https://developer.mozilla.org/static/img/favicon114.png"> <!-- first- and second-generation iPad: --> <link rel="apple-touch-icon-precomposed" sizes="72x72" href="https://developer.mozilla.org/static/img/favicon72.png"> <!-- non-Retina iPhone, iPod Touch, and Android 2.1+ devices: --> <link rel="apple-touch-icon-precomposed" href="https://developer.mozilla.org/static/img/favicon57.png"> <!-- basic favicon --> <link rel="icon" href="https://developer.mozilla.org/static/img/favicon32.png"> تستخدَم الأيقونات السابقة لإظهار أيقونات عالية الدقة عندما يُحفظ موقع ويب على الشاشة الرئيسية لجهاز آيباد. لا تقلق حيال هذا الكم الكبير من الأيقونات وضرورة إدراجها الآن، فهي ميزة متقدِّمة نوعًا ما ولا حاجة لأن تعرف عنها الكثير حتى تكمل معنا المسيرة خلال هذه المقالات، فالغاية الحقيقية من هذا العرض هو إطلاعك على أمور مثل هذه قد تصادفها أثناء تصفحك للشيفرة المصدرية للمواقع. ملاحظة: في حال استخدمت سياسة خصوصية المحتوى Content Security Policy أو CSP اختصارًا في موقعك، فستُطبق هذه السياسة على أيقونة المفضلة favico، فإذا لم يُحمّل المتصفح هذه الأيقونة، فتأكد من أنّ التوجيه img-src العائد للترويسة Content-Security-Policy لا يعيق الوصول إلى الأيقونة. تطبيق تنسيقات CSS وشيفرة جافاسكربت على لغة HTML تستخدِم معظم المواقع في أيامنا هذه لغة CSS لتحسين مظهر الصفحة وجافاسكربت لإكساب الصفحة قدرات تفاعلية مع المستخدِم مثل مشغلات الفيديو والخرائط والألعاب وغيرها، إذ تُطبَّق هذه الأمور على الصفحات باستخدام العنصر <link> والعنصر <script>: <link>: ينبغي وضع هذا العنصر ضمن ترويسة الملف <head> ويمتلك صفتين هما "rel="stylesheet والتي تشير إلى أنّ الرابط سيستخدَم لتنسيق الصفحة، والسمة href التي تحدد مسارًا إلى ملف التنسيق: <link rel="stylesheet" href="my-css-file.css"> <script>: ينبغي أن يكون أيضًا ضمن الترويسة، كما ينبغي أن يمتلك السمة src التي تحتوي على مسار ملف جافاسكربت الذي تريد استخدامه، بالإضافة إلى السمة defer التي تدفع بالمتصفح إلى تحميل ملف الشيفرة بعد الانتهاء من تفسير شيفرة لغة HTML، وللأمر أهميته في ضمان تحميل كل عناصر لغة HTML قبل تشغيل شيفرة جافاسكربت، وبالتالي لن تقع أخطاء نتيجة محاولة جافاسكربت الوصول إلى عناصر لغة HTML غير موجودة بعد في الصفحة، وهناك طرق عدة للتحكم بتحميل شيفرة جافاسكربت ضمن الصفحات، لكنه الأسلوب الأكثر فعاليةً واستخدامًا في المتصفحات الحديثة: <script src="my-js-file.js" defer></script> ملاحظة: قد يبدو لك العنصر <script> فارغًا لكنه ليس كذلك، كما ينبغي استخدام وسم النهاية، إذ نستطيع أيضًا كتابة الشيفرة داخل هذا العنصر بدلًا من الإشارة إلى ملف خارجي. تطبيق: استخدام لغة CSS ولغة جافاسكربت في صفحتنا لبدء العمل على هذا التطبيق، أحضر نسخًا عن الملفات meta-example.html و script.js و style.css وخزِّنها على حاسوبك في المجلد نفسه، ثم تأكد من حفظها تمامًا بالأسماء واللواحق -أي الامتدادات- السابقة. افتح ملف HTML في المتصفح وفي محرِّر النصوص معًا. أضف العنصر <link> إلى شيفرة HTML لكي تشير إلى ملفَي CSS وجافاسكربت كلًا على حدة كما فعلنا سابقًا. إذا نجحت في تنفيذ الأمر، فسترى أنّ الأمور قد تغيرت ليعرض المتصفح بعد تحديثه ما يلي: أضافت شيفرة جافاسكربت قائمةً فارغةً إلى الصفحة، فإذا نقرت الآن في أيّ مكان من الصفحة، فستظهر لك رسالةً منبثقةً تسألك إضافة بعض الكلمات لتكوين عنصر قائمة جديد، وعند النقر على الزر "موافق OK"، سيُضاف عنصر جديد إلى القائمة يحمل النص الذي أدخلته؛ أما عندما تنقر على عنصر قائمة موجود، فستظهر لك رسالة منبثقة لتغيير محتوى هذا العنصر. غيّرت CSS الخلفية إلى اللون الأخضر وكبّرت حجم النص، كما غيّرت في تنسيق بعض العناصر التي أضافتها جافاسكربت، أي الشريط الأحمر والإطار الأسود للقائمة التي ولّدتها جافاسكربت. ملاحظة: لو وجدت نفسك تائهًا في هذا التمرين ولم تتمكن من إحضار ملفات CSS وجافاسكربت، فحاول التحقق من الملف css-and-js.html. ضبط اللغة الرئيسية للصفحة لابد أخيرًا من الإشارة إلى إمكانية وضرورة تحديد لغة الصفحة، فيمكن إنجاز الأمر من خلال السمة lang للعنصر الجذري <html>: <html lang="en-US"> للأمر فوائد عدة منها فهرسة موقعك بفعالية من قِبَل محركات البحث، إذ يسمح ذلك بظهور الموقع في النتائج المرتبطة بهذه اللغة مثلًا، كما يساعد ذلك كثيرًا الأشخاص ذوي الإعاقات البصرية الذين يستخدِمون قارئات الشاشة، فالكلمة "six" مثلًا موجودة في الفرنسية والإنكليزية لكنها تلفظ بصورة مختلفة، كما يمكنك أيضًا تحديد أجزاء من صفحتك لتعرض بلغة مختلفة، إذ يمكنك مثلًا اختيار اللغة لقسم فقط من الصفحة كما يلي: <p>Japanese example: <span lang="ja">ご飯が熱い。</span>.</p> ملاحظة: تُحدَّد رموز اللغات بواسطة المعيار ISO 639-1. خلاصة بهذا نكون قد وصلنا إلى نهاية هذا العرض التمهيدي لترويسة HTML، وهنالك الكثير مما يمكن إنجازه أيضًا لكن ستكون الإطالة في الأمر مزعجةً ومربكةً في هذه المرحلة، فكل ما نريده هو إيصال الأفكار الأكثر شيوعًا حول الموضوع، وسنلقي نظرةً في المقال القادم على أساسيات نصوص HTML. ترجمة -وبتصرف- للمقال What’s in the head? Metadata in HTML. اقرأ أيضًا HTML و CSS للمبتدئين: كيف تصمم أول صفحة ويب لك HTML و CSS للمبتدئين: مقدمة إلى تنسيقات CSS خمسة أشياء عليك معرفتها عن HTML5 مدخل إلى البيانات الوصفية (microdata) في HTML5
  4. نستكشف في هذا المقال تطبيقات نود Node التي تستخدم قواعد بيانات علاقية، وسنبني خلال تقدمنا في المقال واجهةً خلفيةً تستخدم قاعدة بيانات علاقية ليتعامل معها تطبيق الملاحظات الذي عملنا عليه سابقًا. ولإكمال هذا المقال، لا بُدّ من تعميق معرفتك بقواعد البيانات العلاقية ولغة SQL. يمكنك الاطلاع على سلسلة مقالات المرجع المتقدم إلى SQL في أكاديمية حسوب، وكذلك الاطلاع على توثيق لغة SQL في موسوعة حسوب. ستجد 24 تمرينًا في هذا المقال وعليك أن تنجزها جميعًا لتكمل الدورة التعليمية، تُسلّم الحلول إلى منظومة تسليم الحلول كما هو الحال في الأقسام السابقة ما عدا الأقسام من 0 إلى 7 والتي تُسلّم حلول التمارين فيها إلى مكان آخر. إيجابيات وسلبيات قواعد بيانات المستندات استخدمنا قاعدة البيانات MongoDB في جميع المقال السابقة، وهي قاعدة بيانات مستندات، ومن أهم ميزاتها أنها لا تملك أي مخططات، أي أنّ لها معرفةٌ محدودةٌ بطبيعة البيانات المخزنة في كل مجموعة. يتواجد مخطط قاعدة البيانات schema في شيفرة البرنامج فقط، إذ يفسِّر البيانات بطريقة معينة، كأن يحدد أن بعض الحقول هي مراجعٌ لكائنات في مجموعةٍ أخرى. رأينا في المثال التطبيقي الذي يضم قاعدة بيانات تخزن الملاحظات والمستخدمين (في القسمين 3 و 4)، إذ تُخزِّن الملاحظات "notes" على النحو التالي: [ { "_id": "600c0e410d10256466898a6c", "content": "HTML is easy" "date": 2021-01-23T11:53:37.292+00:00, "important": false "__v": 0 }, { "_id": "600c0edde86c7264ace9bb78", "content": "CSS is hard" "date": 2021-01-23T11:56:13.912+00:00, "important": true "__v": 0 }, ] وتُخزّن أيضًا بيانات المستخدمين في المجموعة "users" على النحو التالي: [ { "_id": "600c0e410d10256466883a6a", "username": "mluukkai", "name": "Matti Luukkainen", "passwordHash" : "$2b$10$Df1yYJRiQuu3Sr4tUrk.SerVz1JKtBHlBOARfY0PBn/Uo7qr8Ocou", "__v": 9, notes: [ "600c0edde86c7264ace9bb78", "600c0e410d10256466898a6c" ] }, ] تعرِف MongoDB نوع البيانات في الحقول التي تُخزِّن كيانات البيانات، لكنها لا تعرف إلى أي مجموعة من الكيانات يشير المعرِّف المميز الذي سجّله المستخدم، ولا تهتم أيضًا بالحقول التي تمتلكها الكيانات المُخزنة في مجموعات البيانات؛ لهذا تترك MongoDB أمر التحقق من صحة المعلومات المخزّنة في قاعدة البيانات إلى المبرمج. هناك طبعًا إيجابيات وسلبيات لعدم وجود مخطط لقاعدة البيانات، وإحدى الإيجابيات هي المرونة التي تضيفها الميزة اللا إدارية في مخطط البيانات، إذ سيسرّع انتفاء الحاجة إلى تعريف مخطط على مستوى قاعدة البيانات من عمل المطوّر في حالات عدة، كما أنه أسهل من تعريف وتعديل المخطط في حالات أخرى؛ في حين تتعلق المشاكل الناجمة عن عدم وجود المخطط غالبًا بسهولة الوقوع في الأخطاء، فكل شيء ملقىً على عاتق المبرمج، وليس لقاعدة البيانات في هذه الحالة طريقةً للتحقق من نزاهتها honest، أي إذا احتوت كل الحقول الإجبارية على القيم الصحيحة، أو إذا أشارت الحقول ذات النوع المرجعي إلى كيانات من النوع الصحيح عمومًا وهكذا. تعتمد قاعدة البيانات العلاقية التي ستكون محور حديثنا في هذا المقال بشدة على وجود مخطط، وتكاد تكون الإيجابيات والسلبيات في استخدامها على نقيض تلك المتعلقة بقاعدة بيانات المستندات. يعود السبب الرئيسي في استخدام قاعدة البيانات MongoDB تحديدًا في الأقسام السابقة إلى طبيعتها التي لا تحتاج مخططات، الأمر الذي يسهّل استخدامها بالنسبة لذوي المعرفة المحدودة بقواعد البيانات العلاقية، لكن نفترض بالنسبة لكل الحالات التي عرضناها في منهاجنا أن القواعد العلاقية هي الأنسب. قاعدة بيانات التطبيق نحتاج في تطبيقنا إلى قاعدة بيانات علاقية، وهناك خيارات عديدة، لكننا سنستخدم حاليًا الحل الأكثر شعبيةً والمفتوح المصدر PostgreSQL؛ ويمكنك تثبيت Postgres (هكذا تُدعى قاعدة البيانات هذه غالبًا) على جهازك إذا أردت؛ والأسهل من هذا هو استخدامها بمثابة خدمة سحابية، مثل ElephantSQL. بإمكانك أيضًا الاستفادة مما جاء في جزئية سابقة من السلسلة لاستخدام Postgres محليًا من خلال دوكر Docker. اخترنا في هذا المقال الاستفادة من إمكانية إنشاء قاعدة بيانات Postgres للتطبيق على خدمة "Heroku" السحابية التي ألفنا العمل معها في المقالين 3 و4. سنبني في الجزء النظري من هذا المقال نسخةً مرتبطة بقاعدة البيانات Postgres عن الواجهة الخلفية لتطبيق تخزين الملاحظات الذي بنيناه في المقالين 3 و4. لننشئ أولًا مجلدًا مناسبًا ضمن تطبيق Heroku ونضيف إليه قاعدة بيانات، ثم نستخدم الأمر heroku config للحصول على "السلسلة النصية connect string" اللازمة للاتصال مع القاعدة: heroku create # heroku يعيد اسم التطبيق للتطبيق الذي أنشأته في heroku addons:create heroku-postgresql:hobby-dev -a <app-name> heroku config -a <app-name> === cryptic-everglades-76708 Config Vars DATABASE_URL: postgres://<username>:<password>@<host-of-postgres-addon>:5432/<db-name> يُعد الوصول إلى قاعدة البيانات مباشرةً من الأمور الأساسية التي ينبغي الانتباه إليها تحديدًا في قواعد البيانات العلاقية، نظرًا لوجود أساليب عدة لتنفيذ الأمر، ووجود عدة واجهات مستخدم رسومية، مثل pgAdmin، لكن سنستخدم أداة سطر الأوامر psql الخاصة بالقاعدة Postgres. يمكن الوصول إلى قاعدة البيانات من خلال تنفيذ أمر psql على خادم Heroku على النحو التالي (لاحظ كيف تعتمد معاملات الأمر على عنوان url للاتصال بقاعدة بيانات Heroku): heroku run psql -h <host-of-postgres-addon> -p 5432 -U <username> <dbname> -a <app-name> بعد إدخال كلمة المرور، سننفذ أمر psql الأساسي وهو d\، الذي يخبرك بمحتوى قاعدة البيانات: Password for user <username>: psql (13.4 (Ubuntu 13.4-1.pgdg20.04+1)) SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off) Type "help" for help. username=> \d Did not find any relations. وكما ترى، لا يوجد شيء حاليًا في قاعدة البيانات. لننشئ إذًا جدولًا للملاحظات: CREATE TABLE notes ( id SERIAL PRIMARY KEY, content text NOT NULL, important boolean, date time ); يمكننا ملاحظة بعض النقاط: يُعرِّف العمود id مفتاحًا أساسيًا primary key، ويعني ذلك أن قيم هذا العمود لا بُدّ أن تكون فريدةً لكل سطر في الجدول ولا ينبغي أن تكون قيمتها فارغة. يُعرّف نوع العمود id على أنه تسلسلي SERIAL، وهو ليس نوع حقيقي بل تمثيل لعمود ذي قيم صحيحة تعيّن Postgres قيمه الفريدة تلقائيًا وتزيد هذه القيمة بمقدار "واحد" عند إنشاء سطر جديد. يكون العمود المُسمى content من النوع النصي ويُعُرِّف كي تعيّن له قيمةٌ في كل سطر. لنلق نظرةً على الوضع الآن من خلال الطرفية، ولننفّذ أولًا الأمر d\ الذي يعرض جداول قاعدة البيانات: username=> \d List of relations Schema | Name | Type | Owner --------+--------------+----------+---------------- public | notes | table | username public | notes_id_seq | sequence | username (2 rows) إذ تُنشئ Postgres إضافةً إلى الجدول notes جدولًا فرعيًا يُدعى notes_id_seq يتتبع القيم المُسندة إلى العمود id عند إنشاء ملاحظة جديدة. وبتنفيذ الأمر d notes\ يمكننا رؤية طريقة تعريف الجدول notes: username=> \d notes; Table "public.notes" Column | Type | Collation | Nullable | Default -----------+------------------------+-----------+----------+----------------------------------- id | integer | not null | nextval('notes_id_seq'::regclass) content | text | | not null | important | boolean | | | | date | time without time zone | | | | Indexes: "notes_pkey" PRIMARY KEY, btree (id) إذًا، للعمود id قيم افتراضية تُستخرج من تنفيذ الدالة الداخلية nextval في Postgres. لنُضِف بعض المحتوى إلى الجدول: insert into notes (content, important) values ('Relational databases rule the world', true); insert into notes (content, important) values ('MongoDB is webscale', false); لنرى الآن كيف يبدو المحتوى الذي أضفناه: username=> select * from notes; id | content | important | date ----+-------------------------------------+-----------+------ 1 | relational databases rule the world | t | 2 | MongoDB is webscale | f | (2 rows) إذا حاولنا تخزين البيانات في قاعدة البيانات دون العودة إلى مخطط، فلن ينجح الأمر، لأنّ قيم الأعمدة الإجبارية لا بُد أن تكون موجودةً: username=> insert into notes (important) values (true); ERROR: null value in column "content" of relation "notes" violates not-null constraint DETAIL: Failing row contains (9, null, t, null). ولا يمكن أن تكون قيمة العمود من النوع الخاطئ: username=> insert into notes (content, important) values ('only valid data can be saved', 1); ERROR: column "important" is of type boolean but expression is of type integer LINE 1: ...tent, important) values ('only valid data can be saved', 1); ^ ولا يمكن القبول بأعمدة غير موجودة في المخطط: username=> insert into notes (content, important, value) values ('only valid data can be saved', true, 10); ERROR: column "value" of relation "notes" does not exist LINE 1: insert into notes (content, important, value) values ('only ... سننتقل تاليًا إلى طريقة الدخول إلى قاعدة البيانات من التطبيق. تطبيق Node يستخدم قاعدة بيانات علاقية لنشغّل التطبيق كما جرت العادة من خلال التعليمة npm init ونثبّت "nodemon" على أنه اعتمادية تطوير development dependency وكذلك اعتماديات زمن التشغيل التالية: npm install express dotenv pg sequelize نجد من بين هذه الاعتماديات sequelize، وهي المكتبة التي يمكننا من خلالها استخدام Postgres؛ وتنتمي هذه المكتبة إلى مكتبات الربط العلاقي للكائنات Object relational mapping -أو اختصارًا ORM-، التي تسمح بتخزين كائنات جافا سكربت JavaScript في قاعدة بيانات علاقية دون استخدام لغة SQL بحد ذاتها وبصورةٍ مشابهة للمكتبة "Mongoose" التي استخدمناها مع قاعدة البيانات MongoDB. لنختبر الآن قدرتنا على الاتصال الناجح بقاعدة البيانات، لهذا أنشئ الملف index.js وأضف إليه الشيفرة التالية: require('dotenv').config() const { Sequelize } = require('sequelize') const sequelize = new Sequelize(process.env.DATABASE_URL, { dialectOptions: { ssl: { require: true, rejectUnauthorized: false } }, }) const main = async () => { try { await sequelize.authenticate() console.log('Connection has been established successfully.') sequelize.close() } catch (error) { console.error('Unable to connect to the database:', error) } } main() ينبغي تخزين سلسلة الاتصال النصية connect string بقاعدة البيانات التي أظهرها الأمر heroku config في ملفٍ له الامتداد env.، وينبغي أن تكون مشابهةً لما يلي: $ cat .env DATABASE_URL=postgres://<username>:<password>@ec2-54-83-137-206.compute-1.amazonaws.com:5432/<databasename> لنتحقق من نجاح الاتصال: $ node index.js Executing (default): SELECT 1+1 AS result Connection has been established successfully. إذا عمل الاتصال، يمكننا حينها تشغيل الاستعلام الأول. لنعدّل البرنامج على النحو التالي: require('dotenv').config() const { Sequelize, QueryTypes } = require('sequelize') const sequelize = new Sequelize(process.env.DATABASE_URL, { dialectOptions: { ssl: { require: true, rejectUnauthorized: false } }, }); const main = async () => { try { await sequelize.authenticate() const notes = await sequelize.query("SELECT * FROM notes", { type: QueryTypes.SELECT }) console.log(notes) sequelize.close() } catch (error) { console.error('Unable to connect to the database:', error) } } main() ينبغي أن يطبع تنفيذ البرنامج ما يلي: Executing (default): SELECT * FROM notes [ { id: 1, content: 'Relational databases rule the world', important: true, date: null }, { id: 2, content: 'MongoDB is webscale', important: false, date: null } ] وعلى الرغم من كون Sequelize مكتبة ربط علاقي للكائنات ORM، أي أنها تحتاج إلى قليلٍ فقط من شيفرة SQL التي تكتبها بنفسك، فقد استخدمنا شيفرة SQL مباشرةً مع تابع Sequelize الذي يُدعى query. طالما أنّ كل شيء يعمل على ما يرام، لنحوّل التطبيق إلى تطبيق ويب. require('dotenv').config() const { Sequelize, QueryTypes } = require('sequelize') const express = require('express') const app = express() const sequelize = new Sequelize(process.env.DATABASE_URL, { dialectOptions: { ssl: { require: true, rejectUnauthorized: false } }, }); app.get('/api/notes', async (req, res) => { const notes = await sequelize.query("SELECT * FROM notes", { type: QueryTypes.SELECT }) res.json(notes) }) const PORT = process.env.PORT || 3001 app.listen(PORT, () => { console.log(`Server running on port ${PORT}`) }) يبدو أن التطبيق يعمل جيدًا. لنتحول الآن إلى استخدام Sequelize بدلًا من SQL. النماذج في Sequelize يُمثَّل كل جدول من جداول قاعدة البيانات عند استخدام Sequelize بنموذج model، والذي يُعدُّ صنف JavaScript الخاص بالجدول. لنعرِّف الآن النموذج Note المتعلق بالجدول notes من التطبيق وذلك بتغيير الشيفرة على النحو التالي: require('dotenv').config() const { Sequelize, Model, DataTypes } = require('sequelize') const express = require('express') const app = express() const sequelize = new Sequelize(process.env.DATABASE_URL, { dialectOptions: { ssl: { require: true, rejectUnauthorized: false } }, }); class Note extends Model {} Note.init({ id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true }, content: { type: DataTypes.TEXT, allowNull: false }, important: { type: DataTypes.BOOLEAN }, date: { type: DataTypes.DATE } }, { sequelize, underscored: true, timestamps: false, modelName: 'note' }) app.get('/api/notes', async (req, res) => { const notes = await Note.findAll() res.json(notes) }) const PORT = process.env.PORT || 3001 app.listen(PORT, () => { console.log(`Server running on port ${PORT}`) }) لا يوجد شيء جديد في تعريف النموذج، فكل عمودٍ له نوع معرَّف، إضافةً إلى خاصيات أخرى عند الضرورة كأن يكون العمود مفتاحًا أساسيًا للجدول. يضم المعامل الثاني في تعريف النموذج السمة sequelize إضافةً إلى غيرها من معلومات التهيئة. كما عرّفنا أن الجدول لا ينبغي أن يضم أعمدة لها بصمات زمنية timestamps، مثل created_atأنشئ عند و updated_at حُدِّث عند. عرّفنا أيضًا القيمة true إلى underscored، والتي تعني أن أسماء الجدول مشتقةٌ من أسماء النموذج لكن بصيغة الجمع وباستخدام تنسيق الأفعى snake_case، الذي تُوضع فيه الشرطة السفلية بدلًا من الفراغات بين الكلمات وتُكتب بأحرف صغيرة؛ ويعني هذا عمليًا أنه إذا كان اسم النموذج "Note"، فسيكون اسم الجدول المقابل "notes". ولو كان اسم النموذج مكونًا من جزئين مثل "StudyGroup"، فسيكون اسم الجدول المقابل "study_groups". وبدلًا من الاستدلال على أسماء الجداول تلقائيًا، تسمح لك المكتبة Sequelize بتعريف أسماء الجداول صراحةً أيضًا. تُطبق سياسة التسمية هذه على أسماء الأعمدة أيضًا. فلو عرّفنا أن ملاحظةً ما سترتبط بعام الإنشاء creationYear أي بالمعلومات المتعلقة بعام إنشاء الملاحظة، سنعرّفها في النموذج على النحو التالي: Note.init({ // ... creationYear: { type: DataTypes.INTEGER, }, }) سيكون اسم العمود المقابل في قاعدة البيانات creation_year، لكن الإشارة إلى العمود في الشيفرة تكون دائمًا باستخدام تنسيق النموذج نفسه أي طريقة سنام الجمل camel case. وعرّفنا كذلك السمة modelName التي أُسندت إليها القيمة note، وستكون القيمة الافتراضية لاسم النموذج بحرف بداية كبير "Note"، وسترى أنّ هذا أكثر ملاءمةً لاحقًا. يسهل التعامل مع قاعدة البيانات من خلال واجهة الاستعلام التي يؤمنها النموذج، إذ يعمل التابع works تمامًا كما يوحي اسمه: app.get('/api/notes', async (req, res) => { const notes = await Note.findAll() res.json(notes) }) تخبرك الطرفية أن التابع ()Note.findAll يُنفِّذ الاستعلام التالي: Executing (default): SELECT "id", "content", "important", "date" FROM "notes" AS "note"; سننجز تاليًا وصلة endpoint بغرض إنشاء ملاحظات جديدة: app.use(express.json()) // ... app.post('/api/notes', async (req, res) => { console.log(req.body) const note = await Note.create(req.body) res.json(note) }) تُضاف الملاحظة الجديدة باستدعاء التابع create الذي يوفّره النموذج "Note" بعد تمرير كائن يعرِّف قيم الأعمدة لسطر الملاحظة الجديدة على أنه وسيطٌ لهذا التابع. وبإمكانك أيضًا حفظ قاعدة البيانات بتنفيذ التابع build أولًا لإنشاء كائن نموذج من البيانات المطلوبة، ثم استدعاء التابع save وذلك بدلًا من التابع create: const note = Note.build(req.body) await note.save() لا يؤدي استدعاء التابع build إلى حفظ الكائن في قاعدة البيانات، وبالتالي من الممكن تعديله قبل تنفيذ أمر الحفظ الفعلي: const note = Note.build(req.body) note.important = true await note.save() يُعد استخدام التابع create في المثال السابق أكثر ملاءمةً لما نريده من التطبيق، لذلك سنلتزم باستخدامه من الآن فصاعدًا. إذا لم يكن الكائن المُنشأ صالحًا، ستظهر رسالة خطأ، لذلك إذا حاولت إضافة ملاحظة جديدة دون محتوى، ستخفق العملية وتكشف لك الطرفية عن السبب بأن "المحتوى لا يمكن أن يكون فارغًا": (node:39109) UnhandledPromiseRejectionWarning: SequelizeValidationError: notNull Violation: Note.content cannot be null at InstanceValidator._validate (/Users/mluukkai/opetus/fs-psql/node_modules/sequelize/lib/instance-validator.js:78:13) at processTicksAndRejections (internal/process/task_queues.js:93:5) لنضف آليةً بسيطةً لاصطياد الأخطاء عند إضافة ملاحظةٍ جديدة: app.post('/api/notes', async (req, res) => { try { const note = await Note.create(req.body) return res.json(note) } catch(error) { return res.status(400).json({ error }) } }) التمرينات 13.1 إلى 13.3 سنبني في هذه التمرينات واجهة خلفية لتطبيق مدوّنات يشابه ما فعلنا في القسم 4، وينبغي أن يتوافق مع الواجهة الأمامية التي أنشأناها في القسم 5 باستثناء معالجة الأخطاء. سنضيف أيضًا ميزات مختلفة إلى الواجهة الخلفية لا يمكن للواجهة الأمامية في القسم 5 التعامل معها. التمرين13.1 أنشئ مستودع غيت هاب GitHub مخصص للتطبيق، ثم أنشئ تطبيق Heroku خاص به إضافةً إلى قاعدة بيانات Postgres، وتأكد من قدرتك على تأسيس اتصال بين التطبيق وقاعدة البيانات. التمرين 13.2 أنشئ باستخدام سطر الأوامر الجدول "blogs" الذي يضم الأعمدة التالية: id: يمثل قيمةً فريدةً تزداد باستمرار. author: قيمة نصية. url: قيمة نصية لا يمكن أن تكون فارغة. title: قيمة نصية لا يمكن أن تكون فارغة. likes: قيمة صحيحة تبدأ من الصفر افتراضيًا. أضف مدونتين على الأقل إلى قاعدة البيانات. احفظ بعد ذلك الأوامر التي استخدمتها في ملف يُدعى "commands.sql" في جذر التطبيق. التمرين 13.3 أضف إلى تطبيقك وظيفة طباعة المدوّنات الموجودة في قاعدة البيانات باستخدام سطر الأوامر كما في المثال التالي: $ node cli.js Executing (default): SELECT * FROM blogs Dan Abramov: 'On let vs const', 0 likes Laurenz Albe: 'Gaps in sequences in PostgreSQL', 0 likes إنشاء جداول قاعدة البيانات تلقائيا يوجد في تطبيقنا الحالي جانب غير مرغوب فهو يفترض وجود قاعدة بيانات مع المخطط المعين، أي أنّ الجدول "notes" قد أُنشأ بتنفيذ الأمر create table. تُخزّن شيفرة البرنامج على غيت هاب GitHub، لذلك من المنطقي تخزين الأوامر التي أنشأت بها قاعدة البيانات ضمن سياق الشيفرة لكي يبقى مخطط قاعدة البيانات نفسه كما تتوقعه شيفرة البرنامج. يُمكن للمكتبة أن تولّد تلقائيًا مخططًا انطلاقًا من تعريف النموذج من خلال التابع sync. لندمر الآن قاعدة البيانات الموجودة من خلال أوامر الطرفية على النحو التالي: drop table notes; تأكد من تدمير القاعدة بتنفيذ الأمر d\: username=> \d Did not find any relations. لن يعمل التطبيق الآن، لهذا سننفِّذ الأمر التالي مباشرةً بعد تعريف النموذج "Note": Note.sync() عندما يعمل التطبيق سيظهر ما يلي على شاشة الطرفية: Executing (default): CREATE TABLE IF NOT EXISTS "notes" ("id" SERIAL , "content" TEXT NOT NULL, "important" BOOLEAN, "date" TIMESTAMP WITH TIME ZONE, PRIMARY KEY ("id")); وهكذا، عندما يعمل التطبيق، تُنفَّذ التعليمة: CREATE TABLE IF NOT EXISTS "notes"... التي تُنشئ الجدول "notes" إن لم يكن موجودًا. خيارات أخرى لنكمل تطبيقنا بإضافة عدة خيارات أخرى. بإمكاننا البحث عن ملاحظة محددة باستخدام التابع findByPk لأنه يبحث ضمن قيم id التي تمثّل المفتاح الرئيسي لقاعدة البيانات: app.get('/api/notes/:id', async (req, res) => { const note = await Note.findByPk(req.params.id) if (note) { res.json(note) } else { res.status(404).end() } }) يؤدي البحث عن ملاحظة محددة إلى تنفيذ أمر SQL التالي: Executing (default): SELECT "id", "content", "important", "date" FROM "notes" AS "note" WHERE "note". "id" = '1'; في حال عدم وجود أية ملاحظات، سيعيد البحث القيمة null (لا شيء)، وسيعطي رمز الحالة المناسب. تُعدَّل الملاحظة على النحو التالي، ولا يمكن تعديل سوى الحقل important لأن الواجهة الأمامية لا تحتاج أي شيء آخر: app.put('/api/notes/:id', async (req, res) => { const note = await Note.findByPk(req.params.id) if (note) { note.important = req.body.important await note.save() res.json(note) } else { res.status(404).end() } }) يُستخرج الكائن المتعلق بسطر قاعدة البيانات باستخدام التابع findByPk، ويُعدّل بعدها الكائن وتُخزَّن النتيجة باستدعاء التابع save. بإمكانك إيجاد شيفرة التطبيق كاملةً في المستودع المخصص على GitHub ضمن الفرع "part13-1". طباعة الكائن الذي تعيده Sequelize على الطرفية تُعد الأداة console.log أفضل أدوات مبرمجي JavaScript إذ يمكّنهم استعمالها المتكرر من التقاط أسوأ الثغرات. لهذا سنطبع الملاحظات على الطرفية: app.get('/api/notes/:id', async (req, res) => { const note = await Note.findByPk(req.params.id) if (note) { console.log(note) res.json(note) } else { res.status(404).end() } }) لاحظ أنّ النتيجة النهائية ليست ما نتوقعه تمامًا: note { dataValues: { id: 1, content: 'Notes are attached to a user', important: true, date: 2021-10-03T15:00:24.582Z, }, _previousDataValues: { id: 1, content: 'Notes are attached to a user', important: true, date: 2021-10-03T15:00:24.582Z, }, _changed: Set(0) {}, _options: { isNewRecord: false, _schema: null, _schemaDelimiter: '', raw: true, attributes: [ 'id', 'content', 'important', 'date' ] }, isNewRecord: false } فكل الأشياء التي يحتويها الكائن إضافةً إلى البيانات قد طُبعت على الطرفية، لهذا يمكن الوصول إلى النتيجة المرجوة باستدعاء التابع toJSON العائد إلى كائن النموذج: app.get('/api/notes/:id', async (req, res) => { const note = await Note.findByPk(req.params.id) if (note) { console.log(note.toJSON()) res.json(note) } else { res.status(404).end() } }) ها هي النتيجة الآن كما هو متوقع. { id: 1, content: 'MongoDB is webscale', important: false, date: 2021-10-09T13:52:58.693Z } في الحالة التي نريد فيها طباعة مجموعة من الكائنات، لن يعمل التابع toJSON مباشرةً، بل يجب أن يُستدعى بصورةٍ مستقلة لكل كائن في المجموعة: router.get('/', async (req, res) => { const notes = await Note.findAll() console.log(notes.map(n=>n.toJSON())) res.json(notes) }) وتبدو النتيجة على النحو التالي: [ { id: 1, content: 'MongoDB is webscale', important: false, date: 2021-10-09T13:52:58.693Z }, { id: 2, content: 'Relational databases rule the world', important: true, date: 2021-10-09T13:53:10.710Z } ] وربما من الأفضل أن تحوّل المجموعة إلى تنسيق JSON لطباعتها باستخدام التابع JSON.stringify: router.get('/', async (req, res) => { const notes = await Note.findAll() console.log(JSON.stringify(notes)) res.json(notes) }) تُعد هذه الطريقة أكثر ملاءمةً خاصةً إذا احتوت الكائنات على كائنات أخرى، كما أنها مفيدةٌ لتنسيق الكائنات على الشاشة بطريقة تُسهِّل القراءة، ويُنفَّذ ذلك من خلال الأمر التالي: console.log(JSON.stringify(notes, null, 2)) وتبدو النتيجة على النحو التالي: [ { "id": 1, "content": "MongoDB is webscale", "important": false, "date": "2021-10-09T13:52:58.693Z" }, { "id": 2, "content": "Relational databases rule the world", "important": true, "date": "2021-10-09T13:53:10.710Z" } ] التمرين 13.4 حوّل تطبيقك إلى تطبيق ويب يدعم العمليات التالية: الحصول على كل المدونات: GET api/blogs. إضافة مدوّنة جديدة: POST api/blogs. حذف مدوّنة: DELETE api/blogs/:id. ترجمة -وبتصرف- للفصل Using relational databases with Sequelize من سلسلة Deep Dive Into Modern Web Development. اقرأ أيضًا المقال السابق: أساسيات تنسيق الحاويات مقدمة عن قواعد البيانات التعامل مع قواعد البيانات مفاهيم نموذج البيانات العلائقية RDM الأساسية المهمة في تصميم قواعد البيانات مقارنة بين أنظمة إدارة قواعد البيانات العلاقية: SQLite مع MySQL مع PostgreSQL
  5. يغطِّي هذا المقال المبادئ الأكثر بساطة لتنطلق، إذ يعرِّف مفهوم العناصر elements والسمات attributes وغيرها من المفاهيم المهمة ويعرض كيفية استخدامها، ثم ينتقل إلى عرض هيكلية صفحة مكتوبة بلغة HTML وكيف تُنظَّم العناصر داخلها، بالإضافة إلى شرح ميزات أساسية مهمة أخرى، كما ستجد خلال قراءتك لهذا المقال بعض الأمثلة التطبيقية التي تعطيك فرصة لتجريب ما تتعلمه، ولا بد أن تمتلك خلفيةً بسيطةً عن الحواسب قبل أن تبدأ قراءة المقال، كما يتطلب الأمر درايةً بالبرمجيات التي ينبغي تثبيتها لبدء العمل، ودرايةً ولو بسيطة بالتعامل مع الملفات. ما هي لغة HTML؟ لا تمثِّل لغة توصيف النص التشعبي Hypertext Markup Language أو لغة HTML اختصارًا لغة برمجة، وإنما لغةً وصفيةً تساعد المتصفحات على هيكلة صفحات الويب التي تزورها، وقد تكون الصفحات بسيطةً جدًا أو معقدةً جدًا وفقًا لرؤية مطور ويب. تتألف لغة HTML من سلسلة من العناصر التي تستخدِمها في إحاطة أو تغليف أو توصيف الأجزاء المختلفة للمحتوى الذي تريد عرضه ليظهر أو يسلك سلوكًا محددًا، فقد تُستخدَم الوسوم tags المحيطة بالمحتوى لتوصيف رابط تشعبي إلى صفحة أخرى أو لإظهار نص بحروف مائلة أو غير ذلك، ولنتأمل على سبيل المثال المحتوى النصي التالي: My cat is very grumpy إذا أردت أن يكون المحتوى فقرةً نصيةً مستقلةً بذاتها، فيمكن إحاطتها بوسمَي بداية ونهاية عنصر الفقرة النصية <p>: <p>My cat is very grumpy</p> ملاحظة: لا تُعَدّ وسوم لغة HTML حساسةً لحالة الأحرف، أي من الممكن كتابتها بحروف كبيرة أو صغيرة، إذ يمكن كتابة العنصر <title> مثلًا بالصورة <TITLE> أو <Title> أو <TiTlE> وسيعمل، لكن كتابة الوسوم بأحرف صغيرة هي ممارسة تطبيقية جيدة تسهِّل قراءة الشيفرة وتُظهر استمرارية في أسلوب الكتابة. تشريح عنصر HTML لنلق نظرةً أعمق على عنصر الفقرة <P>: يتكوَّن العنصر من: وسم البداية Opening tag: يتكون من اسم العنصر -أي p في حالتنا- محاطًا بقوسَي زاوية، كما يشير هذا الوسم إلى النقطة التي يبدأ عندها العنصر أو التي يبدأ تأثيره عندها (بداية الفقرة النصية في حالتنا). وسم النهاية Closing tag: يشابه وسم البداية لكنه يبدأ بشرطة أمامية / قبل اسم العنصر، ويشير هذا الوسم إلى نهاية العنصر -أي نهاية الفقرة في حالتنا-، ويُعَدّ إغفال وسم النهاية من أكثر الأخطاء التي يرتكبها المبتدئون وقد تفضي إلى نتائج غريبة بالفعل. المحتوى Content: يشير إلى المحتوى الفعلي للعنصر وهو في حالتنا نص فقط. العنصر Element: يتكون من وسمَي البداية والنهاية وبينهما المحتوى. تطبيق: إنشاء أول عنصر في لغة HTML استخدم منطقة تحرير الشيفرة Editable code في المحرِّر المضمّن التالي لتغليف السطر المكتوب في تلك المنطقة وذلك بإضافة الوسم <em> قبل السطر والوسم <em/> بعده، إذ سيؤدي ذلك إلى ظهور السطر مكتوبًا بأحرف مائلة، وسترى نتيجة عملك في منطقة الخرج المباشر Live Output"، فإذا ارتكبت خطأً، فيمكنك مسح ما كتبته بالنقر على زر إعادة ضبط Reset، في حين إذا وجدت نفسك تائهًا كليًا، فانقر على زر "أظهر الحل Show solution" لترى الحل الصحيح. عناصر متداخلة يمكن وضع عناصر داخل عناصر أخرى أيضًا وهذا ما يُعرَف بالتداخل nesting، فإذا أردنا إظهار الكلمة "very" في الفقرة "My cat is very grumpy" بخط سميك، فيمكن تغليف هذه الكلمة داخل العنصر <strong> كما يلي: <p>My cat is <strong>very</strong> grumpy.</p> لكن عليك أن تتأكد دومًا من تداخل العناصر بصورة صحيحة، فقد فتحنا في المثال السابق العنصر <p> أولًا ثم <strong>، وبالتالي توجَّب علينا إغلاق العنصر الثاني على الصورة <strong/> ثم إغلاق الأول <p/>، وبالتالي يكون التداخل التالي غير صحيح: <p>My cat is <strong>very grumpy.</p></strong> لا بدّ من فتح وإغلاق العناصر بالصورة الصحيحة لكي تظهر بوضوح داخل أو خارج عنصر آخر، فإذا تداخلت بالصورة التي عرضناها في الشيفرة السابقة، فسيحاول المتصفح أن يخمّن بأفضل صورة ما تحاول قوله، مما قد يسبب بظهور نتائج غير متوقعة، فلا تفعل ذلك. دورة تطوير واجهات المستخدم ابدأ عملك الحر بتطوير واجهات المواقع والمتاجر الإلكترونية فور انتهائك من الدورة اشترك الآن العناصر الكتلية والعناصر السطرية تصنَّف عناصر لغة HTML ضمن مجموعتين مهمتين هما مجموعة العناصر الكتلية Block elements والسطرية inline elements: العناصر الكتلية: تشكل كتلةً مرئيةً ضمن الصفحة، كما تظهر في سطر جديد بعد العنصر الذي يسبقها، أي لا يمكن أن يقع على سطر آخر مع عنصر آخر، وسيظهر العنصر الذي يليه في سطر جديد أيضًا، كما تُعَدّ العناصر الكتلية في الصفحة عناصر هيكلة وتنظيم، فقد تمثِّل فقرةً نصيةً أو عناوين أو قوائم تعداد أو قوائم تنقل أو حواشي سفلية، وتجدر الإشارة إلى أنّ العناصر الكتلية لا توضَع ضمن عناصر سطرية لكنها قد تتداخل مع بعضها. العناصر السطرية: تقع داخل العناصر الكتلية وتحيط بأجزاء صغيرة من المحتوى، أي جزء من فقرة نصية أو مجموعة من بنود قائمة، ولا تسبب هذه العناصر ظهور سطر جديد في المستند، أي لا تشغل بنفسها السطر بالكامل، كما تُستخدَم هذه العناصر مع النصوص غالبًا، إذ ينشئ العنصر <a> مثلًا رابطًا تشعبيًا، في حين يميِّز العنصران <em> و <strong> أجزاءً من النص. انظر إلى المثال التالي: <em>first</em><em>second</em><em>third</em> <p>fourth</p><p>fifth</p><p>sixth</p> لاحظ أنّ <em> هو عنصر سطري، وكما ترى في محرر الشيفرة، ستقع العناصر الثلاث الأولى على السطر ذاته دون أية فراغات بينها، لكن من ناحية أخرى سيظهر العنصر الكتلي <p> في سطر جديد، بحيث يسبقه فراغ ويليه فراغ، وهذه الفراغات هي نتيجة لتطبيق تنسيق CSS الافتراضي لعنصر الفقرة النصية <p>. ملاحظات: أعادت لغة HTML بنسختها الخامسة -أي HTML5- تعريف فئات العناصر، وعلى الرغم من دقة التعاريف الجديدة وتفاديها للكثير من الغموض الذي يلف بعض العناصر، إلا أنها أكثر تعقيدًا موازنةً بمفهومَي العنصر الكتلي والسطري، لذلك سنبقى في مقالنا مع هذين المفهومين. لا يجب الخلط بين مفهومَي العنصر الكتلي والسطري مع أنواع صناديق CSS التي تحمل الأسماء نفسها، وعلى الرغم من الترابط الافتراضي للأسماء في الحالتين، فإنّ تغيير قيمة الخاصية display عند تنسيق العنصر لن يغير من الفئة التي ينتمي إليها العنصر أصلًا، أي لن يؤثّر على محتواه من العناصر الأخرى، لهذا تخلّت HTML5 عن مفهومَي العنصر الكتلي والسطري منعًا لهذا الالتباس الشائع. العناصر الفارغة لا تتقيد جميع العناصر بالنمط المؤلف من وسم البداية والمحتوى ثم وسم النهاية، إذ لا تحتوي بعض العناصر سوى على وسم بداية وتستخدَم عادةً في إدراج شيء ما في الصفحة، كما يُدرَج العنصر <img> على سبيل المثال على أساس صورة في الصفحة: <img src="https://raw.githubusercontent.com/mdn/beginner-html-site/gh-pages/images/firefox-icon.png"> ستكون نتيجة تنفيذ الشيفرة كما يلي: ملاحظة: تُدعى العناصر الفارغة أحيانًا بالعناصر الخالية void elements ولاحاجةً إلى وضع المحرف / قبل إغلاق الوسم، أي بالشكل <‎img ="cat.jpg" alt="cat" /‎> إلا في حالات خاصة مثل التوافق مع صيغة XML. السمات في لغة HTML يمكن أن تحمل عناصر لغة HTML سمات، وتبدو السمات كما يلي: تحتوي السمات على معلومات إضافية عن العنصر، ولا تظهر مع المحتوى الذي يعرضه، إذ تُشير السمة class في الصورة السابقة مثلًا إلى اسم يُستخدَم لتطبيق تنسيقات محددة على العنصر. ينبغي أن تُكتب السمة بحيث تراعي ما يلي: توجد مسافة فارغة بينها وبين اسم العنصر، كما ينبغي أن تفصل مسافة فارغة بينها وبين السمة التي تليها إذا وجدت. تلي اسم السمة إشارة مساواة =. توضع قيمة السمة بين إشارتي تنصيص مزدوجتين " ". تطبيق عملي: إضافة سمات إلى عنصر HTML يُدعى العنصر <a> بعنصر المربط anchor الذي يحوّل النص الموجود ضمنه إلى رابط تشعبي، كما يمكن أن تحمل المرابط سمات عدة منها: href: إن قيمة هذه السمة هي عنوان ويب للرابط التشعبي مثل "href="https://www.hsoub.com. title: تستخدَم لإدراج معلومات إضافية عن الرابط مثل وصف الصفحة التي سينقلنا إليها، أي مثل title="The Mozilla homepage"، كما تظهر هذه المعلومات على أساس تلميح فوق الرابط عندما تمرِّر مؤشر الفارة فوقه. target: تستخدَم لتحديد طريقة عرض الصفحة التي ينقلنا إليها الرابط مثل "target="_blank، إذ سيعرض لك المتصفح الآن الصفحة في نافذة جديدة، في حين سيعرض المتصفح هذه الصفحة في النافذة نفسها ما لم تستخدم هذه السمة. سنحاول تحويل السطر الموجود في منطقة تحرير الشيفرة إلى مربط كما يلي: أضف الوسم <a>. أضف الصفتين href و title. حدد طريقة عرض صفحة الرابط من خلال السمة target. سترى نتيجة عملك في منطقة عرض النتيجة، ومن المفترض أن ترى رابطًا يعرض قيمة السمة titleعندما تمرِّر المؤشر فوقه، وينقلك عند النقر عليه إلى الصفحة التي حددتها في السمة href، وتذكَّر أن تضع مسافة فارغة بين اسم العنصر والسمات، فإذا ارتكبت خطأً، فيمكنك مسح ما كتبته بالنقر على زر "إعادة الضبط Reset"، في حين إذا وجدت نفسك تائهًا كليًا، فانقر على الزر "أظهر الحل Show solution" لترى الحل الصحيح. السمات المنطقية قد تلاحظ أحيانًا سمات دون قيم، وتُدعى هذه السمات بالسمات المنطقية Boolean، إذ تمتلك هذه السمات قيمةً واحدةً وعادةً ما تكون هذه القيمة مطابقةً لاسم السمة، ونضرب مثلًا على ذلك السمة disabled التي تضاف إلى عنصر الإدخال النصي، بحيث تمنع المستخدِم من الكتابة ضمن مربع النص الذي يبدو رماديًا: <input type="text" disabled="disabled"> يمكن أن نكتب السمة المنطقية بصيغة مختصرة كما يلي: <!-- لمنع المستخدم من الكتابة ضمن المربع النصي disabled استخدام السمة --> <input type="text" disabled> <!-- لا يضم عنصر الإدخال النصي هذه السمة ويمكن للمستخدم الكتابة ضمنه --> <input type="text"> تعطيك الشيفرة في فقرة "السمات المنطقية" النتيجة التالية عند تنفيذها: حذف إشارتي التنصيص المحيطتين بقيمة السمة ستلاحظ خلال متابعتك لشيفرات العديد من المواقع وجود بعض الأساليب الغريبة في كتابة الشيفرة، منها وجود سمات لا توُضع قيمها بين إشارتَي تنصيص، إذ يُسمح بهذا الأمر في حالات محددة وقد يُضِر شيفرتك في أخرى، كما يمكن مثلًا كتابة السمة href في مثال المربط السابق كما يلي: <a href=https://www.mozilla.org/>favorite website</a> لكن ستظهر المشاكل عندما نطبِّق الأمر نفسه على السمة title: <a href=https://www.mozilla.org/ title=The Mozilla homepage>favorite website</a> سيخطئ المتصفح في تفسير المطلوب وسيفهم السمة title على أنها ثلاث سمات الأولى هي title وقيمتها "The" وسِمتان منطقيّتان هما Mozilla و homepage، إذ يسبب هذا الأمر أخطاءً غير متوقعة أو سلوكًا غير متوقع كما سترى في نتيجة التنفيذ التالية: لهذا السبب، يجب أن تضع قيمة السمات ضمن إشارتَي تنصيص دومًا لتجنب مشاكل مثل هذه ولتسهِّل قراءة الشيفرة. استخدام إشارتي تنصيص مزدوجتين أو مفردتين ستلاحظ في هذه المقال أننا وضعنا قيم السمات ضمن إشارتَي تنصيص مزدوجتين " "، لكنك قد ترى في مواقع أخرى قيمًا محاطةً بإشارتَي تنصيص مفردتين ' '، والأمر برمته مسألة ذوق، إذ يمكنك اختيار الأسلوب الذي تريد، أي تعطي كلا الكتابتين في المثال التالي النتيجة نفسها: <a href="https://www.example.com">A link to my example.</a> <a href='https://www.example.com'>A link to my example.</a> انتبه إلى عدم المزج بين إشارتَي التنصيص، إذ سيقود المزج الذي سنعرضه في مثالنا التالي إلى أخطاء: <a href="https://www.example.com'>A link to my example.</a> إذا أردت وضع إشارة تنصيص محددة داخل قيمة السمة، فعليك إحاطة القيمة كلها بإشارتَي التنصيص الأخرى كما يلي: <--! "" ضع قيمة السمة بين مزدوجتين title لتضع الإشارة ' ضمن قيمة السمة --> <a href="https://www.example.com" title="Isn't this fun?">A link to my example.</a> استخدم كيانات HTML لتضع إشارة تنصيص ضمن إشارتَي تنصيص من النوع نفسه -أي مفردة ضمن مفردتين مثلًا-، إذ تُعَدّ الكتابة التالية خاطئةً: <a href='https://www.example.com' title='Isn't this fun?'>A link to my example.</a> عليك استخدام كيان HTML الذي يعطي المحرف ' وهو مجموعة المحارف ;apos&: <a href='https://www.example.com' title='Isn&apos;t this fun?'>A link to my example.</a> تشريح مستند HTML لا فائدة كبيرة من عناصر لغة HTML بمفردها، وإنما عليك تعلّم كيفية تنظيمها لتبني صفحة ويب بأكملها: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>My test page</title> </head> <body> <p>This is my page</p> </body> </html> إليك الطريقة: أولًا: استخدم العنصر: <!DOCTYPE html> لقد كانت الغاية من هذا العنصر في الأيام الأولى (1991/1992) أن يعمل على أساس رابط إلى مجموعة من القواعد التي ينبغي أن تحققها صفحة مكتوبة بلغة HTML لكي تُعَدّ صفحةً جيدةً، إذ يبدو هذا العنصر كما يلي: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> يُستخدَم حاليًا هذا العنصر في بداية المستند حتى يعمل كل شيء بالطريقة الصحيحة، إذ تمثل الشيفرة <!DOCTYPE html> أقل مجموعةً من المحارف كي يكون هذا العنصر صالحًا. ثانيًا: استخدم العنصر <html></html> الذي يضم محتوى الصفحة بأكمله، ويعرف أحيانًا بالعنصر الجذري. ثالثًا: استخدم العنصر <head></head> الذي يعمل على أساس حاوية لكل ما تريد وضعه في صفحة HTML دون أن يكون جزءًا من المحتوى المعروض للزائر، ويتضمن ذلك الكلمات المفتاحية ووصف الصفحة الذي يظهر في نتائج محركات البحث وملفات تنسيق CSS ومجموعة المحارف الكتابية المستخدَمة وغيرها. رابعًا: استخدم العنصر <"meta charset="utf-8> الذي يضبط مجموعة المحارف التي تستخدِمها في الصفحة، وهنا اخترنا المجموعة UTF-8 التي تضم محارف الأغلبية الساحقة من اللغات المكتوبة، إذ تستطيع هذا المحارف أن تعرض الآن أيّ محتوى نصي بأي لغة قد تضعه في صفحتك، ولا مبرر لعدم ضبط مجموعة المحارف المستخدَمة، كما ستساعدك على تحاشي الكثير من الأخطاء لاحقًا. خامسًا: استخدم العنصر <title></title> الذي يضبط عنوان صفحتك أعلى المتصفح عند تحميل الصفحة، كما يُستخدَم لوصف الصفحة عندما تضيفها إلى قائمة الصفحات المفضلة. سادسًا: استخدم العنصر <body></body> الذي يضم المحتوى الذي تريد عرضه على زائرِي صفحتك بأكمله، سواءً كان نصًا أو صورًا أو فيديو أو ألعاب أو أيّ شيء آخر. تطبيق عملي: إضافة بعض الميزات إلى مستند HTML إذا أردت تجريب كتابة شيفرة HTML على جهازك، فيمكنك تنفيذ النقاط التالية: نسخ المثال السابق عن هيكلية صفحة HTML. إنشاء ملف نصي جديد باستخدام محرر النصوص لديك. لصق الشيفرة في الملف. حفظ الملف باسم index.html. ملاحظة: ستجد ايضًا قالب HTML الأساسي ضمن المستودع المخصص فريق مطوري موزيللا على جيت-هاب. افتح الملف باستخدام متصفحك لترى نتيجة تصيير الشيفرة، وعدّل الشيفرة كما تشاء وحدِّث ما يعرضه المتصفح لإظهار أيّة تغييرات، إذ ستبدو الصفحة بشكلها الأصلي كما يلي: يمكنك التعديل على الشيفرة في هذا التمرين على حاسوبك كما شرحنا سابقًا أو من خلال المحرر المدمج مع المقال والذي نعرضه بين الفينة والأخرى، كما يمكنك تعزيز مهاراتك بإنجاز المهام التالية: أضف عنوانًا رئيسيًا للصفحة تحت العنصر <body> مباشرةً، إذ يجب أن تضع العنوان بين وسم البداية <h1> ووسم النهاية <h1/>. عدِّل محتوى الفقرة النصية لتتضمن نصًا من اختيارك. أبرز الكلمات الهامة في نصك بتغليفها ضمن العنصر <strong>. أضف رابطًا ضمن الفقرة النصية بالطريقة التي شرحناها سابقًا. أضف صورةً إلى صفحتك تحت الفقرة النصية، إذ ستحصل على نقاط إضافية إذا تمكنت من إنشاء رابط إلى صورة أخرى على حاسوبك الشخصي أو على ويب. إذا ارتكبت خطأً، فيمكنك مسح ما كتبته بالنقر على زر "إعادة الضبط Reset"، في حين إذا وجدت نفسك تائهًا كليًا، فانقر على الزر "أظهر الحل Show solution" لترى الحل الصحيح. المسافات الفارغة في HTML لاحظ استخدامنا للكثير من المسافات الفارغة في شيفرة الصفحة، وهذا أسلوب كتابة قد تعتمده، إذ سيعطي أسلوبَي كتابة الشيفرة التاليين النتيجة ذاتها: <p>Dogs are silly.</p> <p>Dogs are silly.</p> مهما أضفت من مسافات فارغة ضمن محتوى العنصر الذي قد يتضمن مسافات فارغة عدة وربما محرف الانتقال إلى سطر جديد، فسيختزِل محلل HTML كل سلسلة متلاحقة من المسافات الفارغة إلى مسافة فارغة واحدة، فلماذا إذًا نضيف فراغات أكثر؟ القضية تتعلق بسهولة القراءة. من السهل فهم ما فعلته أثناء كتابتك لشيفرة الصفحة إذا رتبتها جيدًا، فما نفعله عادةً أثناء كتابة الشيفرة هو إزاحة العناصر بمقدار مسافتين فارغتين عن العنصر الذي تقع داخله، ويعود الأمر إليك دائمًا في اختيار التنسيق الذي تراه مناسبًا لتنظيم الشيفرة، ومن الأفضل دائمًا تنسيقها. كيانات لغة HTML: إضافة محارف خاصة تُعَدّ المحارف < و > و " و ' و & في HTML محارف خاصة لكونها جزءًا من الصياغة القواعدية للغة، فكيف سنتمكن إذًا من إضافة هذه المحارف إلى محتوى العناصر؟ وكيف سنضع مثلًا إشارة "أكبر من" دون أن تؤثر على تفسير الشيفرة؟ يمكن ذلك من خلال مراجع إلى تلك المحارف، والمراجع هي رموز خاصة تمثِّل محرفًا محددًا يمكن استخدامه في هذه الحالة، كما يبدأ كل مرجع بالمحرف & وينتهي بالمحرف ;. table { width: 100%; } thead { vertical-align: middle; text-align: center; } td, th { border: 1px solid #dddddd; text-align: right; padding: 8px; text-align: inherit; } tr:nth-child(even) { background-color: #dddddd; } المحرف الخاص سلسلة الحروف المرجعية < ;lt& > ;gt& " ;quot& ' ;apos& & ;amp& يمكن تذكر المرجع إلى المحرف الخاص بسهولة لأن النص الذي يشير إلى إشارة أصغر مثلًا ;lt& يمكن أن يُفهم من المصطلح الذي أخذ منه أي Less Than، والأمر مشابه لبقية المراجع. ستجد في المثال التالي فقرتين نصيتين: <p>In HTML, you define a paragraph using the <p> element.</p> <p>In HTML, you define a paragraph using the <p> element.</p> ستُعرض الفقرة الأولى بطريقة غير صحيحة عند التنفيذ، إذ يفسر المتصفح الوسم <p> المكتوب ضمن محتوى الفقرة على أنه بداية فقرة نصية جديدة، في حين ستبدو الفقرة النصية الثانية صحيحةً عند استخدام مراجع إلى المحارف. ملاحظة: لا حاجة إلى استخدام مراجع إلى كيانات الرموز الأخرى، لأن المتصفحات الحديثة ستتعامل مع جميع الرموز جيدًا إذا كانت UTF-8 هي مجموعة محارف المستخدَمة في ترميز HTML. التعليقات في لغة HTML زوِّدت لغة HTML بآلية لإدراج تعليقات ضمن الشيفرة يتجاهلها المتصفح عند تصيير الصفحة ولا تُعرض للزائر، والغاية من التعليقات هي إضافة ملاحظات إلى الشيفرة بغية شرحها أو توضيح منطق العمل، كما تظهر فائدة التعليقات عندما تضطر إلى العمل على شيفرة أنجزتها قبل فترة من الزمن ولم تَعُد تتذكر تفاصيل العمل بأكمله، كما تظهر أهميتها عندما يعمل على تغيير الشيفرة وتحديثها مجموعة من الأشخاص، ولإدراج تعليق، ضعه ضمن الوسمين <-- و --!> كما في المثال التالي: <p>I'm not inside a comment</p> <!-- <p>I am!</p> --> لاحظ أنّ ما سيعرضه المتصفح هو الفقرة الأولى فقط. خلاصة لقد وصلت إلى نهاية المقال، ونتمنى أن تكون قد استمتعت بما جاء فيه، إذ يفترض الآن أنك فهمت ماهية لغة HTML وكيف تعمل بأبسط صورها، كما من المفترض أيضًا أنك أصبحت قادرًا على كتابة بعض العناصر والسمات، كما سنتعمق في المقالات اللاحقة في بعض المفاهيم التي طرحناها وسنضيف مفاهيم جديدة. وطالما أنك شرعت في تعلم لغة HTML، فننصحك بتعلم مبادئ تنسيق الصفحات باستخدام CSS، فهي تعمل جيدًا مع لغة HTML كما سترى لاحقًا. ترجمة -وبتصرف- للمقال Getting started with HTML. اقرأ أيضًا نحو فهم أعمق لتقنيات HTML5 مكونات الويب: عناصر HTML المخصصة وقوالبها HTML و CSS للمبتدئين: كيف تصمم أول صفحة ويب لك
  6. نتعرف في هذا المقال على وضع تطبيق ويب بأكمله مع جميع خدماته ضمن حاويات دوكر Docker وتنسيقها وضبط إعداداتها. استخدام الحاويات مع React سنحاول أن ننشئ تطبيق React ونضعه ضمن حاوية تاليًا، لهذا سنختار npm مديرَا للحزم علمًا أن yarn هو المدير الافتراضي لبرنامج create-react-app: $ npx create-react-app hello-front --use-npm ... Happy hacking! يُثبت create-react-app كل الاعتماديات اللازمة، فلا حاجة لتنفيذ الأمر npm install. ستكون الخطوة الثانية تحويل شيفرة JavaScript و CSS إلى ملفات ساكنة جاهزة لمرحلة الإنتاج. أمّا create-react-app فلديه build ليكون سكربت npm، لهذا سنستفيد من هذه الناحية: $ npm run build ... Creating an optimized production build... ... The build folder is ready to be deployed. ... أما الخطوة الأخيرة هي التفكير بطريقة للعمل مع خادم لتقديم الملفات الساكنة. يمكننا الاستفادة من express.static مع خادم Express لهذا الغرض، لهذا سأترك الموضوع تمرينًا لك، وسننتقل بدلًا من ذلك إلى كتابة ملف Dockerfile: FROM node:16 WORKDIR /usr/src/app COPY . . RUN npm ci RUN npm run build يبدو ما كتبناه صحيحًا تقريبًا، لهذا سنبنيه ونقيّم مسارنا، والهدف أن نبني التمرين دون أخطاء. سنحاول بعد ذلك أن نتحقق من وجود الملفات داخل الحاوية من خلال أوامر "bash". $ docker build . -t hello-front [+] Building 172.4s (10/10) FINISHED $ docker run -it hello-front bash root@98fa9483ee85:/usr/src/app# ls Dockerfile README.md build node_modules package-lock.json package.json public src root@98fa9483ee85:/usr/src/app# ls build/ asset-manifest.json favicon.ico index.html logo192.png logo512.png manifest.json robots.txt static الخيار الصحيح لتخديم الملفات الساكنة ضمن الحاوية هو serve نظرًا لوجود Node ضمن الحاوية. لنحاول تثبيت serve لتخديم الملفات الساكنة ونحن داخل الحاوية: root@98fa9483ee85:/usr/src/app# npm install -g serve added 88 packages, and audited 89 packages in 6s root@98fa9483ee85:/usr/src/app# serve build ┌───────────────────────────────────┐ │ │ │ Serving! │ │ │ │ Local: http://localhost:5000 │ │ │ └───────────────────────────────────┘ أغلق الحاوية باستخدام الاختصار "Ctrl+C" لإضافة بعض التوجيهات إلى ملف Dockerfile. يتحوّل تثبيت serve إلى عملية تشغيل RUN في ملف Dockerfile، وهكذا ستُثبّت الاعتمادية أثناء عملية البناء، وسيكون أمر تخديم مجلد البناء هو نفسه أمر تشغيل الحاوية: FROM node:16 WORKDIR /usr/src/app COPY . . RUN npm ci RUN npm run build RUN npm install -g serve CMD ["serve", "build"] يتضمن الأمر ‍‍CMD الآن قوسين مربعين ونكون بذلك قد استخدمنا ما يُعرف بالشكل التنفيذي exec form من الأمر ‍‍CMD، علمًا أنه يُكتب بثلاثة أشكال لكن الشكل السابق هو المُفضّل. عندما نبني الصورة الآن باستخدام الأمر: docker build . -t hello-front ثم تشغيلها باستخدام الأمر: docker run -p 5000:3000 hello-front سيكون التطبيق متاحًا على العنوان "http://localhost:5000". استخدام المراحل المتعددة على الرغم من أن serve خيار صحيح لكن بالإمكان إيجاد بديل أفضل، إذ أن الغاية هنا هي إنشاء صورة لا تحتوي أي شيء غير مطلوب وبأقل عدد من الاعتماديات، وبالتالي ينخفض احتمال توقف الصورة عن العمل أو أن تصبح عرضةً للهجمات مع الوقت. صُمّمت عملية البناء متعددة المراحل Multi-stage builds لتفصل عملية البناء إلى مراحل مختلفة، ومن الممكن حينها تحديد ملفات الصورة التي يُسمح لها بالانتقال من مرحلة إلى أخرى، كما يتيح ذلك أيضًا إمكانية التحكم بحجم الصورة، فلن نحتاج إلى جميع الملفات الجانبية التي تنتج عن عملية البناء ضمن الصورة الناتجة. إنّ الصورة الأصغر أسرع في التحميل والتنزيل وتساعد في تخفيض عدد نقاط الضعف في برنامجك. عند استخدام البناء متعدد المراحل، بالإمكان الاعتماد على حلول تتبع نهج المحاولة والخطأ مثل استخدام الخادم Nginx في إدارة الملفات الساكنة دون عناء شديد. تدلنا صفحة استخدام Nginx مع Docker Hub على المعلومات الضرورية لفتح المنافذ واستضافة محتوى ساكن. لنستخدم الآن ملف Dockerfile السابق بعد تغيير ما يلي التعليمة FROM لإضافة اسم المرحلة: # called build-stage الأولى الآن إلى مرحلة تُدعى FROM تشير تعليمة FROM node:16 AS build-stage WORKDIR /usr/src/app COPY . . RUN npm ci RUN npm run build # هذه مرحلة جديدة الآن، وسيختفي كل شيء قبلها ما عدا الملفات التي نريد نسخها FROM nginx:1.20-alpine # /usr/share/nginx/html إلى build-stage نسخ مجلد البناء من # docker hub page وجد الموقع الوجهة من خلال صفحة شروحات COPY --from=build-stage /usr/src/app/build /usr/share/nginx/html لقد صرحنا أيضًا عن مرحلة أخرى تُنقل إليها فقط الملفات الضرورية من المرحلة الأولى (المجلد "build" الذي يضم المحتوى الساكن). بعد بنائها ثانيةً، ستكون الصورة جاهزة لتخديم المحتوى الساكن. إن المنفذ الافتراضي لخادم Nginx هو 80 وبالتالي سينفع تنفيذ الأمر p 8000:80-، لهذا لا بد من تغيير معاملات أمر التشغيل قليلًا. تنطوي عملية البناء متعددة المراحل على تحسينات داخلية قد تؤثر على عملية البناء، إذ تتجاوز عملية البناء متعددة المراحل مثلًا المراحل التي لم تُستخدم، وبالتالي علينا تمرير بعض البيانات إلى المرحلة القادمة إن أردنا استخدام مرحلة ما لتبديل جزء من خط البناء مثل الاختبارات أو التنبيهات. لهذا الأمر تبريراته في بعض الحالات كأن تَنسخ الشيفرة من مرحلة الاختبار إلى مرحلة البناء كي تضمن بناء الشيفرة المُختبرة. التمرينان 12.13 و 12.14 حاول أن تحل التمرينين التاليين: التمرين 12.13: الواجهة الامامية لتطبيق المهام لقد وصلنا أخيرًا إلى الواجهة الأمامية، لهذا عليك أن تلقي نظرة على محتويات المجلد "todo-app/todo-frontend" وتقرأ ملف "اقرأني README". ابدأ بتشغيل الواجهة الأمامية خارج الحاوية، وتأكد من تناغم عملها مع الواجهة الخلفية. ضع التطبيق ضمن الحاوية بعد ذلك أنشئ الملف "todo-app/todo-frontend/Dockerfile" ثم استخدم التعليمة ENV لتمرير متغير البيئة REACT_APP_BACKEND_URL إلى التطبيق وشغّله مع الواجهة الخلفية. ينبغي أن تعمل الواجهة الخلفية حاليًا خارج الحاوية. وانتبه إلى ضرورة ضبط المتغير REACT_APP_BACKEND_URL قبل بناء الواجهة الأمامية وإلا لن يُعرَّف ضمن الشيفرة. التمرين 12.14: إجراء الاختبارات أثناء عملية البناء من الميزات الهامة للبناء متعدد المراحل، استخدام مرحلة البناء لإجراء الاختبارات على التطبيق testing. فإن أخفقت مرحلة الاختبار ستُخفق عملية البناء بأكملها. لكن تنفيذ الاختبارات جميعها خلال عملية بناء الصورة ليست فكرة جيدة؛ لهذا قد تكون الاختبارات المتعلقة بالحاوية هي الأنسب. استخلص مكوّن Todo يمثل مهمة واحدة من الشيفرة ، ثم اكتب اختبارًا للمكوّن الجديد وأضف تنفيذ الاختبارات إلى عملية البناء. نفّذ الاختبار من خلال الأمر CI=true npm test وإلا سيبدأ برنامج create-react-app بمراقبة التغييرات مسببًا توقف خط العمل pipeline. بإمكانك إضافة مرحلة بناء جديدة لإجراء الاختبار إن أردت ذلك. لكن تذكر في هذه الحالة أن تقرأ آخر فقرة قبل التمرين 12.13 مجددًا. التطوير ضمن الحاويات لننقل الآن تطبيق المهام بأكمله إلى حاوية. وإليك بعض الأسباب التي قد تدفعنا إلى ذلك: للإبقاء على تماثل بيئة العمل بين التطوير والإنتاج تفاديًا للثغرات التي تظهر فقط في بيئة التشغيل. لتفادي الاختلافات بين المطورين وبيئات عملهم الخاصة والتي تقود إلى صعوبات أثناء تطوير التطبيق. لمساعدة أعضاء الفريق الجدد على البدء بتثبيت بيئة تشغيل الحاوية دون الحاجة إلى أي شيء آخر. في المقابل، قد نواجه سلوكًا غير معهود عندما لا نشغّل التطبيق كما اعتدنا، ولهذا لا بد من فعل ما يلي على الأقل لنقل التطبيق إلى الحاوية: تشغيل التطبيق في وضع التطوير. الوصول إلى الشيفرة من خلال برنامج VSCode. لنبدأ بالواجهة الأمامية، وطالما أن ملف Dockerfile لمرحلة التطوير سيختلف تمامًا عن ملف Dockerfile لنسخة الإنتاج، سننشئ ملف جديد اسمه "dev.Dockerfile". لنشغّل create-react-app في وضع التطوير ومن المفترض أن يكون الأمر بسيطًا على النحو التالي: FROM node:16 WORKDIR /usr/src/app COPY . . # طالما سنعمل في وضع التطوير npm install إلى npm ci غيّر RUN npm install # npm start إن أمر التشغيل في وضع التطوير هو CMD ["npm", "start"] تُستخدم الراية f- أثناء البناء لتحديد الملف الذي يُستخدم، وإلا سيقع الاختيار على الملف Dockerfile، لهذا يكون أمر بناء الصورة على النحو التالي: docker build -f ./dev.Dockerfile -t hello-front-dev . سيُخدَّم create-react-app على المنفذ 3000، لهذا يمكنك اختباره بتشغيل الحاوية على هذا المنفذ. تقتضي المهمة الثانية الوصول إلى الملفات باستخدام VSCode، وهناك على الأقل طريقتان لإنجاز الأمر: باستخدام الموسِّع Visual Studio Code Remote - Containers. باستخدام الأقراص volumes، وبنفس طريقة تخزين البيانات في قاعدة البيانات. لنتجاوز المهمة الثانية كوننا سنضطر فيها إلى التعامل مع محررات أخرى، ولنجرب تشغيل الحاوية مع الراية v-، فإذا جرى كل شيء على ما يرام ننقل الإعدادات إلى ملف docker-compose. لاستخدام تلك الراية علينا تزويدها بالمجلد الحالي من خلال تنفيذ الأمر pwd الذي يعطي المسار إلى المجلد الحالي. حاول أن تنفِّذ ذلك من خلال الأمر (echo $(pwd في واجهة سطر الأوامر لديك وبالترتيب التالي: $ docker run -p 3000:3000 -v "$(pwd):/usr/src/app/" hello-front-dev Compiled successfully! You can now view hello-front in the browser. بإمكاننا الآن تعديل الملف "src/App.js" وستُعرض التغييرات مباشرةً على المتصفح. سننقل تاليًا الإعدادات إلى الملف "docker-compose.yml" الذي ينبغي أن يكون موجودًا في جذر المشروع: services: app: image: hello-front-dev build: context: . # يختار السياق هذا المجلد ليكون سياق البناء dockerfile: dev.Dockerfile # الذي سيُستخدم Dockerfile لاختيار ملف volumes: - ./:/usr/src/app # يمكن أن يكون المسار نسبي, so ./ is enough to say # ./ لهذا يكفي استخدام #docker-compose.yml للقول أنه نفس مكان وجود الملف ports: - 3000:3000 container_name: hello-front-dev # hello-front-dev لتسمية الحاوية بالاسم يمكننا بهذه الإعدادات الآن تشغيل التطبيق في وضع التطوير من خلال الأمر docker-compose up، ولن تحتاج حتى إلى تثبيت Node. يسبب تثبيت اعتماديات جديدة عدة مشاكل في إعداد بيئة تطوير كهذه، لهذا ستجد أن تثبيت الاعتمادية الجديدة ضمن الحاوية هو أحد الخيارات الجيدة. فبلدلًا من تنفيذ الأمر التالي مثلًا npm install axios ، ثبّت هذه الاعتمادية ضمن الحاوية التي تعمل من خلال الأمر docker exec hello-front-dev npm install axios أو أضفها إلى الملف ثم نفِّذ الأمر docker build من جديد. التمرين 12.15: إعداد بيئة تطوير الواجهة الأمامية أنشئ الملف واستخدم الأقراص لتمكين تطوير الواجهة الأمامية لتطبيق المهام عندما يعمل ضمن الحاوية. التواصل بين الحاويات في شبكة Docker تهيئ الأداة Docker شبكةً بين الحاويات وتضيف خادمًا لأسماء النطاقات DNS لربط أي حاويتين بسهولة. دعونا إذًا نضيف خدمة جديدة إلى وسنرى كيف تعمل الشبكة وخادم DNS. سنستخدم الحزمة التنفيذية Busybox التي تضم مجموعةً من الأدوات التي قد تحتاجها وتُعرف هذه الحزمة باسم "سكين الجيش السويسري الخاصة بنظام Linux المدمج". لهذا يمكننا بالتأكيد الاستفادة منها. تساعدنا Busybox في تنقيح إعداداتنا، لهذا إن لم تتمكن من حل التمرين السابق، عليك استخدام Busybox لمعرفة ما يعمل من إعداداتك وما لا يعمل. لنختبر ما قلناه الآن. تتواجد تلك الحاويات ضمن شبكة ويمكنك الربط بينها بسهولة، ويمكن إضافة Busybox إلى الخلطة بتغيير الملف "docker-compose.yml" إلى: services: app: image: hello-front-dev build: context: . dockerfile: dev.Dockerfile volumes: - ./:/usr/src/app ports: - 3000:3000 container_name: hello-front-dev Debug-helper: image: busybox لن تتضمن حاوية Busybox على أية عمليات تجري ضمنها لذلك يمكننا تنفيذ الأمر exec. عندها سيبدو الخرج الناتج عن تنفيذ التعليمة docker-compose up على النحو التالي: $ docker-compose up Pulling debug-helper (busybox:)... latest: Pulling from library/busybox 8ec32b265e94: Pull complete Digest: sha256:b37dd066f59a4961024cf4bed74cae5e68ac26b48807292bd12198afa3ecb778 Status: Downloaded newer image for busybox:latest Starting hello-front-dev ... done Creating react-app_debug-helper_1 ... done Attaching to react-app_debug-helper_1, hello-front-dev react-app_debug-helper_1 exited with code 0 hello-front-dev | hello-front-dev | > react-app@0.1.0 start hello-front-dev | > react-scripts start هذا الخرج متوقع كون الحزمة هي مجموعة أدوات مثل غيرها. لنستخدم الحزمة في إرسال طلب إلى الحاوية "hello-front-dev" لنرى كيف يعمل خادم DNS. بإمكاننا تنفيذ الطلب wget أثناء عمل الحاوية فهو أداةٌ موجودةٌ ضمن Busybox مهمتها إرسال طلب إلى الحاوية hello-front-dev من مساعد التنقيح debug-helper. يمكننا استخدام الأمر docker-compose run SERVICE COMMAND لتنفيذ خدمة مع أمر محدد، ويتطلب استخدام الأمر wget السابق الراية O- تليها - لنقل الاستجابة إلى مجرى الخرج: $ docker-compose run debug-helper wget -O - http://app:3000 Creating react-app_debug-helper_run ... done Connecting to hello-front-dev:3000 (172.26.0.2:3000) writing to stdout <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> ... يُعد عنوان URL الجزء المهم هنا، فهو يشير إلى أننا اتصلنا بالخدمة "hello-front-dev" والمنفذ 3000. لقد منحنا الحاوية اسمها "hello-front-dev" باستخدام التعليمة container_name في ملف "docker-compose"، أما المنفذ فهو المنفذ المتاح للوصول إلى التطبيق ضمن الحاوية. ولا حاجة لنشر المنفذ كي تتصل به بقية الخدمات الموجودة على نفس الشبكة، فالمنافذ المحددة في الملف "docker-compose" هي للوصول الداخلي وحسب. دعونا نغيّر رقم المنفذ في الملف "docker-compose.yml" لتوضيح الأمر: services: app: image: hello-front-dev build: context: . dockerfile: dev.Dockerfile volumes: - ./:/usr/src/app ports: - 3210:3000 container_name: hello-front-dev debug-helper: image: busybox سيُتاح التطبيق على الحاسوب المضيف وعلى العنوان http://localhost:3210 عند تنفيذ الأمر docker-compose up، لكن التطبيق لا يزال يعمل وفقًا للأمر السابق docker-compose run debug-helper wget -O - http://app:3000 طالما أن المنفذ هو 3000 أيضًا ضمن شبكة دوكر. يطلب الأمر docker-compose run -كما تشرح الصورة السابقة-من مساعد التنقيح أن يرسل طلبًا ضمن شبكة دوكر docker بينما يرسل المتصفح في الجهاز المضيف الطلب من خارج الشبكة. أما الآن وقد علمت سهولة إيجاد الخدمات في الملف "docker-compose.yml" وليس لدينا أي شيء للتنقيح، سنزيل مساعد التنقيح و نُعيد المنافذ إلى 3000:3000 في الملف "docker-compose.yml". التمرين 12.16: تشغيل الواجهة الخلفية لتطبيق المهام ضمن حاوية التطوير استخدم الأقراص والمكتبة Nodemon لتمكين عملية تطوير الواجهة الخلفية لتطبيق المهام وهي تعمل ضمن الحاوية. أنشئ الملف "todo-backend/dev.Dockerfile" وعدّل الملف "todo-backend/docker-compose.dev.yml". عليك إعادة التفكير أيضًا في الاتصالات بين الواجهة الخلفية و قاعدة البيانات MongoDB، أو Redis. ولحسن الحظ يدعم docker-compose استخدام متحولات بيئة يمكن تمريرها إلى التطبيق: services: server: image: ... volumes: - ... ports: - ... environment: - REDIS_URL=... - MONGO_URL=... وُضعت عناوين URL للخادم المحلي بطريقة خاطئة عمدًا وعليك وضع القيم الصحيحة. وتذكر أن تراقب دائمًا ما يحدث على شاشة الطرفية، فقد ستلمّح رسائل الخطأ إلى مكان المشكلة إن حدث خلل ما. إليك هذه الصورة التوضيحة التي قد تنفع في توضيح الاتصالات ضمن شبكة docker: التواصل بين الحاويات في بيئة أكثر حيوية سنضيف تاليًا خادم وكيل معكوس reverse proxy إلى الملف " docker-compose.yml". واستنادًا إلى ويكيبيديا: سيكون الخادم الوكيل المعكوس في حالتنا نقطة دخول مفردة إلى تطبيقنا، أم الهدف النهائي فهو إعداد واجهة React الأمامية و واجهة Express الخلفية معًا خلف الخادم والوكيل المعكوس. لديك عدة خيارات تساعدك في إنجاز الخادم الوكيل، مثل Traefik و Caddy و Nginx و Apache وقد رُتبت من الأحدث إلى الأقدم ظهورًا، لكن خيارنا سيكون Nginx. لنضع الآن الواجهة الأمامية المتمثلة بالحاوية "hello-frontend" خلف الخادم الوكيل المعكوس. لهذا عليك إنشاء الملف "nginx.conf" في جذر المشروع واتّبع القالب التالي بمثابة نقطة انطلاق. لا بُد من إنجاز بعض التعديلات الثانوية لتشغيل التطبيق: # الأحداث مطلوبة لكن لا بأس بالاعتماد على الأحداث الافتراضية events { } # 80 يستمع إلى المنفذ http خادم http { server { listen 80; # (/) تُعالج الطلبات التي تبدأ بالمحرف location / { # نحتاج الأسطر الثلاثة التالية للتحميل المباشر للتغييرات proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; # http://localhost:3000 تُوجَّه الطلبات مباشرةً إلى العنوان proxy_pass http://localhost:3000; } } } أنشئ تاليًا خدمة Nginx ضمن الملف "docker-compose.yml"، ثم أضف قرصًا كما ورد في إرشادات الصفحة الرسمية للأداة Docker Hub حيث يكون الجانب الأيمن على الشكل هو: etc/nginx/nginx.conf:ro/:،إذ يدل التصريح ro على أن القرص للقراءة فقط: services: app: # ... nginx: image: nginx:1.20.1 volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro ports: - 8080:80 container_name: reverse-proxy depends_on: - app # انتظر حاوية الواجهة الخلفية حتى تُقلع يمكنك الآن تنفيذ الأمر ومراقبة نتيجة العمل: $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES a02ae58f3e8d nginx:1.20.1 "/docker-entrypoint.…" 4 minutes ago Up 4 minutes 0.0.0.0:8080->80/tcp, :::8080->80/tcp reverse-proxy 5ee0284566b4 hello-front-dev "docker-entrypoint.s…" 4 minutes ago Up 4 minutes 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp hello-front-dev عند الانتقال إلى العنوان http://localhost:8080 ستظهر صفحة الحالة 502 المألوفة، وهذا لأن توجيه الطلبات إلى العنوان http://localhost:3000 لن يقود إلى أي مكان، لأن حاوية الخادم Nginx لا تحتوي أي تطبيق يعمل على المنفذ 3000. إن مصطلح "خادم محلي" يُستخدم عادة للدلالة على الحاسوب الحالي الذي نلج إليه، بينما يكون الخادم المحلي فريدًا في عالم الحاويات لكل حاوية ويقود إلى الحاوية نفسها. لنختبر ذلك بالدخول إلى الحاوية Nginx واستخدام الأداة curl لإرسال طلب إلى التطبيق نفسه، وتشابه هذه الأداة من حيث الطريقة التي نستخدمها الأداة wget لكنها لا تحتاج أية رايات: $ docker exec -it reverse-proxy bash root@374f9e62bfa8:/# curl http://localhost:80 <html> <head><title>502 Bad Gateway</title></head> ... يمكننا الاستفادة من الشبكة التي يهيئها docker-compose عند تنفيذ الأمر docker-compose up، فهي تضيف كل الحاويات الموجودة في الملف "docker-compose.yml" إلى الشبكة. يتأكد خادم DNS من إمكانية إيجاد بقية الحاويات، وتُمنح كل منها اسمان الأول اسم الخدمة والآخر اسم الحاوية. طالما أننا ضمن الحاوية، سنختبر خادم DNS، لنستخدم إذًا curl لطلب الخدمة التي تُدعى (app) على المنفذ 3000 root@374f9e62bfa8:/# curl http://app:3000 <!DOCTYPE html> <html lang="en"> <head> ... <meta name="description" content="Web site created using create-react-app" /> ... هذا كل ما في الأمر. لنبدل الآن عنوان proxy_pass في الملف "nginx.conf" بهذا العنوان (http://app:3000). إن ظهرت الصفحة 502 مجددًا ، تأكد من بناء create-react-app أولًا، واقرأ الخرج الناتج عن تنفيذ الأمر docker-compose up. أمر آخر: لقد أضفنا الخيار depends_on إلى الإعدادات لنتأكد أن حاوية "nginx" لن تعمل قبل حاوية الواجهة الأمامية "app": services: app: # ... nginx: image: nginx:1.20.1 volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro ports: - 8080:80 container_name: reverse-proxy Depends_on: - app إن لم نفرض تسلسل الإقلاع هذا باستخدام الخيار depends_on فقد نقع في خطر فشل إقلاع لأنه يحاول تحليل أسماء نطاقات DNS التي يُشار إليها في ملف الإعدادات: http { server { listen 80; location / { proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_pass http://app:3000; } } } تجدر الملاحظة أن الخيار depends_on لا يضمن أن تكون الخدمة في الحاوية جاهزةً للعمل، بل يضمن أن الحاوية قد بدأت العمل وأضيف المُدخل الخاص بها إلى خادم DNS، لكن إذا أردت من خدمة أن تنتظر أخرى حتى تجهز، فعليك الاطلاع على حلول أخرى. التمرينات 12.17 - 12.19 حاول إنجاز التمرينات التالية: التمرين 12.17: إعداد خادم وكيل معكوس Nginx أمام الواجهة الأمامية لتطبيق المهام سنحاول في هذا التمرين وضع خادم Nginx أمام الواجهتين الأمامية والخلفية لتطبيق المهام todo-app. سنبدأ بإنشاء ملف docker-compose جديد "todo-app/docker-compose.dev.yml" وملف تهيئة Nginx يحمل الاسم "todo-app/nginx.conf". todo-app ├── todo-frontend ├── todo-backend ├── nginx.conf └── docker-compose.dev.yml أضف الخدمتين nginx و todo-app/todo-frontend/dev.Dockerfile إلى الملف "todo-app/docker-compose.dev.yml". التمرين 12.18: إعداد خادم ليكون أمام الواجهة الخلفية لتطبيق المهام أضف الخدمة todo-backend إلى الملف "*todo-app/docker-compose.dev.yml" في وضع التطوير، ثم أضف مكانًا جديدًا للملف كي تُخدَّم الطلبات إلى العنوان "api/" عبر الخادم الوكيل إلى الواجهة الخلفية. قد يكون القالب التالي مناسبًا لإنجاز الأمر: server { listen 80; # (/) تُعالج الطلبات التي تبدأ بالمحرف location / { # نحتاج الأسطر الثلاثة التالية للتحميل المباشر للتغييرات proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; # http://localhost:3000 تُوجَّه الطلبات مباشرةً إلى العنوان proxy_pass http://localhost:3000; } # (/api/) تُعالج الطلبات التي تبدأ بالمسار location /api/ { ... } } للتوجيه proxy_pass ميزةٌ مهمة عندما تُضاف إليه الشرطة المائلة الزائدة "/"، وطالما أننا نستخدم المسار "api/" لتحديد المكان علمًا أن تطبيق الواجهة الخلفية سيجيب فقط على العنوان "/" أو "todos/" فلا بد من إزالة "api/" من الطلب. وبكلمات أخرى، حتى لو أرسل المتصفح الطلب GET إلى العنوان "api/todos/1/" نريد من الخادم Nginx أن ينقل الطلب بالوكالة إلى العنوان "todos/1/". لتنفيذ الأمر لا بد من إضافة شرطة مائلة "/" زائدة إلى العنوان في نهاية التوجيه proxy_pass. انتبه للموضوع جيدًا فقد تقضي ساعات في البحث عن حل لمشاكل سببها إهمال الشرطة الزائدة. وهذه إحدى المشاكل الشائعة: إليك هذه الصورة التوضيحة التي قد تنفع في توضيح الأمر عندما تواجهك المشاكل: التمرين 12.19: ربط الخدمتين todo-frontend و todo-backend سلّم في هذا التمرين بيئة التطوير بأكملها بما في ذلك تطبيقي Express و React وملفات Dockerfiles والملف "docker-compose.yml". تأكد بدايةً من عمل الواجهة الأمامية مع الواجهة الخلفية، وسيتطلب ذلك تغييرات في متغير البيئة REACT_APP_BACKEND_URL. وإذا كانت الواجهة جاهزةً للعمل خلال التمرين السابق يمكنك تجاوز هذه الخطوة. تأكد من أن بيئة التطوير تعمل الآن بكامل طاقتها، أي: أن تعمل جميع ميزات تطبيق المهام. عندما تغيّر الشيفرة المصدرية لا بُد أن تظهر نتيجة التغيرات مباشرةً في حال كان التغيير في الواجهة الأمامية وبإعادة تحميل التطبيق إن كان التغيير في الواجهة الخلفية. أدوات لمرحلة الإنتاج التعامل مع الحاويات ممتعٌ في مرحلة التطوير، لكن أفضل حالات الاستخدام ستكون في مرحلة الإنتاج، إذ توجد أدوات أكثر قدرة من docker-compose في تشغيل الحاويات في مرحلة الإنتاج. تسمح لنا أداة تنسيق الحاويات Kubernetes مثلًا بإدارة الحاويات ضمن مستوى جديد بالكامل، وتخفي تلك الأدوات عمومًا التجهيزات الفيزيائية المستخدمة مما يجعل المطورين أقل انشغالًا بأمور البنية التحتية. إن كنت ترغب في الاطلاع أكثر على الحاويات باستخدام Docker فعليك بالمنهاج DevOps with Docker، وكذلك المنهاج DevOps with Kubernetes لتتعلم تنسيق الحاويات باستخدام Kubernetes. التمرينات 12.20-12.22 حاول إنجاز التمرينات التالية: التمرين 12.20 أنشئ نسخة إنتاج من الملف "todo-app/docker-compose.yml" تضم كل الخدمات: Nginx و todo-backend و todo-frontend و MongoDB و Redis. استخدم الملف "Dockerfiles" بدلًا من "dev.Dockerfiles" وتأكد من تشغيل التطبيق في وضع الإنتاج. استخدم الهيكلية التالية في هذا التمرين: todo-app ├── todo-frontend ├── todo-backend ├── nginx.conf ├── docker-compose.dev.yml └── docker-compose.yml التمرين 12.21 أنشئ بيئة تطوير تعتمد على الحاويات مشابهة لما فعلنا، وذلك لأحد التطبيقات التي مرّت معك خلال منهاجنا أو التي أنشاتها في أوقات فراغك. اجعل هيكلية تطبيقك ضمن مستودع التسليم على النحو التالي: └── my-app ├── frontend | └── dev.Dockerfile ├── backend | └── dev.Dockerfile └── docker-compose.dev.yml التمرين 12.22 أنهِ القسم بإعداد نسخة إنتاج تعتمد على الحاويات خاصة بالتطبيق الذي اخترته. جعل هيكلية تطبيقك ضمن مستودع التسليم على النحو التالي: └── my-app ├── frontend | ├── dev.Dockerfile | └── Dockerfile ├── backend | └── dev.Dockerfile | └── Dockerfile ├── docker-compose.dev.yml └── docker-compose.yml ترجمة -وبتصرف- للفصل basics of orchestration من سلسلة Deep Dive Into Modern Web Development اقرأ أيضًا المقال السابق: بناء الصور وتهيئة بيئة العمل للتطبيقات المبنية ضمن الحاويات مدخل إلى الحاويات أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات
  7. وصلنا في سلسلة مقالتنا إلى موضوع أكثر عمقًا، بعد أن خدشنا السطح الخارجي لأوبونتو، الذي هو أحد التوزيعات الشهيرة لنظام لينكس مفتوح المصدر. تأتي إنتاجية نظام التشغيل من كم ونوع التطبيقات التي يشغلها ويدعمها، وسهولة تثبيت هذه البرمجيات وإزالتها بأبسط طريقة ممكنة، دون أية مضاعفات تؤثر على استقرار النظام. وليس من السهل على القادمين الجدد إلى لينكس إدارة عملية تثبيت وإزالة التطبيقات، فليست جميعها بسهولة برمجيات ويندوز أو ماك التي يكفي في معظم اﻷحيان النقر المزدوج على ملف التثبيت ليقودك معالج التثبيت خطوةً خطوة خلال العملية، عن طريق واجهات رسومية يمكن استيعابها. مع ذلك، وكمستخدم مبتدئ، ومن منطلق أنك تستخدم حاسوبك لأغراض عامة؛ سنقدم لك مجموعةً مفيدةً من اﻷفكار والنصائح التي تساعدك في تثبيت معظم البرمجيات التي تحتاجها، دون أن تغوص في متاهات أوامر الطرفية Terminal، التي تتيح لك إمكانات واسعة في العمل مع النظام وفهمه في آن معًا، وإن كنت في مرحلة ما خلال مسيرتك، فستضطر إلى التعامل معها. تحديث النظام قبل كل شيء أوبونتو وكغيره من توزيعات لينكس، يُحدَّث باستمرار من جميع النواحي انطلاقًا من عملياته البنيوية وحتى البرمجيات التي ثُبِّتت عليه. ولهذا السبب وقبل أن تفكر في تثبيت أي تطبيق جديد، احرص أن يكوُن نظام التشغيل محدّثًا ومدعومًا بآخر إصدارات مكتباته واعتمادياته. في واقع اﻷمر، يُحدّث أوبونتو نفسه بنفسه، فهو مُعدٌّ افتراضيًا للتحديث التلقائي؛ إذ يتصل النظام بخوادم مخصصة وموثوقة للحصول على آخر إصدارات الحزم Packages التي تضم معلومات التحديث أو الموارد الجديدة، ثم يفك هذه الحزم ويثبت محتواها. مع ذلك، لابد من الاطلاع على بعض التفاصيل المهمة التي يمكن الوصول إليها من خلال تطبيق "البرمجيات والتحديثات". تطبيق "البرمجيات والتحديثات" لننتقل إذًا إلى قائمة التطبيقات ونفتح هذا التطبيق. يعرض التطبيق مجموعةً من النوافذ الفرعية، وسنلقي نظرةً على ما نحتاجه منها حاليًا: برمجيات Ubuntu: يحدد هذا الخيار ما يمكن تثبيته من برمجيات خاصة لدعم نظام أوبونتو من خلال اﻹنترنت، إذ يعطيك خيار تثبيت برمجيات حرة ومفتوحة المصدر مصدرها الرئيسي هو شركة كانونيكال (الشركة المطورة لنظام التشغيل أوبونتو) وتدعى البرمجيات الرئيسية Main؛ كما يعطيك خيار تثبيت برمجيات حرة ومفتوحة المصدر من شركاء تدعمها كانونيكال (تُدعى برمجيات عامة Universe)، وخيار البحث عن تعريفات للعتاد الصلب مملوكة من قبل مطوريها، وخيار تثبيت برمجيات مقيدة ومملوكة لأصحابها (تُدعى برمجيات مختلفة المصادر Multiverse). قد تختار بعض هذه اﻷنواع من البرمجيات أو جميعها بما يلائمك وننصحك بترك هذه اﻹعداد كما هو. برمجيات أخرى: لتنزيل برمجيات وتعريفات تطوّرها كانونيكال لحساب شُركائها مع شيفرتها المصدرية. التحديثات: يتيح لك التطبيق مجموعةً من الخيارات للتحقق من وجود تحديثات وهي: التسجيل في قائمة التحديثات subscribe to: تتيح لك اﻹشتراك بكل التحديثات all updates أو بالتحديثات اﻷمنية والتحديثات المزكّاة security and recommended updates، أو التحديثات اﻷمنية فقط. ويُنصح المبتدئ هنا بترك الخيار اﻹفتراضي. فحص التحديثات تلقائيًا: قد تختار فحص التحديثات يوميًا أو أسبوعًا أو شهريًا، وقد لا تختار الفحص التلقائي أبدًا. لذا يُنصح هنا باختيار الفحص التلقائي اﻷسبوعي، لأنها فترة زمنية معقولة لوجود تحديثات أمنية، وهذا أمر حيوي جدًا. عند وجود تحديثات أمنية: يُنصح هنا بخيار "اعرضها فورًا"، فهذا الخيار أكثر مرونةً، ويبقيك على اطلاع بما سيجري تثبيته؛ إذ يعرض لك النظام من خلال تطبيق آخر يُدعى "مُحدث البرمجيات" نوع التحديثات المتوفرة، ويترك لك الخيار بتثبيتها أو لا. قد يجد الكثيرون أن خيار "نزّلها وثبتها تلقائيًا" هو اﻷنسب كمبتدئ -وهذا صحيح للوهلة اﻷولى-، لكنك قد تشعر بالصدمة إن كنت تحاول في وقت ما تثبيت برنامج ضروري ولا تلاحظ أي تقدم في عملية تثبيته! عند توفر تحديثات أخرى: ننصحك أيضًا بخيار "اعرضها فورًا" للأسباب السابقة نفسها. لن نتحدث عن باقي النوافذ الفرعية للتطبيق كي لا ندخلك في دوّامات لست مستعدًا لها حاليًا، وسنكتفي بهذا القدر من الشرح، فهذا ما تحتاجه حاليًا. تطبيق "محدث البرمجيات" يبحث هذا التطبيق عن آخر التحديثات التي تتعلق بالبرمجيات وتحديثات النظام والتحديثات اﻷمنية، وذلك وفقًا لخياراتك التي اعتمدها في تطبيق "البرمجيات والتحديثات"، كما يعرض لك كل جديد تلقائيًا، أو يمكنك فتحه إن أردت من قائمة التطبيقات. يصنف هذا التطبيق التحديثات المتوفرة كما يلي: قاعدة Ubuntu: وتضم التحديثات الجديدة على نواة النظام. تحديثات أمنية. تحديثات برمجية. يمكن أن تختار ما تشاء منها لتثبيته حسب اﻷولوية التي تراها، ووفقًا لوقتك وانشغالك؛ لكن يُنصح هنا بتثبيتها تباعًا إن لم يكن فورًا. انقر على زر "ثبت اﻵن" لبدأ عملية تثبيت التحديثات، أو "تذكير لاحقًا" للتثبيت في وقت لاحق. تثبيت تطبيقات "سناب" من خلال تطبيق "برمجيات" من جنوم سناب هو اﻹسم الذي يُطلق على حزمة برمجية تضم كل ما يتطلبه تطبيق معين من ملفات وموارد واعتماديات، ليعمل باستقلالية وبشكل منفصل عن ملفات النظام واعتمادياته. لقد صُممت حزم سناب من قِبل شركة كانونيكال نفسها التي صممت أوبونتو، وكان الهدف منها أن تعمل التطبيقات على كل توزيعات لينكس بسلاسة، ودون أن تُضطر إلى مشاركة بيانات أو ملفات مع تطبيقات أخرى مخصصة للعمل على توزيعات مختلفة. يُحمِّل المطوّرون وأصحاب الشأن (وفق آلية محددة) تطبيقات سناب إلى ما يُعرف بمخزن سناب أو Snap-Store بما يشابه ما يجري على متجر غوغل بلاي أو ماك ستور. ولكي تستعرض هذه التطبيقات وتثبتها، ستجد نفسك أمام خيارين: اﻷول هو تعليمات سطر اﻷوامر (الطرفية)، أو الثاني هو خيار الواجهات الرسومية التي يفضّلها المستخدمون العاديون أو حديثو العهد. وفي كلتا الحالتين يعمل تطبيق سناب-دي أو سناب دايمون snapd على ضمان تنزيل وتثبيت وتحديث تطبيقات سناب تلقائيًا. لا أنوي إطلاقًا الخوض في تفاصيل الطرفية وتعليماتها في هذا المقال، لذا سأتوجه مباشرةً إلى الواجهات الرسومية التي يتوفر منها الكثير، والتي تختلف اﻷراء واﻷهواء في استحسانها واستهجانها. مع ذلك، وطالما أننا نعمل على سطح مكتب جنوم سنستخدم الواجهة الرسومية الخاصة به وتُدعى "برمجيات" في النسخة العربية. نظرة على "برمجيات" يرتبط هذا البرنامج تلقائيًا بمخزن سناب (وبغيره من المصادر الموثوقة أيَضًا)، ويعرض لك من خلال واجهة رسومية واضحة وسهلة الاستخدامات. أغلب التطبيقات المتوفرة مصنفة بطريقة يسهل معها البحث، كما تساعدك الواجهة أيضًا على تثبيت وإزالة التطبيقات واستعراضها؛ مع اﻹشارة إلى التطبيقات التي تتوفر لها تحديثات (مع أن تطبيقات سناب تُحدّث تلقائيًا). انقر على زر "أظهر التطبيقات" في شريط التطبيقات، ثم انقر على تطبيق "برمجيات". إن طُبَّقت أية تحديثات مؤخرًا على نظامك، فستظهر لك الشاشة التالية: تخبرك هذه الشاشة بأن التطبيق يتواصل مع مخازن التطبيقات لتنزيل قوائم بأسماء التطبيقات المتوفرة حاليًا ومعلومات عنها. وعند اﻹنتهاء من تنزيل هذه المعلومات، ستظهر لك نافذة التطبيق كما في اللقطة التالية: لنبدأ أولًا باستكشاف نافذة التطبيق قبل أن ننتقل إلى موضوع تثبيت وإزالة التطبيقات. يحتوي شريط المهام على ثلاثة أزرار تَعرض ثلاثة نوافذ فرعية: استكشاف Explore: وهي النافذة التي تُعرض افتراضيًا. تُصنِّف النافذة التطبيقات الموجودة في المخازن إلى فئات مختلفة مثل "الصوتيات والمرئيات" و"اﻷلعاب" و"أدوات التطوير" وغيرها، وبالنقر على أية فئة، ستعرض لك النافذة مجموعة التطبيقات التي تنتمي إلى هذه الفئة، كما يمكنك ترتيبها وفقًا للتقييم أو وفقًا للاسم؛ كما تعرض النافذة بعض التطبيقات التي يزكّيها المحررون، إذ يرونها مناسبة ومفيدة للمستخدم. المنصَّبة: تعرض هذه النافذة جميع التطبيقات المثبتة على حاسوبك سواءٌ التطبيقات التي ثبتها بنفسك، أو التي ثُبتت افتراضيًا مع النظام؛ باﻹضافة إلى تطبيقات النظام. ستجد إلى جوار كل تطبيق الزر "أزل" ﻹزالة التطبيق، كما ستجد أسفل الزر حجم هذا التطبيق. التحديثات: تعرض هذه النافذة التطبيقات التي تتوفر لها تحديثات جديدة إن لم تكن هنالك تطبيقات تحتاج إلى تحديث تبلغك النافذة أن برامجك محدّثة. تطبيق عملي: تثبيت تطبيق معالجة الصور "جيمب" من جنوم كمثال عن تثبيت البرمجيات افتح تطبيق "برمجيات"، ثم اختر فئة "الرسوميات والتصوير"؛ وابحث بين التطبيقات المتوفرة عن تطبيق"جنو لمعالجة الصور"، أو انقر على زر البحث في أقصى يمين شريط مهام النافذة، واكتب "gimp" ليبحث عنه التطبيق؛ عندها ستظهر النتائج التي تتطابق مع معايير البحث، ومن بينها تطبيق "جيمب". انقر على أيقونة التطبيق لتنتقل إلى نافذة التثبيت التي تقدم معلومات عن عمل التطبيق ورخصة استخدامه ومصمميه وبعض المراجعات والتقييمات التي وضعها المستخدمون. لتثبيت البرنامج، انقر على زر "نصّب". قد تستغرق العملية وقتًا طويلًا أو قصيرًا وفقًا لحجم التطبيق ولسرعة اتصالك باﻹنترنت، والضغط على خادم التثبيت ومشغولية نظامك (فقد يكون مشغولًا في تنصيب أو تحديث بعض البرمجيات). على أية حال، اترك لتطبيق "برمجيات" الوقت الكافي ﻹنجاز الأمر. يخبرك "برمجيات" عند انتهاء التنزيل والتثبيت بأن البرنامج قد ثُبِّت بنجاح على حاسوبك، وستظهر أيقونته ضمن قائمة التطبيقات. تثبيت التطبيقات المجمّعة في حزم ديبيان Dep. لا بد من اﻹشارة إلى وجود الكثير من التطبيقات التي ينتجها مطوّرون مستقلون أو شركات معروفة مجمّعة أو محزّمةً بأرشيفات تدعى حزم ديبيان (نسبةً لنظام التشغيل لينكس ديبيان الذي يتفرع عنه أوبونتو)؛ إذ تقدم هذه الشركات تطبيقاتها على مواقعها الخاصة ويمكنك عندها تنزيل هذه الحزمة وتثبيت التطبيق. تُعدُّ حزم ديبيان أقل حجمًا من مثيلاتها في سناب، إلا أنها أقل أمانًا، وقد ترتبط بتطبيقات أخرى أو بموارد واعتماديات للنظام. تظهر حزم ديبيان على شكل مجلد مضغوط وردي اللون ينتهي اسم الحزمة باللاحقة dep.. ولتثبيت هذه الحزمة، انقر نقرًا مزدوجًا عليه، وهذا كل ما في اﻷمر. يهتم تطبيق "البرمجيات" بأمر تثبيت هذا التطبيق بالطريقة الملائمة، فعلى الرغم من ارتباط هذا التطبيق بمخازن سناب لتنزيل وتثبيت هذا النوع من التطبيقات، إلا أنه قادر أيضًا على الارتباط بتطبيق "تنصيب البرمجيات" لإدارة حزم ديبان وتنصيبها بطريقة مشابهة لتطبيقات سناب. تطبيق عملي: تثبيت برنامج "فيجوال ستديو كود" من حزمة ديبيان ابحث بكل بساطة مستخدمًا أي محرك بحث عن "visual studio code" وهو تطبيق من شركة مايكروسوفت لتحرير كم هائل من ملفات الشيفرة للكثير من اللغات ويُعد محررًا ممتازًا لك كمبتدئ أيضًا إن أردت البدء بتعلم البرمجة. تعرض لقطة الشاشة التالية لمتصفح فايرفوكس صفحة تنزيل البرنامج: لاحظ أن التطبيق متوفر على الموقع بصيغة dep. لتوزيعات ديبيان وأوبونتو. انقر على الرابط، وسيبدأ المتصفح بتنزيل حزمة التطبيق. عند اكتمال التطبيق، انقر نقرًا مزدوجًا على اﻷرشيف، وستظهر لك النافذة التالية المرتبطة بتطبيق "البرمجيات": انقر زر "نصّب" وانتظر اكتمال عملية التثبيت. حزم أخرى هل سأصادف حزمًا أخرى يفهمها أوبونتو على أنها تطبيقات ويثبتها من خلال تطبيق "البرمجيات"؟ في الواقع نعم، وسنتكلم بإيجاز عن أحدها، وهي حزم "فلات-باك"، اختصارًا للامتداد flatpak. أو اﻹصدار اﻷحدث الذي يضم تعليمات وصفية أكثر عن التطبيق flatpakref. تمتاز هذه الحزم بأنها محتواة في حاوية خاصة بها وتثبّت في معظم توزيعات لينكس دون الحاجة إلى شيئ خارج إطار الحاوية، فهي مستقلة بذاتها مثل تطبيقات سناب. ويمكنك الحصول على هذه الحزم من مخزن فلات-هب flathub، حيث يرفع المطوّرون تطبيقاتهم إليه، كما يمكن أن تجدها على سناب-ستور أيضًا وقد تكون العديد من التطبيقات التي تثبتها من خلال تطبيق "البرمجيات" هي حزم "فلات-باك" في الواقع. على أية حال إن حصلت على التطبيق الذي تريد تثبيته مجمّعًا في أرشيف فلات-باك، فاﻷمر يسير. انقر نقرًا مزدوجًا على الحزمة، وسيتكفل تطبيق "البرمجيات" بكل شيء كما في حزم "ديبيان". خلاصة يساعدك تطبيق "برمجيات" على البحث عن العديد من التطبيقات الموجودة في مخازن مختلفة وموثوقة، إذ يعرض لك وصفًا عن عملها ورخصة استخدامها والجهة التي طوّرتها وتقييم المستخدمين لها. إن لم تجد التطبيق الذي تبحث عنه في قوائم البرمجيات التي يقدمها تطبيق "البرمجيات"، فعليك البحث عنه ضمن مواقع اﻹنترنت، وبالطبع قد تكون العملية محفوفةً بالمخاطر. بمجرد أن تجد الموقع المناسب لتنزيل التطبيق المطلوب وتتأكد من سلامة الموقع والحزمة، حاول أن تنزّل النسخة الخاصة بنظام أوبونتو. وإن تعّذر اﻷمر أو رأيته غامضًا، فابحث عن حزم بإحدى الامتدادات أو اللواحق التالية: dep. flatpak. flatpakref. نزّل هذه الحزمة ثم انقر نقرًا مزدوجًا عليها لتبدأ عملية التثبيت. وهكذا نكون قد قدمنا في مجموعة المقالات هذه الموجهة إلى القادمين الجدد إلى عالم لينكس، شرحًا مكثفًا ومبسطًا لمعظم النواحي التي يحتاجها مستثمر نظام أوبونتو حتى يألف بيئة العمل ويزيد من إنتاجيته. اقرأ أيضًا المقال السابق: إعدادات أوبونتو 20.04: التجهيزات والإعدادات الإقليمية والشمولية إعدادات أوبونتو 20.04: التطبيقات وإدارة المستخدمين تعرّف على سطح مكتب أوبونتو 20.04 التعامل مع المجلدات والملفات في أوبونتو 20.04 تغيير اللغة في نظام لينكس أوبنتو إلى العربية
  8. يصف هذا المقال ما يجري عند استعراض صفحة ويب على حاسوبك أو هاتفك المحمول بصورة مبسّطة، فقد لا تجد هذا الأمر بدايةً أمرًا أساسيًا لكي تكون قادرًا على كتابة شيفرة ويب، لكنك ستجني بسرعة فائدة معرفتك خفايا العملية. الخوادم والعملاء تُدعى الحواسب التي تتواصل على ويب بخوادم servers وعملاء clients، وإليك توضيحًا مبسّطًا عن طريقة تفاعلها: العميل: يُعرَّف تقليديًا بأنه جهاز متصل بويب مثل حاسوب عبر شبكة لاسلكية أو هاتف محمول عبر شبكة خلوية، بالإضافة إلى برنامجيات على هذه الأجهزة قادرة على الوصول إلى ويب مثل متصفحات ويب مثل كروم أو فايرفوكس عادةً. الخادم: هو حاسوب يخزِّن صفحات ومواقع وتطبيقات ويب. حيث يزوّد الخادم الجهاز العميل بنسخة عن صفحة ويب عندما يريد الوصول إليها ليتمكن من عرضها على متصفح المستخدِم. تفاصيل أكثر لن تكتمل القصة بمفهومَي الخادم والوكيل التي عرضناها قبل قليل، فهنالك الكثير من الأجزاء الأخرى التي سنناقشها تاليًا. دعونا نتخيل الآن أنّ الويب هي طريق يقع العميل في طرفه الأول وليكن بيتك مثلًا ويقع الخادم في طرفه الآخر وليكن متجرًا تشتري منه حاجياتك على سبيل المثال. عليك أن تتعرف على ما يلي بالإضافة إلى الخادم والعميل: الاتصال بالإنترنت: الذي يسمح لك بإرسال واستقبال البيانات من وإلى ويب، فهو تقريبًا مثل الشارع الذي يربط بيتك بالمتجر. بروتوكول TCP/IP: يُعَدّ بروتوكول التحكم بالنقل Transmission Control Protocol وبروتوكول الإنترنت Internet Protocol بروتوكلَي اتصال يحددان آلية نقل البيانات عبر الإنترنت، إذ يشابه هذان البروتوكولان وسيلة التنقل التي تتخذها لتنظيم رحلتك من منزلك إلى المتجر وشراء حاجياتك، فقد تكون في مثالنا سيارة أو دراجة أو أيّ شيء مماثل. خادم أسماء النطاقات Domain Name System أو DNS اختصارًا: يمكن تشبيهه بدفتر عناوين لمواقع ويب، فعندما تكتب عنوان موقع ويب في متصفحك، يبحث خادم DNS عن عنوان بروتوكول الإنترنت IP address الموافق للموقع قبل إحضاره، إذ يحتاج المتصفح لمعرفة الخادم الذي يستضيف الموقع المطلوب ليرسل رسائل HTTP إلى الوجهة الصحيحة، ويشبه الأمر في مثالنا البحث عن عنوان المتجر. بروتوكول HTTP: وهو بروتوكول نقل النص التشعبي Hypertext Transfer Protocol ويُعَدّ بروتوكولًا تطبيقيًا يُوصِّف لغةً بين الخادم والعميل ليتواصلا من خلاله، أي الأمر مشابه أيضًا للغة المحكية التي تتواصل بها لطلب حاجياتك من المتجر. الملفات المكوّنة للموقع: يتألف الموقع من ملفات مختلفة بصورة مشابهة للبضائع المختلفة التي تحتاجها من المتجر، وتأتي هذه الملفات ضمن صنفين أساسيين: ملفات شيفرة Code files: تُبنى مواقع ويب بصورة أساسية من ملفات HTML و CSS وجافاسكربت، كما ستتعرف لاحقًا على ملفات أخرى تخدم تقانات مختلفة. ملفات مساعدة أو الأصول Assets: تشمل كل الأشياء التي يتكوّن منها الموقع مثل الصور والموسيقى والفيديو والمستندات النصية وغيرها. ما الذي يحدث عند طلب موقع ويب؟ إليك ما يحدث عندما تكتب عنوان موقع في شريط متصفحك، وهو أمر مماثل لانطلاقك نحو المتجر: يتوجه المتصفح إلى خادم DNS ويجد العنوان الحقيقي للخادم الذي يستضيف هذا الموقع، أي مثل إيجاد العنوان المفصّل للمتجر. يرسل المتصفح طلب HTTP إلى الخادم يسأله فيها إرسال نسخة عن موقع الويب إلى العميل، أي عندما تذهب إلى المتجر وتطلب حاجياتك، إذ تُرسل الطلبات وغيرها من البيانات بين العميل والخادم عبر اتصال الإنترنت من خلال بروتوكول TCP/IP. سيرسل الخادم إذا وافق على طلب العميل رسالةً تحمل العدد 200 وتعني تمامًا أنه بإمكانك الاطلاع على هذا الموقع، إذ يبدأ الخادم بعدها بإرسال ملفات الموقع إلى المتصفح على أساس سلسلة من القطع تُدعى رزم البيانات، أي عندما يبدأ المشرف على المتجر بإعطائك البضائع التي طلبتها ثم تنقلها إلى منزلك. يُجمِّع المتصفح حزم البيانات ضمن صفحة مكتملة ويعرضها لك مثل وصول حاجياتك كاملة إلى منزلك واستخدامها. الترتيب الذي يستخدمه المتصفح في تفسير الملفات عندما تطلب المتصفحات من الخوادم ملفات HTML، فستشير هذه الملفات من خلال العناصر <link> إلى ملفات CSS خارجية، ومن خلال العناصر <script> إلى ملفات جافاسكربت الخارجية، فمن المهم إذًا معرفة الترتيب الذي تفسر به المتصفحات ملفات موقع عندما تحمّله: يفسّر المتصفح ملف HTML أولًا، وبالتالي سيميّز العناصر <link> التي تشير إلى ملفات CSS الخارجية، كما يميّز العناصر <script> التي تشير إلى ملفات الشيفرة. يرسل المتصفح أثناء تفسير ملف HTML طلبات إلى الخوادم التي قد تستضيف ملفات CSS أو ملفات الشيفرة التي تشير إليها العناصر السابقة، ثم يفسّر هذه الملفات. يولّد المتصفح شجرة DOM مقيمة في الذاكرة انطلاقًا من ملف HTML الذي فسّره ومن ثم شجرة CSSOM بعد تفسير ملف CSS ثم يصرِّف وينفذ شيفرة جافاسكربت التي فسّرها. عندما يبني المتصفح شجرة DOM ويطبّق التنسيقات استنادًا إلى شجرة CSSOM وينفّذ شيفرة جافاسكربت، تُعرض الصفحة على الشاشة بعملية تُدعى الرسم paint ليراها المستخدِم ويكون قادرًا على التفاعل معها. توضيح عن خادم أسماء النطاقات DNS من الصعب تذكُّر عناوين ويب الحقيقية لأنها ليست عبارات نصية واضحة، وإنما هي أعداد خاصة تبدو مشابهةً للسلسلة 63.245.215.20، إذ تُدعى هذه السلسلة بعنوان بروتوكول الإنترنت IP address ويمثل موقعًا فريدًا على ويب، وطالما أنه من الصعب تذكر هذه العناوين، فقد اختُرعت خوادم أسماء النطاقات، وهي خوادم خاصة تربط عنوان موقع ويب الذي تدخله في شريط المتصفح مثل "hsoub.com" بعنوان بروتوكول الإنترنت الحقيقي له. يمكن أيضًا الوصول إلى مواقع ويب من خلال عناوين بروتوكول الإنترنت، لكن عليك أن تحصل أولًا على هذا العنوان عن طريق بعض الأدوات مثل IP Checker. توضيح عن رزم البيانات استُخدم المصطلح رزم packets لوصف هيئة البيانات التي يرسلها الخادم إلى العميل، فما الذي نعنيه بالرزم هنا؟ عندما تُرسل البيانات عبر الويب فإنها تُرسَل عبر آلاف الأجزاء الصغيرة وذلك لأسباب كثيرة، فقد تُهمل هذه البيانات أو تتشوه أثناء نقلها ومن الأسهل استبدال قطعة صغيرة إذا حدث ذلك، إضافةً إلى هذا الأمر، فقد توجّه الرزم إلى مسارات مختلفة، مما يجعل تبادلها أسرع ويسمح لعدد كبير من المستخدِمين تنزيل الموقع نفسه في الوقت عينه، بينما إذا أُرسِل الموقع دفعةً واحدةً، فلن يتمكن سوى مستخدِم واحد من تنزيل الموقع كل مرة، مما يجعل الويب غير فعالة وغير مسليَة. ترجمة -وبتصرف- للمقال How the web works. اقرأ أيضًا كيف تعمل شبكة الإنترنت كيفية التعامل مع الويب المدخل الشامل لتعلم تطوير الويب
  9. تُعَدّ جافاسكربت JavaScript لغة برمجة تزيد من القدرة التفاعلية لمواقع ويب، وتشاهد ذلك مثلًا في الألعاب وفي مظاهر استجابة الصفحات عند نقر الأزرار أو عند إدخال البيانات إلى النماذج والاستمارات الإلكترونية، أو من خلال التغيير الديناميكي لتنسيق الصفحة أو عبر الرسوم المتحركة وغيرها، إذ سيساعدك هذا المقال لتبدأ استخدام جافاسكربت ويعرّفك أكثر بإمكانيات هذه اللغة. تعرف على جافاسكربت تُعَدّ جافاسكربت لغة برمجة قوية تزيد من القدرة التفاعلية لمواقع ويب، وقد ابتكرها برنارد آيتش Brendan Eich، وهو مؤسس مشارك لمشروع موزيللا ومؤسسة موزيللا وشركة موزيللا، كما تُعَدّ جافاسكربت لغةً قريبةً من المبرمجين المبتدئين، إذ ستتمكن مع الممارسة والخبرة من إنشاء ألعاب ثنائية وثلاثية الألعاب وبناء تطبيقات عصرية مرتبطة بقواعد بيانات وغير ذلك الكثير، وصحيح أنّ هذه اللغة مدمجة مع المتصفحات، لكنها مرنة، فقد بنى المطورون الكثير من الأدوات انطلاقًا من جافاسكربت، مقدِّمين كمًا واسعًا من القدرات الوظيفية بأقل مجهود ممكن، ونذكر منها : واجهات برمجية للتطبيقات API مدمجة بالمتصفحات لتزويدها بوظائف إضافية مثل الإنشاء التلقائي لشيفرة HTML وضبط تنسيقات CSS أو التقاط وتعديل الفيديوهات المصورة عن طريق كاميرا ويب أو توليد رسوميات ثلاثية الأبعاد ومقاطع صوتية. واجهات برمجية لتطبيقات صممها طرف ثالث تسمح للمطورين دمج وظائف يقدمها مزوّدو محتوى مثل تويتر وفيس بوك ضمن مواقع أخرى. أطر عمل ومكتبات صممها طرف ثالث ويمكن تطبيقها على صفحات HTML لتسريع بناء المواقع والتطبيقات. لا يغطي مجال هذا المقال تفاصيل الاختلاف بين الأدوات التي بُنيت وتبنى باستخدام جافاسكربت واللغة نفسها، لذلك يمكن دائمًا البحث عن تفاصيل مثل هذه عبر الإنترنت، وسيقدِّم لك القسم التالي من المقال بعض مزايا جافاسكربت البنيوية، كما سيمنحك إمكانية تجريب بعض مزايا الواجهات البرمجية الخاصة بالمتصفحات أيضًا. كتابة أول برنامج في جافاسكربت تُعَدّ جافاسكربت من أكثر التقنيات الحديثة شعبيةً، وستُدخِل مواقعك مع تقدم مهارتك فيها أبعادًا جديدةً من القوة والإبداع، لكن التآلف مع هذه اللغة أصعب من الاعتياد على العمل مع HTML و CSS، فقد تبدأ بالقليل ثم تنمو قدراتك تدريجيًا، ولنبدأ بتفحّص الطريقة التي سنضيف فيها جافاسكربت إلى صفحتك لتنفيذ برنامج "!Hello world"، وهو برنامج معياري لتقديم لغة برمجة إلى المستخدِم لأوّل مرة. تنبيه: إذا لم تكن لسبب ما متابعًا للأفكار التي تحدثنا عنها في مقالات سابقة، فيمكنك تنزيل الشيفرة التجريبية للمثال واعتماده على أساس نقطة انطلاق. انتقل إلى المجلد الذي يضم موقعك التجريبي وانشئ ضمنه مجلدًا باسم scripts، ثم انشئ ضمن الأخير ملفًا نصيًا ثم احفظه بالاسم main.js. افتح الملف index.html واكتب قبل نهاية العنصر <body> سطر الشيفرة التالية: <script src="scripts/main.js"></script> ينفِّذ هذا العنصر ما ينفذه تمامًا العنصر <link> عندما أدرج ملف تنسيق CSS، إذ يطبِّق هذا العنصر شيفرة جافاسكربت الموجودة في الملف على عناصر HTML في الصفحة بالإضافة إلى تنسيقات CCS أو أيّ شيء آخر. أضف الشيفرة التالية إلى الملف main.js: const myHeading = document.querySelector('h1'); myHeading.textContent = 'Hello world!'; تأكد من حفظ التغيرات على الملفَين index.html وmain.js، ثم حمّل الملف index.html مجددًا في المتصفح الذي سيعرض صفحةً شبيهةً بالتالي: ملاحظة: يعود سبب وضع العنصر <script> في نهاية ملف HTML إلى طريقة القراءة المتسلسلة للشيفرة وفق ترتيب ظهور العناصر التي يتّبعها المتصفح. إذا حمّل المتصفح ملف جافاسكربت أولًا قبل عناصر HTML التي يُفترض أن يؤثر فيها، فقد تقع بعض المشاكل، لذلك من الأفضل تحميله بعد تحميل العناصر بوضع العنصر في نهاية صفحة HTML، فهذا سيحل المشكلة. ما الذي حدث؟ لقد تغيّر عنوان الصفحة إلى "!Hello world" باستخدام جافاسكربت، إذ نُفِّذت العملية عبر استدعاء الدالة ()querySelector التي التقطت مرجعًا إلى عنصر العنوان <h1> وخزّنته في المتغير myHeading، أي الأمر مشابه لما فعلناه باستخدام محددِّات CSS، فإذا أردت تطبيق شيء ما على عنصر، فلا بد من تحديده أولًا. بعد ذلك أُسندت إلى الخاصية textContent العائدة للمتغير myHeading والتي تمثل محتوى العنوان القيمة الجديدة "!Hello world". ملاحظة: تُعَدّ كلتا الميزتين المستخدَمين في هذا المثال جزءًا من الواجهة البرمجية لشجرة DOM التي تمتلك القدرة على التعامل مع مستند HTML. دورة تطوير التطبيقات باستخدام لغة JavaScript تعلم البرمجة بلغة جافا سكريبت انطلاقًا من أبسط المفاهيم وحتى بناء تطبيقات حقيقية. اشترك الآن أساسيات لغة جافاسكربت سنشرح لك فيما يأتي بعض الميزات البنيوية لجافاسكربت لتفهم طريقة عملها بصورة أفضل، ومن الجدير بالذكر أنّ هذه الميزات مشتركة بين كل لغات البرمجة، فإذا أتقنت هذه الأساسيات، فستكون قد وضعت حجر الأساس لكتابة شيفرات بلغات أخرى. تنبيه: حاول أن تُدخل أسطر الشيفرة التجريبية التي تتعلمها في هذا المقال ضمن طرفية جافاسكربت JavaScript console المضمنة داخل المتصفح، ولمزيد من المعلومات عن هذه الطرفية راجع مقال أدوات مطوري ويب المدمجة في المتصفحات. المتغيرات تُعدّ المتغيرات حاويات لتخزين القيم، إذ تبدأ القصة عندما تُصرِّح عن متحول باستخدام التعليمة var (مع أنها غير محبّذة، ستجد التفاصيل لاحقًا) أو التعليمة let يتبعها الاسم الذي تختاره للمتغير: let myVariable; تشير الفاصلة المنقوطة في نهاية السطر إلى نهاية الجملة البرمجية، وتحتاجها فقط عندما تريد الفصل بين العبارات البرمجية في السطر ذاته، لكن وضع فاصلة منقوطة في نهاية كل سطر هي عادة برمجية جيدة، وهنالك عدة قواعد أخرى عن وجوب استخدام الفاصلة المنقوطة وعدم وجوب ذلك، كما يمكنك تسمية المتغيِّر أيّ اسم تقريبًا مع بعض القيود، إذ يمكن الاطلاع على توثيق المتغيرات في موسوعة حسوب، وإذا لم تكن متأكدًا مما كتبت، فتستطيع استخدام بعض الخدمات على الإنترنت للتحقق من صحة تسمية المتغيرات، كما ينبغي الانتباه إلى أنّ جافاسكربت حساسة لحالة الأحرف، فالمتغير myVariable يختلف تمامًا عن myvariable، لذلك تحقق من مشاكل مثل هذه عندما تواجهك الأخطاء. يمكنك إسناد قيمة إلى المتغير بعد التصريح عنه: myVariable = 'Bob'; يمكنك تنفيذ خطوتَي التصريح والإسناد في سطر واحد: let myVariable = 'Bob'; استدع المتغير وحسب لتحصل على القيمة: myVariable; يمكن تغيير قيمة المتغير لاحقًا في مواضع أخرى في الشيفرة: let myVariable = 'Bob'; myVariable = 'Steve'; يمكن أن تحمل المتغيرات قيمًا من أنواع مختلفة: String: يمثل مجموعةً من محارف تمثل سلسلةً نصيةً، ولابد من وضع السلسلة بين إشارتَي إقتباس مفردتين كما يلي: let myVariable ='Bob'; Number: يمثل عددًا، ولا يُوضَع ضمن شارتي تنصيص. let myVariable =10; Boolean: ويمثل أحد القيمين المنطقيتين: صحيح true أو خاطئ false، وهاتين الكلمتين من الكلمات الخاصة المحجوزة في لغة جافاسكربت ولا حاجة لوضعهما بين إشارتي تنصيص: let myVariable = true; Array: يمثل مايُدعى بالمصفوفة، وهي بنية لتخزين عدة قيم تحت مرجع واحد: let myVariable = [1,'bob',10,'mad'] // يمكن الإشارة إلى كل عنصر من عناصر المصفوفة كما يلي myVariable[0]; // 1 تعرض أولى قيم المصفوفة وهي myVariable[3]; // 'mad' تعرض القيمة الأخيرة Object: قد يحمل أي نوع من القيم، فكل شيء في جافاسكربت هو كائن Object يمكن أن يُخزَّن في متغير: let myVariable = document.querySelector('h1'); // الشيفرة نفسها التي استخدمناها سابقًا لِمَ نحتاج المتغيِّرات إذًا؟ لأنها ضروية لتنفيذ كل ما له معنى في البرمجة، فالمتغيرات مخازن القيم، ولن تحصل على أية أفعال ديناميكية مثل تخصيص رسالة ترحيب أو تغيير الصورة المعروضة إذا لم تتغير القيم. التعليقات تُعَدّ التعليقات Comments مقتطفات نصيةً تُضاف إلى الشيفرة ويتجاهلها المتصفح، كما يمكن استخدام التعليقات في جافاسكربت الأسباب نفسها التي تستخدمها في CSS، ويشار إلى نص على أنه تعليق كما يلي: /* Everything in between is a comment. */ لكي يمتد التعليق على عدة أسطر، أو كما يلي: // This is a comment إذا كان التعليق على سطر واحد. العوامل يُعَدّ العامل Operator رمزًا رياضيًا يعطي نتيجةً استنادًا إلى قيمتين أو متغيرين، وإليك بعض العوامل الأبسط التي تُستخدَم في جافاسكربت مع بعض الأمثلة، وحاول تجريبها على طرفية جافاسكربت: الجمع: رمزه +، ويضيف عددين معًا أو يضم نصين إلى بعضهما: 9+6; //15 'hello' + 'world'; // helloworld الطرح والضرب والقسمة: رموزها بالترتيب -، *، /، وعملها مشابه تمامًا لعملها الرياضي: 4-5; //-1 3*3;//9 10/2;//5 الإسناد: رمزه =، ويسند قيمة إلى متغير: let myVar = 3; المساواة: رمزها ===، وتختبر تساوي قيمتين وتعيد نتيجةً منطقيةً؛ إما صحيح true أو خاطئ false : let myVar =3; myVar ===4; //fals تعيد النفي: رمزه !، ويعيد القيمة المنطقية المعاكسة لما يتقدمها، إذ تُغيِّر true إلى false وبالعكس: let myVar=3; !(myVar===4); //true تعيد عدم المساواة: رمزها ‎!==‎، وتختبر عدم تساوي قيمتين وتعيد النتيجة المنطقية المناسبة: let myVar=4; myVar==!3; //true تعيد هناك الكثير من العوامل الأخرى في جافاسكربت، لكننا سنكتفي الآن بما ذكرناه. ملاحظة: قد يقود دمج أنواع مختلفة من البيانات عند إجراء العمليات الحسابية إلى أخطاء، لذا فكن حذِرًا بالإشارة إلى متغيراتك لتحصل على النتيجة المتوقعة، فإذا نفَّذت العملية '35' +'25'، فستحصل على 2535 وهذا ما قد لا تتوقعه، لأن إشارات التنصيص المفردة تجعل ما داخلها نصوصًا لا أعدادًا، بينما إذا نفَّذت العملية على الصورة 35 + 25، فستحصل على نتيجة الجمع الصحيحة. العبارات الشرطية تُعَدّ العبارات الشرطية بُنًى تُستخدَم لاختبار تحقق شرط معيَن نتيجته true أو false، ومن أكثر الصيغ الشرطية استخدامًا هي العبارة if...else، وإليك مثالًا كما يلي: let iceCream = 'chocolate'; if(iceCream === 'chocolate') { alert('Yay, I love chocolate ice cream!'); } else { alert('Awwww, but chocolate is my favorite...'); } تختبر البنية الشرطية العبارة التي تقع ضمن (...)if، إذ تستخدِم البنية في الشيفرة السابقة عامل المساواة للموازنة بين قيمتي المتغّير iceCream والنص chocolate والتحقق من تساويهما، فإذا أعادت الموازنة القيمة true، فستُنفَّذ الكتلة الأولى من الشيفرة التي تلي تعليمة الشرط (...)if، وإلا فستُنفَّذ الكتلة الثانية التي تقع بعد العبارة else. الدوال تُعَدّ الدوال طريقةً لتنظيم الشيفرة التي ترغب في استخدامها مرارًا، إذ يمكنك أن تُعرِّف مثلًا مجموعةً من أسطر الشيفرة على أساس دالة يجري تنفيذها عندما تستدعيها باسمها في أي مكان من الشيفرة، إذ تملك هذه الطريقة فعاليةً كبيرةً في منع تكرار كتابة الشيفرة نفسها كلما احتجنا لها، ولقد رأينا في الشيفرات السابقة نماذجًا لدوال مثل: let myVariable = document.querySelector('h1');//هي دالة querySelector وكذلك: alert('hello!'); إنّ الدالتَين document.querySelector و alert مدمجتان مع المتصفح، فإذا رأيت شيئًا يشبه المتغير متبوعًا بقوسين ()، فهو دالة على الأغلب، كما تأخذ الدوال أشياء تُدعى وسائط arguments، وهي بيانات تحتاجها لتنفيذ وظائفها، إذ تُوضع الوسائط داخل قوسَي الدالة وتفصل بينها فاصلة ,. تعرض الدالة alert على سبيل المثال نافذةً منبثقةً ضمن المتصفح، فلا بد من تزويدها بنص على هيئة وسيط لكي تعرضه، كما يمكنك أيضًا تعريف دالة خاصة بك، إذ سننشئ في المثال التالي دالةً تأخذ وسيطَين عددين ثم تعيد ناتج جدائهما: function multiply(num1,num2) { let result = num1 * num2; return result; } حاول تنفيذ ذلك في الطرفية، ثم اختبر الدالة بتغيير قيم الوسيطَين كما يلي: multiply(4, 7); multiply(20, 20); multiply(0.5, 3); ملاحظة: تخبر التعليمة return في نهاية الدالة أن تعيد قيمةً هي نتيجة تنفيذ الدالة غالبًا -مثل result في المثال السابق- لكي تستطيع استخدامها، وهذا الأمر ضروري لأن المتغيرات التي تُعرَّف داخل الدالة لا يمكن استخدامها خارج الدالة وهذا ما يُعرَف بمجال رؤية المتغير variable scoop. الأحداث لابد من معالجة الأحداث التي تقع في الصفحة لتظهر تفاعلية الصفحة، فالأحداث events هي بنى برمجية تُنصِت إلى النشاطات التي تجري في المتصفح وتستجيب لها بتنفيذ شيفرة محددة، ومن أكثر الأحداث وضوحًا هو حدث النقر على الفأرة والذي يقع عندما تنقر بالفأرة على شيء ما ضمن الصفحة، ولشرح الفكرة، أدخِل الشيفرة التالية في طرفية جافاسكربت ثم انقر على الصفحة التي يعرضها المتصفح: document.querySelector('html').addEventListener('click', function() { alert('Ouch! Stop poking me!'); }); هناك طرق عدة لربط معالِج الحدث event handler بالعنصر، فقد اخترنا في الشيفرة السابقة العنصر <html>، ومن ثم استدعينا معه الدالة addEventListener()‎ مع تحديد اسم الحدث (في حالتنا 'click' النقر) المراد الإنصات له مع تحديد الدالة المراد تنفيذها عند وقوع ذلك الحدث. لاحظ الشيفرة التالية التي تشبه تمامًا الشيفرة السابقة في العمل: document.querySelector('html').onclick = function() { alert('Ouch! Stop poking me!'); } أسندنا فيها الخاصية onclick التي تمثل معالج حدث النقر (الذي يكون اسم الحدث مسبوقًا بكلمة on) إلى دالة دون اسم anonymous تضم الشيفرة التي نريد تنفيذها عندما يقع حدث النقر. أيضًا الشيفرة السابقة والتي تسبقها مماثل للشيفرة التالية: let myHTML = document.querySelector('html'); myHTML.addEventListener('click', function() { alert('Ouch! Stop poking me!'); }); لكنه أقصر. الدالة السابقة التي ممرناها إلى الدالة addEventListener()‎ تدعى دالة مجهولة لعدم امتلاكها اسمًا، وهنالك طريقة أخرى لكتابة دوال مجهولة تدعى الدوال السهمية التي تستعمل الصيغة ‎() =>‎ بدلًا من function ()‎، انظر مثلًا: document.querySelector('html').addEventListener('click', () => { alert('Ouch! Stop poking me!'); }); تطوير المثال التجريبي الذي نعمل عليه لنضِف بعض الميزات الجديدة إلى صفحة الويب التي نطوّرها باستخدام ما تعلمناه عن جافاسكربت. احذف بداية محتوى الملف main.js واحفظ الملف فارغًا كي لا تتعارض الشيفرات الجديدة مع تلك التي كتبناها سابقًا. تغيير للصورة سنتعلم استخدام جافاسكربت وميزات واجهة DOM البرمجية لتبديل الصورة مرةً أو مرتين عند النقر عليها. اختر صورةً جديدةً لاستخدامها والأفضل أن يكون لها قياس الصورة الأولى نفسه. احفظ الصورة في المجلد images باسم firefox2.png. أضف شيفرة جافاسكربت التالية إلى الملف main.js: let myImage = document.querySelector('img'); myImage.onclick = function() { let mySrc = myImage.getAttribute('src'); if(mySrc === 'images/firefox-icon.png') { myImage.setAttribute('src','images/firefox2.png'); } else { myImage.setAttribute('src','images/firefox-icon.png'); } } احفظ جميع التغيرات وحمّل الملف index.html في المتصفح وانقر على الصورة، إذ يجب أن تتغير الصورة المعروضة عند النقر. إليك ما حدث: لقد خزنت مرجعًا إلى العنصر <img> في المتغير myImage، ثم أسندت قيمة الخاصية onclick للمتغير والتي تمثل معالِج حدث النقر إلى دالة دون اسم لكي يُنفِّذ محتواها كلما نقرت على الصورة، وما تفعله الشيفرة هو استخلاص قيمة السمة src لعنصر الصورة واستخدم بنية شرطية للتحقق أن قيمتها تساوي مسار الصورة الأصلية، فإذا كانت كذلك، فستُغيِّر الشيفرة قيمة السمة src للعنصر <img> إلى مسار الصورة الثانية لكي يعرضها المتصفح، وإذا لم تكن كذلك، فهذا يعني أنّ قيمة السمة src قد تغيرت سابقًا وستعيدها الشيفرة إلى قيمتها الأصلية لتُعرض الصورة الأساسية وهكذا. إضافة رسالة ترحيب خاصة لنغيّر الآن عنوان الصفحة كي يعرض رسالة ترحيب خاصة بالمستخدِم عند زيارته للصورة وستبقى هذه الرسالة، ولإن غادر الزائر الصفحة وعاد مجددًا ستظهر الرسالة، لأننا سنخزنها باستخدام الواجهة البرمجية للتخزين على المتصفح، وسنقدم أيضًا خيارًا لتغيير المستخدِم، وبالتالي تغيير رسالة الترحيب. أضف السطر التالي في الملف index.html فقط قبل العنصر <script>: <button>Change user</button> أي أسفل الملف. ضع الشيفرة التالية في نهاية الملف main.js كما هي تمامًا، إذ تُخزِّن هذه الشيفرة مرجعًا إلى الزر الجديد ومرجعًا إلى العنوان داخل متغيرَين: let myButton = document.querySelector('button'); let myHeading = document.querySelector('h1'); أضف الدالة التالية لتخصيص رسالة الترحيب، إذ لن تفعل الدالة شيئًا بالطبع حتى اللحظة لكنها ستفعل قريبًا: function setUserName() { let myName = prompt('Please enter your name.'); localStorage.setItem('name', myName); myHeading.textContent = 'Mozilla is cool, ' + myName; } تحتوي الدالة ()setUserName على الدالة ()prompt التي تعرض مربع حوار على شاشة المتصفح بصورة مشابهة للدالة ()alert لكنها تنتظر من المستخدِم إدخال قيمة لكي تخزنها بعد أن ينقر الزر موافق OK. نطلب في هذه الحالة من المستخدِم إدخال اسمه ثم تستدعي الشيفرة الواجهة البرمجية localStorage التي تسمح بتخزين البيانات في ذاكرة المتصفح للوصول إليها لاحقًا، كما نستخدِم الدالة ()setItem لإنشاء وتخزين عنصر بيانات يُدعى name ثم إسناد قيمته إلى المتغير myName الذي يحوي القيمة التي يدخلها المستخدِم للاسم، ونضيف أخيرًا قيمة المتغير myName إلى محتوى العنوان ثم يُسند النص الناتح إلى الخاصية textContent ليظهر الاسم الذي أدخلناه على أساس جزء من العنوان. أضف الكتلة الشرطية if ... else التالية: if(!localStorage.getItem('name')) { setUserName(); } else { let storedName = localStorage.getItem('name'); myHeading.textContent = 'Mozilla is cool, ' + storedName; } يمكننا استدعاء هذه الشيفرة عند بداية تحميل الصفحة كونها شيفرة تهيئة للمحتوى، إذ يستخدِم السطر الأول منها عامل النفي المنطقي ! للتحقق من عدم وجود عنصر البيانات name ضمن مخازن الذاكرة LocalStorage، فإذا لم يجده، فسيستدعي الدالة ()setUserName لإنشاءه؛ أما إذا كان موجودًا -أي أنّ المستخدِم أدخله في أثناء زيارته الأولى-، فسنعيد قيمته باستخدام الدالة ()getItem ثم نضبط قيمة محتوى العنوان ليصبح النص الأصلي إضافة إلى اسم المستخدِم كما فعلنا ضمن الدالة ()setUserName. أضف معالِج الحدث onclick التالي إلى الزر لكي تُستدعى الدالة ()setUserName عند النقر عليه، وبالتالي سيتمكن المستخدِم من تغيير الاسم عند النقر على هذا الزر: myButton.onclick = function() { setUserName(); } ظهور القيمة null إذا نقرت زر إلغاء cancel بدل زر موافق ok أثناء ظهور مربع الحوار، فسيظهر لك عنوان الصفحة "Mozilla is cool, null"، ويعود السبب في ذلك إلى عدم إسناد قيمة إلى المتغير myName وبالتالي سيأخذ القيمة null، وهي قيمة خاصة في جافاسكربت تشير إلى غياب قيمة مطلوبة؛ أما إذا نقرت زر موافق ok دون إدخال اسم، فستكون النتيجة ",Mozilla is cool" وهذا أمر واضح، ولتفادي هذه المشاكل يمكنك التحقق من وجود الاسم فعلًا، لذلك سنعدِّل شيفرة الدالة ()setUserName كما يلي: function setUserName() { let myName = prompt('Please enter your name.'); if(!myName) { setUserName(); } else { localStorage.setItem('name', myName); myHeading.textContent = 'Mozilla is cool, ' + myName; } } يعني هذا إعادة استدعاء الدالة ()setUserName من جديد إذا لم يكن للمتغير قيمة؛ أما إذا كان له قيمة، فستُخزَّن ضمن localStorage وتُضاف إلى العنوان. خلاصة إذا اتبعت التوجيهات التي أشرنا إليها خلال اطلاعك على هذا المقال، فستبدو صفحة الويب التي نبنيها بالشكل التالي تقريبًا: إذا لم تجد عملك صحيحًا، فيمكنك دائمًا موازنة ما فعلته مع النسخة الجاهزة على جيت-هاب. ترجمة -وبتصرف- للمقال JavaScript basics. اقرأ أيضًا تعلم لغة جافا سكريبت من الصفر حتى الاحتراف توثيق لغة JavaScript العربي تعلم البرمجة ما هي جافاسكربت الدليل السريع إلى لغة البرمجة جافاسكريبت JavaScript سلسلة دليل تعلم جافاسكربت
  10. نتابع في هذا المقال ما بدأناه في ضبط إعدادات النظام بما يلائم حاجات المستخدم، وسنناقش المواضيع التي تختص بإدارة التجهيزات المثبتة، مثل التجهيزات الصوتية وشاشات العرض؛ كما نعرّج على مواضيع الإعدادات اللغوية والإقليمية وإعدادات الشمولية (الوصول السهل accessibility) التي تحسّتن من تجربة المستخدمين ذوي القدرات الخاصة، وذوي المشاكل البصرية والسمعية . إعدادات التجهيزات وتتضمن مجموعةً من الخيارات التي تتحكم بإعدادات بعض التجهيزات، مثل الصوت والطاقة والفأرة وغيرها. إعدادات الصوت للولوج إلى هذه اﻹعدادات، انقر على خيار "الصوت" في الشريط الجانبي لنافذة اﻹعدادات، وسيعرض لك التطبيق النافذة التالية: تضم الشاشة السابقة الأقسام التالية: شدة صوت نظام: يحدد مستوى الصوت في حاسوبك، ويمكنك ضبط نفس اﻹعداد من شريط المهام الرئيسي لسطح المكتب. تجدر اﻹشارة إلى وجود آلية لتعزيز الصوت، أي إيصاله إلى مستويات أعلى من 100% (يتعلق اﻷمر بالشريحة الصوتية المدمجة في نظامك وإمكاناتها)، وذلك بالنقر على زالقة خيار "تضخيم زائد over _ amplification". مستويات الصوت: يمكِّنك هذا الخيار من ضبط شدة الصوت لبعض التطبيقات مثل أصوات النظام التي يصدرها عند النقر على أيقونة، أو فتح وإغلاق نافذة، أو التنبيهات الصوتية أو لبعض المشغلات الصوتية (لاحظ وجود زالقة للتحكم بمستوى صوت تطبيق VLC؛ مما يدل على أن هذا المشغل الصوتي مثبّت على جهازك، ويعمل في لحظة التقاط صورة للشاشة). المخرج: عادةً ما يختار النظام تلقائيًا المنفذ الذي يخرج منه الصوت إن دعمت الشريحة الصوتية في حاسوبك أكثر من مخرج، وذلك عن طريق التقاط الإشارة ناتجة عن توصيلك مكبر الصوت أو السماعة في هذا المنفذ. يمكنك بالطبع اختيار أي منفذ لإخراج الصوت من قائمة "جهاز إخراج"، كما يتيح لك الزر "اختبار" المجاور للقائمة أن تختبر عمل هذا المخرج الصوتي. المدخل: ويتيح لك اختيار المنفذ الذي تدخل منه الإشارات الصوتية إلى حاسوبك لتسجيلها أو عرضها عندما تصل مايكروفون أو أي تجهيزة مشابهة بهذا المنفذ. صوت التنبيه: وذلك لاختيار نغمة التنبيه الصوتي أو نغمة الصوت المرافق لظهور تنبيه على الشاشة. إعدادات الطاقة تضم هذه اﻹعدادات بعض الخيارات التي تساعد على تخفيف استهلاك الطاقة قدر المستطاع سواءً أثناء العمل، أو عند التوقف عن العمل مؤقتًا؛ كما تتيح لك التحكم بما سيفعله حاسوبك عند الضغط على زر التشغيل. خيارات حفظ الطاقة تعتيم الشاشة blank screen: وهي الفترة الزمنية التي ينتظرها النظام بعد توقفك عن العمل لإخفاء شاشة سطح المكتب. يمكنك اختيار مدة تتراوح بين دقيقة و"أبدًًا" إن لم تكن تريد إخفاء سطح المكتب. واي فاي: تشغيل أو إطفاء محوّل الشبكة اللاسلكية لتوفير الطاقة إن لم تكن تستخدم الشبكة. بلوتوث: تشغيل أو إطفاء محوّل بلوتوث لتوفير الطاقة إن لم تكن تستخدمه. خيارات تعليق النظام والضغط على زر تشغيل الحاسب نعرض فيما يلي خيارات تعليق النظام والضغط على زر تشغيل الحاسب: التعليق التلقائي للنظام automatic suspend: وذلك لتعليق عمل النظام بعد فترة من الزمن إن لم تكن تستخدم الحاسوب. هذا الخيار معطّل افتراضيًا، لكن بإمكانك تفعيله ثم اختيار الفترة الزمنية التي تناسبك ما بين 15 دقيقة وساعتين. الضغط على زر التشغيل power button action: يمكنك أن تطفئ الجهاز، وهو الخيار اﻹفتراضي أو تعلق عمله أو أن يتجاهل الحاسوب أمر الضغط على زر التشغيل عند اختيار "لا تفعل شيئًا". إعدادات شاشة العرض تتيح لك هذه اﻹعدادات ضبط خواص جميع أجهزة العرض المتصلة مع حاسوبك. ضبط الشاشات الاتجاه: لضبط سياق العرض وفقًا للشاشة المستخدمة، فقد يكون العرض طوليًا أو عرضيًا. الميز أو دقة العرض: يختار النظام اﻹعدادت اﻷنسب تلقائيًا، لكن بإمكانك اختيار دقة العرض التي تراها مناسبة (وهي عدد البكسلات العرضية وعدد البكسلات الطولانية ونسبة الطول إلى العرض). ضبط اﻷبعاد الكسري fractional scaling: ويقصد به تكبير أو تصغير تفاصيل سطح المكتب باستخدام معاملات تكبير أو تصغير عشرية بدلًا من الصحيحة. لا تهتم كثيرًا للأمر إن لم تدرك تمامًا ما المقصود بذلك، لكن تفعيل هذا الخيار قد يزيد من دقة التفاصيل وملائمتها لشاشتك ككل، وقد يستهلك ذلك في المقابل قدرًا أكبر من الطاقة، كما قد يُخفض من حدة تمايز اﻷلوان. الشاشات الليلية للوصول إلى نافذة هذا الخيار، انقر على زر "اﻹضاءة الليلية" في شريط مهام النافذة. يغير هذا الخيار من شدة سطوع الشاشة ليلًا، ويجعل اﻷوان أكثر دفئًا (اقترابًا من اللون اﻷحمر)، مما يعطي راحةً أكبر للعين، ويخفف اﻹجهاد. يضبط النظام تلقائيًا الفترة الزمنية التي يحل فيها الظلام في منطقتك وفقًا للإعدادات المحلية له (عندما تحدد البلد وحزمة التوقيت)، كما يمكنك وضع جدول زمني لساعات اﻹضاءة الليلية بما يناسبك. يتيح لك النظام أيضًا خيار ضبط مقدار دفئ اﻷلوان لتحصل على اﻹضاءة التي تريحك. إعدادات أجهزة الدخل وتتيح ضبط بعض الميزات المتعلقة بالفأرة ولوحة المفاتيح والتطبيقات التي ستفتح تلقائيًا محتوى وسائط التخزين الخارجية. الفأرة ولوحة اللمس الزر اﻷساسي: انقر على الخيار "يسار" لتستخدم زر الفأرة الأيسر في تنفيذ اﻷوامر، أو "يمين" لتستخدم الزر الأيمن. وفي كلتا الحالتين يتحول الزر الآخر إلى زر قوائم (إظهار قائمة إن وجدت). سرعة الفأرة: عندما تحرك الزالقة إلى اليسار، فأنت تزيد بذلك من سرعة مؤشر الفأرة أثناء حركته على الشاشة وتبطئ حركة المؤشر إن حركت الزالقة يمينًا. اختصارات لوحة المفاتيح تعرض لك هذه النافذة جميع اختصارات لوحة المفاتيح مرتبة حسب طبيعة عملها، مثل اختصارات التنقل، أو اختصارات فتح وتشغيل تطبيقات وغيرها. تساعدك هذه الاختصارات في تنفيذ ما تريده بسرعة وتبقى مسألة اختيار الاختصارات التي تستخدمها في عملك أمرًا خاصًا بك وحدك. يقدم لك نظام التشغيل أوبونتو إمكانية تغييرالاختصار الافتراضي أو تعطيله أو إضافة اختصارات أخرى. مثال تطبيقي: يُعد تطبيق "المرقاب" شديد اﻷهمية في معرفة كل التطبيقات التي تعمل حاليًا وحجم الذاكرة الذي يستخدمه كلًا منها، باﻹضافة إلى مراقبة موارد النظام ومنظومة الملفات. سنعين اختصارًا لتشغيل هذا التطبيق: افتح التطبيق بالطريقة التقليدية: من قائمة التطبيقات، اختر "أدوات" ثم "مرقاب النظام". عندما يفتح التطبيق، ابحث عن العملية التي لها أيقونة مشابهة لأيقونة التطبيق، وستجد أن اسمها "gnome-system-monitor". احفظ هذا الاسم. أغلق التطبيق، ثم افتح اﻹعدادات، ومنها إلى خيار "اختصارات لوحة المفاتيح". انقر على الزر (+) في آخر القائمة. ليظهر لك مربع حوار يطلب من إدخال بعض المعلومات ضع في حقل "الاسم" أي اسم تريده لاختصارك، ثم ضع في حقل "اﻷمر" الاسم الذي حفظته في الخطوة الأولى كما هو تمامًا دون أية أخطاء. اضغط على زر حدد اختصارًا لتظهر لك النافذة التالية: انقر على الأزرار التي تريدها أن تشكل الاختصار معًا، مثل Ctrl + Alt + M، وسيكون الاختصار جاهزًا للاستخدام دومًا. التشغيل التلقائي لمحتوى الوسائط الخارجية انقر على خيار "الوسائط المنفصلة" وستظهر النافذة التالية: تقدم النافذة مجموعةً من الخيارات التي ينفذها النظام عند اكتشاف وجود وسيط تخزين خارجي يضم محتوى معين: صوتي، أو فيديو، أو صور، أو برمجيات. بالنقر على عنصر القائمة المجاور لكل نوع من المحتوى، ستظهر مجموعة من الخيارات في مقدمتها البرامج التي يزكّيها النظام لتشغيل هذا النوع من الوسائط، كما يعطيك حرية اختيار البرنامج الذي تريده إن كان مثبتًا على جهاز من خلال اختيار الأمر"تطبيق آخر". يمكنك أيضًا تجاهل الموضوع باختيار اﻷمر "لا تفعل شيئًا"، وإن أردت أن تقرر ما الذي ستفعله في الوقت المناسب، فاختراﻷمر "اسأل ما الذي يجب فعله"، أو بإمكانك فتح وسيط التخزين من خلال تطبيق "الملفات" باختيار اﻷمر "افتح المجلّد" بكل بساطة. إن لم يكن وسيط التخزين مدرجًا ضمن الوسائط المعروضة، فانقر على الخيار "وسائط أخرى"، وسيعرض لك مربع الحوار التالي: اختر نوع الوسيط، ثم اختر اﻹجراء المطلوب اتخاذه عند اكتشافه، وسيتذكر النظام ما اخترته. إن لم تشأ أن يسألك النظام عما يجب فعله عند اكتشاف وسيط تخزين جديد وأن لا يشغل تلقائيًا أية برامج، ففعّل الخيار "لا تسأل أو تبدأ البرامج عند إدخال الوسائط". اﻹعدادات اللغوية واﻹقليمية تتيح هذه اﻹعدادات ضبط الكثير من النقاط الهامة التي تتعلق بلغة نظام التشغيل ولغات لوحة المفاتيح والتنسيقات العامة كالتاريخ والوقت والعملة وغيرها. المنطقة واللغة عند النقر على خيار "المنطقة واللغة" في الشريط الجانبي لتطبيق "اﻹعدادات"، ستظهر نافذة تضم الخيارات التالية: اللغة: وتدل على لغة نظام التشغيل ولغة قوائمه ونوافذه. لتغييرها، انقر على هذا الخيار، ثم اختر إحدى اللغات المثبتة بالفعل على جهازك. ستتطلب العملية إعادة تشغيل النظام عند اﻹنتهاء. النسق: وتعرض تنسيق الوقت والتاريخ والعملة وجملة القياسات المستخدمة في البلد المحدد. لتغيير النسق، انقر على هذا الخيار، واختر نسق البلد الذي تنتمي إليه، ثم انقر على الزر "تمّ". تتطلب التغييرات إعادة تشغيل الحاسوب قبل أن تُطبق. مصادر اﻹدخال: وذلك لاختيار لوحات مفاتيح مخصصة لكتابة لغات معينة. لاحظ إمكانية الكتابة باللغة العربية واﻹنكليزية، لأن تخطيطات لوحة المفاتيح لكل منها مهيئة ومفعلة. إن أردت إضافة لغة إدخال جديدة، فانقر على زر (+) أسفل آخر تخطيط، وسيظهر لك مربع حوار لاختيار لغة جديدة. اختر اللغة، ثم انقر الزر "أضف". إدارة اللغات المثبتة manage installed languages: يفتح هذا الخيار تطبيق "دعم اللغات"، ويعرض اللغات المثبتة حسب اﻷفضلية. اللغة اﻷولى في القائمة هي لغة النظام الحالية، وتكوّن مثبتةً مع كامل اعتمادياتها ومكتباتها، لكن إن لم يجد النظام مقابلًا لتسمية أو زر أو خيار بهذه اللغة، فسيستخدم مقابلها من اللغة التالية في القائمة. عند اختيار ترتيب اللغات بالشكل الذي تريد، انقر على زر "طبّق على مستوى النظام"، وسيُنفذ النظام اﻷمر عند تسجيل الخروج، ثم تسجيله مجددًا. إن أردت أن تستخدم لغةً أخرى لتكون لغةً أساسيةً للنظام، أو رأيت أن دعم اللغة العربية غير مكتمل؛ فانقر على زر "ثبّت/احذف اللغات" الذي يتيح لك قائمةً بمجموعة اللغات التي يدعمها النظام كليًا أو جزئيًا. انقر على اللغة التي تريد تثبيتها، ثم انقر على الزر "طبّق". يبحث النظام عن اﻹعتماديات المثبتة فعلًا ويقرر ما إذا كانت هناك أية نواقص كي يستدعي عندها "محدّث البرمجيات" لتزيل وتثبيت ما يلزم تلقائيًا ودون تدخل منك. اﻹتاحة وشمولية الوصول يقصد بمصطلح اﻹتاحة أو الشمولية accessibility، تمكين أي مستخدم من العمل على نظام التشغيل بأفضل تجربة ممكنة إيًا كانت لغته أو إمكاناته الجسدية (سليم، أو لديه مشاكل بصرية، أو لديه مشاكل سمعية… إلخ). يتيح لك نظام أوبونتو بنسخته 20.04 طيفًا واسعًا من الخيارات التي تسهّل على ذوي القدرات الخاصة التعامل مع النظام، وسنلقي نظرةً سريعةً عليها في هذا القسم. انقر على خيار "اﻹتاحة" في الشريط الجانبي لنيافة تطبيق "اﻹعدادات" وستظهر لك الخيارات التالية: التباين العالي: يغيرّر النظام عند تفعيل هذا الخيار سمة سطح المكتب ليعرض اﻷيقونات والقوائم وأشرطة المهام والنوافذ باللونين اﻷسود واﻷبيض بتدرجات لونية ضئيلة لمساعد ضعيفي البصر على التمييز. نص كبير: تظهر جميع الكتابات بحجم كبير لتسهيل القراءة. حجم المؤشر: لاختيار حجم مؤشر الفأرة الظاهر على الشاشة بما يلائم المستخدم. التقريب: بالنقر على هذا الخيار ستظهر لك النافذة التالية: التكبير: ويحدد مقدار تكبير ما يُعرض على الشاشة ابتداءً من 1 وحتى 5 أضعاف. يتبع مؤشر الفأرة: تلحق المكبرة بمؤشر الفأرة لتكبير المنطقة التي يصل إليها. جزء من الشاشة: تبقى المكبرة في النصف العلوي أو السفلي أو اﻷيمن أو اﻷيسر من الشاشة أو تملؤها كلها. في النافذة الفرعية التالية "محاور الهدف"، يمكنك تفعيل ميزة ظهور محورين متقاطعين يتمركزان حول مؤشر الفأرة لتحديد موضعها مع إمكانية ضبط طول هذين المحورين وسماكتهما ولونهما. قارئ الشاشة: ينطق قارئ الشاشة المدمج مع النظام أي محتوى نصي يقع عليه مؤشر الفأرة، كما ينطق نوع العنصر الذي يحوي هذا المحتوى (زر، أو نافذة، أو رسالة) وموقعه. لا يُنصح به في النسخة العربية لأنه سيشوش المكفوف أكثر مما يساعده. هنالك خيارات أخرى متعددة يتيحها النظام للمستخدمين ذوي القدرات الخاصة وحتى العاديين أحيانًا، نترك للقارئ مهمة استكشافها والتعرف عليها كونها تشرح نفسها بنفسها غالبًا. خلاصة تعرفنا في المقالات الثلاثة التي تحمل عنوان "إعدادات أوبونتو" على طريقة ضبط الإعدادات العامة لنظام تشغيل أوبونتو من وجهة نظر المستخدم أو المستثمر العادي للنظام بما يحقق له الراحة و الفعالية في العمل وعرّجنا على مواضيع هامة تتعلق بإنشاء وضبط شبكات الاتصال بأنواعها وضبط إعدادات التطبيقات، من ثم إدارة التجهيزات. وهكذا سنكون قد خطونا خطوة بسيطة أخرى في عالم نظام التشغيل لينكس وتوزيعاته وأصبحنا جاهزين للانتقال إلى موضوع مهم جديد وهو تثبيت التطبيقات التي يحتاجها المستخدم عبر واجهات رسومية واضحة وسهلة الاستخدام. اقرأ أيضًا المقال السابق: إعدادات أوبونتو 20.04: التطبيقات وإدارة المستخدمين تعرّف على سطح مكتب أوبونتو 20.04 التعامل مع المجلدات والملفات في أوبونتو 20.04 تغيير اللغة في نظام لينكس أوبنتو إلى العربية
  11. استخدمنا في جزئية سابقة من هذه السلسلة صورتين مختلفتين هما "ubuntu" و "node" ونفّذنا بعض الأعمال يدويًا لتشغيل تطبيق "Hello, World". ستساعدنا الأدوات والأوامر التي تعلمناها سابقًا في هذه الجزئية من السلسلة، إذ نتعلم فيه بناء الصور وتهيئة بيئة عمل التطبيق. سنبدأ ببناء واجهة خلفية نمطية باستخدام Express/Node.js ثم نبني عليها مستخدمين خدمات أخرى مثل قواعد بيانات MongoDB. ملفات Dockerfile بإمكاننا إنشاء صورة جديدة تتضمن التطبيق "!Hello, World" بدلًا من تعديل الحاوية بنسخ ملفات جديدة إليها، وتساعدنا في ذلك أداة تُدعى Dockerfile، وهي ملفٌ نصي بسيط يحتوي كل التعليمات الخاصة بإنشاء صورة. سنبدأ إذًا بإنشاء مثال عن Dockerfile من تطبيق "!Hello, World". أنشئ مجلدًا جديدًا على جهازك ثم أنشئ ضمنه الملف "Dockerfile " إن لم تكن قد فعلت ذلك مسبقًا. ولنضع كذلك الملف "index.js" الذي يضم الشيفرة ('!console.log('Hello, World إلى جواره. ستبدو هيكلية المجلد على النحو التالي: ├── index.js └── Dockerfile سنخبر الصورة من خلال "Dockerfile" بثلاثة أمور: استخدم node:16 أساسًا للصورة. ضع الملف "index.js" ضمن الصورة، كي لا نُضطر إلى نسخه يديويًا إلى الحاوية. استخدم node لتنفيذ شيفرة الملف "index.js" عندما نشغّل الحاوية من الصورة. توضع هذه النقاط الثلاث ضمن ملف "Dockerfile" وأفضل مكان لإنشاء هذا الملف هو جذر المشروع، وسيبدو هذا الملف على النحو التالي: FROM node:16 WORKDIR /usr/src/app COPY ./index.js ./index.js CMD node index.js FROM: تخبر هذه التعليمة برنامج دوكر Docker أنّ أساس الصورة هو node:16. COPY: تنسخ هذه التعليمة الملف index.js من الجهاز المضيف إلى ملف بنفس الاسم ضمن الصورة. CMD: تخبر هذه التعليمة البرنامج ما يجب أن يحدث عند تنفيذ الأمر docker run. وهذه التعليمة هي تعليمة تنفيذ افتراضية يمكن استبدالها بالمعامل الذي يُعطي بعد اسم الصورة. اكتب الأمر docker run --help إن نسيت. WORKDIR: وضعت هذه التعليمة للتأكد من أننا لن نفعل شيئًا يتداخل مع محتوى الحاوية. إذ سيضمن أنّ كل التعليمات التي ستليه ستكون ضمن المجلد "usr/src/app/" الذي يُعد مجلد العمل في هذه الحالة. فإن لم يكن هذا المجلد موجودًا في الصورة، سيُنشأ تدريجيًا. لم نحدد مجلد عمل WORKDIR، فقد نجازف بتغيير ملفات هامة بطريق الخطأ. لو تحققت من الجذر / للصورة بتنفيذ الأمر docker run node:16 ls ستجد العديد من المجلدات والملفات في هذه الصورة. نستطيع الآن استخدام الأمر docker build لبناء الصورة بناءً على ملف Dockerfile، لكننا سنضيف الراية t- إلى هذا الأمر كي تساعدنا في إعادة تسمية الصورة: $ docker build -t fs-hello-world . [+] Building 3.9s (8/8) FINISHED ... ستكون نتيجة تنفيذ الأمر هي: "docker please build with tag fs-hello-world the Dockerfile in this directory" والتي تشير إلى بناء صورة بالاسم "fs-hello-world" بالاعتماد على ملف Dockerfile الموجود في المجلد. يمكنك الإشارة إلى أي ملف Dockerfile لكن في حالتنا البسيطة يكفي وضع . للإشارة إلى هذا الملف ضمن المجلد لهذا انتهى الأمر بالنقطة. يمكنك تشغيل الحاوية الآن باستخدام الأمر docker run fs-hello-world ويمكن نقل الصورة أو تنزيلها أو حذفها فهي في طبيعتها ملفات. إذ يمكنك تشكيل قائمة بالصور التي يضمها حاسوبك باستخدام الأمر docker image ls أو حذف الصورة ‍‍docker image rm. اطلع على بقية الأوامر المتاحة بتنفيذ أمر المساعدة docker image --help. صور أكثر فائدة ينبغي أن يكون نقل خادم Express إلى حاوية ببساطة نقل تطبيق "!Hello, World"، إذ يكمن الاختلاف الوحيد بين الحالتين في وجود ملفات أكثر في حالة الخادم، لكن ستغدو الأمور أبسط بوجود التعليمة COPY. لنحذف الآن الملف "index.js" وننشئ خادم Express باستخدام express-generator الذي يساعدنا على بناء هيكلية بسيطة للتطبيق. $ npx express-generator ... install dependencies: $ npm install run the app: $ DEBUG=playground:* npm start لنشغل التطبيق الآن كي نرى ما فعلنا، وانتبه أن أمر التشغيل قد يختلف في جهازك، فالمجلد في المثال السابق يُدعى "playground". $ npm install $ DEBUG=playground:* npm start playground:server Listening on port 3000 +0ms يمكنك الانتقال الآن إلى العنوان "http://localhost:3000" حيث يعمل التطبيق. إن ضم الملفات في حاويات هي عملية سهلة نوعًا ما بناءً على ما واجهناه حتى الآن: استخدم الأساس node. اضبط مجلد العمل كي لا يتداخل عملك مع بقية محتويات الصورة. انسخ كل ملفات المجلد إلى الصورة. ابدأ بتنفيذ الأمر DEBUG=playground:* npm start بعد الأمر CMD. لنضع ملف Dockerfile التالي في جذر المشروع: FROM node:16 WORKDIR /usr/src/app COPY . . CMD DEBUG=playground:* npm start سنبني الآن الصورة انطلاقًا من ملف باستخدام الأمر: docker build -t express-server . وسنشغلها باستخدام الأمر: docker run -p 3123:3000 express-server تبلّغ الراية p- دوكر بضرورة فتح منفذ الجهاز المضيف وتوجيهه إلى منفذ للحاوية ولهذا الأمر الصيغة التالية: p host-port:application-port- $ docker run -p 3123:3000 express-server > playground@0.0.0 start > node ./bin/www Tue, 29 Jun 2021 10:55:10 GMT playground:server Listening on port 3000 إن لم يفلح الأمر، تجاوز الفقرة التالية، فهناك تفسير لعدم نجاح الأمر حتى لو اتبعت الخطوات السابقة تمامًا. سيبدأ التطبيق عمله الآن، لهذا سنختبره بإرسال الطلب GET إلى العنوان "/http://localhost:3123". بالنسبة لإيقاف الخادم فهو أمر عصيبٌ حاليًا، لهذا افتح نافذة أخرى للطرفية ونفذ الأمر docker kill لإيقاف التطبيق؛ إذ يرسل هذا الأمر الإشارة SIGKILL إلى التطبيق ويجبره على الإنهاء. ستحتاج إلى اسم أو معرّف الحاوية ID مثل وسيط لتنفيذ الأمر السابق. وتجدر الإشارة أنه يكفي استخدام بداية المعرّف id عند تمريره وسيطًا، إذ سيعرف دوكر مباشرةً الحاوية المقصودة. $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 48096ca3ffec express-server "docker-entrypoint.s…" 9 seconds ago Up 6 seconds 0.0.0.0:3123->3000/tcp, :::3123->3000/tcp infallible_booth $ docker kill 48 48 لنعمل من الآن وصاعدًا على نفس المنفذ في كلا الجانبين p-. وهكذا لن تضطر إلى تذكُّر ما المنفذ الذي عليك اختياره. إصلاح المشاكل المحتملة الناتجة عن عملية النسخ واللصق لا بُد من تغيير بعض الخطوات لإنشاء ملف Dockerfile متقدم، وقد لا يعمل المثال الذي أوردناه سابقًا على الإطلاق، لأننا أهملنا خطوة هامة. عندما تنفِّذ الأمر npm install على حاسوبك، فقد يُثبّت مدير حزم Node بعض الاعتمادات التي تتعلق بنظام التشغيل أثناء تقدم التثبيت. وقد ننقل صدفةً أجزاءً غير وظيفية إلى الصورة عند استخدام التعليمة COPY، ويحدث ذلك بسهولة إن نسخنا المجلد "node_modules" إلى الصورة. من المهم جدًا إبقاء تلك النقاط في ذاكرتنا عند بناء الصورة، فمن الأفضل أن ننفذ معظم الأعمال مثل npm install أثناء عملية البناء ضمن الحاوية بدلًا من تنفيذها قبل البناء؛ إذ أن القاعدة الجوهرية هنا هي نسخ الملفات التي ستدفعها إلى غيت هب GitHub فقط، ولا ينبغي نسخ الاعتماديات ومتطلبات البناء كونها أشياء يمكن تثبيتها أثناء بناء الصورة. يمكنك استخدام الملف "dockerignore." لحل المشكلة، وهو ملفٌ شبيه بملف التجاهل "gitignore." لمنع نسخ الملفات غير المطلوبة إلى الصورة. يُوضع هذا الملف إلى جوار الملف Dockerfile، وإليك مثالًا عن محتوياته: .dockerignore .gitignore node_modules Dockerfile لكننا سنحتاج إضافةً إلى ملف "dockerignore." في حالتنا إلى تثبيت الاعتماديات خلال خطوة البناء، لهذا سيتغير ملف Dockerfile إلى الشكل: FROM node:16 WORKDIR /usr/src/app COPY . . RUN npm install CMD DEBUG=playground:* npm start قد يكون تنفيذ الأمر npm install خطرًا، لهذا يزوّدنا npm بأداة أفضل لتثبيت الاعتماديات وهو الأمر ci. تُلخّص الاختلافات بين ci و install على النحو التالي : قد يُحدّث install الملف "package-lock.json". قد يُثبِّت install نسخةً مختلفةً من الاعتمادية إن ظهرت المحارف "^" أو "~" في نسخة الاعتمادية. سيحذف ci المجلد "node_modules" قبل تثبيت أي شيء. سيتبع ci الملف "package-lock.json" ولا يبدّل أي ملف. باختصار: يقدم ci نسخًا يمكن الاعتماد عليها، بينما يُستخدم install عند تثبيت اعتماديات جديدة. طالما أننا لن نثبِّت أي شيء جديد في خطوة البناء، ولا نريد تغييرات فجائية في النسخ، سنستخدم الأمر ci: FROM node:16 WORKDIR /usr/src/app COPY . . RUN npm ci CMD DEBUG=playground:* npm start كما يمكننا تحسين الحالة أكثر باستخدام الأمر npm ci --only=production كي لا نهدر الوقت في تثبيت الاعتماديات. سيحذف ci المجلد "node_modules" كما أشرنا قبل قليل، وبالتالي لن نضطر إلى إنشاء الملف "dockerignore.". مع ذلك، يُعد هذا الملف أداةً رائعةً عندما تريد تحسين عملية البناء، وسنتحدث باختصار عن هذا الموضوع لاحقًا. ينبغي أن يعمل الملف من جديد، لهذا حاول تنفيذ الأمر التالي: docker build -t express-server . && docker run -p 3000:3000 express-server لاحظ كيف وصلنا هنا أمري باش bash باستخدام &&، وسنحصل تقريبًا على نفس النتيجة إذا نفذنا كلا الأمرين كلًّا على حدة؛ لكن عندما تربط أمرين باستخدام &&، فلن يُنفَّذ الأمر الآخر إذا فشل تنفيذ أحدهما. ضبطنا سابقًا متغير البيئة :DEBUG=playground من خلال الأمر CMD لتشغيل npm، كما يمكننا أيضًا استخدام التعليمة ENV في ملف Dockerfiles لضبط متغيرات البيئة، فلنفعل ذلك: FROM node:16 WORKDIR /usr/src/app COPY . . RUN npm ci ENV DEBUG=playground:* CMD npm start أفضل الممارسات المتعلقة باستخدام Dockerfiles عليك اتباع القاعدتين الجوهريّتين التاليتين عند إنشاء الصور: حاول أن تبني صورةً آمنة قدر المستطاع. حاول أن تُنشئ صورةً صغيرة قدر الإمكان. تُعد الصور الأصغر أكثر أمانًا لأن مجال الهجوم عليها محدود، كما يمكن نقلها بسرعة أكبر ضمن أنابيب النشر. وأخيرًا لا بُد من إصلاح آخر نقطة أهملناها وهي تشغيل التطبيق مثل جذر بدلًا من مستخدم بصلاحيات منخفضة: FROM node:16 WORKDIR /usr/src/app COPY --chown=node:node . . RUN npm ci ENV DEBUG=playground:* USER node CMD npm start التمرين 12.5: إنشاء حاوية لتطبيق Node يضم المستودع الذي نسخته في التمرين الأول تطبيق لائحة مهام todo-app. اطلع على الواجهة الخلفية "todo-app/todo-backend" للتطبيق واقرأ الملف "اقرأني README". لن نقترب حاليًا من الواجهة الأمامية "todo-frontend" الخطوة الأولى: وضع الواجهة الخلفية "todo-backend" ضمن حاوية بإنشاء الملف "todo-app/todo-backend/Dockerfile" ثم بناء الصورة. الخطوة الثانية: تشغيل الصورة على المنفذ الصحيح، والتأكد أن عدّاد الزيارات سيزداد عند استخدامه عبر المتصفح على العنوان "/http://localhost:3000" (أو على أي منفذ آخر قد تهيّئه). تلميح: شغّل التطبيق خارج الحاوية أولًا للتحقق منه قبل وضعه في الحاوية. استخدام الأداة docker-compose أنشأنا في جزئية سابقة من هذه السلسلة خادمًا وعلمنا أنه يعمل على المنفذ 3000 وشغّلناه باستخدام الأمر: docker build -t express-server . && docker run -p 3000:3000 express-server ويبدو أننا سنحتاج إلى سكربت لتذكر هذه التعليمات، لكن لحسن الحظ يقدّم دوكر لنا حلًا أفضل. تُعد الأداة Docker-compose من الأدوات الرائعة الأخرى التي تساعدك على إدارة الحاوية، لهذا سنبدأ استخدام هذه الأداة خلال رحلتنا في دراسة الحاويات، إذ ستساعد على توفير بعض الوقت عند تهيئة الحاوية. ثبّت الأداة Docker-compose ثم تأكد من عملها على النحو التالي: $ docker-compose -v docker-compose version 1.29.2, build 5becea4c سنحوّل الآن الأوامر السابقة إلى ملف yaml يمكن تخزينه في مستودع غيت Git. أنشئ الملف "docker-compose.yml" وضعه في جذر المشروع إلى جوار ملف Dockerfile، ثم ضع المحتوى التالي ضمنه: version: '3.8' # نسخة جديدة لا بد أن تعمل services: app: # اسم الخدمة وقد يكون أي شيء image: express-server # صرّح عن الصورة التي تريد استخدامها build: . # حدد مكان بناء الصورة إن لم تكن موجودة ports: # حدد المنفذ الذي يرتبط به التطبيق - 3000:3000 وضعنا شرحًا لكل سطر إلى جواره، لكن إذا أردت معرفة المواصفات الكاملة فعُد إلى التوثيق. سنتمكن الآن من استخدام الأمر docker-compose up في بناء وتشغيل التطبيق، وإن أردت إعادة بناء الصور، استخدم الأمر docker-compose up --build. بإمكانك أيضًا تشغيل التطبيق في الخلفية باستخدام الأمر docker-compose up -d (الراية d- لفصل التطبيق) وإيقافه بتنفيذ الأمر docker-compose down. يُصرِّح إنشاء الملفات بهذه الطريقة عمّا تريده بدلًا من ملفات السكربت التي عليك تنفيذها وفق ترتيبٍ محدد أو عددٍ محددٍ من المرات، وهذه ممارسةٌ جيدةٌ جدًا. التمرين 12.6: الأداة docker-compose أنشئ الملف "todo-app/todo-backend/docker-compose.yml" الذي يعمل مع تطبيق node من التمرين السابق. وعليك الانتباه إلى عدّاد الزيارات فهو الميزة الوحيدة التي ينبغي أن تعمل. استخدام الحاويات في مرحلة التطوير يمكن استخدام الحاويات أثناء تطوير التطبيقات بطرق متعددة لتسهيل عملك، ومن إحدى فوائدها تجاوز تثبيت وتهيئة الأدوات مرتين. قد لا يكون خيارك الأفضل أن تنقل كامل بيئة التطوير إلى الحاوية، لكن إذا أردت ذلك فهذا ممكن. سنعود إلى هذه الفكرة في آخر القسم، لكن حتى ذلك الوقت عليك تشغيل تطبيق node بنفسه خارج الحاويات. يستخدم التطبيق الذي تعرفنا عليه في التمارين السابقة MongoDB، لهذا دعونا نستخدم Docker Hub لإيجاد صورة MongoDB، إذ أنه المكان الافتراضي الذي نسحب الصور منه، كما يمكنك استخدام مسجلات أخرى أيضًا، لكن طالما أننا نتعمق في دوكر فهو خيارٌ جيد. يمكنك أن تجد من خلال بحث سريع الصورة المطلوبة على العنوان https://hub.docker.com/_/mongo. أنشئ ملف yaml يُدعى "todo-app/todo-backend/docker-compose.dev.yml" يحتوي ما يلي: version: '3.8' services: mongo: image: mongo ports: - 3456:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example MONGO_INITDB_DATABASE: the_database يُوضَّح معنى أول متغيري بيئة معرفين في الشيفرة السابقة في صفحة Docker Hub: "تُنشِئ المتغيرات المُستخدمة على التوازي مستخدمًا جديدًا وتضبط كلمة المرور له. يُنشأ هذا المستخدم في قاعدة بيانات إدارة الاستيثاق ويعطى دور الجذر root، وهو دور المستخدم الأعلى superuser." يخبر متغير البيئة الأخير MONGO_INITDB_DATABASE قاعدة البيانات MongoDB أن تنشئ قاعدة بيانات بهذا الاسم. بإمكانك استخدام الراية f- لتخصيص ملف لتشغيل أمر Docker Compose مثل: docker-compose -f docker-compose.dev.yml up شغًل الآن MongoDB من خلال الأمر: docker-compose -f docker-compose.dev.yml up -d أما الراية d- فلتشغيل العملية في الخلفية. يمكنك متابعة سجلات الخرج بتنفيذ الأمر: docker-compose -f docker-compose.dev.yml logs -f وتُستخدم الراية f- للتأكد من متابعة السجلات. لا نحتاج حاليًا لتشغيل تطبيق Node ضمن الحاوية، فهذا أمرٌ ينطوي على قدر من التحدي، لكننا سنكتشف هذا الخيار في آخر قسم. نفِّذ الأمر القديم npm install على جهازك لإعداد تطبيق Node، ثم شغل التطبيق باستخدام متغيرات البيئة اللازمة. يمكنك تعديل الشيفرة لجعل متغيرات البيئة متغيرات افتراضية أو استخدم الملف env.. لا ضرر من وضع هذه المفاتيح على غيت هب لأنها تُستخدم ضمن بيئة التطوير المحلية، لذلك سأضعها هناك عن طريق الأمر npm run dev لمساعدتك في النسخ واللصق. $ MONGO_URL=mongodb://localhost:3456/the_database npm run dev لن يكون ذلك كافيًا، بل نحتاج إلى إنشاء مستخدم نستوثِق منه ضمن الحاوية، إذ سيقود الولوج إلى العنوان " http://localhost:3000/todos" إلى خطأ في الاستيثاق. [nodemon] 2.0.12 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node ./bin/www` (node:37616) UnhandledPromiseRejectionWarning: MongoError: command find requires authentication at MessageStream.messageHandler (/Users/mluukkai/opetus/docker-fs/container-app/express-app/node_modules/mongodb/lib/cmap/connection.js:272:20) at MessageStream.emit (events.js:314:20) ربط وتركيب وتهيئة قاعدة البيانات ستجد في صفحة MongoDB Docker Hub تحت عنوان " تهيئة نسخة جديدة Initializing a fresh instance" معلومات عن تنفيذ شيفرة لتهيئة قاعدة البيانات ومستخدم لها. في المشروع التجريبي ملف "todo-app/todo-backend/mongo/mongo-init.js" يضم المحتوى التالي: db.createUser({ user: 'the_username', pwd: 'the_password', roles: [ { role: 'dbOwner', db: 'the_database', }, ], }); db.createCollection('todos'); db.todos.insert({ text: 'Write code', done: true }); db.todos.insert({ text: 'Learn about containers', done: false }); يهيئ الملف قاعدة البيانات مع مستخدم وبعض المهام المخزّنة في القاعدة، وعلينا في الخطوة التالية نقلها إلى الحاوية وتشغيلها. من الممكن إنشاء صورة جديدة من mongo ثم نسخ الملف إلى الداخل، أو استخدام الأمر لتركيب الملف ضمن الحاوية، لكننا سنختار الطريقة الأخيرة. إن التركيب بالربط Bind mount هي عملية ربط ملف على حاسوبك المحلي بملف في الحاوية، ويمكننا تنفيذ ذلك باستخدام الراية v- مع الأمر بالصيغة التالية: v FILE-IN-HOST:FILE-IN-CONTAINER-. يُعرّف التركيب بالربط تحت المفتاح volumes في الملف "docker-compose"، أما صياغة التصريح فتكون على النحو التالي "مضيف ثم حاوية": mongo: image: mongo ports: - 3456:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example MONGO_INITDB_DATABASE: the_database volumes: - ./mongo/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js إن النتيجة هي أنّ الملف "mongo-init.js" في مجلد mongo على حاسوبك سيكون نفسه الملف "mongo-init.js" في المجلد "docker-entrypoint-initdb.d/" من الحاوية، وسيؤدي تعديل أحدهما إلى تعديل الآخر. لا حاجة لتغيير أي شيء أثناء التشغيل، وهذا هو مفتاح تطوير البرمجيات ضمن الحاويات. نفّذ الأمر التالي للتأكد من وجود كل شيء في مكانه: docker-compose -f docker-compose.dev.yml down --volumes ثم ابدأ لائحة جديدة بتنفيذ الأمر التالي لتهيئة قاعدة البيانات: docker-compose -f docker-compose.dev.yml up إذا واجهك خطأ على النحو التالي: mongo_database | failed to load: /docker-entrypoint-initdb.d/mongo-init.js mongo_database | exiting with code -3 فقد يكون لديك مشكلةً في إذن القراءة، وهذا أمرٌ قد يحدث عند التعامل مع الأقراص volumes. بإمكانك في حالتنا استخدام الأمر chmod a+r mongo-init.js الذي يمنح أيًا كان إمكانية قراءة الملف، لكن كن حذرًا عند استخدام التعليمة chmod لأن السماح بإذونات أكبر قد يقود إلى مشاكل أمنية، لهذا استخدم تلك التعليمة على الملف "mongo-init.js" الموجود على جهازك فقط. سيعمل تطبيق express الآن عبر متغيرات البيئة الصحيحة: $ MONGO_URL=mongodb://the_username:the_password@localhost:3456/the_database npm run dev لنتأكد أن الطلب إلى العنوان http://localhost:3000/todos سيعيد كل المهام الموجودة في قاعدة البيانات، فمن المفترض أن يعيد المهمتان اللتان هيأناهما، ولا بد من استخدام Postman لاختبار وظائف التطبيق الأساسية مثل إضافة وحذف مهام من قاعدة البيانات. البيانات المقيمة في الأقراص لا تُخزّن الحاويات بياناتنا افتراضيًا، فعندما تغلق حاوية قد تستطيع أو لا تستطيع استعادة البيانات. وبصورةٍ عامة هناك طريقتان مختلفتان لتخزين البيانات: التصريح عن مكان ضمن منظومة الملفات (عملية الربط بالتركيب bind mount). ترك الأمر لبرنامج دوكر كي يقرر تخزين البيانات (استخدام الأقراص volume) يُفضّل الخيار الأول عادةً في معظم الحالات التي تحتاج فيها حقًا إلى تفادي حذف البيانات، وسنرى الأسلوبين بالتطبيق العملي: services: mongo: image: mongo ports: - 3456:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example MONGO_INITDB_DATABASE: the_database volumes: - ./mongo/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js - ./mongo_data:/data/db ستُنشئ الإعدادات السابقة مجلدًا يُدعى mongo_data ضمن منظومة الملفات في حاسوبك ثم تربطه بالحاوية بالاسم data/db/. أي أنّ البيانات الموجودة في المجلد data/db/ ستُخزّن خارج الحاوية لكن بإمكانها الوصول إليها. وتذكر إضافة المجلد إلى ملف التجاهل "gitignore.". يمكن تحقيق الأمر ذاته باستخدام أقراص التخزين المسماة: services: mongo: image: mongo ports: - 3456:27017 environment: MONGO_INITDB_ROOT_USERNAME: root MONGO_INITDB_ROOT_PASSWORD: example MONGO_INITDB_DATABASE: the_database volumes: - ./mongo/mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js - mongo_data:/data/db volumes: mongo_data: يُنشأ القرص الآن ويدار من قبل دوكر، وبإمكانك قبل تشغيل التطبيق استعراض الأقراص الموجودة بتنفيذ الأمر docker volume ls، أو فحصّها docker volume inspect، أو حذفها docker volume rm. ليس اختيار مكان تخزين البيانات محليًا في هذا الخيار أمرًا قليل الأهمية موازنةً بالخيار السابق. التمرين 12.7: كتابة القليل من الشيفرة للتعامل مع MongoDB نفترض في هذا التمرين أنك أنجزت جميع الإعدادات التي تحدثنا عنها سابقًا بعد التمرين 12.5. وسيبقى تشغيل التطبيق خارج الحاوية وستوضع قاعدة البيانات MongoDB فقط ضمن الحاوية. لا توجد طريق ملائمة حتى الآن للحصول على مهمة واحدة (GET* /todos/:id*) وتحديث مهمة واحدة (PUT* /todos/:id*). جد حلًا لهاتين المشكلتين. تنقيح المشاكل في الحاويات لا بُد من تعلم استعمال بعض الأدوات لتنقيح التطبيقات ضمن الحاويات، لأننا لا نستطيع استخدام الأمر console.log دائمًا. عندما تظهر الثغرات في شيفرتك، فلا بد وأن يعمل شيء ما في شيفرتك لتنطلق منه. وعمومًا هناك وضعان لتبدأ منهما: الأول هو تطبيق يعمل والثاني هو تطبيق لا يعمل، لهذا سنطلع على بعض الأدوات التي تساعد في تنقيح التطبيق في الحالة الثانية. يمكن أن تنتقل أثناء تطوير البرنامج في عملك خطوة خطوة لتتاكد طوال الوقت أن كل شيء يعمل كما تتوقع. لكن لا ينطبق هذا الأمر عند ضبط الإعدادات، فقد تخفق الإعدادات التي تكتبها حتى لحظة الإنتهاء منها؛ لهذا إذا كتبت ملف "docker-compose.yml" طويل أو ملف Dockerfile ولم يعمل، عليك التروي برهة والتفكير بالطرق المختلفة التي تتأكد منها أن شيء ما يعمل بالشكل المطلوب. لا تزال طريقة التشكيك بكل شيء واردة هنا، وكما قلنا في القسم الثالث: عليك أن تكون منظّما، وطالما أنّ المشكلة قد تكون في أي مكان، عليك التشكيك بكل شيء، وإزالة كل مصادر الخطأ واحدًا تلو الآخر. فالتوقف والتفكير بالمشكلة بدلًا من كتابة مزيدٍ من الإعدادات هي الطريقة الأنسب في الحصول على حل بسيط، كما أن البحث السريع باستخدام محركات البحث قد يساعدك في التقدم. الأمر exec يمكن استخدام الأمر exec للقفز مباشرةً إلى الحاوية وهي تعمل. لهذا سنهيّئ خادم ويب في الخلفية ونجري بعض الإعدادات كي نشغّله ليعرض الرسالة "!Hello, exec" ضمن المتصفح. سنستخدم الخادم Nginx القادر على تخديم الملفات الساكنة، ويدعم الملف "index.html" الذي يمثل الصفحة الافتراضية ويسمح لنا بتعديلها أو استبدالها: $ docker container run -d nginx الأسئلة المطروحة الآن هي: أين سنتوجه عبر المتصفح؟ هل يعمل الخادم بالفعل؟ لكن نعرف كيف نجيب على هذه الأسئلة وذلك باستعراض الحاويات التي تعمل: $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3f831a57b7cc nginx "/docker-entrypoint.…" About a minute ago Up About a minute 80/tcp keen_darwin لقد حصلنا على جواب السؤال الأول فعلًا، فيبدو أن الخادم ينصت إلى المنفذ 80. لهذا سنطفئ الخادم ثم نعيد تشغيله مستخدمين الراية p- للسماح للمتصفح بالوصول إليه: $ docker container stop keen_darwin $ docker container rm keen_darwin $ docker container run -d -p 8080:80 nginx لنلقي نظرةً على التطبيق بالانتقال إلى العنوان http://localhost:8080، وستجد أنّ التطبيق يعرض رسالة خاطئة، لهذا سنقفز إلى الحاوية مباشرةً لإصلاحها. أبقِ متصفحك مفتوحًا، فلا نريد إغلاق الحاوية عند إصلاحها، بل سنستخدم أوامر الطرفية ضمن الحاوية، ولا تنسى استخدام الراية it- لضمان قدرتك على التفاعل مع الحاوية: $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 7edcb36aff08 nginx "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:8080->80/tcp, :::8080->80/tcp wonderful_ramanujan $ docker exec -it wonderful_ramanujan bash root@7edcb36aff08:/# بعد أن قفزنا داخل الحاوية، لا بد من إيجاد الملف الخاطئ واستبداله، إذ يتضح لنا من خلال بحث سريع باستخدام محرك بحث أن الملف هو "usr/share/nginx/html/index.html/". لننتقل إلى المجلد ونحذف الملف: root@7edcb36aff08:/# cd /usr/share/nginx/html/ root@7edcb36aff08:/# rm index.html لو انتقلنا الآن إلى العنوان http://localhost:8080 فسنحصل على الصفحة 404 لأننا حذفنا الملف الصحيح، لهذا سنستبدله بملف آخر يضم المحتوى الصحيح: root@7edcb36aff08:/# echo "Hello, exec!" > index.html أعد تحميل الصفحة وسترى كيف يعرض المتصفح الرسالة الصحيحة. وهكذا نرى كيف نستفيد من الأمر exec في التفاعل مع الحاوية. ستُفقد كل التغييرات التي أجريتها عند حذف الحاوية، ولتحفظ هذه التغييرات لا بد من دفعها باستخدام الأمر commit. التمرين 12.8: واجهة سطر أوامر Mongo استخدم script لتسجيل ما تفعله، ثم احفظ الملف بالاسم "script-answers/exercise12_8.txt". حاول الولوج إلى قاعدة البيانات في التمرين السابق أثناء تشغيل MongoDB باستخدام سطر أوامر CLI. يمكنك تنفيذ الأمر باستخدام exec، ثم أضف بعد ذلك مهمة جديدة عبر سطر الأوامر CLI. شغّل سطر الأوامر وأنت ضمن الحاوية باستخدام الأمر mongo. يتطلب سطر أوامر mongo رايتي اسم مستخدم وكلمة مرور للاستيثاق على نحوٍ صحيح،. وستعمل الرايات u root -p example- جيدًا، وتكون القيم مأخوذةً من الملف docker-compose.dev.yml. الخطوة الأولى: شغّل MongoDB. الخطوة الثانية: استخدم الأمر exec للدخول إلى الحاوية. الخطوة الثالثة: افتح واجهة سطر أوامر mongo. عندما تصل إلى واجهة سطر أوامر mongo يمكنك أن تطلب منها عرض قواعد البيانات: > show dbs admin 0.000GB config 0.000GB local 0.000GB the_database 0.000GB للولوج إلى قاعدة البيانات الصحيحة: > use the_database ولإيجاد المجموعات: > show collections todos يمكنك الآن الوصول إلى البيانات الموجودة ضمن تلك المجموعات: > db.todos.find({}) { "_id" : ObjectId("611e54b688ddbb7e84d3c46b"), "text" : "Write code", "done" : true } { "_id" : ObjectId("611e54b688ddbb7e84d3c46c"), "text" : "Learn about containers", "done" : false } أضف مهمةً جديدة نصها: "Increase the number of tools in my toolbelt" مع ضبط حالتها على false. راجع التوثيق لتتعلم إضافة المدخلات إلى قاعدة البيانات، وتأكد من رؤية المهمة الجديدة في تطبيق Express وعند الاستعلام عنه عبر سطر أوامر Mongo CLI. قاعدة البيانات Redis Redis هي قاعدة بيانات من الشكل (مفتاح-قيمة) مما يجعلها قاعدة لحفظ البيانات بهيكلية أدنى من MongoDB مثلًا، فلن تجد فيها مجموعات أو جداول بل كميات من البيانات يمكن الحصول عليها وفقًا لقيم المفاتيح المرتبط بالبيانات (القيم). تعمل قاعدة البيانات Redis ضمن الذاكرة المؤقتة أي أنها لا تحتفظ بالبيانات دائمًا، ومن أفضل طرق الاستفادة منها هي استخدامها مثل مخزن مؤقت لتطبيق cache، إذ تُستخدم المخازن المؤقتة غالبًا لتخزين البيانات التي قد تكون بطيئة عند إحضارها وحفظها حتى تفقد صلاحيتها فيتوجب عليك إحضارها مجددًا بعد ذلك وتخزينها وهكذا. لا علاقة لقاعدة البيانات Redis بالحاويات لكن وطالما أننا قادرين على إضافة أي خدمة مصدرها طرف آخر إلى التطبيق، فلماذا لا نتعلم شيئًا جديدًا؟ التمارين 12.9 - 12.11 التمرين 12.9: إعداد Redis للمشروع هُيّئ خادم Express مسبقًا للتعامل مع Redis ويفتقد فقط إلى متغير البيئة REDIS_URL، إذ يستخدم التطبيق متغير البيئة للاتصال بقاعدة البيانات. اطلع على عمل Redis مع Docker Hub ثم أضفها إلى الملف "todo-app/todo-backend/docker-compose.dev.yml" من خلال تعريف خدمة جديدة بعد mogo. services: mongo: ... redis: ??? بما أن صفحة Docker Hub لا تضم جميع المعلومات الكافية، استخدم غوغل مثلًا في البحث. ستجد المنفذ الافتراضي للقاعدة بإجراء البحث الموضح في الصورة التالية: لا نعلم بعد إن كان الإعداد سيعمل ما لم نجرّب. لن يستخدم التطبيق Redis من تلقاء نفسه طبعًا، وهذا ما سنراه في التمرين التالي. حالما تهيئ Redis وتشغله، أعد تشغيل الواجهة الخلفية ومرر لها متغير البيئة ‍‍REDIS_URL بالصيغة redis://host:port. $ REDIS_URL=insert-redis-url-here MONGO_URL=mongodb://localhost:3456/the_database npm run dev يمكنك الآن اختبار الإعداد بإضافة السطر التالي: const redis = require('../redis') إلى مثال خادم Express في الملف "routes/index.js". إن لم يحدث شيء فقد طُبِّق الإعداد بصورةٍ صحيحة وإلا سينهار الخادم: events.js:291 throw er; // Unhandled 'error' event ^ Error: Redis connection to localhost:637 failed - connect ECONNREFUSED 127.0.0.1:6379 at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1144:16) Emitted 'error' event on RedisClient instance at: at RedisClient.on_error (/Users/mluukkai/opetus/docker-fs/container-app/express-app/node_modules/redis/index.js:342:14) at Socket.<anonymous> (/Users/mluukkai/opetus/docker-fs/container-app/express-app/node_modules/redis/index.js:223:14) at Socket.emit (events.js:314:20) at emitErrorNT (internal/streams/destroy.js:100:8) at emitErrorCloseNT (internal/streams/destroy.js:68:3) at processTicksAndRejections (internal/process/task_queues.js:80:21) { errno: -61, code: 'ECONNREFUSED', syscall: 'connect', address: '127.0.0.1', port: 6379 } [nodemon] app crashed - waiting for file changes before starting... التمرين 12.10 ثُبتت قاعدة البيانات https://www.npmjs.com/package/redis في المشروع مسبقًا وأضيفت إليه دالتين تعيدان وعودًا وهما getAsync و setAsync: تقبل الدالة setAsync قيمةً ومفتاحًا، ويُستخدم المفتاح لتخزين القيمة. تقبل الدالة getAsync مفتاحًا وتعيد قيمته المقابلة في الوعد promise. أنجز عدادًا للمهام يخزّن عدد المهام التي تُنشئها في قاعدة البيانات Redis: الخطوة الأولى: زد العداد بمقدار واحد في أي لحظة يُرسل فيها طلب لإضافة مهمة. الخطوة الثانية: أنشئ وصلة GET/statics ساكنة يمكنك طلب معلومات وصفية منها، وينبغي أن تُعاد بصيغة JSON على النحو التالي: { "added_todos": 0 } التمرين 12.11 استخدم script لتسجيل ما تفعله، ثم تحفظ الملف بالاسم "script-answers/exercise12_11.txt". إن لم يسلك التطبيق السلوك المطلوب، فقد يساعدك الولوج المباشر إلى قاعدة البيانات في الدلالة على المشاكل. لنلقي نظرةً إذًا على طريقة استخدام واجهة سطر أوامر Redis في الوصول إلى قاعدة البيانات: انتقل إلى حاوية redis باستخدام الأمر docker exec، ثم افتح الواجهة redis-cli. ابحث عن المفتاح الذي استخدمته باستخدام الأمر * KEYS. تحقق من قيمة المفتاح من خلال الأمر GET. راجع أوامر redis-cli للبحث عن الأمر المناسب لضبط قيمة العداد على 9001. تأكد من أن القيمة الجديدة ستعمل عند تحديث الصفحة http://localhost:3000/statistics. احذف المفتاح باستخدام واجهة سطر الأوامر وتأكد أن العداد سيعمل عند إضافة مهام جديدة. الذاكرة المقيمة وقاعدة البيانات Redis أشرنا سابقًا أنّ قاعدة البيانات Redis لا تحتفظ بالبيانات افتراضيًا، لكنه أمر يمكن حله بسهولة، إذ كل ما علينا فعله هو تشغيل Redis بأمر مختلف كما توضح صفحة Docker hub: services: redis: # أي شيء آخر command: ['redis-server', '--appendonly', 'yes'] # CMD تعديل volumes: # التصريح عن القرص - ./redis_data:/data ستقيم البيانات الآن في المجلد على الجهاز المُضيف، وتذكّر إضافة المجلد إلى الملف gitignore.. إمكانات أخرى لقاعدة البيانات Redis تقدم Redis عدّة ميزات إضافةً إلى عمليات إضافة وضبط وحذف المفاتيح، فهي تحدد مثلًا فترة صلاحية المفتاح وهذه ميزة شديدة الأهمية عند استعمالها مثل مخزن مؤقت. كما يمكن استخدام Redis في إنجاز نماذج نشر-اشتراك publish-subscribe -أواختصارًا PubSub-، وهي آلية تواصل غير متزامنة للتطبيقات الموزّعة. تعمل Redis في هذه الحالة مثل وسيط بين تطبيقين أو أكثر، ينشر بعضها رسائل بإرسالها إلى Redis وعند وصول هذه الرسائل تُبلغ Redis جميع الأطراف بأنها اشتركت بهذه الرسائل. التمرين 12.12 البيانات المقيمة و Redis تأكد أن البيانات لا تُخزّن افتراضيًا في قاعدة البيانات Redis بأن تكون قيمة العداد 0 بعد تنفيذ الأمرين docker-compose -f docker-compose.dev.yml down و docker-compose -f docker-compose.dev.yml up. أنشئ بعد ذلك قرصًا للبيانات عن طريق تعديل الملف "todo-app/todo-backend/docker-compose.dev.yml"، وتأكد من بقاء البيانات بعد تنفيذ الأمرين docker-compose -f docker-compose.dev.yml down وdocker-compose -f docker-compose.dev.yml up. ترجمة -وبتصرف- للفصل Building and Configuring Environments من سلسلة Deep Dive Into Modern Web Development اقرأ أيضًا المقال السابق: مدخل إلى الحاويات أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات حاوية دوكر Docker ومخزن APCu في PHP
  12. تُعَدّ CSS أو التنسيقات الانسيابية Cascading Style Sheets الشيفرة التي تنسِّق محتوى ويب، إذ سيقودك هذا المقال عبر الأساسيات اللازمة لتبدأ، كما سيجيب على أسئلة مثل كيف أجعل النص باللون الأحمر؟ كيف سأعرض محتوًى معينًا في منطقة معينة من مخطط الصفحة؟ كيف سأضع صورةً لخلفية الصفحة أو كيف نختار لونًا لها؟ تعرف على لغة CSS لا تُعَدّ CSS لغة برمجة وليست لغة توصيف mark up أيضًا، وإنما هي لغة تنسيق صفحات، إذ تُستخدَم لغة التنسيق هذه لتطبيق تنسيقات مختارة على عناصر HTML، فشيفرة التنسيق التالية مثلًا تجعل لون النص في كل الفقرات النصية <p> باللون الأحمر: p { color: red; } لنجرِّب ذلك، لذا أنشئ ملفًا نصيًا جديدًا وضع فيه الأسطر الثلاثة السابقة، ثم احفظه بالاسم style.css في المجلد styles. لا بد من تطبيق قواعد التنسيق السابقة على مستند HTML وإلا فلن يفيدك ملف التنسيق في شيء، فإذا فاتك شيء مما ذكرناه في المقالات السابقة، فعُد وانظر في مقالي التعامل مع الملفات وأساسيات HTML. افتح الملف index.html انسخ الشيفرة التالية: <link href="styles/style.css" rel="stylesheet"> ضع هذه الشيفرة بين وسمَي البداية والنهاية للعنصر head أي بين <head> و</head>. احفظ التغييرات التي أجريتها على الملف index.html ثم حمّله على متصفحك وسترى ما يشبه الصورة التالية: إذا ظهرت الفقرات النصية باللون الأحمر، فقد أنجزت المطلوب. تشريح مجموعة قواعد لغة CSS لنناقش شيفرة CSS الخاصة بالفقرات النصية ذات اللون الأحمر لكي نفهم طريقة عملها: تُدعى الهيكلية السابقة بمجموعة القواعد ruleset ويُشار إليها غالبًا "بالقواعد" فقط؛ أما الأجزاء التي تتكون منها فهي: المُحدِّد Selector: وهو اسم لأحد عناصر HTML في بداية مجموعة القواعد، ويحدد العنصر أو العناصر التي ستُطبَّق عليها تلك القواعد، وهو العنصر <p> في حالتنا. التصريح Declaration: يمثِّل قاعدةً واحدةً مثل ;color: red، ويحدد الخاصية التي تريد تنسيقها من خواص العنصر. الخواص Properties: وهي الطرق التي تستطيع من خلالها تنسيق عناصر HTML، إذ تمثِّل color في مثالنا خاصيةً لتنسيق العنصر <p>، كما يمكنك من خلال CSS اختيار الخاصيات التي تريد أن تستهدفها قاعدة معينة. قيمة الخاصية Property Value: وتكتب إلى يمين الخاصية بعد النقطتين، إذ يمكنك اختيار واحدة من عدة خيارات ممكنة لهذه الخاصية، فهناك مثلًا الكثير من الألوان وليس فقط اللون الأحمر red. انتبه إلى التفاصيل التالية في صياغة القواعد: القوسان المعقوصان ({}? توضع بعد المحدد، وينبغي أن توضع كل مجموعة من القواعد ضمن قوسين معقوصين. النقطتان الرأسيتان (:? ينبغي استخدامها لفصل الخاصية عن قيمتها أو قيمها في كل تصريح. الفاصلة المنقوطة (;? لفصل كل تصريح عن الذي يليه. لتغيير عدة خواص معًا ضمن مجموعة قواعد واحدة، اكتب التصريح اللازم لكل منها وافصل بين التصاريح بفاصلة منقوطة: p { color: red; width: 500px; border: 1px solid black; } استهداف عدة عناصر بمجموعة قواعد واحدة إن أردت استهداف عدة عناصر بمجموعة قواعد واحدة لكي تتجنب التكرار، فاكتب بكل بساطة أسماء عناصر HTML التي تريد تطبيق القواعد عليها بحيث تفصل بينها فواصل , كما يلي: p, li, h1 { color: red; } دورة تطوير واجهات المستخدم ابدأ عملك الحر بتطوير واجهات المواقع والمتاجر الإلكترونية فور انتهائك من الدورة اشترك الآن أنواع مختلفة من المحددات يشرح المثال الذي أوردناه سابقًا ما يُعرف بمحدِّد العنصر element selector وهو النوع الأول من أنواع المحددات الذي يطبق قواعد تنسيق معينة على عنصر أو عناصر HTML محددة الاسم، لكن هناك أنواع أخرى سنستعرض ما شاع منها فيما يلي: مُحدِّد مُعرِّف العنصر ID selector: يستهدف عنصر ذو مُعرِّف محدد، إذ يمتلك كل عنصر من عناصر صفحة HTML مُعرّفًا فريدًا، وإليك مثالًا كما يلي: #my-id { color: red; } يستهدِف هذا المحدِّد العنصر <a> في الشيفرة التالية: <p id="the-id">somthing here</p> <a id="my-id">somthing here</a> مُحدَّد الصنف class selector: يستهدف جميع العناصر التي تمتلك القيمة نفسها للسمة class، كما يمكن لعدة عناصر أن تأخذ قيمةً واحدةً للسمة class، وإليك المثال التالي: .my-class { color: red; } يستهدف هذا المحدد العنصرين <p> و <a>: <h2 class="x">some subtitle here</h2> <p class="my-class">somthing here</p> <a class="my-class">somthing here</a> مُحدِّد السمة attribute selector: يستهدف العنصر أو العناصر التي تمتلك السمة نفسها كما في المثال التالي: ol[type] { color: red; } يستهدف هذا المحدد القوائم المرتبة التي تمتلك الصفة type: <h2 class="x">some subtitle here</h2> <ol type="I"><li>list item 1</li><li>list item 2</li><ol>//هذه فقط <ol><li>list item 1</li><li>list item 2</li><ol> محدد الصنف المجرّد psuedo-class selector: يستهدف عناصر محددة بالاسم عندما تكون في حالة معينة، مثل مرور مؤشر الفأرة Hover فوق رابط، وإليك مثالًا: a:hover { color: red; } عندما يمر مؤشر الفأرة فوق أي رابط سيتحول لونه إلى الأحمر. يمكنك الاطلاع على أنواع أخرى من المحددات في توثيق CSS العربي في موسوعة حسوب. خطوط الكتابة والنصوص بعد اطلاعنا على بعض مبادئ CSS سنحاول تطبيق ما تعلمناه في تحسين مظهر صفحتنا التجريبية index.html وذلك بإضافة بعض القواعد والمعلومات في الملف style.css. أولًا، جد أولًا شيفرة الخط الذي اخترته سابقًا من خطوط جوجل وخزنته. ثانيًا، أضف الشيفرة التالية ضمن عنصر <link> الموجود داخل العنصر <head> في الملف index.html لتبدو كما يلي: <link href="https://fonts.googleapis.com/css?family=Open+Sans" rel="stylesheet"> ستربِط هذه الشيفرة الملف إلى ملف تنسيق تُعرِّف عائلة خطوط الكتابة "Open Sans" مع صفحتك. ثالثًا، احذف القاعدة الموجودة في الملف style.css، فقد استخدِمت لتجربة تنسيق العناصر. رابعًا، أضف أسطر الشيفرة التالية، بعد تبديل font-family بما قد خزّنته سابقًا: html { font-size: 10px; /* px means "pixels": the base font size is now 10 pixels high */ font-family: "Open Sans", sans-serif; /* this should be the rest of the output you got from Google fonts */ } تشير الخاصية font-family إلى اسم الخط الذي تريد استخدامه، بينما تشير الخاصية font-size إلى حجمه، وقد اخترنا محدِّد العنصر html تحديدًا، لكي نعتمد حجم الخط ذاك أساسًا في الصفحة بالكامل، إذ العنصر html هو العنصر الأب لكل العناصر في الصفحة التي تتفرع منه لذا فهي ترث منه حجم الخط ونوعه. ملاحظة: كل ما يرد بين المجموعتين /* و */ هو تعليق في CSS، وسيتجاهلة المتصفح عندما يُصيّر الشيفرة. خامسًا، لنضبط حجم الخط للعناصر التي تقع داخل مستند HTML وهي <h1> و <li> و <p>، ولنضبط موقع العنصر <h1> ليظهر في منتصف الصفحة، وأخيرًا سنضبط ارتفاع سطر الكتابة والتباعد بين الأسطر للعنصرين <li> و <p> من خلال الخاصيتين line-height و letter-spacing على التوالي لتسهيل قراءة المحتوى: h1 { font-size: 60px; text-align: center; } p, li { font-size: 16px; line-height: 2; letter-spacing: 1px; } اضبط قيمة الحجم بواحدة px كما تريد وسيبدو عملك الآن على نحو مشابه للصورة التالية: فكرة الصناديق في لغة CSS قد تلاحظ في مرحلة ما عند كتابة قواعد CSS أنها تعتمد فكرة الصناديق في ضبط الأحجام والألوان ومواقع العناصر، إذ يمكنك التفكير في عناصر HTML على أساس أنها صناديق فوق بعضها. يعتمد تخطيط CSS بأكمله تقريبًا على نموذج الصندوق box model، إذ يشغل كل صندوق حيزًا من مساحة الصفحة ويتمتع بخصائص مثل: padding: الحشو أو الحاشية، وهو الفراغ المحيط بمحتوى العنصر، ويمثل في الشكل الذي يُعرض تاليًا الفراغ المحيط بمحتوى الفقرة النصية. border: الإطار، وهو الخط الصلب الذي يحيط بمنطقة الحشو. margin: الهامش، وهو الفراغ الخاص بالعنصر ويحيط بالإطار. سنستخدِم أيضًا في هذا القسم: width: يضبط عرض العنصر. background-color: يضبط لون الخلفية حول المحتوى ومنطقة الحشو. color: يضبط لون المحتوى الذي يضمه العنصر. text-shadow: ويضبط تدرج الظل حول النص داخل العنصر. display: يضبط نمط عرض العنصر، ويمكنك استئناف القراءة لتفهم أكثر. لنكمل بإضافة بعض قواعد CSS ونضعها في أسفل الملف style.css، ويمكنك تغيير القيم كما تشاء ومراقبة ما يحدث. تغيير لون الصفحة html { background-color: #00539F; } تضبط هذه القاعدة لون خلفية الصفحة بالكامل. غيّر قيمة اللون 00539F# بالطريقة التي تعلمناها في مقالات سابقة وراقب التغيّر الذي يحدث. تنسيق جسم الصفحة في لغة CSS body { width: 600px; margin: 0 auto; background-color: #FF9500; padding: 0 20px 20px 20px; border: 5px solid black; } لنناقش القواعد السابقة التي تنسق العنصر body: width: 600px: تحدِّد قياس جسم الصفحة ليبقى دائمًا 600 بكسل. margin: 0 auto: عندما تضع قيمتين لخاصية مثل margin أو padding، فستضبط القيمة الأولى الحدود العليا والدنيا على 0 في هذه الحالة، بينما ستضبط القيمة الثانية الحدَّين اليساري واليميني على القيمة auto، وهي قيمة خاصة تقسِّم الحيز الأفقي المتوفر بالتساوي بين اليمين واليسار، ويمكنك أيضًا استخدام ثلاث قيم أو أربع للخاصية margin. background-color: #FF9500: تضبط لون خلفية العنصر، إذ نستخدِم في مشروعنا اللون البرتقالي المائل إلى الحمرة وهو اللون المتمِّم للون الأزرق الداكن الذي نستخدِمه للعنصر <html>، كما يمكنك تجريب ما تشاء. padding: 0 20px 20px 20px: تضبط قيم الأطراف الأربعة للحاشية المحيطة بالعنصر، والغاية من الحاشية إضافة مساحة فارغة حول العناصر، إذ تدل القيم المبينة على عدم وجود حاشية أعلى جسم الصفحة ووجود حاشية مقدارها 20 بكسل حول بقية الأطراف، كما يمكنك أيضًا استخدام قيمة واحدة للخاصية padding أو قيمتين أو ثلاث أيضًا. border: 5px solid black: يضبط قيم عرض وتنسيق ولون الإطار المحيط بالعنصر، فهو خط مستمر solid وأسود بعرض 5 بكسل في حالتنا ويحيط بكامل جسم الصفحة. ضبط موقع عنوان الصفحة وتنسيقه h1 { margin: 0; padding: 20px 0; color: #00539F; text-shadow: 3px 3px 1px black; } قد تلاحظ فراغًا مخيفًا أعلى جسم الصفحة، وذلك لأن المتصفح سيطبق التنسيق الافتراضي للعنصر <h1>، وقد تبدو الفكرة سيئةً، لكنه أمر ضروري لتحسين القراءة في الصفحات غير المنسقة، لذا سنتجاوز التنسيق الافتراضي للمتصفح ونضبط الحاشية على القيمة 0 باستخدام التصريح ;margin: 0 لنحل المشكلة، ثم نعيد ضبط الحاشية العليا والسفلى على القيمة 20 بكسل، كما سنضبط بعد ذلك لون العنوان ليكون مماثلًا للون المتمم للخلفية -أي أزرق غامق-، وأخيرًا سنعرض بعض الظلال حول العنوان باستخدام الخاصية text-shadow والتي تأخذ أربع قيم: تضبط القيمة الأولى الانزياح الأفقي للظل عن النص: كم يبعد عنه مارًا من خلاله. تضبط القيمة الثانية الانزياح العمودي للظل عن النص: كم يبعد عنه نحو الأسفل. تضبط القيمة الثالثة نصف قطر الضبابية blur: كلما كانت القيمة أكبر كلما بدا ظل النص ضبابيًا أكثر. تضبط القيمة الرابعة الأساس اللوني للظل. ضبط الصورة في المنتصف img { display: block; margin: 0 auto; } سنجعل الصورة في مركز الصفحة لتبدو أفضل، وقد نستخدِم حيلة الهوامش السابقة margin: 0 auto، لكن هناك بعض الاختلافات التي تتطلب إعدادات إضافية. يُعّدُ العنصر <body> عنصرًا كتليًا block أي أنه يشغُل مساحةً من الصفحة، كما ستحترم بقية العناصر الهوامش التي يأخدها؛ أما الصور، فهي عناصر مضمنة inline ولا بد من إعطائها صفة كتلية حتى تعمل فكرة الهوامش عن طريقة الخاصية ;display: block. ملاحظة: تفترض التعليمات السابقة أنّ عرض الصورة لا يزيد عن عرض جسم الصفحة الذي ضبطناه ليكون 600 بكسل، فإذا كانت الصورة أكبر، فستخرج عن إطار جسم الصفحة لتغطي بقية أجزائها، كما يمكن حل المشكلة بتصغير حجم الصورة باستخدام محرر صور أو تنسيقها بضبط الخاصية width للعنصر <img> على قيم أصغر. ملاحظة: لا تقلق إذا لم تفهم تمامًا التصريح ;display: block أو الفرق بين العنصر الكتلي والمضمن، إذ سيغدو الأمر أكثر وضوحًا مع تقدمك في تعلِّم CSS. خلاصة إذا اتبعت كل الإرشادات التي تحدثنا عنها في المقال، فستبدو الصفحة التجريبية التي تعمل عليها على النحو التالي: إذا لم تجد عملك صحيحًا، فيمكنك دائمًا موازنة ما فعلته مع النسخة الجاهزة على جيت-هاب. ترجمة -وبتصرف- للمقال CSS Basics. اقرأ أيضًا المقال السابق: أساسيات لغة HTML أساسيات استعمال لغة CSS تقنيات كتابة شيفرات CSS احترافية وسهلة الصّيانة توثيق لغة CSS العربي
  13. تعرفنا في المقال السابق على طريقة ضبط اﻹعدادات البسيطة لشبكات الاتصال المختلفة، كما تعلمنا ضبط إعدادات مظهر سطح المكتب والمواضيع المتعلقة به. نتابع في هذا المقال استكشاف بقية اﻹعدادات التي تختص بإدارة التطبيقات المثبتة وإدارة حسابات المستخدمين. إعدادات خاصة بالتطبيقات افتح تطبيق "اﻹعدادات"، وانتقل منه إلى خيار "التطبيقات". عند النقر على هذا الخيار، سيعرض لك الشريط الجانبي لنافذة اﻹعدادات جميع التطبيقات المثبتة على حاسوبك. انقر اﻵن على أي تطبيق، وستظهر لك نافذة مشابهة للنافذ التالية: تتكون النافذة من قسمين رئيسيين: التكامل: وتعرض مجموعة موارد النظام التي يمكن لهذا التطبيق الوصول إليها. نلاحظ أن أغلب التطبيقات يمكنها فقط عرض تنبيهاتها ورسائلها للمستخدم. وإن أردت إلغاء التنبيهات التي يعرضها تطبيق ما، فانقر على الزالقة المجاور لحقل التنبيهات. المناولات المبدئية: يعرض هذا القسم مجموعة الملفات التي يمكن لهذا التطبيق فتحها، باﻹضافة إلى الروابط التي يمكنه الاتصال بها وتنزيل البيانات منها. تعرض الصورة التالية الملفات المبدئية لتطبيق "الملفات": لاحظ أن التطبيق قادر على فتح المجلدات العادية و 19 نوعًا من الأرشيفات (المجلدات المضغوطة). وﻹزالة أي نوع من هذه اﻷنواع لأسباب تتعلق بك (كأن تفضل أن تُفتح من قبل تطبيق آخر)، انقر على الزر" أزل" إلى جوار نوع الملفات أو اﻷرشيفات الذي لا تريد لهذا التطبيق فتحه. ولكي تطلع على تفاصيل أكثر عن التطبيق الذي اخترته، انقر على زر "افتح في البرمجيات" أعلى النافذة ضمن شريط المهام ليفتح بدوره تطبيق "برمجيات" الذي يعرض لمحةً عن التطبيق، ويتيح تشغيل التطبيق أو إزالته كليًا من حاسوبك. للخروج من قائمة التطبيقات، انقر على أيقونة التراجع الموجودة أقصى يسار شريط مهام النافذة. إعدادات الخصوصية ويُقصد بها اﻹعدادات التي تساعد المستخدم في منع تجاوز خصوصيته من خلال الاطلاع على موقعه أو مراقبة نشاطه على شبكات الاتصال أو الوصول إلى معلومات حساسة عنه. يتيح لك تطبيق "اﻹعدادات"، إمكانية ضبط بعض اﻹعدادات التي تسهم في المحافظة على خصوصية ما تقوم به على حاسوبك، وسيعرض لك التطبيق هذه الخيارات بمجرد النقر على "الخصوصية": الموصولية يتيح لك هذا الخيار إمكانية تشغيل تطبيق "التحقق من الموصولية connectivity checking"، الذي يتفقد تلقائيًا إعدادات شبكة الاتصال التي تستخدمها ويعالج المشاكل التي تظهر ليبقى اتصالك مستقرًا دائمًا؛ كما يساعد هذا التطبيق في الحصول على معلومات عن أي جهاز ضمن الشبكة يحاول مراقبة اتصالك. لتفعيل هذا التطبيق، انقر على الزالقة المجاورة، فتتحول إلى اللون البنفسجي. وانقر مجددًا ﻹلغاء تفعيله. خدمات التموضع يسمح لك هذا الخيار بإلغاء أو تفعيل تحديد مكانك الحقيقي، إذ تطلب بعض التطبيقات اﻹذن للوصول إلى موقعك الجغرافي الحقيقي لتقديم خدمات معينة. لتفعيل خدمة تحديد الموضع، انقر على الزالقة في شريط أدوات النافذة، وعندها ستظهر لك قائمة بالتطبيقات التي تطلب إذنًا في الوصول إلى موقعك الجغرافي. تاريخ الملفات وسلة المهملات تضم هذه اﻹعدادات قسمين من الخيارات: تاريخ الملفات عندما تعمل على ملف معين، سيسجل النظام ذلك ويحدد موقع هذا الملف في منظومة الملفات، كما يحدد التطبيق الذي أنشأه والتطبيق الذي تستخدمه في تحرير هذا الملف. يشارك النظام هذه السجلات بين جميع التطبيقات لكي يسهل وصولك إلى آخر الملفات التي عملت عليها، والتي قد ترغب في إعادة فتحها. قد يحمل هذا اﻷمر تهديدًا ممكنًا للخصوصية، ولذلك يتيح لك النظام القدرة على ضبط الفترة الزمنية التي يحتفظ فيها بتلك السجلات. وبافتراض أنك الوحيد الذي يستثمر هذا الحاسوب مستخدمًا الحساب الذي ولجت فيه، فإن الخيار الافتراضي هو "أبدًا". أي أن هذه السجلات ستبقى محفوظة إن لم يطرأ خلل ما ولن يجري مسحها. في المقابل ترك لك النظام عدة خيارات أخرى هي: يوم واحد 7 أيام 30 يومًا كما يعطيك خيار الحذف الفوري للسجلات من خلال النقر على زر "مسح السجلات clear history". سلة المهملات والملفات المؤقتة يضبط هذا القسم الفترة الزمنية التي تحتفظ بها سلة المهملات أو مخازن الملفات المؤقتة بمحتوياتها. قد تضم هذه اﻷماكن بعض المعلومات الحساسة التي يمكن استرجاعها من قِبل جهات غير مخولة بالاطلاع عليها، لذلك يعطيك النظام إمكانية الحذف التلقائي لهذه الملفات. ستلاحظ وجود ثلاثة خيارات: الحذف التلقائي لمحتويات سلة المهملات ِAutomaticaaly delete trash content: بالنقر على هذا الخيار ستفعل عملية الحذف التلقائي لمحتويات سلة المهملات. الحذف التلقائي للملفات المؤقتة Automatically delete temporary files: بالنقر على هذا الخيار تفعل عملية الحذف التلقائي للملفات المؤقتة. فترة تنفيذ الحذف التلقائي Automatically delete period: ويحدد الفترة الزمنية التي تبقى فيها الملفات المؤقتة ومحتويات سلة المهملات موجودة قبل أن تحذف تلقائيًا. ستجد أيضًا أسفل النافذة زرين: اﻷول "أفرغ المهملات" لحذف محتويات سلة المهملات مباشرةً، واﻵخر "delete temporary files"، لحذف الملفات المؤقتة مباشرةً. إقفال شاشة المستخدم تُستخدم هذه الميزة عادةً لمنع أيٍّ كان من استخدام حسابك على الجهاز إن غادرت مكان عملك لبرهة وتركته مفتوحًا. إذ تُقفل شاشة سطح المكتب في هذه الحالة ويُطلب إليك إدخال كلمة المرور من جديد حتى يسمح لك بمتابعة العمل. يتيح لك هذا القسم من اﻹعدادات أن تضبط مايلي: زمن تعتيم الشاشة blank screen delay: وهي الفترة الزمنية التي يختفي بعدها سطح المكتب عند ترك العمل، ويمكنك ضبط هذه الفترة "من دقيقة واحدة" إلى "أبدًا" وتعني أن يبقى سطح المكتب ظاهرًا دائمًا. إيصاد تلقائي للشاشة: يقفل النظام الشاشة تلقائيًا إن تركت العمل على حاسوبك فترةً من الزمن. الفترة الزمنية للقفل التلقائي للشاشة Automatic screen lock delay: إن اخترت أن تُقفل الشاشة تلقائيًا، فسيحدد لك هذا الخيار الفترة الذي ينتظرها النظام قبل اﻹيصاد التقائي لها. وتتراوح الخيارات بين 30 ثانية حتى ساعة؛ كما يتيح خيار اﻹيصاد بمجرد أن تنطفئ الشاشة. قفل الشاشة عند تعليق العمل lock screen on suspend: عند تفعيل هذا الخيار سيقفل النظام شاشة سطح المكتب عندما تعلّق العمل على الجهاز (النقر على خيار "علّق" ضمن خيارات اﻹطفاء في شريط المهام الرئيسي)، وبالتالي ستضطر إلى كتابة كلمة السر عندما تعيد تشغيل الجهاز. أظهر التنبيهات على شاشة القفل show notifications on lock screen: يمكن لأي تطبيق عند تفعيل هذا الخيار عرض تنبيهاته ورسائله على الشاشة حتى إن كانت موصدة. إدارة المستخدمين انقر على خيار "المستخدمين" الموجود في الشريط الجانبي لنافذة تطبيق "الإعدادات"، وستظهر النافذة التالية: يتطلب اﻷمر عادةً فك الحظر عن تغيير هذه الإعدادات، وذلك بالنقر على الزر "فك unlock" الموجود في الشريط السماوي أعلى النافذة، ثم يطلب إليك النظام عندها إدخال كلمة السر للاستيثاق منك، وستكون بعدها جاهزًا للتحكم بخيارات مثل: تغيير اسم المستخدم: بالنقر على أيقونة القلم بجانب اسم المستخدم. تغيير كلمة السر: يطلب إليك عندها تزويده بالكلمة القديمة ثم الجديدة وتأكيدها. خيار الولوج التلقائي: سينقلك النظام إلى سطح المكتب مباشرةً عند تشغيل الحاسوب دون المرور بشاشة تسجيل الدخول إن فعّلت هذا الخيار. نشاطات الحساب account activity: ويعرض لك قائمةً بوقتي بداية ونهاية كل جلسة على حاسوبك خلال أسبوع. إضافة مستخدم جديد: انقر على زر "أضف مستخدمًا" في شريط مهام النافذة، وسيعرض لك النظام مربع الحوار التالي: اختر نوع الحساب: عادي أو إداري (إن لم ترغب بإعطاء المستخدم صلاحيات واسعة اختر "عادي"). اكتب اسمك. اكتب اسم المستخدم الذي سيكون نفسه اسم مجلد "المنزل" ولا يمكن تغييره لاحقًا. اختر كلمة سر، ثم أكدها. انقر الزر "أضف". اقرأ أيضًا المقال السابق: إعدادات أوبونتو 20.04: الاتصالات وسطح المكتب تعرّف على سطح مكتب أوبونتو 20.04 التعامل مع المجلدات والملفات في أوبونتو 20.04 تغيير اللغة في نظام لينكس أوبنتو إلى العربية
  14. لغات البرمجة هو مفهوم واسع كبير يحتوي على أسماء عدة شتى قد يحتار فيها الداخل الجديد إلى مجال علوم الحاسوب لتعلم البرمجة، فقد يتساءل أي لغة برمجة يجب أن أتعلم أولًا، وما هي أشهر لغة برمجة تُستعمل على نطاق واسع استفيد منها، وما هي أسهل لغة برمجة ابدأ بها وغيرها من الأسئلة المحيرة، فإن فتحت مثلًا قائمة لغات البرمجة على ويكيبيديا فستجد عشرات لغات البرمجة المذكورة وهو ما يزيد المشكلة وهنا لابد من الاستعانة بدليل. يقودك هذا المقال تدريجيًا في جولة سريعة في عالم لغات البرمجة كي تتكون لديك صورة عامة موسّعة عن لغات البرمجة لتكون عونًا لك إن كنت ستتعلم إحداها، وسينتقل بك المقال من شروحات مبسطة لمفهوم لغات البرمجة ومستويات لغة البرمجة وأقسامها إلى أنواعها واستخداماتها سواء لبرمجة الحواسيب أو الأجهزة الذكية أو الأجهزة الإلكترونية وحتى الروبوتات، ثم سنجاوب على بعض الأسئلة الشائعة المتعلقة بلغات البرمجة مثل التي أشرنا إليها قبل قليل في نهاية المقال. ينبغي أن تكوّن في نهاية المقال فكرة واضحة عن ما يُدعى لغة برمجة ولغات البرمجة عمومًا تكون ركيزة صلبة تعتمد عليها في اختيار لغة البرمجة التي تناسب المجال الذي تخطط الدخول له إن أردت تطوير مسارك المهني ودخول عالم البرمجة. إليك فهرس المقال لكي يسهل عليك التنقل بين أقسامه: تعريف البرمجة ما هي لغات البرمجة لغة البرمجة واللغات البشرية أنواع لغات البرمجة لغات برمجة السكربت Scripting Languages اللغات التوصيفية Markup Languages أسئلة شائعة عن لغات البرمجة تعريف البرمجة البرمجة هي العملية التي تستطيع بواسطتها إنجاز فكرة معينة أو حل مشكلة ما عن طريق تقسيم الفكرة أو المشكلة إلى خطوات متتالية قابلة لإعادة التكرار وصولًا إلى النتيجة المطلوبة. فلو سألت أحدهم ما هو ناتج العملية الرياضية التالية 5*(2-3) فقد يعطيك مباشرة الجواب 5 لكن ما فعله الدماغ هو عملية تحليل للمسألة ووضع طريقة لحلها وهذا ما يُدعى بوضع خوارزمية Algorithm. فخوارزمية حل المسألة البسيطة السابقة هي: اطرح 2 من 3 وسجل الجواب: 1 =2-3. احسب جداء الناتج مع العدد 5: 5x1. قل الجواب: 5. وخلاصة القول أن البرمجة هي طريقة لترتيب وتنظيم مسألة ما للحصول على النتيجة بالمعنى العام، أما في عالم الحواسيب فهي وسيلة للتخاطب مع هذه الأجهزة. أما الأسلوب المقترح لتنفيذ هذه الخطوات فهي الخوارزمية، وستكون عندها لغة البرمجة هي الوسيلة التي نخبر فيها الحاسوب أو التجهيزة كيفية تنفيذ الخوارزمية لحل المشكلة المعروضة. ما هي لغات البرمجة؟ لغات البرمجة تشير ببساطة على أنها وسيلة للتواصل بين البشر والحواسب أو بعض التجهيزات أو الآلات المهيأة لتنفيذ برامج متغيرة والتي تُدعى تجهيزات قابلة للبرمجة. ونظرًا للتقدم التكنولوجي الهائل ودخول تقنيات المعلومات إلى مختلف نواحي الحياة، ازداد توجه الصانعين إلى إنتاج تجهيزات قادرة على التخاطب والتفاعل مع المستخدم لتنفيذ وظائف متعددة كالصرافات الآلية ونقاط الخدمة الذاتية والهواتف الذكية وحتى التجهيزات المنزلية والسيارات، وكلما زاد تعقيد هذه التجهيزات وتعددت مهامها احتاجت إلى طريقة فعّالة لتخبرها بما هو مطلوب منها. وهنا تأتي أهمية لغات البرمجة وضرورة وجود أنواع مختلفة من لغات البرمجة وفق سويات مختلفة لتأمين إدارة تلك التجهيزات والتواصل الأمثل معها. إذًا فلغة البرمجة هي مجموعة من التعليمات والتوجيهات التي تكتب أو تُجمّع أو تُركّب ضمن سياق معين كي تنقل بعد معالجتها إلى الجهاز الهدف بغية تنفيذها. ويُقصد بمعالجة لغات البرمجة هو تحويلها من تعليمات مقروءة -في لغات البرمجة المكتوبة Written Programming Languages- أو مرئية بالنسبة للبشر -في لغات البرمجة المرئية Visual programing languages- إلى توجيهات تفهمها الآلة المستهدفة سواء حاسوب أو أية أنظمة إلكترونية أخرى. فما يفهمه الحاسوب هو برنامج مكوّن من الواحدات 1 والأصفار 0 التي تخبره وفقًا لتسلسلها بطريقة محددة سلفًا ما عليه فعله، وتعرف لغة البرمجة التي تُكتب برنامجًا بهذه الطريقة "لغة الآلة" machine language. ونظرًا لصعوبة فهمها للبشر، ظهرت الحاجة إلى لغات برمجة أكثر قربًا من البشر وهنا بدأت الحكاية. تتكون لغة البرمجة -مثلها مثل أي لغة- عمومًا من الأقسام التالية: صياغة لغة البرمجة Syntax هي الطريقة التي نصيغ فيها تعليمات لغة البرمجة ونربطها مع بعضها لإنتاج عبارات صحيحة الصياغة يمكن استخدامها في تنفيذ البرنامج وقد تكون الصياغة: نصية: وتمثل تعليمات اللغة وكلماتها المفتاحية keywords وعباراتها ومتنها. رسومية أو كتلية: تُنظَّم فيها التعليمات التي تؤدي عملًا محددًا ضمن كتلة واحدة، ثم تُمثَّل هذه الكتلة بطريقة مرئية كمربع أو دائرة تُعطى لونا واسمًا يدل على طبيعة العمل الذي تنفذه. ويبنى البرنامج عندها بضم هذه الكتل إلى بعضها لإنجاز الوظيفة المنوطة بالبرنامج. تعطي الصياغة إذًا الشكل العام الصحيح لطريقة كتابة التعليمات بناء على معايير خاصة خارج نطاق مقالنا، وإن أردنا تقريب الأمر فهي بمثابة القواعد النحوية للغات البشر أو اللغات الطبيعية. إذ تُعد مثلًا الجملة "إن المبرمجون مبدعون." في اللغة العربية خاطئة الصياغة لمخالفتها قواعد اللغة ومن المفترض أن نقول" إن المبرمجين مبدعون.". دلالة لغة البرمجة Semantic هي مجموعة قواعد تحدد ما إن كانت طريقة صياغة التعليمات ستعطي النتيجة المرجوة أم لا، وتضع بعض القيود على الصياغة الصحيحة التي قد لا تؤدي إلى نتيجة. لن نخوض كثيرًا في هذه الفكرة لكن سنسهل الأمر عليك عزيزي القارئ: لن تمنعنا أية قاعدة نحوية في اللغة العربية من القول بأنني "أتناول برتقالًا حامضًا لا طعم له" لكن كيف يكون حامضًا ولا طعم له في نفس الوقت! صياغة صحيحة ومدلول لا معنى له. تحديد الأنواع في لغة البرمجة Types وهي الطريقة التي تصنّف فيها لغة البرمجة القيم والتعابير ضمن أنواع مختلفة وكيفية التعامل مع هذه الأنواع والتحويل فيما بينها. فهنالك مثلًا قيم نصية كأن استخدم القيمة "انقر هنا" وقيم عددية كأن استخدم الرقم 5 وقيم منطقية كأن استخدم القيمة "صحيح" true. وقد تكون نتيجة تنفيذ العملية نوعًا محددًا من البيانات كأن تُنتج العملية الحسابية عددًا أو تنتج نصًا. لهذا تحاول معظم اللغات وضع أنواع للقيم التي تتعامل معها. لكن في المقابل ستجد عدة لغات لا تعتمد على الأنواع مثل جافاسكربت JavaScript وماتلاب MatLab. إن وجدت هذا الفكرة غامضة قليلًا لا تكترث وتابع القراءة فستبدو هذه الفكرة غاية الوضوح ما أن تكتب برنامجك الأول في لغة تختارها. المكتبات المعيارية Standard Libraries وهي مجموعة من التعليمات أو العمليات الجاهزة التي توفرها لغة البرمجة لعمل مختلف أجزائها مع بعضها ولتنفيذ المهام الأساسية المنوطة بلغة البرمجة تلك مثل التعامل مع النصوص والأعداد والتواصل مع نظام التشغيل ونظام الملفات وغيرها، وتوضع عادة في ملفات منفصلة وتضاف إلى البرنامج الذي تُنفّذه. إذ تساعدك بعض المكتبات مثلًا على إضافة نصٍ إلى نص آخر مباشرة باستخدام إشارة الجمع + على الرغم من كونها عملية حسابية تجري على الأعداد. إذ تضم تلك المكتبات القدرة على فهم أن هذه العملية هنا ليست لجمع عددين بل لضم نصين. تصنّف لغات البرمجة في فئات ومجموعات وفقًا لصياغتها وطريقة معالجتها للمعلومات وطريقة تنفيذ تعليمات لغة البرمجة وهذا ما سنتوسع فيه بعد قليل. لغة البرمجة واللغات البشرية إنّ اللغات البشرية هي الطريقة الطبيعية للتواصل بين البشر وتتكون من حروف تكوّن كلمات ومن ثم تشكل جملًا وفق مجموعة من القواعد التي ندعوها في العربية "نحوًا" ومن ثم نستخدم هذه الجمل بعناية لإيصال المعنى المطلوب. ويُفترض بالإنسان أن يُلم بأساسيات لغة الشخص الذي يحاوره كي لا تقع مشاكل في التواصل. الأمر مشابه في لغات البرمجة كونها لغة للتخاطب بين البشر والحواسيب أو الآلات التي تقبل البرمجة عمومًا. إضافة إلى ذلك، للغات الطبيعية ولغات البرمجة نوع من الهيكلية أو البنية التي تنتظم وفقها تلك اللغات، فالكلمات في اللغات الطبيعية قد تشابه التعليمات في لغة البرمجة والجمل sentences قد تشابه التعابير البرمجية Expressions، وتستخدم لغات البرمجة ما تستخدمه اللغات الطبيعية من علامات للترقيم لكن لأغراض خاصة بوظيفتها، وتستخدم عوامل تشابه حروف العطف والاختيار والموازنة في اللغات الطبيعية للربط بين التعابير البرمجية واستخلاص المعنى، فلهذه اللغات جميعها أساليب في الصياغة والمدلول. ومن أوجه الشبه أيضًا ما يُدعى بالعائلات، فاللغات الطبيعية تنحدر من عائلات تتشابه اللغات فيها كاللغات السامية أو اللغات الجرمانية أو السلافية. كذلك الأمر في لغات البرمجة التي تنحدر جميعها من لغات ظهرت في البداية مثل Fortran التي انحدرت منها Algol ثم C و ++C إذ تتشابه هاتين الأخيرتين كثيرًا. أما الاختلافات فتأتي من كون التعليمات في لغات البرمجة معدة سلفًا وقابلة للحصر حتى إن تطورت سيكون التطور بإلغاء تعليمة ووضع أخرى بينما تتطور اللغات الطبيعية وفقًا للحاجة ويظهر ذلك تلقائيًا. كذلك يظهر الاختلاف في عدم قدرة لغات البرمجة على التعبير بطرح أسئلة أو استخدام الإيحاءات بل تعتمد على مجموعة قواعد صارمة فيما يخص الصياغة وطريقة استخدام اللغة. تأتي أهمية هذا النقاش من ضرورة الفهم الأصيل للغات البشرية ولغات البرمجة لما سيقود ذلك من تطور في مجال البرمجيات التي تتعرف على الكلام أو المترجمات الآلية أو الذكاء الاصطناعي وغيرها الكثير. دورة الذكاء الاصطناعي احترف برمجة الذكاء الاصطناعي AI وتحليل البيانات وتعلم كافة المعلومات التي تحتاجها لبناء نماذج ذكاء اصطناعي متخصصة. اشترك الآن أنواع لغات البرمجة هنالك المئات من لغات البرمجة وجدت لتحقيق أهداف مختلفة وحتى هذه اللحظة تظهر لغات برمجة جديدة باستمرار، لذا تصنف هذه اللغات إلى أنواع عدة بالاعتماد على وظائف كل لغة وتطبيقاتها وطريقة معالجتها وغيرها من المقاييس ونوضح في الصورة التالية بعض الأنواع أو الفئات التي تنضوي تحتها لغات البرمجة. أنواع لغات البرمجة الشائعة هي تنقسم إلى الأنواع التالية: أنواع لغات البرمجة وفق مستوى الترميز لغات البرمجة منخفضة المستوى Low level Programming Languages لغة الآلة Machine language لغات التجميع Assembly languages لغات البرمجة عالية المستوى High level languages أنواع لغات البرمجة وفق طريقة معالجة التعليمات اللغات المُصرَّفة Compiled Languages اللغات المُفسَّرة Interpreted languages اللغات الهجينة المصرّفة المفسّرة أنواع لغات البرمجة وفق أسلوب تنظيم الشيفرة لغات البرمجة الوظيفية Functional Programming لغات البرمجة الإجرائية Procedural Programming لغات البرمجة الكائنية Object-oriented Programming أنواع لغات البرمجة وفق مجالات الاستخدام لغات البرمجة عامة الغرض General Purpose programming Languages لغات البرمجة خاصة الغرض Special Programming Languages شرح كل الأنواع السابقة سيطيل المقال وهو خارج موضوع التعريف بلغات البرمجة ويكفي أن تأخذ فكرة على أنواع لغات البرمجة وتتعرف على أشهر أصنافها لأنك عادة عندما تتعلم لغة برمجة يفترض أن تعرف نوعها والصنف الذي تتبع له لأن الأنواع الواحدة تشترك بمفاهيم واحدة تقريبًا يمكن تعلمها بشكل منفصل. ويمكنك الرجوع إلى مقال أنواع لغات البرمجة لتكتشف المزيد من التفاصيل حول معايير وطرق تصنيف وتنظيم لغات البرمجة وشرح مفصل لكل الأنواع التي ذكرناها بالأعلى. لغات برمجة السكربت تُعد لغة برمجة ما أنها لغة سكربت إن كانت: تستخدم مجموعة من التعليمات النصية المكتوبة لتنفيذ أي نوع من العمليات. تعتمد على مضيف: إذ لا يمكن أن تُنتج برامجًا تنفيذية قائمة بحد ذاتها بل ترتبط بنظام تشغيل مثل (سكربتات الطرفيات) أو بيئة عمل (سكربت ويب على الخادم) أو برنامج ( سكربت كتابة ماكرو أو موسّع) أو لغة برمجة أخرى لتنفيذ مجموعة من العمليات التي تهدف إلى تعديل أو تطوير أو زيادة القدرة الوظيفية للمضيف أو تتوسط بينها وبين منظومات أخرى ليشار إليها عندها إلى أنها لغات صمغية glue code. أن تكون لغة مفسّرة وليست مُصرَّفة. تتميز لغات السكربت بكونها، مفتوحة المصدر غالبًا، وسهلة التعليم نسبيًا ولا ضرورة لوجودها ضمن ملف خاص كي تُفسّر، ولا يمكن كتابة تطبيقات أو برامج باستخدامها مباشرة بل تحتاج إلى مضيف لذلك يُقال أنها محمولة. أضف إلى ذلك أنها سريعة التنفيذ كونها تستطيع تنفيذ الطلبات مباشرة (تنفيذ أمر مباشرة) دون الحاجة إلى تعليمات أخرى لتفعيل الطلب كما يزيد سرعتها كونها لغة برمجة مفسّرة. يمكنك مثلًا أن تُنفّذ الأمر ('مرحبًا')window.alert مباشرة في جافاسكربت وهي لغة سكربت مشهورة جدًا، لكنك لن تستطيع فعل ذلك باستخدام لغة ++C. لاحظ ماذا سيتطلب الأمر: // لنقول مرحبًا C++ برنامج بلغة #include <iostream> using namespace std; int main(){ cout<<"مرحبًا"; return 0; } تُصنف لغات برمجة السكربت إلى: سكربتات تطوير الويب: وتستخدم لكتابة صفحات ويب ديناميكية وتطوير مواقع وتطبيقات الويب، ويمكن أن نميز بين نوعين من السكربتات في هذه الصدد سكربتات تعمل من جانب العميل (أي تُستخدم في بناء الواجهة الأمامية للتطبيق أو الموقع) نجد: JavaScript React Next.js سكربتات تعمل من جانب الخادم (أي تستخدم لبناء الواجهة الخلفية للتطبيق، وهي ما يستعرضه المتصفح) نجد منها: JavaScript PHP Node.js ASP.net Ruby Perl Python سكربتات تعمل مع طرفيات أنظمة التشغيل: وتستخدم لتنفيذ الأوامر ضمن واجهات سطر الأوامر في أنظمة التشغيل المختلفة مثل ويندوز ولينكس. من الأمثلة عليها: BASH: في طرفيات لينكس، وتنفذ طيفًا واسعًا من التعليمات مثل الكتابة والقراءة من وإلى الملفات وتنزيل البرمجيات من الإنترنت وتثبيتها وتشغيل البرمجيات وغيرها الكثير. Windows powerShell: في ويندوز، وتنفذ مهامًا مشابهة لما تنفذه طرفية لينكس. سكربتات للأغراض العامة: يمكن أن تُنفذ تقريبًا أي شيء نذكر منها: Ruby Python يمكن استخدام السكربتات في كل المجالات تقريبًا مثل تطبيقات الويب وتطبيقات الهواتف المحمولة وسطح المكتب والتعامل مع الأنظمة وأتمتة المهام على المنظومات السحابية والتنقيب عن البيانات وبرمجة الموّسعات والإضافات إلى البرامج وغيرها الكثير. نفذ مشاريعك البرمجية باللغة التي تحتاجها استعن بأفضل المبرمجين في كتابة وتصحيح الأكواد البرمجية على خمسات اطلب خدمتك الآن اللغات التوصيفية Markup Languages يشير مصطلح اللغات التوصيفية markup languages إلى آلية لترميز النصوص عن طريق إضافة مجموعة من الرموز إلى النص تتحكم بتنسيقه أو بإيجاد علاقات بين أقسامه وتسهل عملية أتمتته. إذ تضم اللغات التوصيفية مجموعة من الوسوم أو القواعد التي يشير كل منها إلى دلالة معينة وتستخدم لتنظيم البيانات النصية وعرضها بطريقة يسهل تمييزها بالنسبة للإنسان أو الحاسوب كأن تشير إلى عبارة على أنها عنوان وتبرز عبارة أخرى بكتابتها بخط ثخين وهكذا. لا تُعرض تلك القواعد أو الوسوم ولا تُضاف إلى المحتوى الفعلي بل وظيفتها وصف البيانات فقط وترتيبها. تتطلب هذه اللغات فقط برنامجًا ليحلل الوسوم ويعرض المحتوى وفقًا لمدلول كل وسم وغالبًا ما تحلل المتصفحات أكواد HTML وأكواد XML ثم تعرض النتائج بينما تحلل برمجيات أخرى مخصصة لغات توصيفية أخرى مثل Markdown و DocBook اللتان تستخدمان في المحررات النصية ولاتخ LaTex لكتابة الأوراق البحثية الأكاديمية وغيرها الكثير. لا تحتاج اللغات التوصيفية كلغات البرمجة إلى تصريف Compilation أو تفسير Interpretation كي تحول الشيفرة المكتوبة إلى مجموعة أخرى من التعليمات التي يفهمها الحاسوب، فهي لا تنفذ أية عمليات أو إجرائيات لذلك لا تُعد اللغات التوصيفية لغات برمجة. فكل ما تفعله محللات اللغات التوصيفية هو قراءة الوسوم ومعرفة بدايتها ونهايتها ثم عرض هذه الوسوم بالطريقة الصحيحة تبعًا لدلالة تلك الوسوم. يمكن استخدام هذه اللغات لتصميم صفحات ويب مثل HTML أو لتنسيق صفحات الويب مثل CSS أو لتخزين البيانات مثل XML أو لتنسيق النصوص مثل Markdown وغيرها الكثير. تشرح دورة تطوير واجهات المستخدم من أكاديمية حسوب كل من لغات تطوير واجهات المستخدم الأمامية وهي HTML و CSS وجافاسكربت شرحًا شاملًا بعدد ساعات يزيد عن 50 ساعة فيديو بتطبيقات عملية تطبيقية تناسب سوق العمل. يمكنك الرجوع في هذا الصدد إلى مقال تعلم لغة HTML الذي يتحدث عن اللغات التوصيفية عمومًا ولغة HTML خصوصًا. أسئلة شائعة عن لغات البرمجة إليك بعض الإجابات عن أكثر الأسئلة شيوعًا حول لغات البرمجة. كم عدد لغات البرمجة الموجودة حاليًا؟ عدد لغات البرمجة وفقًا لموقع ويكيبيديا يقارب 700 لغة برمجة مستخدمة أو توقف استخدامها باستثناء اللغات التوصيفية. لكن يرى البعض أنها قد تصل بجميع تصنيفاتها إلى 8000-9000 وهذا رقم مبالغ فيه قليلًا. ما السبب في وجود عدد كبير من لغات البرمجة؟ التنوع نابع عن الحاجة، فلن تتمتع لغة برمجة واحدة بجميع المزايا التي تسهّل العمل في جميع المجالات بفعالية ودقة. قد يكون الأمر مشابهًا لوجود أنواع عدة من المركبات منها السيارة والطائرة والدراجة الهوائية والنارية فلكل استخدامه، إذ يولّد التطور التقني المتسارع الحاجة إلى وجود لغات أكثر مرونة وفعالية في مجالات مختلفة مما يدفع إلى تطوير لغات جديدة تلبي هذه الحاجة. ما هي أحدث لغات البرمجة؟ إذا استثنينا الإصدارات الجديدة من اللغات القديمة مثل الإصدار 3 من لغة Python يمكن أن نجد: لغات البرمجة بالدوال: ELIXIR ELM PURESCRIPT SWIFT لغات البرمجة الإجرائية: Go لغات البرمجة بالكائنات: DART PONY CARBON لغات أغراض عامة: HACK Kotlin NIM RUST ما هي لغات البرمجة السائدة (أشهر لغات البرمجة)؟ إليك أكثر لغات البرمجة استخدامًا وأشهر لغات البرمجة لكن دون نسب مئوية للاستخدام ودون أفضلية لعدم وجود إحصائيات دقيقة وموثوقة. Python JavaScript JAVA C++/C GO Ruby #C PHP أشهر لغة من بينها تُستعمل في بناء تطبيقات سطح مكتب تعمل على مختلف أنظمة التشغيل الحاسوبية هي لغة جافا Java وبايثون Python والأخيرة تستعمل في مجال الذكاء الاصطناعي وتعلم الآلة والتعامل مع البيانات وغيرها، أما في مجال الويب، فأشهر لغة فيه حاليًا هي لغة جافاسكربت JavaScript فيمكنك باستعمالها تطوير الواجهات الأمامية Frontend وأصبح بإمكانك أيضًا تطوير الواجهات الخلفية Backend عبر بيئة Node.js وكما يمكنك باستعمال تقنيات الويب تطوير تطبيقات لسطح المكتب تعمل على كل أنظمة التشغيل باستعمال إطار العمل Electron.js، وتعد دورة تطوير التطبيقات باستخدام لغة JavaScript أفضل مرجع عربي يشرح لغة جافاسكربت وكافة تطبيقاتها لبناء تطبيقات الويب وتطبيقات سطح المكتب شرحًا عمليًا. ما هي أسهل لغات البرمجة من حيث التعلم والاستخدام؟ لا يمكن أن نجد إجابة محددة عن سؤال أسهل لغات البرمجة فالغرض من استخدامك للغة البرمجة وتآلفك معها مسألة حاجة ورغبة: PHP Python JavaScript Ruby C/C++/JAVA Kotlin Swift عمومًا، يقال عن لغة بايثون أنها أسهل لغة برمجة يمكن البدء بتعلمها والسبب أنها صياغة اللغة قريبة جدًا من صياغة اللغة الإنجليزية فعندما تكتب شيفرة برمجية كأنك تصيغ فقرة من لغة إنجليزية وهذا ما يميزها عن بقية اللغات التي تكثر فيها الأحرف والكلمات الغامضة وعلامات الترقيم والأقواس المتناثرة هنا وهناك، ولهذا السبب تبدأ أغلب الجامعات بشرح هذه اللغة كأول لغة برمجة لطلاب علوم الحاسوب وتبدأ أغلب الدورات والكورسات البرمجة بشرحها لمن يريد تعلم البرمجة بمفرده، وأفضل مرجع عربي شامل متكامل من تلك الدورات هي دورة تطوير التطبيقات باستخدام لغة Python من أكاديمية حسوب. هل عليَّ تعلم لغة برمجة؟ إن كنت تنوي تطوير مهنتك الهندسية أو العلمية أو التسويقية أو الاقتصادية أو التحليلية فالجواب نعم بكل تأكيد. ما الفائدة من تعلم لغة برمجة؟ تطوير مسيرتك المهنية، فكل الدلائل تشير إلى أننا مقبلون وبشدة على عصر الآلات الذكية. الاطلاع على الطريقة التي تعمل بها الآلات المبرمجة مما قد يساعدك على استخدامها بالشكل الصحيح. تحسين قدراتك العقلية على التحليل وترتيب الأفكار. إضافة إلى تلك الأسباب العامة، ستجد من مهنة المبرمج أو مطور ويب مهنة ممتعة ومجزية ماديًا فالطلب يزداد بشكل كبير على المنتجات الرقمية وتتنوع مجالات استخداماتها لما تقدمه من فوائد على جميع الأصعدة مثل أتمتة المهام المختلفة وتحليل البيانات والحسابات الرياضية والتقنية المتقدمة وإدارة المتاجر الإلكترونية والأعمال وغير ذلك الكثير. فالبرمجة هي مهنة المستقبل بامتياز، وأيًا كانت مهنتك الأصلية ستجد البرمجة سبيلًا إليها، وهكذا ستجني فائدة مضاعفة بتعلمك إحدى لغات البرمجة. هل من الصعب تعلم لغة برمجة؟ الجواب لا إن كنت تمتلك الإرادة والمثابرة. تعلم البرمجة شبيه بركوب الدراجة ما أن تُصِرَّ على تعلمها ثم تتعلم كيف تستخدمها لن تنسى أبدًا كيف ستقودها وكذلك لن تنسى كيف تفكر برمجيًا. لكن إن أردت أن تصبح محترفًا، فهذا طريق طويل تعترضك فيه بعض الصعوبات. ما هي مجالات استخدام لغات البرمجة؟ من كتابة كلمة "مرحبًا" على شاشة حاسوبك إلى التحكم بمسبار فضائي يحط على كوكب آخر. فكل ما تعرضه لك الأجهزة الإلكترونية من بسيطها إلى أعقدها هي نتاج عملية برمجة لهذه الأجهزة. ويمكن تلخيص استخدامات لغات البرمجة وفق الخطوط العريضة التالية: تطوير تطبيقات للحواسب تطوير تطبيقات للويب تطوير تطبيقات للهواتف الذكية برمجة الآلات والتجهيزات القابلة للبرمجة تطوير وإدارة الخدمات السحابية تطوير أنظمة تشغيل تطوير أنظمة وآلات ذاتية التعلم مجالات الذكاء الاصطناعي والروبوتات أنشئ موقع إلكتروني لأعمالك بدون خبرة برمجية صمم موقع احترافي لأعمالك بالسحب والإفلات مع أكثر من 70 قالب جاهز وقابل للتخصيص ليناسب هويتك التجارية جرب سنديان الآن خاتمة تعرفنا في هذه المقال على لغات البرمجة التي نستخدمها في برمجة الحواسب والأجهزة الإلكترونية الذكية أو القابلة للبرمجة واستعرضنا الخطوط العامة لمكوّنات أي لغة برمجة ثم فصلنا في أنواعها وقلنا أنها قد تكون عامة الغرض أو مخصصة من ناحية الاستخدام أو لغات مكتوبة أو مرئية من ناحية إظهار الشيفرة وأنها مفسّرة أو مصرّفة من ناحية معالجة المعلومات. كما ذكرنا أن لغات البرمجة التي تقترب تعليماتها وظيفيًا من التعليمات التي يفهمها الحاسوب هي لغات منخفضة المستوى ويزداد مستواها باستخدامها تعليمات تنفذ وظائف أكبر من تعليمات الحاسوب الأساسية. ومررنا أخيرًا على مفهوم لغات السكربت واستخداماتها بشيء من التفصيل، وعلى مفهوم اللغات التوصيفية واختلافها عن لغات البرمجة. وبهذا الشكل نكون قد أحطنا ولو بالشيء اليسير بمفاهيم هذا العالم الواسع لتكون خطوتك الأولى في الإنطلاق إلى عالم البرمجة. اقرأ أيضًا المدخل الشامل لتعلم علوم الحاسوب كيف تتعلم البرمجة فوائد تعلم البرمجة دليلك إلى: لغات برمجة الألعاب المدخل الشامل لتعلم تطوير الويب وبرمجة المواقع المرجع الشامل إلى تعلم لغة بايثون البرمجة بلغة جافاسكربت مقارنة بين JavaScript و TypeScript مقارنة بين PHP و NodeJS
  15. تُعَدّ HTML أو لغة توصيف النصوص التشعبية بأنها الشيفرة التي تُستخدَم في هيكلة صفحات ويب ومحتوياتها، فقد تُنظَّم الصفحة مثلًا على هيئة مجموعة من المقاطع النصية أو قائمة من النقاط أو أن تعرض الصور أو جداول البيانات، وسنقدِّم لك في هذا المقال مجموعة معارف أساسية لفهم لغة HTML ووظائفها. ما هي HTML؟ تُعَدّ HTML بأنها لغة توصيفية تُعرِّف هيكلية المحتوى الذي تقدِّمه الصفحة، وتتألف اللغة من سلسلة من العناصر التي تستخدِمها لتضمين أو تغليف الأجزاء المختلفة من المحتوى لتظهر بطريقة محددة أو لتعمل بطريقة محددة، كما يمكن أن تربط الوسوم المغلقة enclosing tags كلمةً أو صورةً بمكان آخر، أو يمكنها عرض الكلمات عرضًا مائلًا أو تكبّر خط الكتابة أو تصغّره وهكذا، وإليك على سبيل المثال المحتوى التالي الذي لا يتعدى السطر: My cat is very grumpy إذا أردنا أن يظهر السطر كما هو، فيمكننا أن نجعله فقرةً نصيةً بتضمينه داخل وسمَي فقرة، أي <p> </p>: <p>My cat is very grumpy</p> تشريح عنصر HTML دعونا نستكشف عنصر الفقرة السابق بصورة أعمق: إن الأجزاء الرئيسية من العنصر هي: وسم البداية Opening tag: يشير هذا الوسم إلى النقطة التي يبدأ عندها العنصر أو التي يبدأ تأثيره عندها (بداية الفقرة النصية في حالتنا)، ويتكوّن من اسم العنصر (p في حالتنا) محاطًا بقوسَي زاوية. وسم النهاية Closing tag: يشير هذا الوسم إلى نهاية العنصر (نهاية الفقرة في حالتنا)، ويشبه وسم البداية لكنه يبدأ بشرطة أمامية / قبل اسم العنصر، كما يُعَدّ إغفال وسم النهاية من أكثر الأخطاء التي يرتكبها المبتدئون، وقد تفضي إلى نتائج غريبة بالفعل. المحتوى Content: يشير إلى المحتوى الفعلي للعنصر، وهو في حالتنا مجرد نص. العنصر Element: ويتكون من وسمَي البداية والنهاية والمحتوى معًا. يمكن أن تمتلك العناصر سمات attributes تبدو شبيهةً كما يلي: تتضمن السمات معلومات إضافية عن العنصر لا تريدها أنت أن تظهر مثل جزء من المحتوى الفعلي، فالكلمة class في الشكل السابق هي اسم السمة وقيمتها هي editor-note، إذ تسمح لك السمة class بإعطاء العنصر معرِّفًا عامًا يمكن استخدامه لتطبيق معلومات تنسيق على هذا العنصر أو أي عنصر تحمل السمة class فيه القيمة نفسها أو غيرها. لكتابة السمة لا بد من: مسافة فارغة بينها وبين اسم العنصر، أو بينها وبين السمة التي تسبقها في حال امتلك العنصر سمتَين أو أكثر. اسم السمة تليها إشارة المساواة =. قيمة السمة بين إشارتَي تنصيص. ملاحظة: يمكنك عدم وضع قيمة السمة بين إشارتي تنصيص إذا لم تتضمن رمز ASCII الخاص بالمسافة الفارغة أو أيّ من المحارف " أو ' أو ` أو = أو < أو >، لكن يفضَّل وضع قيمة السمة دائمًا داخل إشارتَي التنصيص لأنها تجعل الشيفرة واضحةً ومفهومةً. دورة تطوير واجهات المستخدم ابدأ عملك الحر بتطوير واجهات المواقع والمتاجر الإلكترونية فور انتهائك من الدورة اشترك الآن العناصر المتداخلة يمكن وضع عناصر داخل عناصرأخرى أيضًا وهذا ما يُعرف بالتداخل nesting، فإذا أردنا أن تظهر الكلمة "very" في الفقرة "My cat is very grumpy" بخط سميك، فيمكن تغليف هذه الكلمة داخل العنصر <strong></strong> كما يلي: <p>My cat is <strong>very</strong> grumpy.</p> لكن عليك التأكد دومًا من أنّ العناصر متداخلة بصورة صحيحة، فقد فتحنا في المثال السابق العنصر <p> أولًا ثم <strong>، وبالتالي توجَّب إغلاق العنصر الثاني آخر من فتح على الشكل <strong/> ثم إغلاق الأول <p/>، أي أنّ التداخل التالي غير صحيح: <p>My cat is <strong>very grumpy.</p></strong> لا بد من فتح وإغلاق العناصر بالصورة الصحيحة لكي تظهر بوضوح داخل أو خارج عنصر آخر، فإذا تداخلت بالشكل الذي عرضناه في الشيفرة السابقة، فسيحاول المتصفح أن يخمّن بأفضل شكل ما تحاول قوله، مما قد يتسبب بظهور نتائج غير متوقعة، فلا تفعل ذلك. العناصر الفارغة تُدعى بعض العناصر التي لا تحمل أيّ محتوى بالعناصر الفارغة Empty elements مثل العنصر <img> الذي استخدمناه سابقًا: <img src="images/firefox-icon.png" alt="My test image"> إذ يمتلك العنصر سمتَين ولا يمتلك وسم نهاية <img/> أو محتوى، والسبب أن عنصر الصورة لا يغلِّف محتوًى لكي يتأثر بوجود أو عدم وجود وسم النهاية، فوظيفته هي إدراج صورة في صفحة HTML في المكان الذي يظهر فيه. تشريح مستند HTML سنشرح الآن الطريقة التي نجمّع بها عناصر HTML المفردة لنشكِّل صفحةً كاملةً، فلنعُد قليلًا إلى الشيفرة التي وضعناها في الملف index.html والذي رأيناه أول مرة في المقال السابق: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>My test page</title> </head> <body> <img src="images/firefox-icon.png" alt="My test image"> </body> </html> لدينا هنا الأشياء التالية: <DOCTYPE html!>: وهو عنصر تمهيدي مطلوب، فقد كانت الغاية من هذا العنصر في الأيام الأولى (1991/1992) أن يعمل على أساس رابط إلى مجموعة من القواعد التي ينبغي أن تحققها صفحة HTML لكي تُعَدّ صفحةً جيدةً بما في ذلك الاكتشاف التلقائي للأخطاء وغيرها من النقاط المفيدة، وليس لهذا العنصر في أيامنا هذه وظيفةً سوى التأكد من سلوك المستند للسلوك المطلوب، وهذا كل ما عليك معرفته حاليًا. <html></html>: يضم هذا العنصر كامل محتوى الصفحة، ويُدعى أحيانًا بالعنصر الجذري root element. <head></head>: يعمل على أساس حاوية لتضع فيها كل الأشياء التي تريدها في صفحتك ولكنها لا تمثِّل محتوًى تريد إظهاره لمتابعيك مثل الكلمات المفتاحية ووصف الصفحة الذي تريد إظهاره عندما يعرضها محرك البحث على أساس نتيجة وقواعد CSS لتنسيق محتوى الصفحة ونوع المحارف التي تستخدمها في الصفحة وغيرها الكثير. <"meta charset="utf-8>: يضبط هذا العنصر مجموعة المحارف التي تستخدِمها في الصفحة، وهنا اخترنا المجموعة UTF-8 التي تضم محارف الأغلبية الساحقة من اللغات المكتوبة، إذ تستطيع هذا المحارف أن تعرض الآن أيّ محتوى نصي بأيّ لغة قد تضعه في صفحتك، ولا مبرر لعدم ضبط مجموعة المحارف المستخدَمة، كما ستساعدك على تحاشي الكثير من الأخطاء لاحقًا. <title></title>: يضبط هذا العنصر عنوان صفحتك الذي يظهر أعلى المتصفح عند تحميل الصفحة، كما يُستخدم لوصف الصفحة عندما تضيفها إلى قائمة الصفحات المفضلة. <body></body>: يضم المحتوى الذي تريد عرضه على زائري صفحتك بأكمله، سواءً كان نصًا أو صورًا أو فيديو أو ألعاب أو أيّ شيء آخر. الصور لنعُد إلى العنصر <img> مجددًا: <img src="images/firefox-icon.png" alt="My test image"> يُدرِج هذا العنصر صورةً ضمن الصفحة في المكان الذي تضعه فيه، إذ نحدِّد الصورة المعروضة بكتابة عنوانها على أساس قيمة للسمة src -أي مصدر source-، كما يمتلك عنصر الصورة أيضًا سمةً أخرى تُدعى alt -أي بديل alternative-، إذ تُستخدَم هذه السمة لعرض نص بديل عن الصورة للأشخاص الذين لا يستطيعون رؤيتها لأسباب عديدة منها: المشاكل البصرية: إذ يستخدِم الكثيرون من فاقدي البصر أدوات تُدعى قارئات الشاشة تستطيع قراءة النص البديل. أخطاء في عرض الصورة: حاول مثلًا تغيير المسار الموجود داخل السمة في مثالنا السابق ثم احفظ الملف وأعد تحميله ضمن المتصفح، إذ لن تظهر الصورة، وإنما نص كما يلي: ما يهم فعلًا في النص البديل هو أن يكون وصفيًا تمامًا لمحتوى الصورة، إذ ينبغي أن يزوِّد النص القارئ بمعلومات كافية ليكوِّن فكرةً جيدةً عن محتوى الصورة، فالنص البديل "My test image" في مثالنا غير جيد على الإطلاق، وسيكون الأنسب لشعار فايرفوكس هو "Firefox logo: a flaming fox surrounding the Earth" وبالعربية "شعار فايرفوكس: ثعلب ملتهب يحيط بالكرة الأرضية"، أي حاول إذًا ابتكار نصوص بديلة جيدة تصف الصورة. توصيف محتوى صفحات HTML يغطي هذا القسم من المقال عناصر HTML الأساسية لتوصيف محتوى الصفحات. العناوين تساعدك العناوين Headings على عرض أجزاء من محتوى الصفحة على أساس عناوين رئيسية أو فرعية، وكما هو حال الكتب التي تحمل عنوانًا رئيسيًا وعناوين للفصول وعناوين فرعية في كل فصل، تحمل صفحة HTML الميزة ذاتها، إذ تضم اللغة عناوين تتدرج إلى مستويات ستة من <h1> إلى <h6>، وغالبًا ما ستستخدِم ثلاث إلى أربع مستويات كحد أقصى. <!-- 4 heading levels: --> <h1>My main title</h1> <h2>My top level heading</h2> <h3>My subheading</h3> <h4>My sub-subheading</h4> ملاحظة: كل ما يرد بين الوسمين <-- و --!> في صفحة HTML هو تعليق سيتجاهل المتصفح محتواه عندما يصيّر الشيفرة ولن يُعرَض على الشاشة، إذ تساعدك التعليقات في وضع ملاحظات مفيدة عن مقطع محدَّد من الشيفرة أو عن منطق معيّن اعتمدته. حاول الآن وضع عنوان فرعي في صفحة HTML التي نبنيها فوق العنصر <img> مباشرةً. ملاحظة: تملك العناوين من المستوى الأول تنسيقًا ضمنيًا خاصًا، لذلك لا تستخدِمها لتكبير النص أو جعله سميكًا Bold لأنها تُستخدَم لغايات أخرى مثل سهولة الوصول أو لأسباب أخرى مثل تحسين محركات البحث، لذا حاول أن تعطي تدرجًا منطقيًا للعناوين في صفحتك دون تجاوز أيّ مستوى إلى ما دونه مثل الانتقال من 4 إلى 2 مباشرةً دون المرور بالمستوى 3. الفقرات النصية يحتوي العنصر <p> كما شرحنا سابقًا مقطعًا نصيًا، وستستخدمه بكثرة عندما ترمز المحتوى النصي في الصفحة: <p>This is a single paragraph</p> يمكنك على سبيل التجربة إضافة عينة من نص ما على هيئة فقرة نصية أو أكثر تحت العنصر <img> في الصفحة التجريبية التي نبنيها. القوائم تُصنَّف كمية لا بأس بها من محتوى ويب على صورة قوائم Lists وتمتلك HTML القدرة على ذلك، إذ تتضمن القائمة عنصرين على الأقل، وأكثر القوائم شيوعًا هي القوائم المرتبة Ordered وغير المرتبة Unordered: القوائم المرتبة: توضع بنود هذه القائمة ضمن العنصر <ol>، وتُستخدَم لعرض القوائم التي يهمنا فيها ترتيب العناصر مثل وصفات تحضير الطعام أو ترتيب فِرق دوري لكرة القدم وغيرها. القوائم غير المرتبة: توضع بنود هذه القائمة ضمن العنصر <ul>، وتُستخدَم لعرض القوائم التي لا نهتم فيها لترتيب العناصر مثل قائمة التسوق. يوضع كل بند من بنود القائمة ضمن العنصر <li> -أي بند من قائمة list item-، فإذا أردنا مثلًا تحويل جزء من الفقرة النصية التالية إلى قائمة: <p>At Mozilla, we’re a global community of technologists, thinkers, and builders working together ... </p> فيمكننا تعديل توصيف محتوى الفقرة ليصبح كما يلي: <p>At Mozilla, we’re a global community of</p> <ul> <li>technologists</li> <li>thinkers</li> <li>builders</li> </ul> <p>working together ... </p> الروابط تُعَدّ الروابط العنصر الأكثر أهميةً، فهي ما تجعل من الويب شبكةً حقيقيةً، إذ نستخدِم العنصر البسيط <a> لإنشاء رابط، وهو اختصار للكلمة "anchor"، ولتجعل جزءًا من الفقرة النصية السابقة رابطًا اتبع الخطوات التالية: اختر جزءًا من النص وليكن "Mozilla Manifesto". ضَع هذا الجزء ضمن الوسمَين <a></a> كما يلي: <a>Mozilla Manifesto</a> زوّد الرابط بعنوان للانتقال إليه من خلال السمة href كما يلي: <a href="">Mozilla Manifesto</a> اكتب العنوان المطلوب بين علامَتي تنصيص السمة href كما يلي: <a href="https://www.mozilla.org/en-US/about/manifesto/">Mozilla Manifesto</a> قد تصل إلى نتيجة غير متوقعة إذا حذفت بداية العنوان الذي يُدعى بروتوكول، أي //:https أو ://http، لذلك تحقق من وصولك إلى المكان المطلوب عند النقر على الرابط. ملاحظة: قد يبدو اختيار اسم السمة href غامضًا في البداية، فإذا صعُب عليك الأمر، تذكر أنه مشتق من كلمتَي "مرجع إلى نص تشعبي hypertext reference" خلاصة إذا اتبعت التوجيهات التي أشرنا إليها خلال اطلاعك على هذا المقال، فستبدو صفحة الويب التي نبنيها بالشكل التالي تقريبًا إذا لم تغير في العناوين أو النصوص. إذا لم تجد عملك صحيحًا، فيمكنك دائمًا موازنة ما فعلته مع النسخة الجاهزة على جيت-هاب. ترجمة -وبتصرف- للمقال HTML Basics. اقرأ أيضًا المقال السابق: التعامل مع الملفات في عملية تطوير موقع الويب توثيق لغة HTML العربي مكونات الويب: عناصر HTML المخصصة وقوالبها دليل: تعلم لغة HTML
  16. تتمتع البرمجيات بدورة حياة كاملة ابتداءً من التصورات الأولية وانتقالًا إلى البرمجة ومنها إلى إصدار البرنامج إلى المستخدم النهائي وصيانته. سنتعرف في هذا المقال على مفهوم الحاويات Containers، وهي أداةٌ عصرية تُستخدم في المراحل النهائية من دورة حياة البرنامج. تُغلّف الحاويات تطبيقك ضمن حزمةٍ واحدة تتضمن كل الاعتماديات التي يحتاجها التطبيق، وتكون النتيجة حاويةً يمكن أن تعمل بصورةٍ مستقل عن غيرها من الحاويات. تمنع الحاويات التطبيق من الوصول واستخدام ملفات وموارد الأجهزة، وقد يعطي مطورو التطبيقات بعض الأذونات للبرنامج الذي تضمه الحاوية في الوصول إلى ملفات محددة وتخصيص موارد محددة أيضًا. ولنكون أكثر دقة، تُعدّ الحاويات بمثابة بيئات عمل افتراضية على مستوى نظام التشغيل، وأكثر ما يشابهها من الناحية التقنية هي الآلات الافتراضية Virtual machines التي تساعد على تشغيل عدة أنظمة تشغيل على جهاز فيزيائي واحد. وينبغي على الآلات الافتراضية تشغيل النظام بأكمله، بينما تُشغّل الحاوية التطبيق بالاستفادة من نظام التشغيل الذي يستضيفها. وهكذا سيكون الاختلاف بين الحاويات والآلات الافتراضية هي أنك لن تلاحظ إلا نادرًا جدًا زيادة في تحميل النظام عند استخدام الحاويات، فهي عادةً ما تحتاج إلى تنفيذ عملية واحدة. يمكن للحاويات أن تكون سريعةً وقابلةً للتوسع لكونها خفيفة الوزن موازنةً بالآلات الافتراضية على الأقل، وطالما أنها تعزل التطبيق الذي يُنفَّذ ضمنها، سيساعد ذلك في الحصول على نفس الأداء تمامًا وفي أي مكان. وبالنتيجة، تُعد الحاويات خيارًا ممتازًا في أي بيئة تشغيل سحابية أو للتطبيقات التي يكثر مُستخدميها. تدعم معظم الخدمات السحابية، مثل AWS و Google Cloud و Microsoft Azure الحاويات وبأشكال متعددة، بما في ذلك AWS Fargate و Google Cloud Run وهما خدمتان لتشغيل الحاويات دون خادم serverless إذ لا حاجة مطلقًا لتنفيذ الحاوية إن لم تُستخدم. يمكن أيضًا تثبيت مقومات تشغيل البيئة على أغلب الأجهزة وتشغيل الحاويات بنفسك، بما في ذلك حاسوبك الخاص. طالما أن الحاويات تعمل في البيئات السحابية وحتى أثناء تطوير التطبيقات، فما الفائدة من الفعلية منها إذًا؟ إليك حالتي الاستخدام التاليتين: الحالة الأولى: عندما تحاول أن تطور تطبيقًا من المفترض أن يعمل على نفس الجهاز لدعم إصدار أقدم، فكلاهما يحتاج إلى تثبيت نسخ مختلفة من Node.js. بإمكانك استخدام nvm أو آلة افتراضية أو حتى اختراع طريقة سحرية لجعل النسختين تعملان معًا، لكن تبقى الحاويات حلًا ممتازًا لأنك تستطيع تشغيل كلا التطبيقين كلًا ضمن حاويته الخاصة، فهما معزولان عن بعضهما ولا يتداخلان. الحالة الثانية: عندما يعمل تطبيقك على حاسوبك وتحاول نقله إلى خادم، فمن الأمور الشائعة ألا يعمل التطبيق على الخادم علمًا أنه يعمل جيدًا على حاسوبك. قد يكون الأمر اعتماديات مفقودة، أو اختلافات أخرى في بيئات التشغيل. في حالة كهذه، تظهر الحاويات مثل حل رائع، لأنها قادرة على تنفيذ التطبيق في نفس بيئة التنفيذ على حاسوبك أو على الخادم. وقد لا يكون حلًا مثاليًا نظرًا لاختلاف العتاد الصلب، لكن يمكنك أن تجعل الاختلافات في بيئات التنفيذ محدودةً جدًا. قد تسمع أحيانًا عبارات من قبيل "يعمل ضمن حاويتي"، إذ تصف هذه العبارة الحالة التي يعمل فيها التطبيق جيدًا ضمن حاوية على جهازك لكنها تُخفق في تنفيذ التطبيق عندما تنقلها إلى الخادم. هذه العبارة هي تلاعب في العبارة "يعمل على جهازي"، فالحاويات مصممةٌ لتعمل، وغالبًا ما تكون هذه الحالة خطأ في الاستخدام. حول هذا القسم لن نركز اهتمامنا في هذا القسم على شيفرة جافاسكربت JavaScript، بل تهيئة البيئة التي سيُنفَّذ فيها التطبيق. لهذا قد لا تحتوي التمارين أية شيفرات. كما ستجد التطبيق جاهزًا على غيت هب GitHub ويحتاج فقط إلى التهيئة. ينبغي رفع التمارين إلى مستودع GitHub واحد يضم كل الشيفرات المصدرية وإعدادات التهيئة التي نفّذتها في هذا القسم. لا بد أن تمتلك معرفة مبدئية بالتعامل مع Node و Express و React، وعليك إكمال الأقسام الجوهرية من 1 إلى 5 للمتابعة في هذا القسم. التمرين 12.1 تنبيه سنخرج في هذا القسم من دائرة الراحة الخاصة بمطوري JavaScript، وقد يتطلب منك ذلك جولةً لتتآلف مع أوامر الصدفة shell أو سطر الأوامر command line أو محث الأوامر command prompt أو الطرفية terminal قبل أن تبدأ؛ فإذا كنت تستخدم واجهة مستخدم رسومية ولم تلمس أبدًا طرفية لينوكس Linux أو ماك Mac، وشعرت بأنك تائه لا تعرف كيف تبدأ حل التمرين، ننصحك بالاطلاع على القسم الأول من كتاب "Computing tools for CS studies"، فهو يضم كل ما تحتاجه لمتابعة العمل هنا. التمرين 12.1: استخدام الحاسوب دون واجهة مستخدم رسومية الخطوة الأولى: اقرأ النص الذي يلي التنبيه. الخطوة الثانية: نزّل مستودع التمرين واجعله مستودع تسليم التمارين لهذا القسم. الخطوة الثالثة: انتقل إلى العنوان "http://helsinki.fi" واحفظ الخرج ضمن ملف، ثم احفظ الملف ضمن مستودعك بالاسم "script-answers/exercise12_1.txt". وتذكر أنك أنشأت بالفعل المجلد "script-answers" في الخطوة السابقة. أدوات العمل تحتاج إلى بعض الأدوات الأساسية التي تختلف وفقًا لنظام التشغيل. ستحتاج WSL 2 terminal على نظام ويندوز. الطرفية على نظام ماك. سطر الأوامر على نظام لينوكس. تثبيت كل ما تحتاجه للعمل سنبدأ بتثبيت البرامج التي نحتاجها، وقد تشكل هذه الخطوة إحدى العقبات المحتملة، إذ لا بد من أذونات واسعة النطاق على جهازك طالما أننا نتحدث عن شكلٍ من البيئات الافتراضية على مستوى نظام التشغيل، وهذا ما يتطلب الولوج إلى نواته. تتركز مادة القسم حول دوكر Docker، وهو يمثّل مجموعةً من المنتجات التي سنستخدمها في تشكيل وإدارة الحاويات، لكن إن لم تستطع تثبيته فلن تتمكن لسوء الحظ من متابعة العمل في هذا القسم. ستختلف إرشادات عملية تثبيت المنتجات على حاسوبك وفقًا لنظام التشغيل، لذلك عليك أن تجد الإرشادات الصحيحة لتثبيت المنتج من خلال صفحة Docker المخصصة للغرض، وتذكر وجود خيارات عدة للتثبيت على نفس نظام التشغيل. لنفترض أن كل شيء جرى على ما يُرام، لهذا سنتأكد من تطابق الإصدارات، إذ لا بُد أن يكون إصدار نسختك أعلى من هذا الإصدار: $ docker -v Docker version 20.10.5, build 55c4c88 الحاويات والصور هناك مفهومان جوهريان فيما يتعلق بالحاويات، ومن السهل الخلط بينهما: الحاوية والصورة؛ إذ أن الحاوية هي نسخة التشغيل من الصورة، لهذا فكلا العبارتين التاليتين صحيحة: تضم الصور الشيفرات والاعتماديات والإرشادات اللازمة لتشغيل التطبيق. تُحزّم الحاويات البرنامج ضمن وحدات معيارية. وبالتالي لا عجب من سهولة الخلط بينهما. للمساعدة في تجاوز الأمر، تُستخدم كلمة "حاوية" للإشارة إلى كلا المفهومين. لكنك لن تتمكن أبدًا من بناء حاوية أو تنزيلها لأنها موجودةٌ فقط أثناء التشغيل؛ أما الصورة فهي ملفاتٌ غير قابلة للتغيير، وبالتالي لن تكون قادرًا على تعديل صورة بعد إنشائها. لكن يمكنك استخدام صورة موجودة لإنشاء صورة جديدة بإضافة طبقات جديدة فوق القديمة. وبعبارات مجازية مستوردة من عالم الطبخ: الصورة هي وجبة مطبوخة مسبقًا ومجمّدة. الحاويات هي تلك الوجبة اللذيذة الجاهزة للأكل. يُعد دوكر Docker أكثر تقنيات إنشاء الحاويات شهرةً وقد أبدع المعايير التي تستخدمها معظم تقنيات إنشاء الحاويات حاليًا. هذا المنتج تقنيًا هو مجموعة منتجات تساعد في إدارة الصور والحاويات وتمنحنا القدرة على العمل مع كل ميزاتها، إذ سيتولى Docker مثلًا مهمة تحويل الصور إلى حاويات. هناك أداةٌ تُدعى Docker Compose لإدارة حاويات دوكر، إذ تسمح لك بتنسيق العمل على عدة حاويات في نفس الوقت، لهذا سنستخدمها في هذا القسم لبناء بيئة تطوير محلية مركّبة، ولم يعد هناك حاجةٌ لنثبت Node عند الانتهاء من إعداد بيئة التطوير هذه. يبقى هناك مجموعةٌ من المصطلحات التي علينا أن نعرّج عليها، لكننا سنتجاهل ذلك مؤقتًا لنتعلم أكثر عن دوكر. لنبدأ من الأمر docker container run الذي يُستخدم لتشغيل الصور ضمن حاوية. لهذا الأمر الهيكلية التالية: container run *IMAGE-NAME* التي تخبر دوكر بإنشاء حاويةٍ من الصورة. ومن الميزات الجيدة لهذا الأمر هو إمكانية تشغيل حاوية حتى لو لم تُنزّل الحاوية على الجهاز بعد. لننفِّذ الأمر: § docker container run hello-world ستكون النتيجة العديد من المُخرجات، لهذا سنحاول فصلها إلى عدة أقسام كي نفسر هذه المخرجات معًا. رقمنا الأسطر لسهولة شرح ما فيها، فلن تجد أية أرقام لأسطر الخرج في الواقع: 1. Unable to find image 'hello-world:latest' locally 2. latest: Pulling from library/hello-world 3. b8dfde127a29: Pull complete 4. Digest: sha256:5122f6204b6a3596e048758cabba3c46b1c937a46b5be6225b835d091b90e46c 5. Status: Downloaded newer image for hello-world:latest لم يعثُر الأمر على الصورة في جهازك، لذلك نزّلها من مُسجّل مجاني يُدعى Docker Hub. بإمكانك الاطلاع على صفحة الويب الخاصة بهذا المسجّل باستخدام المتصفح. تنص الرسالة الأولى أن الصورة "hello-world:latest" غير موجودة بعد، وهذا ما يكشف بعض التفاصيل عن الصور بحد ذاتها، إذ تتألف أسماء الصور من عدة أجزاء بصورةٍ مشابهة لعناوين URL وهي على الشكل: registry/organisation/image:tag في حالتنا يعوَّض عن الحقول الثلاثة المفقودة بالقيم الافتراضية: index.docker.io/library/hello-world:latest يعرض السطر الثاني اسم المنظمة والمكتبة التي تحصل على الصورة منها، ويُختصر عنوان المكتبة في Docker Hub إلى _. يعرض السطران الثالث والخامس الحالة فقط، لكن ما يضمه السطر الرابع هو المهم. لكل صورة معرّف تعمية مختزل وفريد digest بناءً على الطبقات التي بُنيت منها الصورة. وفي واقع الأمر تُنشئ كل تعليمة أو خطوة استُخدمت في بناء الصورة طبقةً فريدة، لهذا يستخدم دوكر معرّف التعمية المختزل لتمييز أن الصورة بقيت كما هي. وهكذا نرى أن خرج الأمر السابق هو سحب ثم إخراج معلومات عن الصورة. تخبرنا الحالة بعد ذلك أن نسخةً جديدةً من الصورة "hello-world:latest" قد نُزّلت بالفعل. بإمكانك سحب الصورة باستخدام الأمر docker image pull hello-world ومتابعة ما سيحدث. يمثل التالي خرجًا ناتجًا عن حاوية، ويشرح ما يجري عند تنفيذ الأمر docker container run hello-world: Hello from Docker! This message shows that your installation appears to be working correctly. To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal. To try something more ambitious, you can run an Ubuntu container with: $ docker container run -it ubuntu bash Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/ For more examples and ideas, visit: https://docs.docker.com/get-started/ يتضمن الخرج بعض الأشياء الجديدة التي يجب تعلّمها مثل: Docker daemon: وهي خدمة في الخلفية تتحقق من عمل الحاوية. Docker client: واجهة للتفاعل مع الخدمة السابقة. لقد تفاعلنا مع الصورة الأولى وأنشأنا حاوية من هذه الصورة، وتلقينا الخرج أثناء تنفيذ الحاوية. التمرين 12.2 لا تتطلب منك بعض هذه التمارين كتابة أية شيفرات أو إعدادات تهيئة في ملف. عليك أن تستخدم في هذا التمرين الأمر script لتسجيل الأوامر التي استخدمتها. جرّب ذلك بتنفيذ التعليمة script لتبدأ التسجيل ثم الأمر "echo "hello لتوليد خرج ما، ومن ثم التعليمة exit لإيقاف التسجيل. تُسجِّل تلك التعليمات أفعالك في ملف اسمه "typescript". يمكنك أيضًا نسخ ولصق جميع الأوامر التي استخدمتها في ملف نصي إن لم تستطع استخدام script. التمرين 12.2 تشغيل حاويتك الثانية استخدم script لتسجيل ما تفعله واحفظ الملف بالاسم "script-answers/exercise12_2.txt". نفّذ ما يلي: الخطوة الأولى: شغّل حاوية بنفس أسلوب تشغيل الحاوية، إذ ستربطك هذه الخطوة مباشرةً مع الحاوية عبر تعليمات bash وستكون قادرًا على الوصول إلى كل الملفات والأدوات الموجودة ضمن الحاوية. لهذا نفذ الخطوات التالية ضمن الحاوية. الخطوة الثانية: أنشئ المجلد "usr/src/app/". الخطوة الثالثة: أنشئ الملف "usr/src/app/index.js/". الخطوة الرابعة: نفّذ التعليمة exit للخروج من الحاوية. ابحث عن طريقة إنشاء الملفات أو المجلدات بمساعدة محركات البحث إن لزم الأمر. صورة Ubuntu يحتوي الأمر التالي المُستخدم في تشغيل حاوية "ubuntu": docker container run -it ubuntu bash بعض الإضافات عن أمر تشغيل الحاوية "hello-world". سنستخدم التعليمة help-- لفهم الموضوع أكثر، وسنجتزئ بعض المعلومات التي تتعلق بحديثنا مما يرد في خرج العملية: $ docker container run --help Usage: docker container run [OPTIONS] IMAGE [COMMAND] [ARG...] Run a command in a new container Options: ... -i, --interactive Keep STDIN open even if not attached -t, --tty Allocate a pseudo-TTY ... تتحقق الرايتان أو الخياران it- من قدرتنا على التفاعل مع الحاوية، ثم نحدد بعد ذلك أن الصورة التي نشغلها هي "ubuntu"، ثم لدينا الأمر bash الذي يُنفَّذ داخل الحاوية عندما نشغلها. يمكنك تجريب أوامر أخرى يمكن للصورة أن تُنفذها، مثل: docker container run --rm ubuntu ls إذ يرتب الأمر ls كل الملفات في المجلد ضمن قائمة، بينما يُزيل الأمر rm-- الحاوية بعد التنفيذ، فلا تُحذف الحاويات تلقائيًا عادةً. لنتابع الآن مع أول حاوية "ubuntu" من خلال الملف "index.js" داخلها. لقد توقفت الحاوية عن العمل في اللحظة التي خرجنا منها. ولاستعراض جميع الحاويات، نستخدم الأمر container ls -a. إذ تستعرض الراية a- أو all-- كل الحاويات التي خرجنا منها توًا. $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES b8548b9faec3 ubuntu "bash" 3 minutes ago Exited (0) 6 seconds ago hopeful_clarke أمامنا خياران يتعلقان بالتخاطب مع الحاوية: المعرّف في العمود الأول الذي يمكن استخدامه للتفاعل مع الحاوية في أي وقت، كما تقبل معظم الأوامر تمرير اسم الحاوية إليها. لاحظ أن الحاوية في مثالنا قد ولّدت تلقائيًا باسم "hopeful_clarke". تُظهر حالة الحاوية أننا خرجنا منها منذ 6 ثوان ويمكنك تشغيلها مجددًا باستخدام الأمر start الذي يقبل معرّف الحاوية id أو اسمها: $ docker start hopeful_clarke hopeful_clarke يشغّل الأمر الحاوية نفسها التي عملنا عليها سابقًا، لكن انتبه إلى أننا أغفلنا لسوء الحظ استخدام الراية interactive-- وبالتالي لن نتمكن من التفاعل معها. مع ذلك فالحاوية تعمل فعلًا، ويُظهر تنفيذ الأمر container ls -a ذلك، إلا أننا لا نستطيع التواصل معها: $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES b8548b9faec3 ubuntu "bash" 7 minutes ago Up (0) 15 seconds ago hopeful_clarke يمكنك أيضًا تجاهل الراية a- في الأمر السابق لعرض الحاويات التي تعمل فقط: $ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES 8f5abc55242a ubuntu "bash" 8 minutes ago Up 1 minutes hopeful_clarke لنُنهي عمل الحاوية باستخدام الأمر kill يليه اسم الحاوية أو معرّفها ثم نحاول ثانية: $ docker kill hopeful_clarke hopeful_clarke يُرسل الأمر الإشارة SIGKILL إلى العملية ليجبرها على التوقف، ونستطيع التأكد من حالتها بتنفيذ الأمر container ls -a: $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES b8548b9faec3 ubuntu "bash" 26 minutes ago Exited 2 seconds ago hopeful_clarke لنشغل الحاوية الآن في وضع التفاعل: $ docker start -i hopeful_clarke root@b8548b9faec3:/# سنجري بعض التعديلات على الملف"index.js" بإضافة شيفرة جافا سكربت، لكن تنقصنا الأداة اللازمة لذلك. سيكون المحرر Nano خيارًا جيدًا حاليًا، لذلك سننزّله ونشغله. يمكنك الاطلاع على طريقة استخدام هذا المحرر بإجراء بحث بسيط ضمن أي محرك بحث. انتبه أنه لا حاجة لاستخدام التعليمة sudo فنحن فعليًا داخل المجلد الجذري: root@b8548b9faec3:/# apt-get update root@b8548b9faec3:/# apt-get -y install nano root@b8548b9faec3:/# nano /usr/src/app/index.js وهكذا يكون Nano جاهزًا للاستخدام. التمرينان 12.3 - 12.4 حاول أن تحل التمرينين التاليين: التمرين 12.3: 101 Ubuntu استخدم script لتسجيل ما تفعله واحفظ الملف بالاسم "script-answers/exercise12_3.txt" واستخدم المحرر النصي Nano لتعديل الملف "usr/src/app/index.js/" ضمن الحاوية بإضافة السطر التالي: console.log('Hello World') يمكنك الاطلاع على طريقة استخدام محرر Nano بإجراء بحث بسيط ضمن أي محرك بحث. التمرين 12.4: 102 Ubuntu استخدم script لتسجيل ما تفعله واحفظ الملف بالاسم "script-answers/exercise12_4.txt" ثم ثبّت Node طالما أنك ضمن الحاوية، ثم شغِّل الملف "index" باستخدام الأمر ‍‍node /usr/src/app/index.js. من الصعب أحيانًا إيجاد تعليمات واضحة لتثبيت Node لهذا إليك بعض الأوامر التي يمكنك نسخها ولصقها: curl -sL https://deb.nodesource.com/setup_16.x | bash apt install -y nodejs لا بد أيضًا من تثبيت curl ضمن الحاوية بطريقة مشابهة لتثبيت Nano، وتأكد بعد اكتمال التثبيت من قدرتك على تنفيذ شيفرتك داخل الحاوية. root@b8548b9faec3:/# node /usr/src/app/index.js Hello World أوامر دوكر أخرى بعد أن ثبتنا Node ضمن الحاوية يمكننا تنفيذ شيفرة جافاسكريبت JavaScript داخلها. لنحاول الآن إنشاء صورة جديدة من الحاوية بعد التعديلات التي أجريناها. للأمر الصيغة التالية: "commit" + اسم أو معرف الحاوية + اسم الصورة الجديدة. يمكنك استخدام الأمر container diff للتحقق من التغييرات بين الصورة الأصلية والجديدة. $ docker commit hopeful_clarke hello-node-world كما يمكنك عرض قائمة بالصور الموجودة باستخدام image Is على النحو التالي: $ docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE hello-node-world latest eef776183732 9 minutes ago 252MB ubuntu latest 1318b700e415 2 weeks ago 72.8MB hello-world latest d1165f221234 5 months ago 13.3kB وستتمكن من تشغيل الصورة الجديدة على النحو التالي: docker run -it hello-node-world bash root@4d1b322e1aff:/# node /usr/src/app/index.js هناك عدة طرق لتحقيق النتيجة نفسها. لهذا دعونا نتحوّل إلى حل أفضل، وسنبدأ أولًا بإزالة الحاوية القديمة باستخدام الأمر container rm: $ docker container ls -a CONTAINER ID IMAGE COMMAND CREATED STATUS NAMES b8548b9faec3 ubuntu "bash" 31 minutes ago Exited (0) 9 seconds ago hopeful_clarke $ docker container rm hopeful_clarke hopeful_clarke أنشئ الملف "index.js" في المجلد الحالي واكتب التعليمة ('console.log('Hello, World ضمنها. لا حاجة للحاويات بعد، ولنتفادى أيضًا تثبيت Node. هناك العديد من الصور المفيدة الجاهزة للاستخدام يقدمها Docker Hub، لهذا سنستخدم الصورة " https://hub.docker.com/_/Node" التي تضم Node، وعلينا فقط اختيار الإصدار المناسب. كما تساعد الراية name-- في الأمر container run على تسمية الحاوية: $ docker container run -it --name hello-node node:16 bash سننشئ الآن مجلدًا للشيفرة ضمن الحاوية: root@77d1023af893:/# mkdir /usr/src/app وطالما أننا ضمن الحاوية، شغّل نسخة جديدة من الطرفية ونفّذ الأمر container cp لنسخ الملف من مكان وجوده في جهازك إلى الحاوية: $ docker container cp ./index.js hello-node:/usr/src/app/index.js يمكنك الآن تنفيذ الأمر ضمن الحاوية، كما يمكن تحضيرها على هيئة صورة جديدة، لكن هناك حل أفضل أيضًا. وهذا ما سنتابع البحث فيه لاحقًا. ترجمة -وبتصرف- للفصل Introduction to Containers من سلسلة Deep Dive Into Modern Web Development اقرأ أيضًا أبرز المفاهيم التي يجب عليك الإلمام بها عن الحاويات حاوية دوكر Docker ومخزن APCu في PHP كيفية تثبيت دوكر Docker على فيدورا لينكس
  17. سنعرض في هذا المقال طريقة التعامل مع اﻹعدادات اﻷساسية لنظام التشغيل لينكس أوبونتو، من خلال تطبيق "اﻹعدادات" الذي يسمح لك من خلال واجهة رسومية بسيطة وقوية، بضبط معظم ما تحتاجه بصفتك مستثمرًا للنظام، وذلك من إعدادات تتعلق بالعتاد الصلب والاتصال والتطبيقات والخصوصية والحماية واﻹنترنت وإعدادات اللغة وسهولة الوصول أو (اﻹتاحة)، وغيرها من اﻹعدادات. ننصحك قبل قراءة المقال أن تطلع على طريقة التعامل مع سطح مكتب أوبونتو وكذلك طريقة التعامل مع المجلدات والملفات. للوصول إلى هذا التطبيق، انقر على زر "أظهر التطبيقات" ضمن شريط التطبيقات لتظهر لك نافذة التطبيقات، ثم انقر على تطبيق "اﻹعدادات"؛ كما يمكنك الوصول إلى التطبيق مباشرةً من شريط المهام، أو بالنقر على سطح المكتب بالزر اﻷيمن لمؤشر الفأرة، واختيار "إعدادات settings". يعرض الشريط الجانبي للنافذة قوائم منفصلة من اﻹعدادات، تتعلق كل قائمة منها بمجموعة محددة، مثل الاتصالات والمظهر والتنسيقات المحلية واﻹقليمية، وغير ذلك، وسنناقش كل منها بشيء من التفصيل. ملاحظة: قد يتغير شريط المهام في نافذة التطبيق وفقًا للموضوع الذي نضبط إعداداته، إذ تظهر أحيانًا أشرطة مهام إضافية أو أزرار قوائم إضافية لتطبيق التغييرات التي نريدها بسرعة. إعدادات شبكات الاتصال يتيح لك أبونتو الاتصال مع معظم شبكات الاتصالات السلكية واللاسلكية، وحزم الاتصال العريضة عبر شبكات الهاتف المحمول وغيرها الكثير، لذلك سنطلع في الفقرات القادمة على طريقة إعداد وضبط بعض أنواعها. 1. إعداد واي فاي WiFi بالنقر على هذا الخيار، يبدأ معالج اﻹعدادات بالبحث عن الشبكات المرئية المتوفرة في الجوار ويضعها في قائمة على رأسها الشبكة اللاسلكية التي تتصل معها حاليًا. وفي حال لم تتصل بعد بأي شبكة لكنك تعرف معلومات الاستيثاق الخاصة بالولوج إليها (كأن تكون شبكتك الشخصية أو شبكة الشركة التي تعمل فيها)، انقر على هذه الشبكة، وستظهر لك نافذة تطلب إليك كتابة عبارة الوصول المشتركة: بعد كتابة كلمة السر أو عبارة الاستيثاق، انقر على الزر "اتصل"، وستصبح الشبكة جاهزةً للاستخدام؛ كما سيظهر إلى جوارها إشارة (صح)، باﻹضافة إلى أيقونة تشير إلى قوة اﻹشارة المستقبلة. قد يظهر إلى جوار الشبكة التي تتصل بها أيضًا أيقونة على شكل قفل لتدلّك أنّ هذه الشبكة محمية بآلية تشفير محددة. تفحص تفاصيل الاتصال لتفحص إعدادات الشبكة التي اتصلت بها، انقر على أيقونة اﻹعدادات في آخر السطر الذي يعرض اسم الشبكة. تعرض نافذة التفاصيل بعض النقاط العامة، مثل قوة اﻹشارة المستقبلة، وسرعة الاتصال، وعناوين بروتوكول الإنترنت IP، وعنوان بروتوكول وصول الوسائط (ماك MAC)، وذلك تحت مسمى "عنوان العتاد". وما يهمنا فعلًا في هذه النافذة هما الخياران: اتصل تلقائيًا: باختياره سيتصل الحاسوب بهذه الشبكة تلقائيًا بمجرد التقاط إشارتها في الجوار لتصبح الشبكة المبدئية. اجعله متاحًا للمستخدمين اﻵخرين: بتفعيل هذا الخيار يمكن لأي مستخدم مسجَّلٍ في نظامك أن يصل إلى هذه الشبكة، وإن لم يكن مفعلًا ستستخدمه أنت فقط. تشاهد أسفل النافذة زرًا باللون اﻷحمر عنوانه "انسَ الاتصال". انقر عليه لحذف الشبكة الحالية من قائمة الشبكات التي وجدها النظام في الجوار، وذلك إن أردت إعادة ضبط معلومات الاتصال من جديد. لن نتابع في تفصيل بقية النوافذ لأن مواضيعها خارج نطاق سلسلة المقالات هذه، لكن تجدر اﻹشارة إلى أنّ نافذة "أمان"، ستعرض لك كلمة السر التي أدخلتها عند تأسيس اتصالك مع الشبكة، ويمكنك العودة إليها دائمًا إن أردت تذكر الكلمة، وذلك بعد تفعيل خيار "أظهر كلمة السر". لتطبيق أية تغييرات أجريتها على تفاصيل شبكة الاتصال، انقر الزر "طبّق" أعلى يسار النافذة أو "ألغِ" للخروج دون أن تحفظ أية تغييرات. الاتصال بشبكة لاسلكية مخفية الشبكة اللاسلكية المخفية هي شبكة لا تبث هويتها SSID، ويُعَد ذلك عاملًا من عوامل اﻷمان. إن كنت تعرف بوجود شبكة لاسلكية خفية في الجوار وتريد الاتصال بها، فعليك أولًا أن تعرف هويتها تمامًا، وأن تعرف نظام التشفير الذي تستخدمه. وعند توفر هاتين المعلومتين، انقر على زر قائمة الاتصال اللاسلكي في شريط مهام نافذة اﻹعدادات (يبدو على شكل ثلاث نقاط متعامدة)، ثم اختر بعد ذلك اﻷمر "اتصل بشبكة مخفية" من القائمة المنسدلة، وستظهر لك الشاشة التالية: عليك كتابة اسم الشبكة في حقل "اسم الشبكة network name"، واختيار نظام اﻷمان من الحقل "أمان واي-فاي wifi security". انقر بعد ذلك على زر "اتصل" لتتمكن من استخدام الشبكة إن كان ما أدخلته صحيحًا. تفعيل نقطة بث شبكة لاسلكية إن أردت مشاركة اتصالك باﻹنترنت مع مجموعة من المستخدمين اﻵخرين عن طريق إنشاء شبكة لا سلكية خاصة بك، فاﻷمر بسيط جدًا، وذلك من خلال الخيار "شغل نقطة بث واي فاي" المتواجد ضمن قائمة الاتصال اللاسلكي التي أشرنا إليها سابقًا. اختر اسمًا لشبكتك ثم ضع كلمة السر التي ينبغي على المشتركين استخدامها لولوج الشبكة، ثم انقر على الأمر "شغّل". 2. إعداد الشبكة يعرض هذا اﻹعداد بقية أنواع شبكات الاتصال التي يدعمها حاسوبك، مثل الاتصال السلكي عبر محول الشبكة السلكية ethernet، أو اتصالات الحزمة العريضة عن طريق الشبكات الخلوية، أو الشبكات الافتراضية الخاصة VPN، وغيرها. تعرض لقطة الشاشة السابقة وجود محوّل شبكة سلكي تحت القسم "سلكي Wired" لكنه معطل لأن كابل الشبكة غير موصول بالمحوّل؛ بينما يشير القسم الثاني "حزمة خلوية عريضة Mobile Broadband" إلى وجود اتصال فعّال باﻹنترنت عن طريق الاتصال بالهاتف المحمول، ويعرض لك تفاصيل هذا الاتصال. يدل القسم الثالث "VPN" على وجود شبكة افتراضية اسمها "VPN1" لكنها معطلة؛ أما القسم اﻷخير، فهو مخصص لحالات وجود "وسيط الشبكة proxy"، وهو خادم وسيط بين حاسوبك وبقية الحواسيب على الشبكة؛ أو بين حاسوبك وشبكة اﻹنترنت. لا يوجد وسيط شبكة كما تدل اﻹعدادات، فهو معطل؛ لكن إن كان موجودًا، فانقر على زر اﻹعدادات في حقل "وسيط الشبكة"، ثم اختر اﻷمر "تلقائي" للكشف عن اﻹعدادات تلقائيًا أو "يدويًا" ﻹدخالها بنفسك. تظهر لك عند النقر على زر اﻹعدادات إلى جوار كل نوع من أنواع الاتصالات المعروضة نافذة تفاصيل الاتصال، وهي مشابهة من ناحية الخيارات لتفاصيل اتصال واي-فاي، ولن نخوض فيها أيضًا. 3. إعداد البلوتوث يتيح لك هذا الإعداد القدرة على البحث عن أية أجهزة تدعم اتصال بلوتوث إن كان حاسوبك يدعم هذا الخيار، وسيظهر حاسوبك عند اﻵخرين بنفس الاسم الذي اخترته أثناء تثبيت النظام. يبحث حاسوبك باستمرار عن اﻷجهزة الموجودة في النطاق ويعرضها في قائمة. للإتصال بأي جهاز مجاور موجود ضمن قائمة اﻷجهزة التي وجدها النظام، انقر على اسم الجهاز، وستظهر لك نافذة تحتوي على رمز التحقق الذي ينبغي على الجهاز المستهدف إدخاله لتأكيد قبول الاتصال مع حاسوبك. عندما يقبل الطرف اﻵخر الاقتران مع حاسوبك، سيكون كل شيء جاهزًا ﻹرسال الملفات واستقبالها. إرسال ملف من حاسوبك إلى جهاز مقترن به عبر بلوتوث عندما يقترن حاسوبك بجهاز ما، وتظهر عبارة "متصل" إلى جوار هذا الجهاز، فانقر عندها على هذا الجهاز، وستظهر لك النافذة التالية. انقر على الزر "إرسال ملفات"، ثم اختر الملف الذي تريد إرساله من النافذة التي ستظهر. ستبدأ بعدها مباشرةً عملية النقل وتدلك نافذة اﻹرسال على تقدم العملية. استقبال ملف من جهاز مقترن بحاسوبك عبر بلوتوث تتم عملية الاستقبال آليًا من الجهاز المقترن بالحاسوب، ويخبرك النظام بأنه استقبل ملفًا من هذا الجهاز وخزّنه في التنزيلات عندما تنتهي العملية. إعدادات مظهر سطح المكتب ومحتوياته يتيح لك سطح مكتب جنوم خيارات متعددة للتحكم بمظهر سطح المكتب وموقع شريط التطبيقات وسماته اللونية وحجم أيقوناته وغيرها. سنتعرف في هذه الفقرة على أهم اﻹعدادات التي يمكن التحكم بها من خلال تطبيق "إعدادات". اختيار خلفية سطح المكتب افتح تطبيق اﻹعدادات، ثم انتقل إلى الخيار "خلفية"، وستظهر لك الشاشة التالية: يمكنك اختيار أية صورة من الصور الموجودة أصلًا مع هذه النسخة من أوبونتو، وذلك بالنقر عليها لتتحول مباشرةً إلى خلفية لسطح المكتب؛ كما يمكن النقر على زر "أضف صورة" لاختيار صورة أخرى من اختيارك. اختيار سمة وضبط خصائص شريط التطبيقات يتيح لك أوبونتو مجموعة إعدادات لضبط المظهر العام لسطح المكتب والنوافذ من ناحية الشكل واللون، وهذا ما سنناقشه تاليًا. اختيار السمة اللونية انتقل إلى خيار "مظهر" في الشريط الجانبي لنافذة اﻹعدادات، وستظهر لك النافذة التالية: يتيح لك سطح مكتب جنوم إمكانية استخدام ثلاث سمات لسطح المكتب وهي: سمة اﻷلوان الفاتحة light: وتتميز النوافذ بخلفيتها البيضاء وشريط المهام الرمادي الفاتح. السمة القياسية standard: وتتميز النوافذ فيها بالخلفية البيضاء وشريط المهام الأسود. سمة اﻷلوان الداكنة dark: وتتميز النوافذ فيها بالخلفية الرمادية الداكنة وشريط المهام اﻷسود. بالنقر على أي من هذه السمات نقرةً واحدة، سيُطبّق النظام التغيرات مباشرةً. التحكم بإعدادات عرض شريط التطبيقات يتيح لك سطح مكتب جنوم الخيارات التالية: اﻹخفاء التلقائي Auto-hide the dock: بالنقر على الزالقة المجاورة لهذا الخيار، ستتحول إلى اللون البنفسجي ويختفي شريط التطبيقات عن سطح المكتب لكن بمجرد اﻹقتراب من مكانه يظهر مجددًا. التحكم بحجم اﻷيقونات Icon size: يمكنك جعل اﻷيقونات كبيرة بحجم 64 بكسل أو صغيرة بحجم 16 بكسل، أو أن تختار أي قيمة بينهما بما يناسبك عن طريق الزالقة المجاورة لهذا الخيار. وتجدر اﻹشارة إلى أنّ القيمة الافتراضية لحجم اﻷيقونات هي 48 بكسل. شاشة عرض الشريط show on screen: يُمكِّنك هذا الخيار من إظهار الشريط على الشاشة الحالية فقط، أو إظهاره على كل الشاشات المتصلة بحاسوبك، أو ما يُعرف باسم توسعة سطح المكتب. تغيير موقع شريط التطبيقات position on screen: يقع شريط التطبيقات في النسخة العربية عموديًا على يمين الشاشة، لكن إن أردته أن يكون إلى اﻷسفل كما هي حال أشرطة المهام في ويندوز أو ماك، فاختر القيمة "أسفل button"، أو اختر القيمة "يسار left" إن أردته أن يظهر يسارًا. ضبط التنبيهات التنبيهات هي مجموعة من الرسائل النصية أو الصوتية التي يصدرها النظام أو أحد التطبيقات المثبتة ﻹبلاغ المستخدم بوجود مشكلة أو اﻹشارة إلى تنفيذ عمل ما. وتُعَدّ التنبيهات إحدى موارد النظام التي يمكن للبرمجيات الوصول إليها، فعندما يكتمل تحميل برنامج مثلًا، سيعرض لك النظام تنبيهًا على شكل رسالة منبثقة أعلى ومنتصف شريط المهام يخبرك فيها باكتمال التحميل، أو عندما يتصل النظام بشبكة أو ينقطع الاتصال تعرض لك رسائل وتنبيهات صوتية مناسبة. ولضبط التنبيهات التي تريدها أن تظهر أو إهمال تنبيهات لا تعتقد أنها مهمة، فافتح تطبيق "إعدادات"، وانتقل إلى الخيار "التنبيهات": تتيح لك هذه النافذة مجموعتين من الخيارات، اﻷولى تتعلق بالنظام واﻷخرى بالتطبيقات: تنبيهات النظام يمكن التحكم بميزتين تتعلقان بتنبيهات النظام وهما: عدم اﻹزعاج Do not disturb: عند تفعيل هذا الخيار بالنقر على الزالقة المجاورة، سيمنع النظام ظهور أية تنبيهات أيًا كان مصدرها، ويعرض أيقونة عدم اﻹزعاج إلى جوار الساعة وسط شريط المهام. تنبيهات شاشة القفل: سيعرض النظام عند تفعيل هذا الخيار التنبيهات حتى لو كانت شاشة سطح المكتب مقفلة؛ بينما لن يعرض التنبيهات عند تقفل الشاشة في حال لم يكن هذا الخيار مفعلًا (الزالقة إلى اليسار ورمادية اللون). تنبيهات التطبيقات يعرض هذا القسم من النافذة جميع التطبيقات التي تستخدم تنبيهات النظام لعرض رسائل حالة التطبيق. وبالنقر على أيٍ من هذه التطبيقات، ستنبثق نافذة تضم مجموعة من الخيارات هي: التنبيهات: لعرض تنبيهات التطبيق عمومًا أو إبطالها. التنبيهات الصوتية: تفعيل أو إسكات التنبيهات الصوتية. نوافذ التنبيهات المنبثقة: يعرض النظام تنبيهات التطبيق على شكل نوافذ منبثقة عند تفعيل الخيار، ولن يعرضها ضمن قائمة منبثقة، بل ضمن قائمة التنبيهات فقط (تظهر القائمة بالنقر على الساعة في شريط المهام) إن لم يكن مفعلًا. أظهر محتوى الرسائل في نافذة منبثقة: يعرض النظام رسائل التطبيق عند تفعيل الخيار في نوافذ منبثقة مستقلة بدلًا من عرضها في شريط المهام أو في قائمة التنبيهات. تنبيهات شاشة القفل: يعرض تنبيهات التطبيق في شريط المهام أو في قائمة التنبيهات حتى لو كانت الشاشة مقفلة. أظهر محتوى الرسالة في شاشة القفل: عند تمكين هذا الخيار، سيعرض النظام رسالة التطبيق على الشاشة حتى لو كانت مقفلة. البحث في سطح المكتب والتطبيقات سيعرض لك النظام عند النقر على خيار "البحث" ضمن الشريط الجانبي لتطبيق "اﻹعدادات" قائمةً باﻷماكن التي سيبحث فيها النظام عن مدخلاتك عندما تحاول البحث عن شيء ما. تُرتب هذه اﻷماكن وفقًا لأولوية ظهور نتائج البحث المتعلقة بها، فالمكان الموجود أعلى القائمة، ستظهر نتائجه أولًا وهكذا. يمكنك أيضًا تغيير اﻷولوية بالنقر على زر النقاط الثلاث المتعامدة آخر كل حقل واختيار "انقل لأعلى" أو "انقل لأسفل" لرفع ترتيب الحقل أو تخفيضه؛ كما يمكنك النقر على الزالقة المجاورة لكل حقل ﻹلغاء البحث في هذا المكان، أو يمكنك النقر أيضًا على الزالقة الموجودة في شريط مهام النافذة لتعطيل البحث في هذه اﻷماكن. اقرأ أيضًا تعرّف على سطح مكتب أوبونتو 20.04 التعامل مع المجلدات والملفات في أوبونتو 20.04 تغيير اللغة في نظام لينكس أوبنتو إلى العربية
  18. يتألف موقع ويب من ملفات عدة منها ملفات المحتوى وملفات الشيفرة وملفات التنسيق والوسائط المتعددة وغيرها، فعندما تبني موقعك، عليك تجميع هذه الملفات ضمن هيكلية معقولة على حاسوبك، والتأكد من أنها قادرة على التواصل مع بعضها وأنّ كل شيء يبدو على ما يرام قبل أن ترفع هذه الملفات إلى الخادم، إذ سيناقش هذا المقال بعض الأمور التي ينبغي الانتباه إليها لكي تنظم ملفاتك ضمن هيكلية واضحة لتكوين موقعك. مكان تجميع موقع ويب على حاسوبك عندما تشرع في بناء موقع ويب على حاسوبك، لا بد أن تُبقي كل الملفات المرتبطة به ضمن مجلد واحد يحاكي في تنظيمه تنظيم الملفات ضمن موقع الويب عندما تنشره، كما يمكنك اختيار أيّ مكان ضمن حاسوبك لوضع هذا المجلد شرط أن يكون إيجاده سهلًا مثل سطح المكتب أو في المجلد الرئيسي Home أو ضمن المجلد الجذري للقرص الصلب. اختر مكانًا لتخزِّن ضمنه مشروع موقع الويب، ثم انشئ مجلدًا جديدًا في هذا المكان وسمِّه web-projects أو ما شابه، إذ سيكون هذا المجلد المكان الذي تخزّن فيه جميع مواقع الويب التي تصممها. انشئ ضمن هذا المجلد مجلدًا جديدًا لتخزين موقعك الأول، وسمِّه test-site أو ما شابه. تسمية الملفات والمجلدات ستلاحظ خلال هذا المقال أننا نسمي المجلدات والملفات بأحرف صغيرة ودون فراغات وذلك لأن: تُعَدّ الكثير من الحواسب وخاصةً الخوادم حساسةً لحالة الأحرف، فإذا وضعت مثلًا صورةً على موقعك عنوانها test-site/MyImage.jpg وأردت تشغيلها من ملف آخر وكتبت العنوان test-site/myimage.jpg، فربما لا تعمل. لا تعامِل خوادم ويب والمتصفحات ولغات البرمجة الفراغات بالطريقة نفسها، فإذا وضعت فراغات في اسم الملف مثلًا، فستعامِل بعض الأنظمة اسم الملف هذا على أساس ملفين منفصلين، إذ تملأ بعض خوادم ويب الفراغات في أسماء الملفات بالرمز "%20" (وهو رمز المسافة الفارغة في عناوين URL)، وتكون النتيجة أخطاءً في جميع الروابط، ومن الأفضل أيضًا فصل الكلمات بشرطة hyphen مثل my-file.html بدلًا من الشرطة السفلية مثل my_file.html، والسبب في ذلك أنّ محرك بحث جوجل يعامل الشرطات على أساس فواصل بين الكلمات بينما لا يعامل الشرطات السفلية بالطريقة ذاتها. خلاصة الأمر أنه عليك اعتياد استخدام الأحرف الصغيرة دون فراغات، واستخدام الشرطات للفصل بين الكلمات حتى تدرك ما تفعل على الأقل، فإنّ تقيدك بذلك يريحك من بعض المشاكل التي قد تنبثق أمامك هنا وهناك. الهيكلية التي ينبغي أن يبنى عليها موقع ويب لنلق نظرةً فيما سيأتي على هيكلية الموقع البسيط الذي نبنيه، إذ يُعَدّ الشيء المشترك بين معظم مشاريع مواقع ويب هو إنشاء ملف HTML يعمل تلقائيًا عند استدعاء الموقع ويُدعى عادة "index"، بالإضافة إلى مجلد يحتوي على الصور وملفات التنسيق وملفات الشيفرة، فلنبن هذه الأشياء إذًا: index.html: استخدم محرر النصوص الذي تملكه لإنشاء ملف جديد يُدعى index.html، ثم احفظه ضمن المجلد test-site، إذ يحتوي هذا الملف عمومًا على محتويات الصفحة الرئيسية للموقع مثل النصوص والصور التي يراها الزائر عند دخول موقعك. المجلد images: أنشئ مجلدًا بهذا الاسم داخل المجلد test-site، إذ يضم كل الصور التي تستخدمها في موقعك. المجلد styles: أنشئ مجلدًا بهذا الاسم ثم احفظه ضمن المجلد test-site، إذ يضم كل الملفات التي تحتوي على شيفرة التنسيقات المورثة CSS والتي تتحكم بمظهر الصفحة مثل لون النصوص والخلفية. المجلد scripts: أنشئ مجلدًا بهذا الاسم ثم احفظه في المجلد test-site، إذ يضم شيفرة جافاسكربت التي تُستخدَم لإضافة وظائف تفاعلية إلى صفحتك مثل الأزرار التي تعرض بيانات عند نقرها. ملاحظة: قد تجد صعوبةً في رؤية أسماء الملفات كاملة على الحواسب التي تشغل النظام ويندوز لأنه يقدِّم خيارًا بإخفاء امتدادات الملفات معروفة النوع، وهذا الخيار مفعَّل افتراضيًا، إذ يمكنك عادةً إيقاف هذا الخيار بالانتقال إلى مستكشف ويندوز Windows Explorer ثم خيارات المجلد Folder Options وبعدها ألغ تفعيل خيار "إخفاء الامتدادات للملفات معروفة النوع Hide extensions for known file types"، كما يمكنك دومًا البحث عبر الويب لإيجاد التفاصيل الخاصة بنسختك من ويندوز. مسارات الملفات لا بد من تحديد مسار الملف بطريقة صحيحة حتى تتمكن الملفات في الموقع من التخاطب مع بعضها بعضًا، ومبدئيًا لا بد من تحديد وجهة ليتمكن أيّ ملف من معرفة مكان الآخر، ولتوضيح الأمر سنكتب بعض الشيفرة في الملف index.html لكي يعرض الصورة التي اخترتها لموقعك البسيط أو أية صورة مناسبة قد تجدها على حاسوبك أو على الويب: انسخ الصورة التي اخترتها إلى المجلد images. افتح الملف index.html وانقل الشيفرة التالية إليه كما هي تمامًا، ولا تكترث حاليًا بمعنى هذه الشيفرة، إذ سنهتم بالتفاصيل لاحقًا: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>My test page</title> </head> <body> <img src="" alt="My test image"> </body> </html> يُدرِج سطر شيفرة HTML التالي الصورة ضمن صفحتك: <img src="" alt="My test image"> لابد من إخبار HTML بمكان وجود الصورة، إذ تتواجد الصورة ضمن مجلد الصور وهذا المجلد موجود في المجلد نفسه الذي يحوي الملف index.html، وبالتالي سنحتاج إلى المسار images/your-image-filename للوصول من هذا الملف إلى الصورة، فإذا كان اسم الصورة firefox-icon.png، لكان المسار images/firefox-icon.png. ضع اسم المسار في شيفرة إدراج الصورة بين علامتي إقتباس بالشكل ""=src، ثم احفظ الملف ثم افتحه باستخدام المتصفح بالنقر المزدوج على أيقونة الملف، إذ ستظهر الآن الصورة المطلوبة. إليك بعض القواعد العامة في تحديد مسارات الملفات: لاستدعاء ملف يقع في المجلد نفسه الذي يقع فيه الملف الذي يستدعي، استخدم فقط اسم هذا الملف مثل my-image.jpg. للإشارة إلى ملف في مجلد فرعي مجاور للملف الذي يستدعي، اكتب اسم المجلد الفرعي متبوعًا بالمحرف "/" ثم اسم الملف المطلوب مثل subdirectory/my-image.jpg. للإشارة إلى ملف يقع في المجلد الأب للمجلد الذي يقع فيه الملف الذي يستدعيه، اكتب نقطتين .. ثم المحرف "/" ثم اسم الملف مثل my-image.jpg/... يمكنك الدمج بين القواعد السابقة كما تشاء مثل ‎../subdirectory/another-subdirectory/my-image.jpg، وهذا كل شيء تحتاجه إلى الآن. ملاحظة: يستخدِم نظام التشغيل ويندوز الشرطة الخلفية \ وليست الأمامية / لتحديد المسارات مثل C:\Windows، ولكن عليك استخدام الشرطة الأمامية دائمًا عند تطوير مواقع ويب حتى لو عملت على ويندوز. ما الذي يجب فعله أيضا لا شيء الآن، إذ يجب أن تبدو هيكلية موقعك مشابهةً للهيكلية التي تعرضها الصورة التالية: ترجمة -وبتصرف- للمقال Dealing with files اقرأ أيضًا التعامل مع الملفات في البرمجة رفع ملفات موقع الويب إلى خادم على الإنترنت الأدوات المستخدمة في بناء مواقع ويب
  19. لقد حان الوقت لتوسيع مشروعنا بعد أن أسسنا له قاعدةً معرفيةً جيدة، وسنسخّر لهذه المهمة كل إمكانيات React Native التي تعلمناها حتى الآن. سنغطي بالإضافة إلى توسيع المشروع نواحٍ جديدة، مثل الاختبارات والموارد الإضافية. اختبار تطبيقات React Native سنحتاج إلى إطار عمل للاختبارات عند البدء باختبار أي نوع من الشيفرة، لتنفيذ مجموعة من الحالات المختبرة، والتدقيق في نتائجها. فلاختبار تطبيق جافا سكربت JavaScript سنجد أن إطار العمل Jest هو الأكثر شعبية لأداء المهمة. ولاختبار تطبيقات React Native المبنية على المنصة Expo باستخدام Jest، ستزودنا Expo بمجموعة من إعدادات تهيئة على هيئة مجموعة جاهزة تدعى jest-expo. ولكي نستخدم المدقق ESLint في ملف اختبار Jest، سنحتاج إلى الإضافة eslint-plugin-jest الخاصة به. لنبدأ إذًا بتثبيت الحزم: npm install --save-dev jest jest-expo eslint-plugin-jest لاستخدام المجموعة jest-expo في Jest، لا بدّ من إضافة إعدادات التهيئة التالية في الملف "package.json"، مع سكربت الاختبار: { // ... "scripts": { // other scripts... "test": "jest" }, "jest": { "preset": "jest-expo", "transform": { "^.+\\.jsx?$": "babel-jest" }, "transformIgnorePatterns": [ "node_modules/(?!((jest-)?react-native|@react-native(-community)?)|expo(nent)?|@expo(nent)?/.*|@expo-google-fonts/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|react-native-svg|react-router-native)" ] }, // ... } يطلب الخيار transform من Jest تحويل شيفرة الملفين "js." و"jsx." باستخدام مصرّف Babel. بينما يُستخدم الخيار transformIgnorePatterns لتجاهل مجلدات محددة ضمن المجلد "node_modules" أثناء تحويل الملفات. وتتطابق تقريبًا إعدادات Jest هذه مع تلك المقترحة في توثيق Expo. لاستخدام الإضافة eslint-plugin-jest، لا بدّ من وضعها ضمن مصفوفة الإضافات والموسِّعات في الملف "eslintrc.". { "plugins": ["react", "react-native"], "settings": { "react": { "version": "detect" } }, "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:jest/recommended"], "parser": "@babel/eslint-parser", "env": { "react-native/react-native": true }, "rules": { "react/prop-types": "off", "react/react-in-jsx-scope": "off" } } وللتأكد من نجاح التهيئة، أنشئ المجلد "tests" في المجلد "src"، ثم أنشئ ضمنه الملف "example.js" وضع فيه الشيفرة التالية: describe('Example', () => { it('works', () => { expect(1).toBe(1); }); }); لننفذ الآن هذا المثال من خلال الأمر: npm test. ينبغي أن يشير خرج العملية إلى نجاح الاختبار المتواجد في الملف "src/tests/example.js". تنظيم الاختبارات تقتضي إحدى طرق تنظيم ملفات الاختبارات بوضعها في مجلد وحيد يُدعى "tests". ويفضّل عند استخدام هذه الطريقة وضع ملفات الاختبار ضمن المجلدات الفرعية الملائمة كما نفعل مع ملفات الشيفرة، إذ ستُوضع الاختبارات التي تتعلق بالمكوّنات مثلًا في المجلّد "components"، وتلك التي تتعلق بالخدمات ستُوضع في المجلّد "utils"، وهكذا. سينتج عن هذا التنظيم الهيكلية التالية: src/ __tests__/ components/ AppBar.js RepositoryList.js ... utils/ authStorage.js ... ... أما الطريقة الأخرى فهي وضع ملف الاختبار بجانب ملف الشيفرة الذي سيستخدمه. ويعني ذلك أننا سنضع الملف الذي يتضمن اختبارات للمكوّن AppBar مثلًا في نفس المجلد الذي يحتوي شيفرة هذا المكوّن. وستنتج عن هذه الطريقة في التنظيم الهيكلية التالية: src/ components/ AppBar/ AppBar.test.jsx index.jsx ... ... شيفرة المكوّن في المثال السابق موجودةٌ في الملف "index.jsx"، بينما ستجد الاختبارات في الملف "AppBar.test.jsx". ولكي تجد بسهولة الملفات التي تحتوي الاختبارات، عليك أن تضعها في المجلد "tests" أو في ملفات تحمل إحدى اللاحقتين "test." أو "spec."، أو أن تهيئ يدويًا الأنماط العامة global patterns. اختبار المكونات بعد أن ضبطنا إعدادات Jest وجربناها بتنفيذ مثال بسيط، حان الوقت لنتعرف على كيفية اختبار المكوّنات. يتطلب اختبار المكوّنات، كما نعلم، طريقة لتفسير الخرج الناتج عن تصيير المكوّن ومحاكاة عملية معالجة الأحداث المختلفة مثل الضغط على زر. ولحسن الحظ تتوافر عائلة من مكتبات الاختبار تؤمن مكتبات لاختبار مكوًنات واجهة المكوّن في منصات عدة. وتتشارك جميع هذه المكتبات بواجهة برمجية واحدة API لاختبار مكونات واجهة المستخدم بأسلوب يركّز على المستخدم. تعرّفنا في القسم 5 على إحدى هذه المكتبات وهي React Testing Library. لكن لسوء الحظ فهذه المكتبة مخصصة فقط لاختبار تطبيقات الويب المبنية باستخدام React. لكن بالطبع هناك مقابل لها في React Native وهي المكتبة React Native Testing Library التي سنستخدمها في اختبار مكوّنات تطبيقات React Native. وكما ذكرنا فإن جميع هذه المكتبات تتشارك بنفس الواجهة البرمجية، ولن تضطر إلى تعلّم الكثير من المفاهيم الجديدة. بالإضافة إلى هذه المكتبة، سنحتاج إلى مجموعة من مُطابقات Jest مخصصة للمكتبة مثل toHaveTextContent و toHaveProp. تزوّدنا المكتبة jest-native بهذه المُطابقات، لذلك لا بدّ أولًا من تثبيت هذه الحزم: npm install --save-dev react-test-renderer@17.0.1 @testing-library/react-native @testing-library/jest-native ملاحظة: إذا واجهت مشاكل في اعتمادية النظير peer، فتأكد من مطابقة إصدار react-test-renderer مع نسخة react للمشروع في أمر npm install المذكور أعلاه. يمكنك التحقق من إصدار react من خلال تنفيذ الأمر: npm list react --depth=0 وفي حال فشل التثبيت بسبب مشكلات اعتمادية النظير، فحاول مرةً أخرى باستخدام الراية ‎--legacy-peer-deps في الأمر npm install. لنتمكن من استخدام هذه المُطابقات، علينا توسيع الكائن expect العائد للمكتبة Jest. ويجري تنفيذ ذلك باستخدام ملف إعداد عام. لذلك أنشئ الملف "setupTests.js" في المجلد الجذري لمشروعك، وهو نفسه المجلد الذ++ي يحتوي الملف "package.json". أضف الشيفرة التالية إلى هذا الملف: import '@testing-library/jest-native/extend-expect'; هيئ الملف السابق على انه ملف إعداد ضمن أوامر تهيئة Jest الموجودة في الملف "package.json": { // ... "jest": { "preset": "jest-expo", "transform": { "^.+\\.jsx?$": "babel-jest" }, "transformIgnorePatterns": [ "node_modules/(?!(jest-)?react-native|react-clone-referenced-element|@react-native-community|expo(nent)?|@expo(nent)?/.*|react-navigation|@react-navigation/.*|@unimodules/.*|unimodules|sentry-expo|native-base|@sentry/.*|react-router-native)" ], "setupFilesAfterEnv": ["<rootDir>/setupTests.js"] } // ... } تعتمد المكتبة على مفهومين أساسيين هما الاستعلامات (query) وإطلاق الأحداث (fire event)؛ إذ تُستخدم الاستعلامات لاستخلاص مجموعة من العقد من المكوّن الذي يُصيّر بواسطة الدالة render، ونستفيد منها في اختبار وجود عنصر أو كائن ما، مثل نص معين، ضمن المكوّن المُصيَّر. يمكنك تحديد العقد باستخدام الخاصية testID ثم الاستعلام عنها باستخدام الدالة getByTestId. تقبل جميع المكوّنات البنيوية في الخاصية testID. وإليك مثالًا عن كيفية استخدام الاستعلامات: import { Text, View } from 'react-native'; import { render } from '@testing-library/react-native'; const Greeting = ({ name }) => { return ( <View> <Text>Hello {name}!</Text> </View> ); }; describe('Greeting', () => { it('renders a greeting message based on the name prop', () => { const { debug, getByText } = render(<Greeting name="Kalle" />); debug(); expect(getByText('Hello Kalle!')).toBeDefined(); }); }); تعيد الدالة render الاستعلامات ودوال مساعدة أخرى مثل الدالة debug التي تطبع شجرة React بطريقة واضحة بالنسبة للمستخدم. استخدم هذه الدالة إن كنت غير متأكد من شكل المكوّن الذي صيَّرته الدالة render. يمكننا أن نحصل على العقدة Text التي تحتوي نصًا محددًا عن طريق الدالة getByText. وللاطلاع على كل الاستعلامات المتاحة، راجع توثيق المكتبة React Native Testing. يُستخدم المُطابق toHaveTextContent للتأكد من أن المحتوى النصي للعقدة صحيح. وللاطلاع كذلك على كل المُطابقات الخاصة بالمكتبة React Native، راجع توثيق jest-native. وتذكر أنك ستجد معلومات عن كل مُطابقٍ عام في توثيق Jest. أما المفهوم الثاني الذي ترتكز عليه المكتبة React Native Testing في عملها هو إطلاق الأحداث firing events، إذ يمكننا إطلاق حدث ضمن عقدة معينة باستخدام توابع الكائن fireEvent. سنستفيد من ذلك على سبيل المثال في طباعة نص ضمن حقل نصي أو على زر. وإليك مثال عن اختبار تسليم نموذج بسيط: import { useState } from 'react'; import { Text, TextInput, Pressable, View } from 'react-native'; import { render, fireEvent } from '@testing-library/react-native'; const Form = ({ onSubmit }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const handleSubmit = () => { onSubmit({ username, password }); }; return ( <View> <View> <TextInput value={username} onChangeText={(text) => setUsername(text)} placeholder="Username" /> </View> <View> <TextInput value={password} onChangeText={(text) => setPassword(text)} placeholder="Password" /> </View> <View> <Pressable onPress={handleSubmit}> <Text>Submit</Text> </Pressable> </View> </View> ); }; describe('Form', () => { it('calls function provided by onSubmit prop after pressing the submit button', () => { const onSubmit = jest.fn(); const { getByPlaceholderText, getByText } = render(<Form onSubmit={onSubmit} />); fireEvent.changeText(getByPlaceholderText('Username'), 'kalle'); fireEvent.changeText(getByPlaceholderText('Password'), 'password'); fireEvent.press(getByText('Submit')); expect(onSubmit).toHaveBeenCalledTimes(1); // تحوي أول وسيط من أول استدعاء onSubmit.mock.calls[0][0] expect(onSubmit.mock.calls[0][0]).toEqual({ username: 'kalle', password: 'password', }); }); }); نريد في هذا الاختبار أن نتأكد من أنّ الدالة onSubmit ستُستدعى على نحوٍ صحيح بعد أن تُملأ حقول النموذج باستخدام التابع fireEvent.changeText ويُضغط على الزر باستخدام التابع fireEvent.press. ولتحري إذا ما استُدعيت الدالة onSubmit وبأية وسطاء، يمكن استخدام الدالة المقلّدة mock function؛ والدوال المقلدة هي دوال ذات سلوك مبرمج مسبقًا كأن تعيد قيمةً محددة. كما يمكننا تحديد توقّعات expectations لهذه الدوال، فمثلًا "توقّع أن الدالة المقلّدة قد استدعيت مرةً واحدة". يمكنك الاطلاع على قائمة بكل التوقعات المتاحة في توثيق توقعات Jest. عدّل وجرّب في تلك الأمثلة السابقة، وأضف اختبارات جديدة في المجلد "tests "، بحيث تتأكد من فهمك للأفكار السابقة قبل الغوص أعمق في موضوع الاختبارات. التعامل مع الاعتماديات أثناء الاختبارات يُعد اختبار المكوّنات سهلًا نوعًا ما لأنها صرفة بشكلٍ أو بآخر. فلا تعتمد المكوّنات الصرّفة على التأثيرات الجانبية side effects، مثل طلبات الشبكة أو استخدام واجهات برمجية أصيلة، مثل AsyncStorage. فالمكوّن Form ليس صرفًا بقدر المكون Greeting، لأن تغيرات حالته قد تُعد أثرًا جانبيًا. لكن الاختبارات تبقى سهلةً مع ذلك. لنلقِ الآن نظرةً على استراتيجية لاختبار المكوّنات مع الآثار الجانبية، وسنختار على سبيل المثال المكوّن RepositoryList من تطبيقنا. لهذا المكوّن حاليًا أثر جانبي واحد وهو استعلام GraphQl الذي يحضر قائمة بالمستودعات المقيّمة. يُستخدم المكوّن حاليًا كالتالي: const RepositoryList = () => { const { repositories } = useRepositories(); const repositoryNodes = repositories ? repositories.edges.map((edge) => edge.node) : []; return ( <FlatList data={repositoryNodes} // ... /> ); }; export default RepositoryList; ينتج التأثير الجانبي عن استخدام الخطاف useRepositories الذي يرسل استعلام GraphQL. هناك عدة طرق لاختبار المكوّن، إحدى هذه الطرق هي تقليد استجابات المكتبة Apollo Client كما هو مشروح في توثيقها. أما الطريقة الأبسط هي افتراض أن المكوّن يعمل كما نتوقع (ويُفضل من خلال اختباره)، ثم استخلاص الشيفرة "الصرفة" له ووضعها في مكوّن آخر مثل RepositoryListContainer: export const RepositoryListContainer = ({ repositories }) => { const repositoryNodes = repositories ? repositories.edges.map((edge) => edge.node) : []; return ( <FlatList data={repositoryNodes} // ... /> ); }; const RepositoryList = () => { const { repositories } = useRepositories(); return <RepositoryListContainer repositories={repositories} />; }; export default RepositoryList; يحتوي المكوّن الآن التأثيرات الجانبية فقط وتنفيذه سهل. يمكن اختبار المكوّن RepositoryListContainer بتمرير بيانات عن مستودع من القائمة من خلال الخاصية repositories، والتأكد أنّ معلومات المحتوى المُصيَّر صحيحة. التمرينان 10.17 - 10.18 10.17 اختبار قائمة المستودعات المقيمة أنجز اختبارًا يتأكد أن المكوّن RepositoryListContainer سيصيِّر بصورةٍ صحيحة كلًا من: اسم المستودع ووصفه واللغة وعدد التشعبات وعدد النجوم ومعدل التقييم وعدد التقييمات. تذكر أنه بالإمكان الاستفادة من المطابق toHaveTextContent في التحقق من احتواء العقدة على سلسلة نصية محددة. const RepositoryItem = (/* ... */) => { // ... return ( <View testID="repositoryItem" {/* ... */}> {/* ... */} </View> ) }; كما يمكنك استخدام الاستعلام getAllByTestId للحصول على كل العقد التي تمتلك قيمة محددة للخاصية testID على هيئة مصفوفة، وإن كنت غير متأكد من المكوّن الذي صُيَّر، استفد من الدالة debug في تفكيك نتيجة التصيير. const repositoryItems = getAllByTestId('repositoryItem'); const [firstRepositoryItem, secondRepositoryItem] = repositoryItems; // توقع شيئًا من عنصر المستودع الأول والثاني استخدم الشيفرة التالية أساسًا لاختبارك: describe('RepositoryList', () => { describe('RepositoryListContainer', () => { it('renders repository information correctly', () => { const repositories = { pageInfo: { totalCount: 8, hasNextPage: true, endCursor: 'WyJhc3luYy1saWJyYXJ5LnJlYWN0LWFzeW5jIiwxNTg4NjU2NzUwMDc2XQ==', startCursor: 'WyJqYXJlZHBhbG1lci5mb3JtaWsiLDE1ODg2NjAzNTAwNzZd', }, edges: [ { node: { id: 'jaredpalmer.formik', fullName: 'jaredpalmer/formik', description: 'Build forms in React, without the tears', language: 'TypeScript', forksCount: 1619, stargazersCount: 21856, ratingAverage: 88, reviewCount: 3, ownerAvatarUrl: 'https://avatars2.githubusercontent.com/u/4060187?v=4', }, cursor: 'WyJqYXJlZHBhbG1lci5mb3JtaWsiLDE1ODg2NjAzNTAwNzZd', }, { node: { id: 'async-library.react-async', fullName: 'async-library/react-async', description: 'Flexible promise-based React data loader', language: 'JavaScript', forksCount: 69, stargazersCount: 1760, ratingAverage: 72, reviewCount: 3, ownerAvatarUrl: 'https://avatars1.githubusercontent.com/u/54310907?v=4', }, cursor: 'WyJhc3luYy1saWJyYXJ5LnJlYWN0LWFzeW5jIiwxNTg4NjU2NzUwMDc2XQ==', }, ], }; // أضف شيفرة اختبارك هنا }); }); }); يمكن أن تضع ملف الاختبار أينما تشاء، لكن من الأفضل أن تتبع إحدى الطرق التي ذكرناها في تنظيم ملفات الاختبار. استخدم المتغير repositories مثل مصدر للبيانات في الاختبار، ولا حاجة لتغيير قيمته. تحتوي البيانات على مستودعين، فعليك بالتالي التحقق من وجود المعلومات في كليهما. 10.18 اختبار نموذج تسجيل الدخول أنجز اختبارًا يتحقق أن الدالة onSubmit ستُستدعى بوسطاء مناسبين عندما يُملأ حقلي اسم المستخدم وكلمة المرور في النموذج ويُضغط على زر الإرسال. ينبغي أن يكون الوسيط الأول كائنًا يمثّل قيم النموذج، ويمكنك إهمال بقية وسطاء الدالة. تذكر أنك قد تستعمل توابع fireEvent لتنفيذ أحداث ودوال مقلِّدة للتحقق من استدعاء معالج الحدث onSubmit أو لا. لا حاجة لاختبار أي شيفرة متعلقة بالمكتبة Apollo Client أو AsyncStorage والموجودة في الخطاف useSignIn. وكما فعلنا في التمرين السابق، استخلص الشيفرة الصرفة وضعها في مكوّن خاص واختبره. انتبه إلى إرسالات نماذج Formik فهي غير متزامنة، فلا تتوقع استدعاء الدالة onSubmit مباشرة بعد الضغط على زر الإرسال. يمكنك الالتفاف على المشكلة بجعل دالة الاختبار دالة غير متزامنة باستخدام التعليمة Async واستخدام الدالة المساعدة waitFor العائدة للمكتبة React Native Testing. كما يمكنك يمكن أن تستخدم الدالة waitFor لانتظار تحقق التوقعات. فإن لم يتحقق التوقع خلال فترة محددة، سترمي الدالة خطأً. إليك مثالًا يشير إلى أسلوب استخدام هذه الدالة: import { render, fireEvent, waitFor } from '@testing-library/react-native'; // ... describe('SignIn', () => { describe('SignInContainer', () => { it('calls onSubmit function with correct arguments when a valid form is submitted', async () => { //صيّر المكوّن ثم املأ الحقول النصية ثم اضغط زر الإرسال await waitFor(() => { // توقع أن تُستدعى دالة الإرسال مرة واحد وأن يكون الوسيط الأول صحيحًا }); }); }); }); توسيع التطبيق سنبدأ باستخدام كل ما تعلمناه في توسيع تطبيقنا، فلا يزال التطبيق يفتقر لبعض النواحي، مثل عرض معلومات مستودع وتسجيل مستخدمين جدد. سنركز في التمارين القادمة على هذه النقاط. التمرينات 10.19 - 10.24 10.19: واجهة لعرض مستودع واحد أنجز واجهةً لعرض معلومات عن مستودع واحد، تتضمن نفس المعلومات التي تُعرض في قائمة المستودعات بالإضافة إلى زر لفتح المستودع في غيت هب Github. من الجيد أن تجد طريقةً لإعادة استخدام المكونين RepositoryItem و RepositoryList وأن تعرض الزر الذي أشرنا إليه بناء على خاصية محددة تعيد قيمة منطقية مثلًا. ستجد عنوان المستودع ضمن الحقلurl العائد للنوع "Repository" في تخطيط GraphQL. تستطيع إحضار مستودع واحد من خادم Apollo بإجراء الاستعلام repository. يُمرَّر إلى هذا الاستعلام وسيط واحد وهو المعرّف المميز id للمستودع. إليك مثالًا عن استخدام الاستعلام repository. { repository(id: "jaredpalmer.formik") { id fullName url } } اختبر استعلامك كما هو معتاد في أرضية عمل Apollo قبل استخدامه في التطبيق. إن لم تكن على دراية بتخطيط GrapQL أو الاستعلامات التي يتيحها، افتح النافذة "docs" أو النافذة "schema" في أرضية عمل GraphQL. وإن واجهتك صعوبة في استخدام المعرف id مثل متغير في الاستعلام، توقف قليلًا لتطلع على توثيق Apollo Client بما يخص الاستعلامات. لتطلع على طريقة التوجه إلى عنوان URL في المتصفح، اقرأ توثيق Expo حول واجهة الربط البرمجية Linking API، لأنك ستحتاج ذلك عند تنفيذك للزر الذي يفتح المستودع في GitHub. ينبغي أن تمتلك واجهة العرض عنوانًا خاصًا بها، ومن الجيد أن تحدد المعرّف المميز id للمستودع ضمن المسار الذي يوجهك نحو هذا العنوان مثل معامل مسار، كي تستطيع الوصول إليه باستخدام الخطاف useParams. ينبغي أن يكون المستخدم قادرًا على الوصول إلى واجهة العرض بالضغط على المستودع ضمن قائمة المستودعات. ومن الممكن تنفيذ ذلك بتغليف المكوّن RepositoryItem داخل المكّون البنيوي Pressable في المكوّن RepositoryList، ثم استخدام الدالة navigate لتغيير العنوان من خلال معالج الحدث onPress. استخدم الخطاف useNavigate أيضًا للوصول إلى الكائن دالة navigate. ستبدو النسخة النهائية لواجهة عرض مستودع واحد قريبة من الشكل التالي: 10.20: قائمة بالآراء حول مستودع بعد أن أنجزنا واجهة عرض لمستودع وحيد، سنعرض تقييمات المستخدمين ضمن هذه الواجهة. ستجد التقييمات في الحقل reviews العائد للنوع "Repository" في تخطيط GraphQL. تشكل التقييمات قائمةً صغيرةً مرقمةً يمكن الحصول عليها باستخدام الاستعلام repositories. وإليك مثالًا عن طريقة إحضار قائمة التقييمات لمستودع واحد: { repository(id: "jaredpalmer.formik") { id fullName reviews { edges { node { id text rating createdAt user { id username } } } } } } يحتوي الحقل text لكل تقييم على رأي المستخدم للنص، ويحتوي الحقل rating على قيمة عددية بين 0 و100، أما الحقل craetedAt فيحتوي على بيانات إنشاء هذا التقييم، وأخيرًا يحتوي الحقل user معلومات عن مُنشئ التقييم ويحمل النوع "User". نريد أن نعرض التقييمات على شكل شريط تمرير، مما يجعل المكوّن البنيوي FlatList ملائمًا لأداء المهمة. ولكي تعرض معلومات مستودع محدد في أعلى الواجهة، استخدم الخاصية ListHeaderComponent للمكون FlatList. يمكنك أيضًا استخدام المكوّن البنيوي ItemSeparatorComponent لإضافة مساحة بيضاء بين العناصر المعروضة كما هو الحال بين عناصر المكوّن RepositoryList مثلًا. إليك مثالًا عن هيكلية التنفيذ: const RepositoryInfo = ({ repository }) => { // Repository's information implemented in the previous exercise }; const ReviewItem = ({ review }) => { // Single review item }; const SingleRepository = () => { // ... return ( <FlatList data={reviews} renderItem={({ item }) => <ReviewItem review={item} />} keyExtractor={({ id }) => id} ListHeaderComponent={() => <RepositoryInfo repository={repository} />} // ... /> ); }; export default SingleRepository; ستبدو النسخة النهائية عن تطبيق قائمة المستودعات مشابهة للشكل التالي: البيانات التي تراها تحت اسم المستخدم لمنشئ التقييم هي تاريخ الإنشاء الذي نجده في الحقل createdAt وهو من النوع "Review"، وينبغي أن يكون تنسيق البيانات مريحًا للمستخدم مثل الصيغة "يوم.شهر.سنة". قد يساعدك تثبيت المكتبة date-fns على تنسيق تاريخ الإنشاء بالاستفادة من الدالة format. يمكن إنجاز الحاوية ذات الشكل الدائري باستخدام خاصية التنسيق borderRadius. ولتفعل هذا عليك تثبيت قيمتي الخاصيتين width و height، ثم ضبط خاصية التنسيق border-radius عند القيمة "width/2". 10.21: نموذج التقييم أنجز نموذجًا لإنشاء تقييم جديد لمستودع مستخدمًا المكتبة Formik. ينبغي أن يضم النموذج أربعة حقول هي: اسم دخول مالك المستودع. اسم المستودع. التقييم على هيئة قيمة عددية. رأي المستخدم على هيئة نص. تحقق من الحقول مستخدمًا تخطيط Yup، بحيث تتأكد أن: اسم المستخدم لمالك المستودع هو قيمة نصية إجبارية. اسم المستودع هو قيمة نصية إجبارية. التقييم هو قيمة عددية إجبارية بين 0 و100. رأي المستخدم هو قيمة نصية اختيارية. اطلع على توثيق Yup لإيجاد المُقيَّمات validators المناسبة، واستخدم رسائل خطأ مناسبة عند استخدامك لهذه المُقيِّمات، إذ يمكنك تعريف رسالة الخطأ مثل وسيط للتابع message العائد للمقيّم. يمكنك أيضًا توسيع نص الحقل ليمتد على عدة أسطر باستعمال الخاصية multiline العائدة للمكون TextInput. استخدم الطفرة createReview لإنشاء تقييم جديد، واطلع على وسطاء الطفرة بفتح إحدى النافذتين "docs" أو "schema " في أرضية عمل GraphQL. ويمكنك استخدام الخطاف useMutation لإرسال الطفرة إلى خادم Apollo. بعد نجاح عمل الطفرة createReview، حوّل المستخدم إلى واجهة عرض المستودع التي أنجزناها في التمرين السابق. نفّذ ذلك باستخدام التابع navigate بعد أن تستخرج كائن التاريخ باستخدام الخطاف useNavigate. يمتلك التقييم الذي أنشأته حقلًا باسم "repositoryId" يمكنك استخدامه لتأسيس مسار لعنوان التقييم. استخدم سياسة الإحضار "cache-and-network" لتمنع جلب بيانات الذاكرة المؤقتة مع الاستعلام repository في واجهة عرض مستودع وحيد. يمكنك استخدامها مع الخطاف على النحو التالي: useQuery(GET_REPOSITORY, { fetchPolicy: 'cache-and-network', // خيارات أخرى }); انتبه إلى أن التقييم سيكون فقط لمستودعات GitHub العامة الموجودة فعلًا، وأن المستخدم سيقيَّم مستودعًا محددًا مرة واحدة فقط. ليس عليك الآن التعامل مع حالات الخطأ هذه، بل فقط إظهار الخطأ مع الرمز المحدد والرسالة المناسبة. جرّب ما نفّذته على أحد المستودعات العامة التي تملكها، أو أية مستودعات عامة أخرى. ينبغي أن يتمكن المستخدم من الوصول إلى واجهة التقييم من خلال شريط التطبيق. لذلك أنشئ نافذة جديدة عنوانها "Create a review". ينبغي أن تكون النافذة مرئية للمستخدم الذي سجل دخوله فقط. وعليك أن تحدد أيضًا عنوانًا لواجهة التقييم. ستبدو النسخة النهائية للتطبيق مشابهة للشكل التالي: التُقطت شاشة التطبيق هذه بعد إخفاق إرسال بيانات نموذج لإظهار شكله في هذه الحالة. 10.22: نموذج تسجيل مستخدم جديد أنجز نموذجًا لتسجيل مستخدم جديد مستخدمًا Formik. ينبغي أن يضم النموذج ثلاثة حقول: اسم مستخدم، كلمة مرور، وتأكيدًا لكلمة المرور. تحقق من صحة البيانات في النموذج مستخدمًا تخطيط Yup ومتبعًا مايلي: اسم المستخدم هو سلسلة نصية إجبارية طولها بين 1 و 30. كلمة المرور هي سلسلة نصية إجبارية طولها بين 1 و 5. تأكيد كلمة المرور يتطابق تمامًا مع كلمة المرور. قد يربكك قليلًا تأكيد كلمة المرور، لكن يمكن إنجاز الأمر بالاستفادة من التابعين oneOf وref كما هو مبين في إرشادات هذا المشروع. يمكنك إنشاء مستخدم جديدة باستخدام الطفرة createUser، ويمكنك الإطلاع على كيفية استخدامها من خلال توثيق أرضية عمل Apollo. سجّل دخول المستخدم الجديد بعد إنشاء حسابه الخاص مستخدمًا الخطاف useSignIn كما فعلنا في نموذج تسجيل الدخول. وجِّه المستخدم الجديد بعد تسجيل دخوله إلى واجهة عرض المستودعات المقيّمة. ينبغي أن يكون المستخدم قادرًا على الوصول إلى واجهة تسجيل مستخدم جديد من خلال شريط التطبيق، وذلك بالضغط على نافذة "Sign up" التي ستظهر فقط للمستخدم قبل أن يسجّل دخوله. ستبدو النسخة النهائية لواجهة تسجيل مستخدم جديد مشابهة للصورة التالية: التُقطت شاشة التطبيق هذه بعد إخفاق إرسال بيانات نموذج لإظهار شكله في هذه الحالة. 10.23: ترتيب بيانات تطبيق قائمة المستودعات المقّيمة تُرتّب المستودعات حتى هذه اللحظة وفقًا لتاريخ تقييم المستودع. أنجز آلية تسمح للمستخدم أن يختار أساسًا لترتيب هذه المستودعات وفقًا لما يلي: المستودعات الأخيرة: سيكون المستودع الذي قُيَّم آخيرًا وللمرة الأولى في أعلى القائمة. هذا الترتيب هو المتبع حاليًا، وينبغي أن يكون الخيار الإفتراضي. المستودعات الأعلى تقييمًا: سيكون المستودع الذي يحمل أعلى قيمة لمعدّل التقييم في أعلى القائمة. المستودعات الأدنى تقييمًا: سيكون المستودع الذي يحمل أدنى قيمة لمعدّل التقييم في أعلى القائمة. يمتلك الاستعلام repositories الذي يحضر قائمة المستودعات وسيطًا يدعى orderby يمكنك استخدامه لتحديد أسلوب الترتيب المتبع، إذ يمتلك هذه الوسيط قيمتين فقط هما: CREATED_AT: الترتيب وفق تاريخ التقييم لأول مرة. RATING_AVERAGE: الترتيب على أساس معدل التقييم. كما يمتلك الاستعلام وسيطًا يدعى orderDirection ويستخدم لتغيير جهة الترتيب. يملك الوسيط الأخير قيمتين، هما: ASC (تصاعدي، من التقييم الأدنى إلى الأعلى) و DESC (تنازلي، من التقييم الأعلى إلى الأدنى). يمكن المحافظة على خيار الترتيب باستخدام الخطاف useState على سبيل المثال، كما يمكن تمرير المتغيرات التي يستخدمها الاستعلام repositories إلى الخطاف useRepositories مثل وسطاء. يمكنك أيضًا استخدام المكتبة react-native-picker أو المكوّن Menu العائد للمكتبة React Native Paper لإنجاز شيفرة ترتيب القائمة، كما يمكنك استخدام الخاصية ListHeaderComponent العائدة للمكوّن FlatList لتزويدك بترويسة تحتوي على مكوّن الاختيار. ستبدو النسخة النهائية لهذه الميزة، وبناء على مكوَّن الاختيار الذي اعتمدته، قريبةً من الصورة التالية: 10.24: انتقاء مستودعات محددة من القائمة يتيح خادم Apollo ترشيح المستودعات بناءً على اسمها أو اسم مالكها، ويمكن إنجاز ذلك باستخدام الوسيط searchKeyword العائد للاستعلام repositories. إليك مثالًا عن كيفية استخدام الوسيط مع الاستعلام: { repositories(searchKeyword: "ze") { edges { node { id fullName } } } } أنجز ميزةً في التطبيق لترشيح قائمة المستودعات المقيّمة بناءً على كلمة محددة. ينبغي أن يكون المستخدم قادرًا على الكتابة ضمن مربع إدخال نصي وستظهر نتائج الفلترة وفقًا للكلمة المكتوبة مباشرة أثناء الكتابة. يمكنك استخدام المكوّن البسيط TextInput، أو استخدام مكوّن أكثر عصرية مثل المكوّن Searchbar العائد للمكتبة React Native Paper على أنه مربع إدخال نصي. ضع مكوّن الإدخال النصي ضمن ترويسة المكوّن FlatList. ولتفادي تنفيذ عدد كبير من الاستعلامات أثناء كتابة المستخدم للكلمة بسرعة، اختر آخر سلسة كتبها بعد تأخير بسيط. تدعى هذه التقنية بالتريّث debouncing. تمثّل المكتبة use-debounce حلًا جيدًا لتأخير متغيّر الحالة، بعد أن تستخدم زمن انتظار معقول مثل 500 ميلي ثانية. خزّن قيمة النص الذي يُكتب في مربع الإدخال مستخدمًا الخطاف useState، ومرر القيمة المُؤخَّرة إلى الاستعلام كقيمة للوسيط searchKeyword. قد تعترضك مشكلة فقدان مكون الإدخال النصي تركيز الدخل بعد كتابة كل حرف، وذلك لأن المحتوى الذي تقدّمه الخاصية ListHeaderComponent سيُلغى باستمرار. يمكن حل المشكلة بتحويل المكوّن المسؤول عن تصيير المكوّن FlatList إلى مكوّن صنف class component ومن ثم تعريف دالة تصيير الترويسة إلى خاصية للصنف على النحو التالي: export class RepositoryListContainer extends React.Component { renderHeader = () => { // يحوي خاصيات المكون this.props const props = this.props; // ... return ( <RepositoryListHeader // ... /> ); }; render() { return ( <FlatList // ... ListHeaderComponent={this.renderHeader} /> ); } } ستبدو النسخة النهائية لميزة انتقاء المستودعات قريبةً من الصورة التالية: الترقيم بطريقة المؤشرات عندما تعيد الواجهة البرمجية قائمةً مرتبةً من العناصر من مجموعةٍ ما، فإنها ستعيد مجموعةً جزئيةً من العناصر من أصل المجموعة الكاملة لتقليل عرض حزمة الاتصال مع الخادم وتقليل الذاكرة المستخدمة لتخزين القائمة في تطبيق المستخدم. يمكن أن نطلب تلك المجموعة الجزئية لتوافق معاملات محددة لكي يستطيع المستخدم أن يطلب مثلًا أول عشرين عنصرًا منها ابتداءًا من عنصر محدد. تدعى هذه التقنية عادة بالترقيم pagination. وعندما نستطيع الحصول على قائمة بعد عنصر محدد بمؤشر معيّن، سنكون أمام الترقيم المبني على المؤشرات cursor-based pagination. فالمؤشر إذًا هو تحديدٌ لعنصر في قائمة مرتبة. لنلقي نظرةً على قائمة المستودعات المرقمة التي يعيدها الاستعلام repositories باستخدام الاستعلام التالي: { repositories(first: 2) { totalCount edges { node { id fullName createdAt } cursor } pageInfo { endCursor startCursor hasNextPage } } } يبلِّغ الوسيط first الواجهة البرمجية ان تعيد فقط أول مستودعين. وإليك مثالًا عن الاستجابة لهذا الاستعلام: { "data": { "repositories": { "totalCount": 10, "edges": [ { "node": { "id": "zeit.next.js", "fullName": "zeit/next.js", "createdAt": "2020-05-15T11:59:57.557Z" }, "cursor": "WyJ6ZWl0Lm5leHQuanMiLDE1ODk1NDM5OTc1NTdd" }, { "node": { "id": "zeit.swr", "fullName": "zeit/swr", "createdAt": "2020-05-15T11:58:53.867Z" }, "cursor": "WyJ6ZWl0LnN3ciIsMTU4OTU0MzkzMzg2N10=" } ], "pageInfo": { "endCursor": "WyJ6ZWl0LnN3ciIsMTU4OTU0MzkzMzg2N10=", "startCursor": "WyJ6ZWl0Lm5leHQuanMiLDE1ODk1NDM5OTc1NTdd", "hasNextPage": true } } } } يعتمد تنسيق الكائن الناتج والوسطاء على مواصفات اتصالات مؤشر أرضية عمل GraphQL للترحيل، والتي أصبحت مواصفات ترقيم صفحات شائعة جدًا واعتُمدت على نطاقٍ واسع في واجهة برمجة تطبيقات GitHub's GraphQL API. سيحتوي الكائن الذي يحمل النتيجة مصفوفةً باسم edges تحتوي بدورها على عناصر لها سمتين الأولى هي العقدة node والأخرى هي المؤشر cursor؛ إذ تحتوي العقدة على المستودع بحد ذاته كما نعرف؛ أما المؤشر فهو تمثيلٌ مشفّر للعقدة بأسلوب "Base64". ويحتوي المؤشر على المعرّف id للمستودع بالإضافة إلى تاريخ إنشائه. هذه هي كل المعلومات التي نريدها لكي نشير إلى العنصر عندما تُرتَّب العناصر وفق تاريخ إنشاء المستودع. يحتوي الكائن pageInfoعلى معلومات مثل المؤشرات على بداية ونهاية المصفوفة. دعونا نتأمل الحالة التي نريد فيها مجموعة العناصر التي تلي آخر عنصر من المجموعة الحالية وهو العنصر "zeit/swr". يمكننا إسناد قيمة الحقل endCursor إلى الوسيط after للاستعلام على النحو التالي: { repositories(first: 2, after: "WyJ6ZWl0LnN3ciIsMTU4OTU0MzkzMzg2N10=") { totalCount edges { node { id fullName createdAt } cursor } pageInfo { endCursor startCursor hasNextPage } } } والآن سنحصل على العنصرين التاليين، وسنستمر في هذه العملية حتى يأخذ الحقل hasNextPage القيمة false، وهذا يعني أننا وصلنا آخر القائمة، ولتتعمق أكثر في الترقيم المتعلق بالمؤشرات، إقرأ المقالة التي عنوانها Pagination with Relative Cursors على موقع Shopify، حيث تقدم المقالة الكثير من التفاصيل عن طريقة كتابة الشيفرة بالإضافة إلى الفوائد من استخدام الترقيم بالمؤشرات مقارنةً بالترقيم وفق ترتيب العنصر index-based. التمرير اللانهائي تُصمّم القوائم المرتبة عموديًا في تطبيقات سطح المكتب أو تطبيقات الهواتف الذكية بتقنية تدعى التمرير اللانهائي - infinite scrolling. ويعتمد مبدأ هذه التقنية على النقاط التالية: إحضار المجموعة الأولية من العناصر. عند وصول المستخدم إلى آخر عنصر من هذه المجموعة، يجري إحضار المجموعة التالية من العناصر التي تبدأ من العنصر الذي يلي آخر عنصر من المجموعة السابقة. تتكرر الخطوة الثانية حتى يتوقف المستخدم عن تمرير شريط القائمة أو عندما يتجاوز حدًا معينًا من مرات التمرير. ويشير الاسم "تمرير لانهائي" إلى أن القائمة ستبدو لانهائية، فسيبقى المستخدم قادرًا على تمرير العناصر ضمن الشريط وستُعرض العناصر بصورةٍ مستمرة أيضًا. لنرى كيف سننفذ ذلك عمليًا من خلال خطاف Apollo Client الذي يُدعى useQuery. يضم توثيق Apollo Client تفاصيل كثيرة عن طريقة تنفيذ الترقيم باستخدام المؤشرات. لننجز إذًا ميزة التمرير اللانهائي لعرض قائمة المستودعات المقيّمة. علينا أولًا أن نحدد متى يصل المستخدم إلى آخر عنصر في القائمة. ولحسن الحظ، يمتلك المكون FlatList الخاصية onEndReached التي تستدعى الدالة التي ستنفذ العملية عندما يصل المستخدم إلى آخر عنصر من القائمة. يمكنك التحكم بوقت استدعاء الدالة onEndReach باستخدام الخاصية onEndReachedThreshold. غيِّر شيفرة المكوّن FlatList الموجود ضمن المكوّن RepositoryList لكي يستدعي الدالة حالما يصل المستخدم إلى العنصر الأخير: export const RepositoryListContainer = ({ repositories, onEndReach, /* ... */, }) => { const repositoryNodes = repositories ? repositories.edges.map((edge) => edge.node) : []; return ( <FlatList data={repositoryNodes} // ... onEndReached={onEndReach} onEndReachedThreshold={0.5} /> ); }; const RepositoryList = () => { // ... const { repositories } = useRepositories(/* ... */); const onEndReach = () => { console.log('You have reached the end of the list'); }; return ( <RepositoryListContainer repositories={repositories} onEndReach={onEndReach} // ... /> ); }; export default RepositoryList; جرّب عملية التمرير إلى نهاية قائمة المستودعات، وستظهر رسالةً في سجل العمل مفادها أنك وصلت إلى العنصر الأخير. علينا الآن أن نحضر مزيدًا من المستودعات في اللحظة التي نصل فيها إلى نهاية العناصر. يمكن إنجاز ذلك باستخدام الدالة fetchMore التي يؤمنها الخطاف useQuery. يمكننا استخدام سياسة الحقل field policy لوصف عميل Apollo وكيفية دمج المستودعات الموجودة في ذاكرة التخزين المؤقت مع المجموعة التالية من المستودعات، إذ تُستخدم سياسات الحقول عمومًا لتخصيص سلوك ذاكرة التخزين المؤقت أثناء عمليات القراءة والكتابة باستخدام دوال القراءة والدمج. دعنا نضيف سياسة حقل للاستعلام repositories في ملف "apolloClient.js" على النحو التالي: import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; import { setContext } from '@apollo/client/link/context'; import Constants from 'expo-constants'; import { relayStylePagination } from '@apollo/client/utilities'; const { apolloUri } = Constants.manifest.extra; const httpLink = createHttpLink({ uri: apolloUri, }); const cache = new InMemoryCache({ typePolicies: { Query: { fields: { repositories: relayStylePagination(), }, }, }, }); const createApolloClient = (authStorage) => { const authLink = setContext(async (_, { headers }) => { try { const accessToken = await authStorage.getAccessToken(); return { headers: { ...headers, authorization: accessToken ? `Bearer ${accessToken}` : '', }, }; } catch (e) { console.log(e); return { headers, }; } }); return new ApolloClient({ link: authLink.concat(httpLink), cache, }); }; export default createApolloClient; كما ذكرنا سابقًا، يعتمد تنسيق كائن نتيجة ترقيم الصفحات والوسطاء على مواصفات ترقيم الصفحات الخاصة بالترحيل Relay، ولحسن الحظ، يوفر Apollo Client سياسة حقل محددة مسبقًا، relayStylePagination، والتي يمكن استخدامها في هذه الحالة. غيّر في شيفرة الخطاف useRepositories بحيث يعيد الدالة fetchMore بعد فَكِّ تشفيرها، بحيث تستدعي الدالة fetchMore الفعلية مع الحقل endCursor، ثم يُحدّث بعدها الاستعلام بصورة صحيحة وفقًا للبيانات المُحضرة: const useRepositories = (variables) => { const { data, loading, fetchMore, ...result } = useQuery(GET_REPOSITORIES, { variables, // ... }); const handleFetchMore = () => { const canFetchMore = !loading && data?.repositories.pageInfo.hasNextPage; if (!canFetchMore) { return; } fetchMore({ variables: { after: data.repositories.pageInfo.endCursor, ...variables, }, }); }; return { repositories: data?.repositories, fetchMore: handleFetchMore, loading, ...result, }; }; تأكد من وجود الحقلين pageInfo و cursor في الاستعلام repositories كما هو موضح في مثال الترقيم. كما ينبغي عليك أن تضيف الوسيطين first و after إلى الاستعلام. ستستدعي الدالة handleFetchMore الدالة fetchMore العائدة إلى Apollo client إن كان هناك المزيد من العناصر التي ينبغي إحضارها، وذلك بناء على قيمة الخاصية hasNextPage، ومن المفترض أن نمنع إحضار عناصر جديدة طالما أن عملية الإحضار سابقةً لا تزال قيد التنفيذ، إذ ستكون قيمة الحقل loading في هذه الحالة true. سنزود الاستعلام في الدالة fetchMore بالمتغير after الذي يتلقى آخر قيمة endCursor للحقل. وتكون الخطوة الأخيرة استدعاء الدالة fetchMore ضمن دالة معالجة الحدث onEndReach: const RepositoryList = () => { // ... const { repositories, fetchMore } = useRepositories({ first: 8, // ... }); const onEndReach = () => { fetchMore(); }; return ( <RepositoryListContainer repositories={repositories} onEndReach={onEndReach} // ... /> ); }; export default RepositoryList; استخدم قيمة صغيرة نسبيًا (8 مثًلا) للوسيط first عندما تجرب التمرير اللانهائي، وهكذا لن تضطر إلى عرض كثيرٍ من المستودعات، وقد تواجهك أيضًا مشكلة الاستدعاء المباشر لمعالج الحدث onEndReach بعد تحميل واجهة العرض، والسبب على الأرجح، هو العدد الكبير للمستودعات في القائمة، وبالتالي ستصل إلى نهاية القائمة مباشرةً. يمكنك الالتفاف على هذه المشكلة بزيادة قيمة الوسيط first. وبمجرد أن ترى أن قائمة التمرير اللانهائي تعمل جيدًا، يمكنك تجريب قيم كبيرة للوسيط first. التمرينات 10.25- 10.27 10.15: شريط تمرير لانهائي لقائمة المستودعات المقيّمة نفذ شريط تمرير لانهائي لقائمة المستودعات المقيّمة. يمتلك الحقل reviews العائد للنوع "Repository" الوسيطينafter و first كما في الاستعلام repositories. ويمتلك النوع "ReviewConnection" الحقل pageInfo كما يمتلكه النوع "RepositoryConnection". إليك مثالًا عن استعلام: { repository(id: "jaredpalmer.formik") { id fullName reviews(first: 2, after: "WyIxYjEwZTRkOC01N2VlLTRkMDAtODg4Ni1lNGEwNDlkN2ZmOGYuamFyZWRwYWxtZXIuZm9ybWlrIiwxNTg4NjU2NzUwMDgwXQ==") { totalCount edges { node { id text rating createdAt repositoryId user { id username } } cursor } pageInfo { endCursor startCursor hasNextPage } } } } يمكن أن تتشابه سياسة حقل ذاكرة التخزين المؤقت مع استعلام repositories: const cache = new InMemoryCache({ typePolicies: { Query: { fields: { repositories: relayStylePagination(), }, }, Repository: { fields: { reviews: relayStylePagination(), }, }, }, }); استخدم قيمةً صغيرةً نسبيًا للوسيط first عندما تحاول إنجاز قائمة التمرير اللانهائي، وربما ستضطر إلى إنشاء عدة مستخدمين جدد لكي تُقيّم عددًا من المستودعات، وبذلك تصبح قائمة المستودعات المقيّمة طويلة بما يكفي لتجرّب شريط التمرير. اجعل قيمة الوسيط كبيرةً بما يكفي لعدم استدعاء معالج الحدث onEndReach مباشرةً عند عرض الواجهة، وصغيرةً في نفس الوقت بحيث يعرض الشريط المستودعات من جديد عند الوصول إلى نهاية القائمة. وبمجرد أن ترى أن قائمة التمرير اللانهائي تعمل جيدًا، يمكنك تجريب قيم كبيرة للوسيط first. 10.26: واجهة عرض لتقييمات المستخدم أنجز واجهة تُمكِّن المستخدم من عرض ما قيّمه. ينبغي أن يكون المستخدم قادرًا على الوصول إلى هذه الواجهة من خلال الضغط على النافذة "My reviews" في شريط التطبيق بمجرد أن يسّجل دخوله. يمكنك تطبيق أسلوب التمرير اللانهائي إن أردت في هذا التمرين. وإليك الشكل الذي يمكن أن تظهر عليه هذه الواجهة: تذكر أنه بإمكانك الحصول على المستخدم الذي سجّل دخول من خادم Apollo مستعينًا بالاستعلام me. سيعيد الاستعلام النوع "User" الذي يمتلك الحقل review. إن كنت قد أنجزت مسبقًا الاستعلام me يمكنك إعادة استخدامه بعد جعله قادرًا على إحضار الحقل review شرطيWh، وذلك بالاستفادة من توجيه GraphQL الذي يُدعى include. لنفترض أن الاستعلام الحالي قد أنجز بصورةٍ قريبة من التالي: const GET_CURRENT_USER = gql` query { me { # user fields... } } `; يمكنك تزويد الاستعلام بالوسيط includeReviewواستخدام ذلك مع التوجيه include: const GET_CURRENT_USER = gql` query getCurrentUser($includeReviews: Boolean = false) { me { # user fields... reviews @include(if: $includeReviews) { edges { node { # review fields... } cursor } pageInfo { # page info fields... } } } } `; يمتلك الوسيط includeReview القيمة الافتراضية false، لأننا لا نريد أن نسبب حملًا زائدًا على الخادم ما لم نرد صراحةً إحضار المستودعات التي قيّمها المستخدم. إن مبدأ هذا التوجيه بسيط على النحو التالي: إن كانت قيم الوسيط if هي true، أحضر الحقل، وإلا احذفه. 10.27: إضافة أفعال إلى واجهة عرض التقييمات بعد أن أصبح المستخدم قادرًا على استعراض ما قيّمه، لنضف إلى الواجهة بعض الأفعال. ضع أسفل كل تقيم زرين، أحدهما لعرض المستودع، بحيث ينتقل المستخدم بالضغط عليه إلى واجهة عرض مستودع واحد، والآخر لحذف تقييم هذا المستودع. إليك ما قد يبدو عليه الوضع بعد إضافة الزرين: ينبغي أن يلي الضغط على زر الحذف رسالة تأكيد للحذف، فإن أكد المستخدم أمر الحذف ستُنفّذ العملية، وإلا سيُهمل الأمر. يمكنك إنجاز ذلك باستخدام الوحدة البرمجية Alert. وانتبه إلى أن استدعاء التابع Alert.alert لن يفتح رسالة التأكيد في واجهة عرض المنصة Expo، بل عليك استخدام تطبيق جوّال Expo أو المقلّد لترى كيف ستبدو هذه الرسالة. إليك الرسالة التي ينبغي أن تظهر عند الضغط على زر الحذف: يمكنك حذف التقييم باستخدام الطفرة deleteReview. تمتلك هذه الطفرة وسيطًا واحدًا وهو المعرّف id للتقييم الذي سيُحذف. ومن السهل بعد تنفيذ الطفرة تحديث استعلام قائمة المستودعات باستدعاء الدالة refetch. هكذا نكون قد وصلنا إلى التمرين الأخير في هذا المقال، وقد حان وقت تسليم الإجابات إلى GitHub والإشارة إلى التمارين التي أكملتها في منظومة تسيم التمارين. انتبه إلى وضع تمارين هذا المقال في القسم 4 من منظومة التسليم. مصادر إضافية طالما أننا وصلنا إلى نهاية هذا القسم، دعونا نلقي نظرةً على بعض المصادر الهامة للتطوير باستخدام React Native. سنبدأ من توثيق React Native باللغة العربية الذي تقدمه أكاديمية حسوب، والتي تقدم أيضًا توثيق React باللغة العربية، بالإضافة إلى القسم المتخصص باللغة React على موقع الأكاديمية، كما نشير إلى الموقع Awesome React Native الذي يقدم قائمةً مهمةً جدًا من المصادر مثل المكتبات والدورات التعليمية. وبما أن القائمة طويلة جدًا، لنطلع على بعض النقاط. المكتبة React Native Paper المكتبة Paper هي مجموعةٌ من المكوّنات المخصصة والجاهزة للاستخدام في React Native، مبنيةٌ على معايير تصميم Material التي وضعتها غوغل Google. صُممت React Native Paper من أجل لتقدم وظيفة تماثل وظائف المكتبة Material-UI، إذ تقدم مجموعةً واسعةً من مكوّنات واجهة المستخدم UI عالية الجودة التي تدعم إنشاء سمات مخصصة للتطبيق. وتتميز إعدادات استخدام React Native Paper مع تطبيقات React Native المبنية على منصة Expo بالسهولة، حيث يمكنك تجربتها مباشرة في التمارين القادمة إن أردت. المكتبة Styled-components سيعطيك استخدام القوالب المعرّفة المجرّدة tagged template literal -وهي إضافةٌ جديدة في JavaScript-، مع المكتبة styled-components إمكانية كتابة شيفرة CSS فعلية لتنسيق المكوّنات، كما يلغي استخدام هذه المكتبة الحاجة إلى الربط بين المكوّنات والتنسيق، وسيكون استخدام المكوّنات مثل وحدات بناءً تنسيقات على المستوى البرمجي المنخفض سهلًا جدًا. تستخدم المكتبة Styled-components لتنسيق مكوّنات React Native باستخدام تقنية CSS-in-JS. لقد اعتدنا على تعريف تنسيق المكوّنات في React Native باستخدام كائنات JavaScript، وبالتالي لن يكون استخدام CSS-in-JS غريبًا. لكن مقاربة المكتبة Styled-components ستختلف تمامًا عن استخدام التابع StyleSheet.createو الخاصية style. يُعرّف تسيق المكوّن في Styled-components باستخدام ميزة تُدعى القوالب المعرّفة المجرّدة tagged template literal أو كائنات جافا سكربت JavaScript الصِّرفة، التي تُمكننا من تعريف خصائص تنسيق جديدة للمكونات بناءً على خصائص تلك المكوّنات في زمن التنفيذ. سيتيح ذلك الكثير من الإمكانات مثل الانتقال بين السمات القاتمة والمضيئة، كما أنها تدعم كليًا موضوع السمات. إليك مثالًا عن إنشاء المكوّن Text مع إمكانية تغيير تنسيقه بناءً على خصائصه: import styled from 'styled-components/native'; import { css } from 'styled-components'; const FancyText = styled.Text` color: grey; font-size: 14px; ${({ isBlue }) => isBlue && css` color: blue; `} ${({ isBig }) => isBig && css` font-size: 24px; font-weight: 700; `} `; const Main = () => { return ( <> <FancyText>Simple text</FancyText> <FancyText isBlue>Blue text</FancyText> <FancyText isBig>Big text</FancyText> <FancyText isBig isBlue> Big blue text </FancyText> </> ); }; وطالما أن المكتبة styled-components ستعالج تعريف التنسيق، يمكننا استخدام أسلوب كتابة الأفعى snake case المشابه لطريقة كتابة CSS لتسمية الخصائص والواحدات (مثل % أو px)، لكن لا أهمية في الواقع للواحدات، لأن قيم خصائص التنسيق لا تقبل ذلك. للاطلاع على مزيدٍ من المعلومات عن استخدام هذه المكتبة، اقرأ التوثيق الخاص بها. المكتبة React-spring react-spring هي مكتبة رسوم متحركة أساسها المكتبة spring-physics، تغطي معظم احتياجاتك لواجهات مزودة برسوم متحركة، إذ تؤمن لك هذه المكتبة أدوات بالمرونة الكافية لتحويل جميع أفكارك إلى واجهات متحركة. تؤمن لك المكتبة React-spring واجهة برمجية ذات خطافات hook API واضحة الاستخدام لتحريك مكونات React Native. المكتبة React Navigation تؤمن مكتبة React Navigation التنقل والتوجه في تطبيقات React Native هي مكتبةٌ للتحكم بالتنقلات بين وجهات مختلفة في تطبيقات React Native. تتشابه في بعض النواحي مع المكتبة React Router. لكن وعلى خلاف React Router، تقدم مميزات أكثر قربًا للمنصة الأصيلة مثل الإيماءات الأصيلة والرسوميات الانتقالية التي تظهر عند التنقل بين الواجهات المختلفة للتطبيق. كلمة ختامية وهكذا نكون قد أكملنا تطبيقنا وأصبح جاهزًا. لقد تعلمنا خلال هذه الرحلة العديد من المفاهيم الجديدة، مثل إعداد تطبيقات React Native باستخدام المنصة Expo، وطريقة العمل مع مكوّنات React Native البنيوية، وتنسيق مظهر هذه المكوّنات، إضافةً إلى الاتصال مع الخادم واختبار التطبيقات. ستكون الخطوة الأخيرة هي نشر التطبيق على متجر Apple App أو متجر Google Play. يبقى موضوع نشر التطبيق خيارًا شخصيًا وليس أمرًا مُلحًّا، لأنك ستحتاج أيضًا إلى نشر وتوزيع الخادم rate-repository-api، أما من أجل تطبيق React Native بحد ذاته، فعليك أن تُنشئ نسخة بناء لمنصة iOS أو لمنصة أندرويد باتباع الإرشادات في توثيق Expo. وبعد ذلك ستتمكن من توزيع هذه النسخ على متجري Apple App أو Google Play، وستجد تفاصيل عن هذه الخطوة أيضًا في توثيق Expo. ترجمة -وبتصرف- للفصل Testing and extending our application من سلسلة [Deep Dive Into Modern Web Development اقرأ أيضًا المقال السابق: تواصل تطبيق React Native مع الخادم أساسيات React Native مدخل إلى التحريك في React Native
  20. إن أهم ما يميز أنظمة التشغيل من ناحية سهولة الاستخدام (وخاصةً للمبتدئين والمستخدمين العاديين)، هي طريقة إدارة النظام للمجلدات والملفات مثل طريقة عرضها وإنشائها والتحكم بموقعها وخصائصها وطريقة الوصول إليها. لهذا تُعد اﻷنظمة التي تُقدّم واجهة رسومية GUI مثل أوبونتو وويندوز وماك أكثرها انتشارًا واستخدامًا؛ وإن كانت هناك اختلافات بينها، وميزات إيجابية وسلبية تتفاوت بين نظام وآخر. نقدم في مقالنا هذا اﻷفكار اﻷساسية المتعلقة بنظام إدارة المجلدات والملفات في أوبونتو، وذلك لكي تتمكن من التعامل بحرية مع الملفات التي تُنشئها وتنظيمها بالطريقة التي تراها مناسبة؛ كما سنلقي نظرةً على وسائط التخزين الداخلية والخارجية، وفكرة تثبيتها Mounting وإزالتها. ستكون قادرًا في نهاية هذا المقال على استثمار حاسوبك بطريقة مريحة ومثمرة، ومتمكنًا من تنظيم ملفاتك والعمل معها بكل ثقة. النافذة الرئيسية "منزل" وفكرة المجلد الجذري تُبنى هيكلية تنظيم المجلدات والملفات في أوبونتو على فكرة وجود مجلد جذري root folder تتفرع عنه بقية المجلدات. يُعرف هذا المجلد باسم "home/"، ولايوجد ضمنه سوى مجلد وحيد يحمل اسم مستخدم الجهاز، كما يُعرف باسم "المنزل". لا يمكن أن تضع في المجلد الجذري أي مجلدات أخرى أو أية ملفات؛ وبالتالي سيبدأ مجال عملك من "المنزل" وما داخله. ولكي ننتقل مباشرةً إلى النافذة الرئيسية "منزل"، يمكنك النقر نقرةً مزدوجةً على المجلد الذي يحمل اسم المستخدم ضمن نافذة سطح المكتب، أو النقر على تطبيق "الملفات" من شريط التطبيقات. أقسام النافذة "منزل" كمثال عن نوافذ واجهة جنوم الرسومية توضح لقطة الشاشة التالية النافذة الرئيسية "منزل"، واﻷقسام التي تتألف منها عمومًا: تتكون النافذة من: الشريط الجانبي: يقع على يمين النافذة في النسخة العربية ويضم اختصارات للوصول إلى المجلدات اﻷساسية إين ما كنت، باﻹضافة إلى اختصارات للوصول إلى سواقات الأقراص المدمجة وأجهزة التخزين الخارجية وأماكن أخرى كالمجلدات المشتركة على الشبكات. فضاءالعمل: وهي المساحة البيضاء التي تشغل معظم مساحة النافذة، وتُعرض فيها المجلدات والملفات الموجودة في المجلد الحالي. يمكنك العمل مع المجلدات الموجودة ضمن هذه النافذة من نسخ ولصق وتغيير تسمية وغيرها؛ وبالنقر على فضاء العمل نقرةً واحدةً بالزر اليميني، ستظهر لك قائمة مختصرة تتيح بعض خيارات التعامل مع المجلدات. شريط اﻷدوات: شريط أسود يقع أعلى النافذة ويضم مجموعةً من اﻷزرار التي تؤدي وظائف متنوعة. تصنف أزرار شريط الأدوات ضمن مجموعات هي: أزرار التنقل: وتضم زري "أمام" و"خلف" للانتقال إلى الملجد اﻷعلى أو اﻷدنى مستوىً من المجلد الحالي، وإلى جوارها موقع المجلد الحالي بالنسبة لمجلد "المنزل". أزرار التحكم الرئيسية بالنافذة: تقع على يمين شريط المهام وتضم على التسلسل من أقصى اليسار إلى اليمين أزرار اﻹغلاق، وتكبير أو تصغير النافذة وإخفاء النافذة. أزرار تنظيم فضاء العمل: قد يختلف عدد هذه اﻷزرار من نافذة إلى أخرى باختلاف التطبيق الذي يفتحها، لكنها تضم افتراضيًا اﻷزرار التالية مرتبة من اليسار إلى اليمين: زر البحث: بالنقر عليه، يظهر إلى يساره مربع حوار لكتابة ما تريد البحث عنه في هذا المجلد. زر تغيير المنظور: بالنقر عليه، يتبدّل تنظيم اﻷيقونات في فضاء العمل بين عرض القائمة والعرض الحر. زر خيارات المنظور: بالنقر عليه، تظهر قائمة تتيح لك مجموعة إضافية من الخيارات تتعلق بحجم اﻷيقونات المعروضة وطريقة ترتيبها وعمليات التراجع أو إعادة فعل سابق. زر القائمة: يعرض هذا الزر مجموعةً من الخيارات الهامة للعمل ضمن المجلد نستعرضها تاليًا بالتفصيل: أيقونات فتح النوافذ والمجلدات: تقع أعلى القائمة وهي بالترتيب: فتح نافذة جديدة، وفتح نافذة فرعية أخرى ضمن النافذة الواحدة، وإضافة مجلد جديد. أيقونات التحرير: وهي على الترتيب من اليسار إلى اليمين: قص ونسخ ولصق. اختيار الكل: لاختيار جميع محتويات المجلد. أظهر الملفات المخفية: ﻹظهار أية ملفات جرى إخفاؤها ضمن المجلد. أظهر الشريط الجانبي show sidebar: ﻹخفاء وإظهار الشريط الجانبي للنافذة. التفضيلات: تظهر عند النقر على هذا الخيار نافذة التفضيلات التي تضم أدوات تتحكم في طريقة عرض وفتح الملفات والبحث عنها وغيرها. اختصارات لوحة المفاتيح: وتعرض لك نافذةً تضم معظم اختصارات لوحة المفاتيح التي تحتاجها ﻷداء المهام في النوافذ. مساعدة: سيحمّل هذا الخيار تطبيق "مساعدة". عن الملفات: يعرض لمحة عن تطبيق "ملفات". أساسيات التعامل مع المجلدات والملفات بعد أن أخذنا فكرةً واضحةً عن أقسام النافذة، سننتقل مباشرةً إلى العمل مع المجلدات والملفات. إنشاء مجلد جديد وحذف مجلد وتغيير اسمه ﻹشاء مجلد جديد في نافذة، انقر بالزر الأيمن للفأرة ضمن فضاء العمل، واختر مجلد جديد أو اضغط المفاتيح Shift + Ctrl + N؛ كما يمكنك النقر على قائمة النافذة في شريط اﻷدوات، ثم النقر على أيقونة المجلد الجديد. وأيما اخترت من الطرق، ستظهر لك نافذة إنشاء مجلد جديد تضم مربح حوار يطلب إليك اختيار اسم للمجلد الجديد. انقر على الزر "أنشئ" ﻹنشاء المجلد بالاسم الذي اخترته، أو "إلغ" ﻹلغاء اﻷمر. لحذف مجلد ما، انقر على المجلد ثم اضغط على المفتاح Delete، أو انقر بالزر الأيمن للفأرة على المجلد لتظهر قائمة خيارات المجلد. انتقل بعدها إلى الخيار "انقل إلى المهملات"، وانقر عليه. في كلتا الطريقتين سيُحذف المجلد، لكن بعد أن تتم العملية، ستعرض عليك النافذة رسالةً تخبرك فيها أن المجلد قد حُذف وتعطيك خيار "تراجع" إن فعلت ذلك عن طريق الخطأ، فستختفي الرسالة إن أهملتها الرسالة تلقائيًا بعد عدة ثوان. لإعادة تسمية مجلد، انقر عليه، ثم اضغط على المفتاح F2؛ أو انقر عليه بالزر الأيمن للفأرة واختر اﻷمر "غيّر الاسم" من قائمة الخيارات المتاحة. تعرض النافذة في كلتا الطريقتين رسالةً تضم مربع حوار لكتابة الاسم الجديد، وإلى جواره زر "غيّر الاسم". إن غيرت اسم المجلد ولم تنقر على زر "غيّر الاسم"، فلن يحدث شيء. تحريك المجلدات والملفات ونقلها نسخ مجلد ولصقه: لتنشئ نسخةً جديدةً عن مجلدك، انقر على المجلد بالزر الأيمن، ثم اختر اﻷمر "انسخ"؛ كما يمكنك استخدام الاختصار Ctrl + C. عندها يخزن النظام نسخةً من مجلدك أو ملفك في ذاكرته، ويبقيها حتى تُلصق هذه النسخة في المكان المطلوب. إن أردت إنشاء نسخة في نفس النافذة، فانقر بالزر الأيمن للفأرة ضمن فضاء عمل النافذة، واختر اﻷمر "ألصق"، أو استخدم الاختصار Ctrl + V. سيظهر مجلد يحمل اسم المجلد السابق وإلى جواره عبارة "(نسخة)" إن كانت النسخة اﻷولى وعبارة "(نسخة أخرى)" إن كانت الثانية و"(3 نسخ)" إن كانت الثالثة وهكذا!؛ أما إن أردت إنشاء نسخة في نافذة أخرى، فافتح هذه النافذة وكرر خطوات اللصق السابقة. قص مجلد ولصقه: تماثل هذه العملية عملية النسخ، إلا أن النسخة اﻷصلية التي جرى قصها من مكانها ستختفي عند لصقها في مكان آخر، ولن تبقى أيضًا نسخة عنها في الحافظة، إذ لن يتفعّل الخيار "ألصق" في قائمة الخيارات. لقص ملف أو مجلد، انقر عليه بالزر الأيمن للفأرة ثم اختر اﻷمر "قص"، أو اضغط على المفتاحين Ctrl + X، ثم انتقل إلى النافذة الهدف واختر اﻷمر "ألصق". ضغط المجلدات والملفات يقدم لك نظام التشغيل أوبونتو ميزة ضغط الملفات وذلك لغرضين أساسيين: تصغير حجم المجلد أو الملف. سهولة مشاركة المجلد على اﻹنترنت نظرًا لامتناع الكثير من الخدمات عن تحميل أو تنزيل ملفات بامتدادات معينة، ونلجأ إلى تحويلها إلى صيغة ملف مضغوط. لضغط مجلد (أو كما يُعرف بإنشاء أرشيف)، انقر عليه بالزر الأيمن للفأرة، ثم اختر اﻷمر "اضغط"، وستظهر لك النافذة التالية: لاحظ وجود ثلاثة أنواع من أرشيفات الضغط هي: zip: وهو أرشيف عالي الدعم من قِبل معظم أنظمة التشغيل، ويتميز بسرعة إنشائه وفكّه. tar.xz: أرشيف تدعمه معظم توزيعات لينكس، كما توجد برامج تدعمه في ويندوز، لكنه أبطأ في اﻹنشاء والفك مقارنةً بالسابق، ويتميز بحجم أصغر. 7z.: يماثل السابق من حيث سرعة اﻹنشاء والفك والحجم الصغير، وتدعمه توزيعات لينكس؛ كما تجد مجموعة برمجيات تدعمه على ويندوز وماك. اكتب اسم اﻷرشيف الذي ستضغط فيه مجلداتك أو ملفاتك واختر نوعه ثم انقر الزر "أنشئ". خصائص الملفات والمجلدات للاطلاع على خصائص مجلد أو ملف، انقر عليه بالزر الأيمن للفأرة، ثم اختر "خصائص"؛ أو اضغط المفتاحين Ctrl + I معًا. تختلف نافذة الخصائص الظاهرة في حالتي المجلد والملف، وسنستعرض كل منهما سريعًا. خصائص المجلدات تتكون النافذة من النوافذ الفرعية التالية: أساسي: وتقدم معلومات عن نوع وموقع المجلد والمساحة الفارغة ضمن المجلد الجذري. التصاريح: وتقدم خيارات للتحكم بأذونات فتح المجلد للقراءة فقط أو للقراءة والكتابة، وهو موضوع سنتحدث فيه لاحقًا. المشاركة على الشبكة المحلية: إن كان حاسوبك متصلًا بشبكة محلية، فيمكنك عندها مشاركة المجلد مع مستخدمي الشبكة؛ وهذا موضوع خارج نطاق مقالنا. خصائص الملفات تتكون النافذة من نفس النوافذ الفرعية الموجودة في نافذة خصائص المجلد، ويضاف إليها نافذة أخرى هي "افتح باستخدام" تعرضها كما تعرضها لقطة الشاشة السابقة. وكما هو واضح، سيفتح التطبيق المبدئي هذا الملف لعرض محتواه؛ كما يقدم النظام عدة تطبيقات مُزكّاة لفتح هذا الملف، أي التطبيقات القادرة على فتحه والتعامل معه؛ وبإمكانك اختيار أيّ من التطبيقات المزكاة ليكون التطبيق المبدئي بالنقر عليه، ثم النقر على زر "اجعله المبدئي". وإن أردت العودة إلى اﻹعدادات الافتراضية، فانقر الزر"صفِّر". قد تظهر في نافذة الخصائص نافذة فرعية جديدة في بعض أنواع الملفات مثل ملفات الصورة لتقدم معلومات إضافية. ملاحظة: يمكنك النقر على الملف بالزر الأيمن للفأرة، ثم اختيار اﻷمر "افتح بتطبيق آخر" لاختيار تطبيق آخر غير التطبيق المبدئي. لمحة عن إنشاء الملفات وفتحها ما يُنشئ الملفات هي التطبيقات، لهذا عليك أولًا معرفة ما تريد عمله بمساعدة حاسوبك، ثم الاطلاع على التطبيقات التي تتيح لك تنفيذ ما تريده (أو برمجتها إن كنت محبًا للمغامرة والتحدي ومستعدًا لخسارة الشعر أحيانًا!). سنفترض حاليًا أن لديك خلفيةً محدودةً في مبادئ عمل الحاسوب، كما سنفترض أيضًا أنك تريد ببساطة كتابة بعض الملاحظات وبعض التقارير أو إجراء بعض الحسابات وتصفح اﻹنترنت. سنَدُلّك هنا على بعض التطبيقات المثبتة افتراضيًا مع أوبونتو والتي قد تفيدك: LibreOffice Writer: وهو محرر نصوص يمتلك الكثير من الميزات المتقدمة. LiberOffice Calc: تطبيق جداول إلكترونية بميزات متقدمة. Libreoffice Impress: تطبيق إنشاء عروض تقديمية كامل الميزات. LibreOffice Draw: تطبيق ﻹنشاء الرسوميات مع الكثير من الميزات الرائعة. متصفح الويب فايرفوكس: متصفح الويب الغني عن التعريف بكامل إمكاناته. محرر النصوص: تطبيق بسيط لكتابة الملاحظات والنصوص، لكنه شديد اﻷهمية، وخاصةً إن فكرت في البدء بتعلم البرمجة أو تصميم صفحات الويب. تتواجد هذه التطبيقات في قائمة التطبيقات المثبتة، ويمكن فتحها بالنقر على أي منها بالزر الأيسر للفأرة. سيخبرك النظام أن التطبيق جاهز للعمل عندما ينهي تحميله. تطبيق عملي: افتح برنامج "محرر النصوص" ونفذ مايلي: اكتب العبارة "مرحبًا أوبونتو" وانقر الزر "احفظ"، ثم احفظه باسم "test-txt" في مجلد مناسب. احذف اﻵن العبارة السابقة وانسخ والصق ما يلي: <html> <body> <h1> مرحبًا أوبونتو </h1> </body> انقر على زر القائمة بجانب زر "احفظ"، واختر "احفظ باسم"، ثم اجعل اسم هذه النسخة "test-web"، وضعها في نفس المجلد السابق. ستكون النتيجة ظهور الملفين التاليين في المجلد الذي اخترته: لماذا ظهرت أيقونتي الملفين بشكلين مختلفين علمًا أننا استخدمنا نفس البرنامج في إنشائهما؟ الجواب هو: لأن نظام لينكس يقرأ ويفهم محتوى هذا الملف بغض النظر عن التطبيق الذي أنشأه، ومن ثم يبحث عن التطبيق اﻷنسب لتشغيله. إن الملف "test-txt" هو ملف نصي بحت وسيُسند النظام مهمة فتحه إلى محرر النصوص نفسه، لكن اﻵخر يحتوي على معلومات تُدعى شيفرة HTML التي تُستخدم في بناء صفحات ويب، لذلك يسند النظام مهمة فتحه إلى متصفح الويب. جرب بنفسك! تثبيت وإزالة وسائط التخزين كأي نظام تشغيل آخر، تستطيع إضافة وسائط تخزين داخلية أو خارجية إلى جهازك، والعمل معها بكل سهولة ويسر، ماعدا بعض الاستثناءات التقنية البسيطة. سواءً أردت تثبيت قرص صلب خارجي أو بطاقة ذاكرة أو قرص DVD، سيستشعر النظام ما وصلته إلى الحاسب، ويحدد نوعه وينفَّذ ما يُسمى تثبيت للقرص mounting تلقائيًا، ويصبح جاهزًا للاستخدام عندما تظهر أيقونته ضمن شريط التطبيقات. يظهر في لقطة الشاشة السابقة اكتمال تثبيت ذاكرة عبر مدخل USB وقرص مدمج؛ حيث أنه عند النقر على أيقونة أي منهماـ سيفتح النظام هذا الوسيط باستخدام تطبيق "الملفات"، وستكون قادرًا على نقل البيانات منه وإليه. تجدر اﻹشارة أن سواقات اﻷقراص المدمجة لا تُعرض تلقائيًا في شريط التطبيقات أو في أية أماكن أخرى ما لم تضع فيها القرص المدمج؛ وكذلك اﻷمر بالنسبة إلى أقسام القرص الصلب الداخلي أو اﻷقراص الصلبة الداخلية اﻷخرى. فلكي تصل إليها، افتح تطبيق "ملفات"، وانتقل إلى الخيار" أماكن أخرى" أسفل الشريط الجانبي؛ ومن ثم انقر على هذه اﻷقراص نقرًا مزودجًا، وعندها تُثبَّت للاستخدام من قبل مستثمر النظام وتُعرض في شريط التطبيقات. اقرأ أيضًا المقال السابق: تعرّف على سطح مكتب أوبونتو 20.04 تغيير اللغة في نظام لينكس أوبنتو إلى العربية في ماذا يختلف Ubuntu عن Debian؟
  21. ينبغي أن يبقى الفرع الرئيسي دائمًا في المنطقة الخضراء، ويعني ذلك تنفيذ كل خطوات بناء خط الإنتاج بنجاح، إذ يجب أن يُبنى المشروع بنجاح، وأن تُنفَّذ الاختبارات دون أخطاء، ولا يجب أن يعترض المدقق على أي شيء. ما أهمية هذا الأمر؟ من المرجح أنك ستنشر شيفرتك إلى العالم الخارجي من الفرع الرئيسي تحديدًا، وأي إخفاق في هذا الفرع سيعني أنّ الميزات الجديدة لن تُنشر حتى تُحل المشكلة؛ فقد تكتشف أحيانًا ثغرات في نسخة الإنتاج لم يلتقطها خط الإنتاج لمنظومة التكامل المستمر CI/CD، وفي حالات كهذه، سترغب في التراجع إلى النسخة السابقة بطريقة آمنة. كيف ستُبقي فرعك الرئيسي في المنطقة الخضراء إذًا؟ تحاشى دفع أية تغييرات إلى الفرع الرئيسي مباشرةً، بل ادفع شيفرتك إلى فرع آخر يمثّل أحدث نسخة ممكنة للفرع الرئيسي، وعندما ترى أن هذا الفرع جاهزٌ للدمج مع الرئيسي، أنشئ طلب سحب Pull Request -أو اختصارًا PR- على غيت هب GitHub. العمل مع طلبات السحب تمثل طلبات السحب جزءًا جوهريًا من عملية التعاون في تطوير أي مشروع برمجي يُنفَّذه مساهمَين على الأقل، فعندما تُجري أية تعديلات على مشروعك، ستختبرها على جهازك أولًا، ثم تعتمد هذه التغييرات وتدفعها إلى مستودع على الإنترنت -وهو غيت هب GitHub في حالتنا-، ثم تقدم طلب سحب لكي يراجع أحدٌ ما هذه التغييرات قبل أن تُدمج مع الفرع الرئيسي. هناك عدة أسباب تدفعنا لاستخدام طلبات السحب، كما أنّ مراجعة الشيفرة من قبل شخص آخر على الأقل فكرةٌ جيدةٌ دائمًا. قد تتواجد بعض الثغرات حتى في شيفرة المطورين الخبراء، وهذا مشابهٌ لمشكلة الرؤية النفقية tunnel، إذ لا يستطيع المصاب بها سوى رؤية ما أمامه مباشرة وكأنه ينظر من خلال نفق. يمتلك المراجع منظورًا مختلفًا، فقد يقدم وجهة نظر مختلفة. سيكون هناك مطورٌ واحد على الأقل على دراية بالتغيرات التي أجريتها بعد قراءتها. ستسمح لك طلبات السحب أن تُنفِّذ تلقائيًا كل المهام في خط الإنتاج لمنظومة CI قبل أن تصل الشيفرة إلى الفرع الرئيسي، وستزودك GitHub Actions بآلية لتنفيذ طلبات السحب. يمكنك تهيئة مستودع GitHub بطريقة تمنع دمج الشيفرة المرتبطة بطلب سحب إلى أن يُوافق عليها. لتقديم طلب سحب جديد، افتح فرعك في GitHub بالضغط على الزر الأخضر "Compare & pull request" في الأعلى، وسيظهر لك نموذج لتعبئته بوصفٍ لطلب السحب الذي تقدمه. تقدم لك واجهة طلبات السحب في GitHub إمكانية وصف الطلب ومناقشته، حيث تَظهر لك في الأسفل جميع علامات التحقق من منظومة CI (وهي في حالتنا كل فعل من أفعال GitHub Actions التي نستخدمها) التي هُيِّئت للعمل عند كل طلب سحب وحالة هذه العلامات. وما نهدف إليه فعلًا هو لوحة خضراء. يمكنك النقر على تفاصيل "Details" كل علامة تحقق للاطلاع عليها وتشغيل السجلات. تُنفَّذ جميع مخططات العمل التي واجهناها حتى الآن عند دفع الشيفرة إلى الفرع الرئيسي، ولجعلها تعمل عند كل طلب سحب، لا بدّ من تحديث الجزء المسؤول عن مسببات triggers إقلاع المخطط. سنستخدم المسبب "pull_request" للفرع الرئيسي ونربط هذا المسبب بالحدثين "opened"و "synchronize"؛ ويعني هذا مبدئيًا أن المخطط سيُنفَّذ عندما يُقدَّم طلب سحب إلى الفرع الرئيسي، أو عندما يُحدّث. لنغيّر إذًا الأحداث التي تسبب تشغيل مخطط العمل على النحو التالي: on: push: branches: - master pull_request: branches: [master] types: [opened, synchronize] سنمنع قريبًا دفع الشيفرة مباشرةً إلى الفرع الرئيسي، لكننا سننفذ حاليًا مخطط العمل لكل عمليات دفع الشيفرة المباشر إلى الفرع الرئيسي. التمرينان 11.13 - 11.14 يعمل مخطط العمل على النحو المطلوب للحفاظ على نوعية جيدة لشيفرتنا، لكن طالما أنه يُقلع عند دفع الشيفرة إلى الفرع الرئيسي، فسيلتقط الأخطاء متأخرًا. 11.13: طلب سحب حَدِّث مسببات إقلاع مخطط العمل كما اقترحنا سابقًا ليعمل عند تقديم طلب سحب جديد إلى الفرع الرئيسي. أنشئ فرعًا جديدًا وادفع بشيفرتك إليه، ثم افتح طلب سحب إلى الفرع الرئيسي. إذا لم تكن قد تعاملت مع الفروع سابقًا اطلع على المقالات التالية: مقدمة عن التفريع Branching في Git إدارة التفريعات (branches) في Git واستخدامها لتخطيط سير العمل إعادة تأسيس تفريعات طلب السحب وتحديثه في git تنبيه: تأكد عندما تفتح طلب سحب جديد أنك اخترت مستودعك الخاص واجهةً أساسية، إذ سيكون الاختيار افتراضيًا للمستودع الأصلي ولا يجب أن تفعل هذا: سترى في النافذة "Conversation" ضمن واجهة فتح طلب سحب آخر عملية (أو عمليات) دفع للشيفرة، كما سترى الحالة الصفراء لعلامات التحقق الذي يجري تنفيذه: ينبغي أن تكون العلامات باللون الأخضر بعد أن تنتهي عمليات التحقق. تأكد من نجاح كل اختبارات التحقق، ولا تدمج فرعك الآن، فهناك نقاط ينبغي تحسينها في خط الإنتاج. 11.14: تشغيل خطوة النشر للفرع الرئيسي فقط كل شيء يبدو على ما يرام، لكن هناك مشكلةٌ حقيقية في مخطط العمل الحالي، إذ ستُنفَّذ جميع الخطوات بما فيها خطوة النشر عندما نفتح طلب سحب، وبالتأكيد لا نريد حدوث هذا الأمر. ولحسن الحظ، هناك حلٌ بسيط لهذه المشكلة، إذ يمكننا استخدام العبارة الشرطية if في خطوة النشر لكي تضمن عدم تنفيذ هذه الخطوة إن لم تُدمج الشيفرة أو تُدفع إلى الفرع الرئيسي. يتضمن سياق context شتى أنواع المعلومات حول الشيفرة التي ينفذها مخطط العمل. ستجد المعلومات المتعلقة بالموضوع في GitHub context، إذ يمثل الحقل event_name اسم الحدث الذي يسبب تنفيذ المخطط. عندما يُدمج طلب سحب، سيظهر اسم الحدث على أنه "push"، وهو نفسه الحدث الذي يقع عندما تُدفع الشيفرة إلى المستودع، لذلك سنحصل على السلوك المطلوب بإضافة العبارة الشرطية التالية إلى خطوة النشر: if: ${{ github.event_name == 'push' }} ادفع بشيفرة إضافية إلى فرعك، وتأكد من عدم تنفيذ خطوة النشر. ادمج بعد ذلك فرعك مع الفرع الرئيسي وتأكد من حدوث خطوة النشر. إدارة إصدار نسخ البرمجيات الغاية الرئيسية من تحديد الإصدار هي التعريف الفريد للبرمجية التي تعمل والشيفرة المتعلقة بها. يشكّل ترتيب الإصدارات أيضًا جزءًا مهمًا من المعلومات، فلو احتوى الإصدار الحالي لبرنامج ما، على سبيل المثال وظيفةً حيويةً معطلة، سيلزمنا تحديد النسخة السابقة المستقرة من البرنامج لكي نتمكن من التراجع إليها. إدارة الإصدار بطريقة Semantic وبطريقة Hash تُدعى الطريقة التي يُدار بها إصدار برنامج في بعض الأحيان باستراتيجية إدارة الإصدار Versioning strategy. سنطلّع ونوازن استراتيجيتين في هذا المجال، تدعى الأولى الإدارة الدلالية لنُسخ البرمجيات Semantic، إذ يُكتب الإصدار بالشكل التالي: {major}.{minor}.{patch}. فلو كان إصدار نسخة من البرنامج 1.2.3، سيكون 1 هو رقم الإصدار الرئيسي الجذري major و 2 رقم الإصدار الثانوي (البسيط) minor و 3 هو رقم الإصدار الترميمي (الترقيع) patch. وعمومًا، فأية تغييرات تهدف لمعالجة الوظيفة المخفقة في إصدار دون المساس بالطريقة التي يعمل بها التطبيق من الخارج ستكون تغييرات ترميمية؛ أما التغييرات التي تؤدي إلى تغييرات بسيطة في الوظيفة (كما تبدو من الخارج) فستكون تغييرات ثانوية، وندعو تلك التغيرات التي ستؤثر كليًا على التطبيق (أو تغيير وظائفه بصورةٍ كبيرة) بالتغييرات الرئيسية، وقد يختلف تعريف هذه المصطلحات من مشروع لآخر. تتبع على سبيل المثال مكتبات npm ترميز semantic، ففي وقت كتابة هذه السطر (الثالث من آذار 2022)، يُعد الإصدار 17.0.2 هو الأحدث للمكتبة React إذ أن رقم الإصدار الرئيسي هو 17 والثانوي 0 والترميمي 1. أما الإصدار بترميز Hash (أو الترميز SHA) فهو مختلفٌ تمامًا، فرقم الإصدار هو سلسلة نصية عشوائية مشتقة من محتويات المستودع والتغيرات التي اعتمدت فيه. يُنفّذ ذلك في git تلقائيًا مثل رمز Hash معتمد وفريد لأي مجموعة من التغييرات. يستخدم الإصدار برمز غالبًا في الحالات المؤتمتة، فعملية نقل رمز Hash مكون من 32 محرف للتأكد من نشر كل شيء على النحو الصحيح عمليةٌ مضنية وتولّد الكثير من الأخطاء. إلى ماذا يشير الإصدار؟ تحديد الشيفرة الموجودة في كل إصدار أمرٌ مهمٌ جدًا، ويختلف أسلوبي الترميز السابقين في طريقة فعل ذلك؛ فالعملية في Hash (على الأقل في غيت هب GitHub) بسيطة ببساطة البحث عن تغييرات معتمدة للشيفرة commit لها رمز Hash معرّف ومحدد، إذ سيمنحنا ذلك إمكانية معرفة الشيفرة التي نُشرت في كل إصدار. سيتعقد الوضع قليلُا في semantic، فهناك ثلاث طرق لمقاربة الموضوع: الأولى تتعلق بالشيفرة نفسها، والثانية تتعلق بالمستودع أو البيانات الوصفية له، والثالثة تتعلق بأشياء خارج المستودع كليًا. طالما أننا لن نتطرق إلى المقاربة الثالثة (لأنها متشعبة مثل حجر الأرنب)، فستلاحظ أن العملية بسيطة أيضًا وكأنها جدول بيانات يربط بين الإصدار والشيفرة المعتمدة التي يدل عليها. تتلخص المقاربة المتعلقة بالشيفرة برقم إصدار النسخة في ملف، بينما تعتمد مقاربة المستودع وبياناته الوصفية على الوسوم tags أو في حالة GitHub على الإصدارات، إذ يشير الوسم أو الإصدار في هذه المقاربة إلى شيفرة مُعتمدة هي نفسها الموجودة في الإصدار. ترتيب إصدارات النسخ من السهل في semantic ترتيب الإصدارات حتى لو كانت مختلفة (رئيسي، ثانوي، ترميمي)، فسيأتي الإصدار 1.3.7 قبل 2.0.0 والذي سيأتي قبل 2.1.5 وهذا الأخير سيكون قبل 2.2.0. لكننا سنحتاج إلى قائمة بالإصدارت أو الوسوم ( يؤمنها مدير حزم GitHub بصورةٍ ملائمة) لمعرفة الإصدار الأخير، فمن الأسهل الاطلاع على هذه القائمة ومناقشتها، أي من الأسهل أن نقول أننا سنتراجع إلى الإصدار 3.2.4 بدلًا من التعامل بأنفسنا مع رموز hash التي تشير إلى رقم الإصدار. وطبعًا لا نعني إطلاقًا أنّ رموز hash غير ملائمة؛ فلو عرفت أي شيفرة معتمدة سببت مشكلة بعينها، فمن السهل البحث من خلال تاريخ عمليات GitHub والحصول على رموز hash للإصدار السابق؛ لكن لو كانت لديك سلسلتين من رموز Hash على النحو التالي مثلًا: d052aa41edfb4a7671c974c5901f4abe1c2db071 12c6f6738a18154cb1cef7cf0607a681f72eaff3 فلن تستطيع التمييز أيهما قبل الأخرى، وستحتاج إلى مساعدة إضافية مثل سجل عمل GitHub لكشف الترتيب الصحيح. الموازنة بين أسلوبي إدارة الإصدار لقد لمسنا بالمناقشة السابقة بعض إيجابيات وسلبيات كل أسلوب، لكن من الأفضل التطرق إلى مجال استعمال كل منهما. يعمل أسلوب semantic جيدًا عندما يكون لرقم الإصدار أهميةً خاصة أو عندما تكون هناك حاجة للبحث عنه. فكر مثلًا بمكتبات جافا سكربت JavaScript التي تستخدمها؛ فلو كنت تستخدم الإصدار 3.4.6 من مكتبة محددة وظهر تحديث لها يحمل الرقم 3.4.8، فيمكنك أن تأمل بأن انتقالك إلى الإصدار الأحدث سيكون آمنًا؛ لكن لو حمل الإصدار الجديد الرقم 4.0.1 فلن تضمن انتقالًا آمنًا إليه. يُناسب hash الحالات التي تُبنى فيها الشيفرة داخل أدوات برمجية artifact (مثل الملفات الثنائية القابلة للتنفيذ أو قوالب منصة Docker) قابلة بحد ذاتها للرفع أو التخزين، فإذا تطلبت اختباراتك على سبيل المثال بناء حزمتك داخل أداة برمجية، ثم رفعها إلى الخادم واختبارها، فمن الملائم عندها استخدام أسلوب Hash في تحديد الإصدار لأنه سيمنع وقوع الحوادث. تخيل على سبيل المثال أنك تعمل على الإصدار 3.2.2 وفشل أحد الاختبارات، أصلحت بعد ذلك الخلل ودفعت بشيفرتك إلى مستودعك، لكن طالما أنك تعمل ضمن مستودعك فلن تستطيع تحديث رقم الإصدار. لن يتغير اسم الأداة البرمجية دون تحديد إصدار hash. وإن كان هناك خطأ في رفع الأداة البرمجية، فقد يُنفَّذ الاختبار على الأداة الأقدم (طالما أنها لا تزال موجودة وبنفس الاسم). بينما لو أُعطيت الأداة رقم إصدار بأسلوب hash، ينبغي أن يتغير رقم الإصدار عند كل محاولة لدفع شيفرة معتمدة، ويعني هذا أنه في حال أخفق رفع الأداة، سيظهر خطأ لأن الأداة التي ستُنفَّذ عليها الاختبارات غير موجودة. يُعد ظهور الخطأ عندما يحدث شيء ما أفضل غالبًا من مشكلة تهملها منظومة CI بصمت. أفضل ما في الأسلوبين يمكن أن نستشف من المقارنة السابقة أن استخدام أسلوب semantic مناسب لإصدار برمجيات، بينما استخدام أسلوب hash أكثر منطقية أثناء نشر البرمجيات ولن يسبب هذا الأمر تعارضًا بالضرورة. فكر في ذلك على النحو التالي: تتلخص إدارة الإصدارات بأنها تقنية تشير إلى كمية معتمدة من الشيفرة وتعطيها اسمًا فليكن 3.5.5 لن يمنع ذلك طبعًا من إعطاء الشيفرة نفسها رمز hash. هناك أمر ما، فقد ذكرنا في بداية هذا القسم أنه علينا معرفة ما يحدث تمامًا في شيفرتنا، فعلينا أن نكون واثقين مثلًا بأننا اختبرنا الشيفرة التي نريد نشرها. سيعقد وجود أسلوبين متوازيين لإدارة الإصدار (أو تسميته) الأمور قليلًا. فعندما يكون هناك مشروع يستخدم نسخًا من أداة برمجية بحيث يُدار إصدار هذه النسخ بأسلوب hash أثناء اختبارها، يمكننا دائمًا تعقب نتيجة خطوات البناء والتدقيق والاختبار، وبالتالي سيعلم المطورون حالة هذه النسخ. ويحدث ذلك آليًا وبصورةٍ واضحة بالنسبة للمطور، فليس على المطور الاهتمام بما تفعله منظومة CI في الخفاء من حيث استخدام ترميز hash لتسمية النسخة واختبارها. عندما يدمج المطور شيفرته ضمن الفرع الرئيسي، تتولى منظومة CI الأمر من جديد، إذ ستبني هذه المرة وتختبر كل الشيفرة ومن ثم تمنحها رقم إصدار بأسلوب semantic، وتربط هذا الرقم بآخر شيفرة معتمدة ومختبرة وتحمل وسم git. سيكون البرنامج الذي سنصدره في الحالة السابقة مُختبرًا لأن منظومة CI ستتأكد أنّ الاختبارات قد نجحت على الشيفرة الموسومة بوسم git المحدد، وليس خطأً أن نقول أن المشروع سيستخدم أسلوب semantic في إدارة الإصدار ويتجاهل ببساطة فكرة أن منظومة CI ستختبر الفروع الخاصة بكل مطور أو طلبات السحب الخاصة بكل منهم بأسماء مبنية على أسلوب hash في إدارة الإصدار. نقول ذلك لأن النسخة التي تعنينا (التي سنصدرها) ستُمنح رقم إصدار semantic. التمرينان 11.15 - 11.16 لنوسّع مخطط العمل الذي نبنيه لكي يزيد تلقائيًا رقم الإصدار عندما تجري الموافقة على طلب سحب وتنفيذ عملية الدمج ضمن الفرع الرئيسي وأن يوسم النسخة المصّدرة برقم الإصدار. سنستخدم anothrNick/github-tag-action وهو فعل GitHub Actions مفتوح المصدر طوره أحدهم. 11.15: إضافة رقم إصدار سنوّسع مخطط العمل بإضافة خطوة واحدة: - name: Bump version and push tag uses: anothrNick/github-tag-action@1.36.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} سنمرر متغير البيئة environment variable التالي secrets.GITHUB_TOKEN إلى الفعل، وطالما أنّ مصدر الفعل طرف ثالث، سنحتاج إلى شهادة استيثاق token في مستودعك. يمكن الاطلاع أكثر عن الاستيثاق في توثيق GitHub. يمكن أن نمرر عدة متحولات بيئة إلى الفعل anothrNick/github-tag-action. تعدّل هذه المتغيرات الطريقة التي يُعرِّف بها الفعل الإصدارات. يمكنك الاطلاع على تفاصيل أكثر ضمن ملف README الخاص بالفعل على GitHub واستخدام ما يناسبك. وكما سترى في التوثيق سيزداد الرقم الثانوي لرقم الإصدار تلقائيًا minor bump. عدّل التهيئة في الشيفرة السابقة لكي يكون هناك زيادةٌ تلقائية في رقم الترميم patch bump لرقم الإصدار، أي سيزداد الرقم الأخير تلقائيًا. تذكر أننا نريد فقط زيادة رقم الإصدار عندما تطرأ تغييرات على الفرع الرئيسي. لذلك أضف العبارة الشرطية "if" لمنع زيادة رقم الإصدار عند فتح طلب سحب كما فعلنا في التمرين 11.14. أكمل مخطط العمل وحاول تجربته، ولا تضفه مثل خطوة إضافية فقط، وإنما أجري إعداده على أنه عمل منفصل يعتمد على العمل المهتم بتدقيق وكشف الأخطاء والفحص والنشر. أجرِ التغييرات التالية على تعريف مخطط العمل: name: Deployment pipeline on: push: branches: - master pull_request: branches: [master] types: [opened, synchronize] jobs: simple_deployment_pipeline: runs-on: ubuntu-20.04 steps: // steps here tag_release: needs: [simple_deployment_pipeline] runs-on: ubuntu-20.04 steps: // steps here تُنفَّذ أعمال مخطط العمل على التوازي كما ذكرنا سابقًا، لكن طالما أننا نريد إجراء التدقيق والاختبار والنشر أولاً، سنحتاج لضبط اعتمادية tag_release بحيث تنتظر عملًا آخر ليُنفّذ أولًا نظرًا لأننا لا نريد وضع علامة على الإصدار ما لم يجتاز الاختبارات ويُنشر. إذا لم تكن متأكدًا من إعدادات التهيئة، يمكنك ضبط قيمة DRY_RUN على true، وبالتالي سيعطي الفعل رقم الإصدار التالي دون إنشاء وسم للإصدار. حالما يعمل مخطط العمل على نحوٍ صحيح، سيشير المستودع إلى وجود بعض الوسوم: وبالنقر على هذه الوسوم ستُعرض ضمن قائمة بكل الوسوم (والتي تمثل آلية لتعريف الإصدار ووسمه): 11.16: تجاوز إنشاء وسوم لحزمة شيفرة معتمدة ونشرها من الأفضل في أغلب الأوقات نشر الفرع الرئيسي إلى وضع الإنتاج.، لكن قد تكون هناك بعض الأسباب المقنعة لتجاوز إنشاء وسم شيفرة معتمدة أو طلب سحب جرى الموافقة على دمجه أو نشرهما. عدّل إعداداتك لكي لا تُنشر إلى وضع الإنتاج أية شيفرة جرى دمجها إن احتوت رسالة طلب السحب الكلمة "skip#"، ولا توسم برقم الإصدار. تلميح: أسهل طريقة لإنجاز ذلك هو تبديل العبارة الشرطية "if" للخطوة المناسبة. ويمكنك كما فعلنا في التمرين 11.14 الحصول على المعلومات المطلوبة من سياق GitHub لمخطط العمل: يمكنك الإستعانة بالشيفرة التالية مثل نقطة انطلاق: name: Testing stuff on: push: branches: - main jobs: a_test_job: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - name: gihub context env: GITHUB_CONTEXT: ${{ toJson(github) }} run: echo "$GITHUB_CONTEXT" - name: commits env: COMMITS: ${{ toJson(github.event.commits) }} run: echo "$COMMITS" - name: commit messages env: COMMIT_MESSAGES: ${{ toJson(github.event.commits.*.message) }} run: echo "$COMMIT_MESSAGES" لاحظ ما الذي طُبع في سجل مخطط العمل. ملاحظة: يمكنك الوصول إلى الشيفرة المعتمدة أو رسائلها عند الدفع أو الدمج مع الفرع الرئيسي فقط، وبالتالي لن تحمل الخاصية github.event.commits أية قيمة. لا حاجة لهذه القيمة على أية حال طالما أننا نرغب في تجاوز هذه الخطوة. ستحتاج غالبًا إلى الدالتين contains و join ضمن عبارة if الشرطية. لا يُعد نشر مخطط العمل أمرًا سهلًا، وغالبًا ما يكون الخيار المتاح هو المحاولة والخطأ، وينصح أحيانًا بوجود مستودع منفصل للتأكد من صحة تهيئة الإعدادات، ثم تُنقل هذه الإعدادات إلى المستودع الفعلي إن كانت صحيحة. كما يمكن أيضًا تثبيت أداة مثل act، التي تساعدك على تشغيل مخطط العمل على جهازك. وعندما تواجهك حالات مختلفة عن التي ناقشناها، كأن تنشئ أفعال خاصة بك، فإن اقتحام غمار أدوات مثل act وتجربتها سيستحق العناء. ملاحظة لاستخدام الأفعال التي يطورها طرف ثالث عندما تستخدم فعل يؤمنه طرف ثالث مثل github-tag-action، من الأفضل أن تحدد أسلوب hash لإدارة الإصدار بدلًا من رقم الإصدار. والسبب في ذلك أن رقم الإصدار الذي أنجز بوسم git قد يكون عرضة للتغيير، فالإصدار الذي يحمل الرقم 1.33.0 هذا اليوم على سبيل المثال، قد يشير إلى شيفرة مختلفة عن تلك الموجودة في نفس الإصدار لكن في الأسبوع القادم. مع ذلك، فالشيفرة المعتمدة على ترميز hash محددة لا تتغير أيًا كانت الظروف، لذا فإن أردنا التيقن 100% من الشيفرة التي نستخدمها، سيكون الخيار الأكثر أمانًا hash. إنّ الإصدار 1.33.0 من الفعل السابق يتوافق مع شيفرة ترميز hash لها هو الإصدار: eca2b69f9e2c24be7decccd0f15fdb1ea5906598، لذلك من الأفضل أن نغير إعدادات التهيئة على النحو التالي: - name: Bump version and push tag uses: anothrNick/github-tag-action@eca2b69f9e2c24be7decccd0f15fdb1ea5906598 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} وعلينا أن نثق عند استخدام أفعال تزودنا بها GitHub بأنها لن تعبث بوسوم الإصدار version tags وأن نختبر شيفرتها بدّقة. وقد ينتهي بك المطاف عند استخدام أفعال من طرف ثالث أمام أفعال تحتوي ثغرات أو حتى مثيرة للشبهة، وحتى لو لم تكن نية المطور الذي كتب الشيفرة سيئة، فقد ينس معلومات الاستيثاق الخاصة به في ركن إنترنت عام، ومن يدري ما قد يحدث بعدها. سنتأكد عند استخدام أسلوب hash أن هذه شيفرة التي نستخدمها لتشغيل مخطط العمل لن تتغير، لأن تغيير الشيفرة المعتمدة المسؤولة عن تنفيذ المخطط سيؤدي إلى تغيير hash. أبق الفرع الرئيسي آمنا سيسمح لك غيت هب أن تهيئ فروعًا محمية، فمن المهم حماية الفرع الأكثر أهمية الذي يجب ألا يخفق أبدًا وهو الفرع الرئيسي. يمكنك أن تختار بين عدة مستويات حماية عبر إعدادات المستودع. لن نغطي كل هذه الخيارات، لكن يمكنك طبعًا الاطلاع عليها من خلال توثيق GitHub. ومن وجهة نظر منظومة CI، ستتمثل الحماية الأكثر أهمية في نجاح خطوات التحقق من الحالة قبل أن يدمج طلب السحب ضمن الفرع الرئيسي. فلو أعددت أفعال GitHub لتنفذ مهام التدقيق والاختبار، فلن يُدمج طلب السحب قبل تصحيح جميع أخطاء التدقيق وقبل أن تنجح جميع الاختبارات. ونظرًا لكونك مدير مستودعك، ستجد خيارًا لتخطي التقييدات هذه. لن يظهر هذا الخيار لغير المدراء. لتهيئ إعدادات الحماية للفرع الرئيسي، توجه إلى إعدادات "Settings" المستودع من القائمة العليا ضمن المستودع. اختر من قائمة الجهة اليسرى فروع "Branches" ثم انقر زر أضف قاعدة "Add rule" بجوار العنصر قواعد حماية الفروع "Branches protection rules". اكتب نموذج لاسم الفرع ("master" سيكون مناسبًا)، ثم اختر طريقة الحماية التي تريد إعدادها. اختر على الأقل "يجب التحقق من نجاح خطوات التحقق من الحالة قبل الدمج Require status checks to pass before merging" لتضمن تسخير الإمكانات الكاملة لاستضافة GitHub Actions. وتحت هذا الخيار عليك اختيار "يجب أن تكون الفروع محدّثة قبل الدمج Require branches to be up to date before merging"، وفعّل جميع الخيارات التي تتحقق من الحالة قبل دمج أية طلبات سحب. التمرين 11.17 إضافة حماية إلى الفرع الرئيسي أضف حماية لفرعك الرئيسي. إذ عليك حمايته بضمان التالي: الموافقة على جميع طلبات السحب قبل دمجها. نجاح جميع خطوات التحقق من الحالة قبل الدمج. لا تفعِّل الخيار "Include administrators" حاليًا، لأنك لو فعلت ذلك فعليك الانتظار حتى يراجع شخص ما طلب السحب الذي قدمته قبل نشر الشيفرة. ترجمة -وبتصرف- للفصل Keeping Green من سلسلة Deep Dive Into Modern Web Development. اقرأ أيضًا المقال السابق: نشر التطبيقات وتوزيعها وفق نهج التسليم المستمر نشر تطبيقات الويب الموجهة لبيئة الإنتاج نشر تطبيقات iOS على متجر Apple Store
  22. سنلقي نظرةً في مقالنا على اﻷجزاء التي يتكون منها سطح المكتب في نظام التشغيل لينكس أوبنتو Ubuntu في إصدارته 20.04؛ وهي النافذة الذي تظهر مباشرةً عند انتهاء تحميل نظام التشغيل. لن أتطرق بالطبع إلى أية مقارنات مع نسخ أقدم، أو إلى التغييرات التي تجري باستمرار على تنظيم سطح المكتب، بل سأعُدُّك مستخدمًا عاديًا دون أية خبرة مسبقة؛ على أساس أنك أردت أن تجرب نظام تشغيل مفتوح المصدر لمجرد الفضول، أو لأنك سمعت عن بعض ميزاته من أحد معارفك، فوجدت نفسك قد تورّطت وثبتت هذا النظام بمساعدة أو دون مساعدة. دعنا نبدأ إذًا هذه الرحلة السريعة التي تقودك بكل بساطة إلى فهم المكونات اﻷساسية لسطح المكتب وكيفية الوصول إلى بعض المعلومات المتعلقة بحاسوبك. اﻷجزاء الرئيسية لسطح مكتب جنوم يتكون سطح المكتب من عدة أقسام أساسية هي: شريط التطبيقات Applications Panel: يقع افتراضيًا في النسخة العربية على يمين الشاشة وفي النسخة اﻹنكليزية على يسارها. يعرض هذا الشريط اختصارات إلى التطبيقات التي ترغب في الوصول إليها بسرعة، ويمكنك دائمًا إضافة او إزالة الاختصارات كما تراه ملائمًا لاحتياجاتك. يدل ظهور نقطة حمراء بجوار التطبيق على أنه نشط؛ في حين تدل النقطتين على وجود نافذتين تعودان لهذا التطبيق وهكذا. زر إظهار التطبيقات Show Applications: يقع هذا الزر على طرف شريط التطبيقات وينتقل بك عند النقر إليه إلى نافذة التطبيقات المثبتة على حاسوبك. ستجد دائمًا مجموعةً من التطبيقات اﻷساسية التي تُثبّت افتراضيًا مع نظام التشغيل، كما ستُعرض فيه أية تطبيقات ستعمل على تثبيتها بنفسك لاحقًا. شريط المهام Taskbar: يقع الشريط أعلى يمين الشاشة في النسخة العربية ويضم اختصارات لأدوات تحكم ومؤشرات على حالة النظام. قد يختلف عدد هذه الاختصارات وفقًا للإعدادات التي نضبطها ( سنرى ذلك لاحقًا في مقالات أخرى)، لكن الاختصارات الموضحة في الصورة التالية موجودة افتراضيًا: شريط اﻷنشطة Activity Bar: يقع هذا الشريط أعلى يمين الشاشة في النسخة العربية ويضم اختصارًا إلى البرنامج الذي يجري تحميله. وبعد إنتهاء التحميل، يختفي الاختصار من شريط اﻷنشطة وتظهر أيقونة البرنامج ضمن شريط التطبيقات. وعندها يكون دور شريط اﻷنشطة لإظهار اختصار إلى التطبيق الذي نعمل عليه حاليًا، إذ يعرض بالنقر عليه مجموعةً من التعليمات المتعلقة ببعض تفاصيل التطبيق أو بإنهائه. يعرض لك سطح المكتب عند النقر على زر "أنشطة" نوافذ جميع البرامج التي حمّلتها. نافذة سطح المكتب Desktop Window: تشغل هذه النافذة المساحة اﻷكبر من الشاشة وتضم افتراضيًا المجلد اﻷساسي "المنزل" ويعنون باسم مستخدم النظام؛ كما يضم سلة المهملات. ويمكنك إضافة مجلدات جديدة أو نسخ ولصق ملفات من وإلى نافذة سطح المكتب. التوقيت والتاريخ Time and Date: يظهران بين شريطي اﻷدوات واﻷنشطة ويعطي بالنقر عليهما تقويمًا بسيطًا يعرض اليوم والشهر والعام، مع إمكانية عرض الأحداث والتنبيهات المرتبطة بأيام معينة. يمكنك أيضًا تفعيل خيار "بلا إزعاج" لإسكات التنبيهات. تفاصيل سطح مكتب جنوم بعد أن عرضنا اختصار تنظيم سطح المكتب وأقسامه الرئيسية، سنغوص فيها الآن بشيء من التفصيل. استكشاف شريط التطبيقات يضم شريط التطبيقات أيقونات مجموعة من التطبيقات، والتي يظهر بعضها افتراضيًا عند تثبيت النظام مثل "متصفح الويب فايرفوكس" وتطبيق "الملفات" وتطبيق البريد اﻹلكتروني "ثندربيرد ميل Thunderbird Mail"؛ وبعضها قد تضعه بنفسك كما سنرى لاحقًا. سيُحمّل التطبيق بمجرد النقر عليه بالزر اليساري للفارة ويختفي عن سطح المكتب بالنقر على زر التصغير في النافذة مجددًا. تظهر عند النقر بالزر اليميني على أي تطبيق ضمن شريط التطبيقات نافذة مهام تُقدم مجموعةً من الخيارات، ويتعلق نوعها وعددها بالتطبيق ذاته. تحوي هذه القائمة عمومًا على تعليمات مثل: نافذة جديدة: وتفتح نافذة أخرى للتطبيق. لهذا اﻷمر أهميته الخاصة في التطبيقات التي يجري فيها سحب وإفلات العناصر من نافذة لأخرى مثل تطبيق "الملفات". أزل من المفضلة: ويستخدم لإزالة أيقونة التطبيق من شريط التطبيقات. أظهر التفاصيل: تنتقل بك هذه التعليمة إلى نافذة توصيف التطبيق، وهي نافذة من نوافذ تطبيق "برمجيات"، الذي يسمح بإضافة أو إزالة التطبيقات من نظامك. تقدم لك هذه النافذة معلومات عن آلية عمل التطبيق ومؤلفه ورخصة استخدامه وآخر إصداراته وتحديثاته وقائمة باﻹضافات المتوفرة التي تدعم وظائف هذا التطبيق. أنهِ: ﻹغلاق التطبيق إن كان مُحمّلًا، حيث لا يظهر هذا الخيار إن لم يكن كذلك. استكشاف نافذة التطبيقات بالنقر على زر التطبيقات الموجود أسفل شريط التطبيقات، ينتقل بك النظام إلى نافذة تعرض أيقونات التطبيقات المثبتة على حاسوبك. تضم هذه النافذة مجموعةً خدميةً متنوعةً من التطبيقات التي تأتي افتراضيًا مع نظام التشغيل أوبونتو، منها برامج التحديث وضبط اﻹعدادات ودعم اللغات واﻷلعاب. لتشغيل أي تطبيق، انقر عليه بزر الفأرة اليساري مرةً واحدة، وسيظهر اختصاره في شريط اﻷنشطة أثناء التحميل، ثم تظهر أيقونته في شريط التطبيقات عندما يكتمل التحميل. ملاحظة 1: إن اختفى اختصار التطبيق من شريط اﻷنشطة ولم تظهر أيقونته في شريط التطبيقات خلال فترة قصيرة، فهنالك مشكلة في تشغيل التطبيق وقد أنهى النظام تشغيله دون إنذار مسبق. ملاحظة 2: لإضافة أي تطبيق إلى قائمة التطبيقات، انقر بالزر اليميني للفأرة على التطبيق واختر "أضف إلى المفضلة". يتواجد في أسفل نافذة التطبيقات خياران هما: "الكل" و "شائعة". بالنقر على الخيار اﻷول، سيعرض لك سطح المكتب جميع البرامج المثبتة على حاسوبك، بينما سيعرض فقط التطبيقات اﻷكثر استخدامًا إن نقرت على "شائعة". تقدم لك هذه النافذة أيضًا ميزة البحث عن أية تطبيقات أو ملفات أو إعدادات من خلال صندوق البحث في أعلاها. ويمكنك كتابة حرف أو أكثر من الاسم حتى يعرض لك كل ما هو موجود بأسلوب منظم وسهل الفهم. تعرض الصورة التالية محاولة البحث عن محرر نصوص، لذلك كتبنا أول حرفين "مح". لاحظ النتائج المتوفرة: لمغادرة نافذة التطبيقات، اضغط على المفتاح "Esc"؛ أو انقر مجدًا على زر التطبيقات. من أهم التطبيقات التي تعرضها القائمة نشير إلى ما يلي: اﻹعدادات: يتيح لك التطبيق واجهةً للتحكم بإعدادات العتاد الصلب والتطبيقات والخدمات المختلفة، وسنفرد لهذا التطبيق مقالات خاصة لأهميته الكبيرة. مُحدّث البرمجيات: يتيح لك التطبيق إمكانية ضبط إعدادات آليات تحديث النظام والبرمجيات المثبتة عليه، وإمكانية تحديد ما تريد تحديثه وما لاتريده واختيار الخوادم التي ستنزّل منها هذه التحدثيات. لهذا البرنامج أهمية كبيرة جدًا في صيانة تطبيقاتك والتحقق من جودة اعتمادياتها Dependencies. برمجيات: وهو تطبيق شديد اﻷهمية يسمح لك بالوصول إلى مخازن أوبنتو سواءً "جنوم-ستور" أو سناب-ستور" لتثبيت البرمجيات اللازمة في عملك. وقد تجده أحيانًا باسم "أوبنتو سوفتوير Ubuntu Software" وفقًا ﻵخر تحديث مثبت على حاسوبك. يعرض لك التطبيق البرمجيات المتوفرة مصنفةً وفقًا لنوعها وطبيعة عملها وتقييم المستخدمين لها. وسنفرد لاحقًا مقالًا خاصًا عن استخدام هذا التطبيق لتثبيت وإزالة البرمجيات. LibreOffice: وهو مجموعة برامج مكتبية Office مفتوحة المصدر تشابه مجموعة برامج مايكروسوفت أوفيس، تساعدك في كتابة وتنسيق الملفات النصية والعروض التقديمية وتصميم جداول البيانات بإمكانات متقدمة ومتميزة. ستجد من هذه السلسة تطبيقات LibreOffice Writer و LibreOffice Calc و LibreOffice Impress و LibreOffice Draw. تعرف على شريط المهام يضم شريط المهام مجموعةً من الاختصارات لبعض اﻹعدادات، وبعض مؤشرات أداء الجهاز والتطبيقات. تظهر ضمن الشريط مجموعات من القوائم تبدأ كلّ منها بسهم صغير نحو اﻷسفل يفتح بالنقر عليه ما تضمه القائمة من خيارات. يظهر افتراضيًا الشريط الرئيسي الموجود في أقصى اليسار ويضم إعدادات الصوت وشبكات الاتصال وأوامر إنهاء التشغيل وتسجيل الخروج. يعرض القسم اﻷول من قائمة الشريط الرئيسي مستوى الصوت، ويليها قسم شبكات الاتصال بأنواعها. تُظهر القائمة وجود شبكة واي-فاي WiFi جاهزة للعمل وشبكة افتراضية VPN مطفأة وشبكة لاسلكية بتقنية بلوتوث جاهزة للعمل أيضَا، وسيظهر بالطبع أي اتصال ضمن هذه القائمة. وبالنقر على زر القائمة المنسدلة بجوار كل اتصال، ستظهر مجموعة الخيارات للتحكم بالاتصال، مثل اختيار شبكة أو إيقافها أو الانتقال إلى إعدادات الاتصال. يلي هذا القسم التالي خيارات الانتقال إلى لوحة إعدادات النظام وأوامر قفل سطح المكتب "أوصد" وأوامر اﻹطفاء الكامل والخروج. يظهر أيضًا شريط اللغة الذي يضم قائمًةً بمجموعة اللغات المثبتة كمصدر دخل Input Source، أي اللغات التي يمكن الكتابة بها عند استخدام لوحة المفاتيح. ويعرض الشريط أيضًا خيار "أظهر تخطيط لوحة المفاتيح" الذي يعطي تسمية المفاتيح وفقًا لنظام التشغيل أوبنتو. لا تتفاجئ بظهور أشرطة مهام أخرى، فقد تعرض بعض البرمجيات والتطبيقات أشرطة مهام خاصة بها لتساعدك في التعامل مع إعداداتها. تعرف على نافذة سطح المكتب وهي الواجهة الرئيسية لنظام أوبنتو وتمثل في الواقع مجلدًا لا يختلف عن بقية المجلدات عمومًا من الناحية الوظيفية إلا أنه لا يقبل عمليات السحب واﻹفلات. إذ لا يمكن جر وإفلات أي مجلد أو ملف من نافذة ما إلى سطح المكتب، إلا أن عمليات النسخ واللصق متاحة. تظهر قائمة سطح المكتب بالنقر على أي نقطة من نافذة سطح المكتب بالزر اليميني وتعرض الخيارات التالية: "مجلد جديد New Folder": ﻹنشاء مجلد جديد على سطح المكتب. "لصق Paste": لصق المجلدات أو الملفات الموجودة في الحافظة. "اعرض سطح المكتب باستخدام تطبيق الملفات Show Desktop in Files": يفتح تطبيق "الملفات" سطح المكتب ويعرضه كأي مجلد آخر. "افتح باستخدام الطرفية Open in Terminal": يفتح سطح المكتب باستخدام برنامج الطرفية. "غير الخلفية Change Background": يفتح إعدادات تغيير صورة الخلفية. "إعدادت العرض Display Settings": يفتح إعدادات ضبط شاشة العرض المستخدمة. "اﻹعداداتSettings": يفتح تطبيق اﻹعدادات. إطفاء الحاسوب انقر على شريط المهام الرئيسي في أقصى يسار شريط اﻷدوات، ثم اختر اﻷمر " إطفاء \ خروج" لتظهر لك النافذة التالية: اختر اﻷمر "أطفئ" ﻹيقاف التشغيل أو "أعد التشغيل"، كما يمكنك إلغاء العملية بالنقر على زر "ألغِ" إن وصلت إلى هذا المكان بطريق الخطأ. سيُطفأ النظام تلقائيًا بعد 60 ثانية إن لم تتخذ قرارك كما توضح لقطة الشاشة السابقة. خلاصة تعرفنا في هذا المقال على البنية التنظيمية لسطح مكتب جنوم، وهي الواجهة الرسومية الرئيسية لنظام لينكس أوبونتو؛ كما تحدثنا بشيء من التفصيل عن أقسام سطح المكتب وأهم المؤشرات والاختصارات التي يحويها كل قسم، ثم عرضنا آلية إطفاء الحاسوب بالطريقة الصحيحة. لقد أصبحنا اﻵن مستعدين للانتقال إلى المرحلة الثانية من مراحل استثمار نظام أوبنتو، وهي التعامل مع المجلدات والملفات. إقرأ أيضًا ما هو نظام التشغيل لينكس؟ تغيير اللغة في نظام لينكس أوبنتو إلى العربية في ماذا يختلف Ubuntu عن Debian؟
  23. لقد أضفنا حتى الآن عدة ميزات إلى تطبيقنا لكن دون أي اتصال بالخادم، إذ يستخدم تطبيق قائمة المستودعات المقيَّمة الذي نفذناه في المقال السابق أساسيات React Native -على سبيل المثال- بيانات وهمية، ولا يرسل نموذج تسجيل الدخول بيانات التحقق من المستخدم إلى أية وصلة تخديم endpoint مفوَّضة باستقبال هذه البيانات، لذا سنتعلم في هذا المقال كيف سنتصل بالخادم مستخدمين طلبات HTTP، وكيف سنستخدم المكتبة Apollo Client ضمن تطبيقات React Native، وكذلك طريقة تخزين البيانات في جهاز المستخدم. سنحتاج قبل الشروع بتعلم تلك النقاط إلى خادم لنتواصل معه، لذلك فقد أنجزنا خادمًا كاملًا ووضعناه في المستودع rate-repository-api. سيلبي الخادم "rate-repository-api" كل ما تحتاجه الواجهة البرمجية لتطبيقنا خلال هذا القسم، إذ يستخدم "rate-repository-api" قاعدة بيانات SQLite والتي لا تحتاج إلى إعدادات، كما يؤمن واجهة برمجية خاصة بالمكتبة Apollo GraphQL، إضافةً إلى بعض وصلات التخديم التي تؤمن واجهة برمجية متوافقة مع REST. قبل أن تغوص أعمق في مادة هذا المقال، عليك أن تُعدَّ الخادم "rate-repository-api" باتباع الإرشادات الموجودة في ملف README الخاص بالمستودع. ننصحك إن كنت ستستخدم المقلّد emulator لتطوير التطبيق، أن تشغله على نفس الحاسوب الذي يشغّل الخادم، فسيسهل ذلك طلبات الاتصال عبر الشبكة على نحوٍ واضح. طلبات HTTP تزودنا React Native بالواجهة البرمجية Fetch API لتنفيذ طلبات HTTP الخاصة بتطبيقنا، كما تدعم أيضًا الواجهة البرمجية القديمة XMLHttpRequest API والتي ستمكننا من استخدام مكتبات خارجية مصدرها طرف ثالث مثل Axios. وهذه الواجهات هي نفسها التي استخدمناها في بيئة المتصفح، كما أنها متاحة لكامل التطبيق ولا حاجة لإدراجها. قد يتفق المطورون الذين تعاملوا مع الواجهتين البرمجيتين السابقتين، أن Fetch API أسهل استخدامًا وأكثر عصرية، لكن للواجهة XMLHttpRequest API استعمالاتها أيضًا، ولأجل تبسيط الموضوع سنتعامل فقط مع Fetch API في أمثلتنا. يمكن استخدام Fetch API لإرسال طلبات HTTP باستخدام الدالة fetch وسيكون الوسيط الأول لهذه الدالة عنوان المورد URL وطلب HTTP الافتراضي لها هو GET: fetch('https://my-api.com/get-end-point'); أما الوسيط الثاني للدالة فهو كائن خيارات يمكنك استعماله لإنشاء طلب HTTP آخر، أو ترويسة طلب، أو متن body طلب: fetch('https://my-api.com/post-end-point', { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ firstParam: 'firstValue', secondParam: 'secondValue', }), }); انتبه إلى أنّ العناوين "URL's" مصطنعة ولن تستجيب لطلباتك غالبًا. تعمل Fetch API موازنةً بالمكتبة Axios في مستوى أدنى قليلًا، فلا توجد مثلًا أية عمليات تحويل أو تفسير لمتن الطلب أو الاستجابة، فعليك مثلًا أن تحدد نوع المحتوى content-type بنفسك وأن تستخدم التابع JSON.stringify لتحويل متن الطلب إلى نص. تعيد الدالة fetch وعدًا (promise) يُحَل resolves إلى كائن الاستجابة Response، وانتبه إلى أنّ رموز الحالة للأخطاء التي تحصل مثل 400 أو 500 لن تُرفض كما هو الوضع في Axios مثلًا. يمكن أن نفسّر متن الاستجابة المنسقة بأسلوب JSON باستخدام التابع Response.json: const fetchMovies = async () => { const response = await fetch('https://reactnative.dev/movies.json'); const json = await response.json(); return json; }; وللاطلاع على أساسيات Fetch API بشيء من التفصيل، اقرأ مقال الواجهة البرمجية fetch في JavaScript ومقال استعمالات متقدمة للواجهة البرمجية Fetch في جافاسكربت وأضف إليهما مقال تتبع تقدم عملية تنزيل البيانات عبر Fetch ومقاطعتها في جافاسكربت. لنبدأ بالاستخدام التطبيقي للمكتبة Fetch API، إذ يؤمن الخادم rate-repository-api وصلة تخديم تعيد قائمةً مرقمةً من المستودعات المقيَّمة. ستتمكن من الولوج إلى وصلة التخديم الموجودة على العنوان "http://localhost:5000/api/repositories" بمجرد أن يعمل الخادم. تُرقّم البيانات بأسلوب الترقيم المعتمد على المؤشرات cursor based pagination format، وستجد البيانات الفعلية خلف المفتاح "node" في المصفوفة "edges". لا يمكن، لسوء الحظ، الوصول إلى الخادم rate-repository-api مباشرةً من خلال تطبيقنا بطلب العنوان "http://localhost:5000/api/repositories"، إذ نحتاج لإرسال طلب إلى وصلة التخديم هذه عبر التطبيق الوصول إلى الخادم باستخدام عنوان IP الخاص به في الشبكة المحلية التي ينتمي إليها. ولإيجاد هذا العنوان، افتح أدوات تطوير Expo بتنفيذ الأمر npm start. سترى في أدوات التطوير عنوانًا له بالبادئة "//:exp" فوق شيفرة QR: انسخ عنوان IP الموجود بين البادئة "//:exp" والنهاية ":"، وسيكون في مثالنا 192.168.100.16. أنشئ بعد ذلك عنوان URL بالتنسيق التالي "http://:5000/api/repositories" ثم توجه إليه من خلال المتصفح، وستحصل على نفس الاستجابة التي حصلت عليها باستخدام عنوان الخادم المحلي "localhost". بعد أن حددنا عنوان وصل التخديم، سنستخدم البيانات الفعلية التي يزودنا بها الخادم في تطبيق قائمة المستودعات المقيَّمة. أشرنا سابقًا أننا نستخدم بيانات وهمية مخزّنة في المتغير repositories. اِحذف هذا المتغير واستعض عن البيانات الوهمية في الملف "RepositoryList.jsx" الموجود في المجلد "components" بهذه الشيفرة: import { useState, useEffect } from 'react'; // ... const RepositoryList = () => { const [repositories, setRepositories] = useState(); const fetchRepositories = async () => { // ‫صحح عنوان IP إن كان مختلفًا لديك const response = await fetch('http://192.168.100.16:5000/api/repositories'); const json = await response.json(); console.log(json); setRepositories(json); }; useEffect(() => { fetchRepositories(); }, []); // Get the nodes from the edges array const repositoryNodes = repositories ? repositories.edges.map(edge => edge.node) : []; return ( <FlatList data={repositoryNodes} // Other props /> ); }; export default RepositoryList; استخدمنا الخطاف useState للحفاظ على حالة قائمة المستودعات، كما استخدمنا الخطاف useEffect لاستدعاء الدالة fetchRepositoriesعندما يُثبّت المكوّن RepositoryList ضمن شجرة المكوّنات. استخلصنا المستودعات الفعلية ووضعناها في المتغير repositoryNode واستبدلنا به المتغير السابق repositories الموجود في الخاصية data العائدة للمكوّن FlatList، وبذلك يمكننا أن نرى البيانات الفعلية التي يعطيها الخادم ضمن التطبيق. من الجيد عادةً تسجيل استجابة الخادم لتتمكن من تحرّيها كما فعلنا في الدالة fetchRepositories. ينبغي أن تكون قادرًا على الاطلاع على السجلات في أدوات تطوير Expo عن طريق الوصول إلى سجلات جهازك كما تعلمنا في المقال السابق (فقرة عرض سجلات العمل). إن استخدمت تطبيق جوّال Expo وأخفق طلب HTTP، تأكد أن هاتفك وحاسوبك الذي يشغّل الخادم ينتميان إلى نفس الشبكة اللاسلكية. وإن لم تستطع ذلك، استخدم المقلّد على نفس الحاسوب الذي يعمل عليه الخادم أو هيئ نفق توصيل tunnel إلى الخادم المحلي. يمكنك استخدام المكتبة Ngrok مثلًا لإنجاز ذلك. يمكن إعادة كتابة شيفرة إحضار البيانات الموجودة في المكوّن RepositoryList، إذ يعرف المكوّن مثلًا تفاصيل الطلبات عبر الشبكة كعنوان وصلة التخديم، وبالإضافة إلى ذلك، تحمل شيفرة إحضار البيانات إمكانية لإعادة استخدامها. لنعد إذًا كتابة شيفرة المكوّن بنقل شيفرة إحضار البيانات إلى خطاف خاص بها. سننشئ أولًا مجلدًا باسم "hooks" ضمن المجلد "src" ومن ثم سنضع فيه الملف "useRepositories.js" الذي يحتوي الشيفرة التالية: import { useState, useEffect } from 'react'; const useRepositories = () => { const [repositories, setRepositories] = useState(); const [loading, setLoading] = useState(false); const fetchRepositories = async () => { setLoading(true); // ‫صحح عنوان IP إن كان مختلفًا لديك const response = await fetch('http://192.168.100.16:5000/api/repositories'); const json = await response.json(); setLoading(false); setRepositories(json); }; useEffect(() => { fetchRepositories(); }, []); return { repositories, loading, refetch: fetchRepositories }; }; export default useRepositories; لنستخدم الآن الخطاف الجديد useRepositories في المكوّن RepositoryList: // ... import useRepositories from '../hooks/useRepositories'; const RepositoryList = () => { const { repositories } = useRepositories(); const repositoryNodes = repositories ? repositories.edges.map(edge => edge.node) : []; return ( <FlatList data={repositoryNodes} // Other props /> ); }; export default RepositoryList; وهكذا لن يكون لدى المكوّن RepositoryList أية فكرة عن طريقة الحصول على مستودعات القائمة، وربما سنطلب هذه المستودعات مستقبلًا باستخدام الواجهة البرمجية للمكتبة GraphQL بدلًا من واجهة REST، ونرى ما الذي سيحدث. العمل مع المكتبتين GraphQL و Apollo client تعرفنا في القسم 8 على GraphQL وكيفية إرسال استعلامات إلى خادم Apollo في تطبيقات React بالاستفادة من المكتبة Apollo Client. ولحسن الحظ، يمكنك استخدام Apollo client في تطبيقات React Native بنفس الطريقة التي استخدمناها في بناء تطبيقات React الخاصة بالويب. وكما أشرنا سابقًا، سيزودنا الخادم "rate-repository-api" بواجهة برمجية للمكتبة GraphQL نُفِّذت باستخدام Apollo client، وبالتالي ستتمكن من الولوج إلى أرضية عمل GraphQL بالتوجه إلى العنوان http://localhost:5000/graphq. تمثل أرضية عمل GraphQL أداة تطوير لإرسال استعلامات GraphQL، وتفقد تخطيط الواجهة البرمجية للمكتبة GraphQL، وعرض توثيق هذه المكتبة. ويفضل أن تجرب دائمًا أية استعلامات سيرسلها تطبيقك من خلال أرضية عمل GraphQL قبل تضمينها في الشيفرة. ومن الأسهل أيضًا تنقيح الأخطاء المحتملة في الاستعلامات ضمن أرضية عمل GraphQL موازنةً مع تنقيحها باستخدام التطبيق. وإن لم تكن متأكدًا من الاستعلامات المتاحة أو كيفية استخدامها، انقر على النافذة "docs" لفتح التوثيق: سنستعمل في تطبيقات React Native المكتبة Apollo Boost، وهو أسلوب لا يتطلب أية قواعد للتهيئة zero-config للبدء باستخدام Apollo client. وللتكامل مع React، سنستخدم المكتبة apollo/react-hooks@ والتي تؤمن خطافات مثل useQuery و useMutation تساعدنا في العمل مع المكتبة Apollo client. لنبدأ بتثبيت الاعتماديات اللازمة: npm install @apollo/client graphql سنحتاج قبل البدء باستخدام عميل Apollo إلى إعدادٍ جزئي لمجمّع Metro لمعالجة الملفات ذات الامتداد "cjs." التي يستخدمها عميل Apollo. لنثبّت أولًا الحزمة "expo/metro-config@" التي تحتوي على إعدادات Metro الافتراضية: npm install @expo/metro-config يمكننا بعد ذلك إضافة الإعداد التالي إلى الملف "metro.config.js" في المجلد الجذري للمشروع: const { getDefaultConfig } = require('@expo/metro-config'); const defaultConfig = getDefaultConfig(__dirname); defaultConfig.resolver.sourceExts.push('cjs'); module.exports = defaultConfig; أعِد تشغيل أدوات تطوير Expo لتطبيق الإعدادات المُجراة. لننشئ الآن دالةً خدميّة utility function لتكوين عميل Apollo وتهيئته على النحو المطلوب. أنشئ بدايةً المجلد "utils" ضمن المجلد "src"، وأنشئ ضمنه الملف "apolloClient.js"، ثم هيئ عميل Apollo ضمن هذا الملف ليتصل مع خادم Apollo: import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; const httpLink = createHttpLink({ // ‫صحح عنوان IP إن كان مختلفًا لديك uri: 'http://192.168.100.16:4000/graphql', }); const createApolloClient = () => { return new ApolloClient({ link: httpLink, cache: new InMemoryCache(), }); }; export default createApolloClient; يستخدم عميل Apollo للاتصال بخادم Apollo العنوان URL ذاته الذي استخدمناه مع Fetch API ونفس رقم المنفذ 4000، ما عدا أنّ المسار هو "graphql/". وأخيرًا علينا تزويد التطبيق بعميل Apollo وذلك باستخدام سياق العمل ApolloProvider. سنضيفه إذًا إلى المكوّن App في الملف "App.js": import { NativeRouter } from 'react-router-native'; import { ApolloProvider } from '@apollo/client'; import Main from './src/components/Main'; import createApolloClient from './src/utils/apolloClient'; const apolloClient = createApolloClient(); const App = () => { return ( <NativeRouter> <ApolloProvider client={apolloClient}> <Main /> </ApolloProvider> </NativeRouter> ); }; export default App; تنظيم الشيفرة المتعلقة بالمكتبة GraphQL سيعود الأمر إليك دائمًا في تنظيم الشيفرة المتعلقة بالمكتبة GraphQL ضمن تطبيقك. لكن ولأجل الحصول على هيكلية مرجعية، لنلق نظرةً على طريقة بسيطة لكنها فعالة في تنظيم هذه الشيفرة. عرّفنا في هذه الهيكلية كلًا من الاستعلامات والطفرات mutations والاجتزاءات fragments وغيرها من الكيانات التي قد نستخدمها في ملفات خاصة بها وتتواجد في نفس المجلد. إليك صورة توضح هيكلية يمكنك اعتمادها: يمكنك استيراد معرّف القالب المجرّد gql المُستخدم في تعريف استعلامات من مكتبة "apollo/client@". وإن اتبعنا الهيكلية التي اقترحناها منذ قليل، فمن الأفضل إنشاء الملف "queries.js" في المجلد "graphql" ليحتوي الشيفرة اللازمة لتنفيذ استعلامات GraphQL في تطبيقنا. يمكن أن نخزّن كل استعلام ضمن متغّير خاص ثم نصدّره كالتالي: import { gql } from '@apollo/client'; export const GET_REPOSITORIES = gql` query { repositories { ${/* ... */} } } `; // other queries... يمكن إدراج تلك المتغيرات واستخدامها بالاستفادة من الخطاف useQuery على النحو التالي: import { useQuery } from '@apollo/client'; import { GET_REPOSITORIES } from '../graphql/queries'; const Component = () => { const { data, error, loading } = useQuery(GET_REPOSITORIES); // ... }; وينطبق على الطفرات ما ينطبق على الاستعلامات. طبعًا لا بدّ من تعريف الطفرات في ملف منفصل مثل "mutations.js"، ويُفضّل استخدام الاجتزاءات (fragments) في الاستعلامات لتفادي تكرار كتابة الحقول نفسها عدة مرات. تطوير هيكلية التطبيق عندما يأخذ التطبيق في النمو، سيزداد حجم بعض الملفات لتصبح ربما كبيرة جدًا لإدارتها بصورةٍ صحيحة. لنفترض أنه لدينا مكوّن A سيصيّر مكونين B و C. ستُعرّف جميع هذه المكونات ضمن الملف "A.jsx" والذي سيوضع في المجلد"components". نريد الآن أن ننقل المكونين A و B إلى ملفين مستقلين "B.jsx" و "C.jsx" دون أن نجري تغييرًا كبيرُا في تنظيم الشيفرة. سيكون أمامنا في هذه الحالة خيارين: الأول، إنشاء الملفين "B.jsx" و"C.jsx" في المجلد "components". وستكون هيكلية التطبيق على النحو التالي: components/ A.jsx B.jsx C.jsx ... الثاني، إنشاء مجلد A في المجلد "components" ثم إنشاء الملفين "B.jsx"و"C.jsx" ضمنه. ولتفادي إخفاق المكونات التي تُدرِج الملف "A.jsx" في شيفرتها، انقل الملف "A.jsx" إلى المجلد "A" وغيّر اسمه إلى "index.jsx". سينتج عن هذا الأسلوب الهيكلية التالية: components/ A/ B.jsx C.jsx index.jsx ... يُعد الخيار الأول مناسبًا تمامًا إن لم يكن المكونين C وB قابلين للاستخدام خارج حدود المكوّن A، فلا فائدة من وضعهما في ملفات منفصلة في المجلد "components"؛ أما الخيار الثاني فهو مناسبٌ لأسلوب الوحدات المستقلة، إذ أنه لا يسبب أية مشاكل في إدراج المكونات، لأن إدراج مسار مثل "A/." سيطابق إدراج كلا الملفين "A.jsx" و"A/index.jsx". التمرين 10.11 إحضار المستودعات باستخدام Apollo client نريد استبدال ما نفذناه ضمن الخطاف useRepositories مستخدمين المكتبة Fetch API باستعلام GraphQL. شغّل أرضية عمل Apollo على العنوان "http://localhost:4000"، ثم افتح توثيق المكتبة بالنقر على النافذة "docs". يستخدم الاستعلام عدة وسطاء اختيارية، فلا حاجة لتحديدها الآن. كوّن استعلامًا ضمن أرضية عمل Apollo لإحضار المستودعات بالاستفادة من قيم الحقول التي تعرضها حاليًا في واجهة التطبيق. ستُرقّم النتائج التي تضم قرابة 30 نتيجة افتراضيًا. يمكنك تجاهل موضوع الترقيم حاليًا. عندما يعمل الاستعلام ضمن أرضية عمل Apollo، اِستخدمه ليحل مكان شيفرة المكتبة FetchAPI في الخطاف useRepositories. يمكن أن تنفِّذ ذلك باستخدام الخطاف useQuery. أدرج بعدها معّرف القالب المجرّد gql من المكتبة بالطريقة التي شرحناها سابقًا. حاول أن تستخدم الهيكلية التي اقترحناها سابقًا لتنظيم الشيفرة المتعلقة بالمكتبة GraphQL. ولتجنب مشاكل الذاكرة الخبيئة مستقبلًا، استخدم سياسة الإحضار (fetch policy) "cache-and-network"، التي يمكن استخدامها مع الخطاف useQuery على النحو التالي: useQuery(MY_QUERY, { fetchPolicy: 'cache-and-network', // خيارات أخرى }); يجب أن لا تؤثر التغييرات في شيفرة الخطاف useRepositories على أداء المكوّن RepositoryList بأي شكل من الأشكال. متغيرات البيئة سيعمل كل تطبيق على الأرجح في أكثر من بيئة، ومن الواضح أن بيئتي التطوير والإنتاج أقرب مثالين عن الفكرة، لكن خارج هاتين البيئتين، هناك البيئة التي نشغّل عليها التطبيق حاليًا. تحتاج البيئات المختلفة عادةً اعتماديات مختلفة، فقد يستخدم الخادم المحلي الذي نتعامل معه قاعدة بيانات محليّة، بينما سيتعامل الخادم الذي سيُنشَر في بيئة الإنتاج مع قاعدة بيانات بيئة الإنتاج. ولكي نجعل شيفرتنا مستقلة عن بيئة التشغيل، لا بد من ربط هذه الاعتماديات بمعاملات أو متغيرات. لاحظ مثلًا أننا نستخدم في تطبيقنا قيمة جاهزة تعتمد بشدة على بيئة التشغيل وهي عنوان الخادم. تعلمنا سابقًا أنه بالإمكان تزويد البرامج قيد التشغيل بمتغيرات بيئة، إذ يمكن تعريف هذه المتغيرات من خلال سطر الأوامر أو من خلال ملف تهيئة بيئة التشغيل الذي ينتهي بلاحقة مثل "env." ومكتبات يؤمنها طرف ثالث مثل Dotenv. لكن ولسوء الحظ لا تدعم React Native الاستخدام المباشر لمتغيرات البيئة، لكن يمكننا الوصول إلى إعدادت تهيئة Expo الموجودة في الملف "app.json" في زمن التشغيل من خلال شيفرة JavaScript. وبالتالي يمكن استخدام هذه الإعدادات لتعريف المتغيرات المتعلقة بالبيئة والوصول إليها. نستطيع الوصول إلى إعدادات التهيئة بإدراج الثابت Constants من الوحدة "expo-constants" كما فعلنا ذلك عدة مرات مسبقًا. وبإدراج هذا الثابت، ستزودنا الخاصية Constants.manifest بكل الإعدادات. لنجرب ذلك بكتابة شيفرة ضمن المكوّن App وظيفتها طباعة محتويات هذه الخاصية في سجل العمل: import { NativeRouter } from 'react-router-native'; import { ApolloProvider } from '@apollo/client'; import Constants from 'expo-constants'; import Main from './src/components/Main'; import createApolloClient from './src/utils/apolloClient'; const apolloClient = createApolloClient(); const App = () => { console.log(Constants.manifest); return ( <NativeRouter> <ApolloProvider client={apolloClient}> <Main /> </ApolloProvider> </NativeRouter> ); }; export default App; ستجد الآن كل إعدادات التهيئة في سجل العمل. تقتضي الخطوة الثانية استخدام الإعدادات لتعريف متغيّرات تعتمد على بيئة التشغيل. لنبدأ المهمة بتغير اسم الملف "app.json" إلى "app.config.json". بهذا التغيير سنصبح قادرين على استخدام شيفرة جافا سكريبت داخل ملف التهيئة. غير محتوى الملف بحيث يتحوّل الكائن: { "expo": { "name": "rate-repository-app", // … بقية الضبط } } إلى كائن قابل للتصدير، يحتوي على الخاصية expo: export default { name: 'rate-repository-app', // … بقية الضبط }; يتلقى الكائن expo الخاصية extra ضمن إعدادات التهيئة، وذلك لتمرير أية إعدادات خاصة بالتطبيق. ولنرى كيف يعمل هذا الأمر، سنضيف المتغير env إلى إعدادات تهيئة تطبيقنا: export default { name: 'rate-repository-app', // … بقية الضبط extra: { env: 'development' }, }; أعد تشغيل أدوات تطوير Expo لتطبيق التغييرات. من المفترض أن تتغير قيمة الخاصية Constans.manifest بحيث تضم الخاصية extra التي تحتوي على إعدادات التهيئة الخاصة بتطبيقنا. يمكننا الآن الوصول إلى المتغير env من خلال الخاصية Constants.manifest.extra.env ولأن وضع القيم الجاهزة في الشيفرة أمر طائش، سنستخدم متغير بيئة بديلًا لذلك: export default { name: 'rate-repository-app', // rest of the configuration... extra: { env: process.env.ENV, }, }; تعلمنا سابقًا كيفية إسناد قيمة لمتغير البيئة من خلال سطر الأوامر، وذلك بتحديد اسم المتغيّر وقيمته قبل الأمر الفعلي. فعلى سبيل المثال، شغل أدوات تطوير Expo، ثم أسند القيمة "test" إلى متغير البيئة ENV: ENV=test npm start لو ألقيت نظرةً على سجل العمل، فستجد أن الخاصية Constants.manifest.extra.env قد تغيرت. يمكننا كذلك تحميل متغيرات البيئة من الملف "env." كما تعلمنا في الأقسام السابقة. لكن علينا أولًا تثبيت المكتبة Dotenv: npm install dotenv لنضف الآن الملف "env." إلى المجلد الجذري للمشروع على أن يحتوي ما يلي: ENV=development سندرج أخيرًا المكتبة ضمن الملف "app.config.js": import 'dotenv/config'; export default { name: 'rate-repository-app', // … بقية الضبط extra: { env: process.env.ENV, }, }; أعد تشغيل أدوات تطوير Expo لتطبيق التغييرات التي أجريتها على الملف "env". ملاحظة: لا تضع بيانات حساسة ضمن إعدادات التطبيق، لأن المستخدم بعد تنزيل التطبيق قد يكون قادرًا من الناحية النظرية على تحليل شيفرتك وسيحصل بالتالي على هذه البيانات الحساسة. التمرين 10.12 متغيرات البيئة استخدم متغير بيئة ضمن ملف "env." لتحديد عنوان خادم Apollo بدلًا من كتابته ضمن الشيفرة، وذلك عند تهيئة عميل Apollo. يمكنك تسمية هذا المتغير "APOLLO_URI" مثلًا. لا تحاول الوصول إلى متغير البيئة على النحو التالي: process.env.APOLLO_URI خارج الملف "app.config.js"، بل استخدم الكائن Constants.manifest.extra كما فعلنا في المثال السابق. كذلك لا تدرج المكتبة Dotenv خارج الملف "app.config.js" وإلا قد تواجهك المشاكل. تخزين البيانات في جهاز المستخدم قد تحتاج في بعض الأحيان إلى تخزين بعض البيانات في جهاز المستخدم. ومن أبرز السيناريوهات التي تضطرك إلى ذلك هو تخزين شهادة التحقق من المستخدم token لكي تكون قادرًا على الحصول عليها عندما يغلق المستخدم التطبيق ثم يعيد فتحه. لقد استخدمنا عند تطوير تطبيقات الويب الذاكرة المحلية للمتصفح من خلال الكائن localStorage. تؤمن Recat Native بالمقابل ذاكرة تخزين مقيمة هي الذاكرة غير المتزامنة (AsyncStorage). يمكن استخدام أمر التثبيت في Expo لتثبيت نسخة من الحزمة reactnativecommunity/async-storage@ متوافقة مع نسخة Expo SDK المستخدمة: expo install @react-native-async-storage/async-storage تتشابه الواجهة البرمجية للحزمة AsyncStorage مع الواجهة البرمجية للذاكرة المحلية localStorage بعدة نواحٍ، فكلاهما ذاكرة تعتمد مبدأ "مفتاح-قيمة". ويكمن الاختلاف الكبير بينهما، كما يوحي الاسم، أنّ عمليات AsyncStorage غير متزامنة. من المفيد أن نُجري تجريدًا بسيطًا لشيفرة العمليات، طالما أن AsyncStorage ستعمل مع مفاتيح نصية ضمن فضاء الأسماء العام global namespace. يمكن إنجاز هذا التجريد باستخدام الأصناف class. إذ يمكن، على سبيل المثال، إنشاء صنف لبطاقة تسوق، نخزّن فيها المنتجات التي يريد أن يشتريها مستخدم: import AsyncStorage from '@react-native-async-storage/async-storage'; class ShoppingCartStorage { constructor(namespace = 'shoppingCart') { this.namespace = namespace; } async getProducts() { const rawProducts = await AsyncStorage.getItem( `${this.namespace}:products`, ); return rawProducts ? JSON.parse(rawProducts) : []; } async addProduct(productId) { const currentProducts = await this.getProducts(); const newProducts = [...currentProducts, productId]; await AsyncStorage.setItem( `${this.namespace}:products`, JSON.stringify(newProducts), ); } async clearProducts() { await AsyncStorage.removeItem(`${this.namespace}:products`); } } const doShopping = async () => { const shoppingCartA = new ShoppingCartStorage('shoppingCartA'); const shoppingCartB = new ShoppingCartStorage('shoppingCartB'); await shoppingCartA.addProduct('chips'); await shoppingCartA.addProduct('soda'); await shoppingCartB.addProduct('milk'); const productsA = await shoppingCartA.getProducts(); const productsB = await shoppingCartB.getProducts(); console.log(productsA, productsB); await shoppingCartA.clearProducts(); await shoppingCartB.clearProducts(); }; doShopping(); ومن الجيد أيضًا أن نحدد فضاء أسماء للمفاتيح في AsyncStorage، ذلك أنها قيم نصيّة. وفي هذا السياق، سنجد أن فضاء الأسماء هو بادئة نزوّد بها مفاتيح التخزين، إذ سيمنع استخدام فضاء الأسماء مفاتيح التخزين من التصادم مع مفاتيح أخرى للمكتبة AsyncStorage. عرّفنا فضاء الأسماء في المثال السابق على شكل وسيط للدالة البانية، كما استخدمنا التنسيق "namespace:key مفتاح: فضاء أسماء" من أجل مفاتيح التخزين. يمكن إضافة عنصر إلى ذاكرة التخزين مستخدمين التابع AsyncStorage.setItem. سيكون مفتاح العنصر هو الوسيط الأول للتابع بينما ستكون قيمة المفتاح هي المعامل الثاني. وطالما أن قيمة المفتاح هي قيمة نصية، فلا بد من تحويل القيم غير النصية إلى نصية باستخدام التابع JSON.stringify. يُستخدَم التابع AsyncStorage.getItem للحصول على عنصر محدد من ذاكرة التخزين، ووسيط هذا التابع هو مفتاح العنصر. أما التابع AsyncStorage.removeItem فيُستخدم لحذف عنصر من ذاكرة التخزين بتحديد مفتاحه. التمرينان 10.13 - 10.14 10.13: استخدام الطفرات في نموذج تسجيل الدخول لا يستطيع نموذج تسجيل الدخول حاليًا فعل أي شيء حيال معلومات توثيق المستخدم التي يرسلها، لذلك سنحاول تحسين الوضع قليلًا في هذا التمرين. اطلع أولًا على توثيق الاستيثاق authentication documentation الخاص بالخادم "rate-repository-api"، ثم اختبر الاستعلامات التي تُزوَّد بها ضمن أرضية عمل Apollo. إن لم تحتوي قاعدة البيانات على أي مستخدمين يمكن وضع بعض البيانات فيها، وستجد إرشاداتٍ لذلك في الفقرة "getting started"من الملف "README" الموجود في المستودع "rate-repository-ap". عندما تتفهم الطريقة التي تعمل بها استعلامات الاستيثاق، أنشئ الملف "useSignIn.js" في المجلد "hooks" وضع فيه شيفرة الخطافuseSignIn الذي يرسل الطفرة باستخدام الخطاف useMutation. تقبل الطفرة authenticate وسيطًا وحيدًا هو credentials من النوع "AuthenticateInput". يحتوي نوع عنصر الإدخال (input type) حقلين لاسم المستخدم وكلمة المرور. ستكون القيمة التي يعيدها الخطاف على الشكل [signIn, result]، إذ يمثل الوسيط result نتيجة الطفرات كما يعيدها الخطاف useMutation، ويمثل الوسيط signIn دالة تنفيذ الطفرة باستخدام وسيط هو كائن من الشكل {username, password}. تلميح: لا تمرر دالة الطفرة إلى القيمة المعادة مباشرةً، بل أعد دالة تستدعي دالة الطفرة على النحو التالي: const useSignIn = () => { const [mutate, result] = useMutation(/* mutation arguments */); const signIn = async ({ username, password }) => { // استدعي دالة الطفرة بالوسطاء المناسبين }; return [signIn, result]; }; بعد إنجاز الخطاف، استخدمه في دالة الاستدعاء onSubmit العائدة للمكوّن SignIn على النحو التالي مثلًا: const SignIn = () => { const [signIn] = useSignIn(); const onSubmit = async (values) => { const { username, password } = values; try { const { data } = await signIn({ username, password }); console.log(data); } catch (e) { console.log(e); } }; // ... }; يمكنك عدُّ التمرين مكتملًا عندما تُسجَّل نتيجة طفرات authenticate في سجل عمل التطبيق بعد إرسال بيانات النموذج. وينبغي أن تتضمن نتيجة الطفرة شهادة التحقق من المستخدم. 10.14: تخزين شهادة التحقق- الخطوة 1 بعد أن حصلنا على شهادة التحقق لا بدّ من تخزينها. أنشئ الملف "authStorage.js" في المجلد "utils" بحيث يحتوي الشيفرة التالية: import AsyncStorage from '@react-native-async-storage/async-storage'; class AuthStorage { constructor(namespace = 'auth') { this.namespace = namespace; } getAccessToken() { // احصل على شهادة التحقق } setAccessToken(accessToken) { // أضف الشهادة إلى ذاكرة التخزين } removeAccessToken() { // احذف شهادة التحقق من ذاكرة التخزين } } export default AuthStorage; أنجز شيفرة التوابع التالية: AuthStorage.getAccessToken و AuthStorage.setAccessToken و AuthStorage.removeAccessToken. استخدم المتغير namespace لمنح المفاتيح فضاء أسماء كما فعلنا في المثال السابق. تحسين طلبات المكتبة Apollo Client بعد أن أنجزنا آلية لتخزين شهادة التحقق من المستخدم، حان الوقت لاستخدامها. هيئ ذاكرة التخزين في المكوّن App: import { NativeRouter } from 'react-router-native'; import { ApolloProvider } from '@apollo/client'; import Main from './src/components/Main'; import createApolloClient from './src/utils/apolloClient'; import AuthStorage from './src/utils/authStorage'; const authStorage = new AuthStorage(); const apolloClient = createApolloClient(authStorage); const App = () => { return ( <NativeRouter> <ApolloProvider client={apolloClient}> <Main /> </ApolloProvider> </NativeRouter> ); }; export default App; مررنا نسخةً من الصنف الذي يتعامل مع ذاكرة التخزين AuthStorage إلى الدالة createApolloClient مثل وسيط، لأننا سنرسل لاحقًا شهادة التحقق إلى خادم Apollo مع كل طلب. يتوقع خادم Apollo أن تكون شهادة التحقق ضمن ترويسة الاستيثاق وبنفس تنسيق حامل الشهادة <ACCESS_TOKEN>. يمكن تحسين طلب Apollo Client باستخدام الدالة setContext. لنرسل شهادة التحقق إلى خادم Apollo من خلال عميل Apollo بتعديل الدالة createApolloClientفي الملف "apolloClient.js": import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client'; import Constants from 'expo-constants'; import { setContext } from '@apollo/client/link/context'; // ‫قد تحتاج إلى تغيير هذا بناءً على كيفية تكوين URI لخادم Apollo const { apolloUri } = Constants.manifest.extra; const httpLink = createHttpLink({ uri: apolloUri, }); const createApolloClient = (authStorage) => { const authLink = setContext(async (_, { headers }) => { try { const accessToken = await authStorage.getAccessToken(); return { headers: { ...headers, authorization: accessToken ? `Bearer ${accessToken}` : '', }, }; } catch (e) { console.log(e); return { headers, }; } }); return new ApolloClient({ link: authLink.concat(httpLink), cache: new InMemoryCache(), }); }; export default createApolloClient; استخدام React Context لتوزيع البيانات يبقى علينا في النهاية إجراء تكامل بين ذاكرة التخزين والخطاف useSignIn. ولكي ننجز ذلك لا بدّ من السماح للخطاف بولوج شهادة التحقق التي هيئنا نسخة منها في المكوّن App. سنستخدم الواجهة Context التي تزودنا بها React لأداء هذه المهمة. أنشئ المجلد "contexts" ضمن المجلد "src"، ثم أنشئ ضمنه الملف "AuthStorageContext.js" وضع فيه الشيفرة التالية: import React from 'react'; const AuthStorageContext = React.createContext(); export default AuthStorageContext; ستتمكن الآن من استخدام المكوّن AuthStorageContext.Provider لتوزيع نسخة من ذاكرة التخزين على المكوّنات الأبناء ضمن سياق التنفيذ context. لنضفه إلى المكوّن App: import { NativeRouter } from 'react-router-native'; import { ApolloProvider } from '@apollo/client'; import Main from './src/components/Main'; import createApolloClient from './src/utils/apolloClient'; import AuthStorage from './src/utils/authStorage'; import AuthStorageContext from './src/contexts/AuthStorageContext'; const authStorage = new AuthStorage(); const apolloClient = createApolloClient(authStorage); const App = () => { return ( <NativeRouter> <ApolloProvider client={apolloClient}> <AuthStorageContext.Provider value={authStorage}> <Main /> </AuthStorageContext.Provider> </ApolloProvider> </NativeRouter> ); }; export default App; يمكن الوصول إلى نسخة ذاكرة التخزين في دالة الخطاف useSignIn باستخدام الخطاف useContext العائد للمكتبة React على النحو التالي: // ... import { useContext } from 'react'; import AuthStorageContext from '../contexts/AuthStorageContext'; const useSignIn = () => { const authStorage = useContext(AuthStorageContext); // ... }; انتبه إلى أنه من غير الممكن الولوج إلى القيمة التي يعيدها سياق العمل باستخدام الخطاف useContext مالم يُستخدم هذا الخطاف في مكوّن ابن للمكون Context.Provider. يُعد الوصول إلى نسخة ذاكرة التخزين باستخدام (useContext(AuthStorageContext مطولًا تمامًا ويكشف عن تفاصيل التنفيذ. دعنا نحسن هذا من خلال تنفيذ خطاف useAuthStorage في ملف "useAuthStorage.js" في مجلد "hooks": import { useContext } from 'react'; import AuthStorageContext from '../contexts/AuthStorageContext'; const useAuthStorage = () => { return useContext(AuthStorageContext); }; export default useAuthStorage; يعد تنفيذ الخطاف بسيطًا جدًا ولكنه يحسن إمكانية قراءة الخطافات والمكونات التي تستخدمها وصيانتها. يمكننا استخدام الخطاف لإعادة تشكيل خطاف useSignIn كما يلي: // ... import useAuthStorage from '../hooks/useAuthStorage'; const useSignIn = () => { const authStorage = useAuthStorage(); // ... }; ستجعل إمكانية توزيع البيانات على المكونات الأبناء من React Context مفتاحًا لحل عدد هائل من الحالات. لتطلع أكثر على هذه الحالات، اقرأ المقالة How to use React Context effectively للمؤلف "Kent C. Dodd"، لتعرف كيف توافق بين استخدام الخطاف useReducer وReact context لتنفيذ آلية لإدارة الحالة. وربما ستجد هذا مفيدًا لحل التمارين القادمة. التمرينان 10.15 و 10.16 10.15: تخزين شهادة التحقق- الخطوة 2 حسّن شيفرة الخطاف useSignIn لكي يخزّن شهادة التحقق التي تنتج عن الطفرة authenticate، بحيث لا تتغير القيمة التي يعيدها الخطاف. ينبغي أن يكون التغيير الوحيد في المكوّن SignIn هو تحويل المستخدم إلى واجهة عرض قائمة المستودعات بعد أن يسجل دخوله بنجاح. يمكنك إنجاز ذلك باستخدام الخطاف useNavigate. عليك أن تعيد ضبط مخزن عميل Apollo بعد تنفيذ الطفرة authenticate وتخزين شهادة التحقق في ذاكرة التخزين. سيسبب ذلك مسح الذاكرة المؤقتة وإعادة تنفيذ كل الطلبات النشطة. يمكن إنجاز ذلك باستخدام التابع resetStore العائد للمكتبة Apollo Client. يمكن الوصول إلى عميل Apollo عبر الخطاف useSignIn باستخدام الخطاف useApolloClient. وانتبه إلى أنّ ترتيب خطوات التنفيذ مهم جدًا وينبغي أن يكون على النحو التالي: const { data } = await mutate(/* خيارات */); await authStorage.setAccessToken(/* شهادة التحقق مأخوذة من البيانات */); apolloClient.resetStore(); 10.16: تسجيل الخروج ستكون الخطوة الأخيرة هي تنفيذ ميزة تسجيل الخروج. يمكن استخدام الاستعلام me للتحقق من بيانات الشخص الذي سجّل دخوله. فإن كانت نتيجة الاستعلام "null" فلن يكون الشخص مخوّلًا بالوصول. شغّل أرضية عمل Apollo، ثم نفّذ الاستعلام التالي: { me { id username } } ستكون النتيجة غالبًا "null"، والسبب في ذلك أنّ أرضية عمل Apollo غير مفوّضة بالوصول. ويعني ذلك أنها لم ترسل شهادة تحقق مع الطلب. راجع توثيق الاستيثاق لتفهم آلية استخلاص شهادة تحقق من الطفرة authenticate. استخدم هذه الشهادة في ترويسة الاستيثاق كما هو موّضح في التوثيق. نفّذ بعد ذلك الاستعلام me من جديد وسترى معلومات المستخدم الذي سجّل دخوله. افتح الملف "AppBar.jsx" الذي يحتوي على المكوّنAppBar والذي يحتوي بدوره على النافذتين "Sign in" و"Repositories". عدّل النوافذ بحيث تظهر النافذة "Sign out" إن سجل المستخدم دخوله، والنافذة "Sign in" إن سجّل خروجه. يمكنك إنجاز ذلك باستخدام الاستعلام me مع الخطاف useQuery. من المفترض أن تُحذف شهادة التحقق من ذاكرة التخزين عند الضغط على النافذة "Sign out"، وأن يُعاد ضبط مخزن عميل Apollo باستخدام التابع resetStore، إذ سيُعاد تنفيذ كل الاستعلامات النشطة تلقائيًا عند استدعاء التابع resetStore، ويعني ذلك إعادة تنفيذ الاستعلام me. تذكّر أن ترتيب خطوات التنفيذ مهم جدًا، ويجب إزالة شهادة التحقق من ذاكرة التخزين قبل إعادة ضبط مخزن عميل Apollo. ترجمة -وبتصرف- للفصل Communicating with server من سلسلة Deep Dive Into Modern Web Development. اقرأ أيضًا المقال السابق: أساسيات React Native مدخل إلى React Native مدخل إلى التحريك في React Native الاتصال مع الخادم في تطبيق React معتمد على Redux
  24. الآن وبعد أن كتبنا تطبيقًا جيدًا، قد حان الوقت لنشره وتوزيعه على المستخدمين الفعليين. لقد فعلنا ذلك في القسم 3 بدفع مستودع git إلى خوادم الاستضافة السحابية Heroku بكل بساطة. تُعد عملية إصدار برمجيات على منصة هيروكو Heroku أمرًا بسيطًا موازنةً بغيرها من الاستضافات، لكن لا تزال هناك طبعًا بعض المخاطر، فلا شيء سيمنعنا من الدفع بشيفرة لا تعمل إلى وضع الإنتاج عن طريق الخطأ. سنتعرف في الفقرات القادمة على مبادئ النشر الآمن للتطبيقات، ومبادئ نشر البرمجيات بالمقاييس الصغيرة والواسعة. يمكن لأي شيء أن يحدث خطأ نريد تحديد بعض القواعد التي تضبط عملية نشرنا للتطبيقات، لكن علينا بداية إلقاء نظرةٍ على بعض المحدوديات الواقعية: "إن أمكن لشيءٍ ما أن يخفق، فسيخفق": هي عبارة يتضمنها قانون مورفي، ومن المهم تذكرها دائمًا عند التخطيط لمنظومة النشر التي سنتبعها، ومن الأشياء التي ينبغي أخذها بالحسبان أيضًا: ماذا لو توقف الحاسوب عن العمل أثناء نشر التطبيق؟ ماذا سيحدث إن انقطع الاتصال بالإنترنت أثناء نشر التطبيق؟ ماذا سيحدث إن أخفقت أية تعليمات من سكربت أو منظومة النشر؟ ماذا سيحدث إن لم يعمل تطبيقي، لسببٍ أو لآخر، على الخادم بعد نشره؟ هل يمكنني التراجع rollback إلى النسخة السابقة؟ ماذا سيحدث إذ أرسل مستخدم ما طلب HTTP إلى التطبيق قبل أن يكتمل نشره (لا يوجد وقتٌ لإرسال استجابة له)؟ هذه عينة صغيرة من المشاكل التي قد تواجهك أثناء النشر أو بالأحرى عينة من الأمور التي ينبغي التخطيط لها. لا ينبغي لمنظومة النشر التي نعتمدها أن تترك البرنامج في حالة إخفاق أيًا كان سببه، كما ينبغي علينا أن نعرف (أو أن نجد بسهولة) حالة منظومة النشر. وطالما نتحدث عن النشر وعن التكامل المستمر CI عمومًا، هناك قاعدةٌ مهمةٌ أخرى عليك تذكرها: "الأخطاء الصامتة قاتلة". لا يعني هذا طبعًا عرض الأخطاء على مستخدم البرنامج، بل يعني أنه يجب علينا الانتباه إلى أي شيء قد يخفق. فلو كنا على دراية بمشكلة محتملة الوقوع، يمكننا إصلاحها. فإن لم تعطي منظومة النشر أية أخطاء لكنها فشلت، سنصل إلى المرحلة التي نعتقد فيها أننا أصلحنا كل الأخطاء الحرجة ومع ذلك تخفق المنظومة، وبالتالي سنترك الثغرة في بيئة الإنتاج غير مدركين لخطورة الوضع. ما الذي تمنحه منظومة النشر الجيدة؟ من الصعب وضع قواعد محددة أو متطلبات خاصة بمنظومة النشر، لكننا سنحاول ذلك: عندما تخفق المنظومة في أية خطوة، يجب أن تخفق دون أية آثار جانبية. لا ينبغي أبدًا أن تترك المنظومة البرنامج في حالة فشل. ينبغي أن تخبرنا المنظومة عن أية حالات إخفاق، فمن الأفضل تنبيهنا إلى حالات الإخفاق عوضًا عن النجاح. ينبغي على المنظومة أن تسمح لنا بالتراجع إلى النسخة المنشورة سابقًا. يفضل أن نتراجع بسهولة إلى نسخة أقل عرضة للإخفاق بالموازنة مع النسخة المنشورة كاملةً. يبقى الخيار الأفضل لنا هو التراجع الآلي في حال أخفق النشر. ينبغي أن تكون المنظومة قادرةً على التعامل مع طلبات HTTP قبل انتهاء أو أثناء عملية النشر. ينبغي أن تتأكد المنظومة أنّ البرنامج الذي ننشره سيلبّي المتطلبات التي وضعناها لعملية النشر (لا تنشر مثلًا برنامج أخفق في أحد الاختبارات). لنحدد أيضًا بعض الأشياء التي نريدها في منظومة النشر الافتراضية التي نقترحها: نريدها أن تكون سريعة. لا نريد وقتًا يتوقف فيه البرنامج عن العمل downtime أثناء النشر (هذا أمر مختلف عن معالجة طلبات المستخدمين قبل انتهاء أو أثناء النشر). التمارين 11.10 - 11.12 قبل أن نبدأ بالتمارين عليك إعداد تطبيقك لبيئة عمل هيروكو Heroku. وهنا لن ندفع الشيفرة بأنفسنا، إذ سينفِّذ مخطط عمل GitHub Actions المهمة من أجلنا. تأكد من تثبيت Heroku CLI ثم سجِّل دخولك مستخدمًا واجهة سطر الأوامر CLI عن طريق heroku login. أنشئ تطبيقًا على هيروكو HeroKu باستخدام الأمر: heroku create --region eu {your-app-name}‎ وانتق منطقةً قريبةً من موقعك الجغرافي. ولِّد شهادة تحقق Token لملفك على Heroku باستخدام الأمر: heroku authorizations:create ثم خزّن معلومات الاستيثاق على ملف ضمن حاسوبك لكن لا تدفعه إلى GitHub. ستحتاج إلى شهادة التحقق من أجل مخطط عمل نشر التطبيق. اطلع أكثر على شهادة تحقق هيروكو عبر الإنترنت. 11.10: نشر تطبيقك على منصة هيروكو Heroku وسّع مخطط العمل بخطوة لنشر تطبيقك على منصة هيروكو Heroku. نفترض فيما سيأتي أنك ستستخدم AkhileshNS/heroku-deploy وهو فعل GitHub Actions الخاص بالنشر على هيروكو والذي طوره مجتمع GitHub Actions. تحتاج إلى شهادة التفويض التي حصلت عليها سابقًا من أجل نشر تطبيقك. ومن الأفضل تمرير قيمتها إلى GitHub Actions باستخدام أسرار المستودع repository secrets: يمكنك الآن الوصول إلى قيمة الشهادة على النحو التالي: ${{secrets.HEROKU_API_KEY}} إذا جرى كل شيء على ما يرام، سيبدو مخطط العمل على الشكل التالي: جرِّب تطبيقك عبر متصفح، لكن الأخطاء ستظهر غالبًا. سنجد أنّ منصة هيروكو تفترض وجود الملف "Procfile" في المستودع والذي يوجّهه لطريقة تشغيل البرنامج. أضف إذًا ملف "Procfile" مناسب إلى المستودع وتأكد أنّ التطبيق سيعمل جيدًا. تذكرة: راقب باستمرار ما يحدث عبر سِجِل الخادم عندما تجرب عملية النشر. استخدم لذلك الأمر heroku logs دائمًا. 11.11: التحقق من عمل التطبيق قبل التوسّع أكثر في التطبيق، تحقق أن التطبيق يعمل جيدًا بعد النشر. لا نحتاج في الواقع لخطوة جديدة في مخطط العمل، إذ يحتوي الفعل deploy-to-heroku على خيار يفي بالغرض. أضف وصلة تخديم end point بسيطة مهمتها التحقق من عمل التطبيق في الواجهة الخلفية. ويمكنك أيضًا نسخ الشيفرة التالية: app.get('/health', (req, res) => { res.send('ok') }) من الجيد أيضًا وجود وصلة تخديم اختبارية في التطبيق، لكي يمتلك إمكانية إجراء بعض التعديلات في الشيفرة ويتأكد من أن التغيرات قد ظهرت أيضًا في النسخة المنشورة: app.get('/version', (req, res) => { res.send('1') // غير هذه القيمة للتأكد من أنها نُشرت في النسخة الجديدة }) راجع التوثيق لتعرف كيف ستضم آلية التحقق من عمل التطبيق إلى خطوة النشر، واستخدم عنوان وصلة التخديم التي أنشأتها للتحقق من عمل التطبيق، وقد تحتاج غالبًا إلى الخيار "checkstring" حتى يُنفَّذ الأمر. تأكد من أنّ GitHub Actions سينتبه إذا ما سبب النشر إخفاق التطبيق. استخدم لاختبار ذلك أمر إقلاع خاطئ مثلًا في الملف "Procfile". قبل الانتقال إلى التمرين التالي، أصلح مشاكل خطوة النشر وتأكد أن التطبيق سيعمل بالشكل المطلوب مجددًا. 11.12: التراجع من الأفضل إذا أخفق التطبيق بعد النشر أن تتراجع إلى الإصدار السابق. ولحسن الحظ، يجعل هيروكو Heroku هذا الأمر بسيطًا جدًا. ينتج عن كل عملية نشر على Heroku إصدار، ويمكنك معرفة الإصدارات الموجودة لتطبيق بتنفيذ الأمر heroku releases: $ heroku releases === calm-wildwood-40210 Releases - Current: v8 v8 Deploy de15fc2b mluukkai@iki.fi 2022/03/02 19:14:22 +0200 (~ 8m ago) v7 Deploy 8748a04e mluukkai@iki.fi 2022/03/02 19:06:28 +0200 (~ 16m ago) v6 Deploy a617a93d mluukkai@iki.fi 2022/03/02 19:00:02 +0200 (~ 23m ago) v5 Deploy 70f9b219 mluukkai@iki.fi 2022/03/02 18:48:47 +0200 (~ 34m ago) v4 Deploy 0b2db00d mluukkai@iki.fi 2022/03/02 17:53:24 +0200 (~ 1h ago) v3 Deploy f1cd250b mluukkai@iki.fi 2022/03/02 17:44:32 +0200 (~ 1h ago) v2 Enable Logplex mluukkai@iki.fi 2022/03/02 17:00:26 +0200 (~ 2h ago) v1 Initial release mluukkai@iki.fi 2022/03/02 17:00:25 +0200 (~ 2h ago) يمكن التراجع إلى إصدار محدد بكتابة أمر واحد ضمن سطر الأوامر، والأفضل من ذلك سيتولى الفعل deploy-to-heroku مهمة التراجع نيابةً عنا. اقرأ توثيق GitHub Actions مجددًا وعدّل مخطط العمل لتمنع إخفاق التطبيق عند النشر. يمكنك محاكاة الأمر أيضًا بكتابة أمر خاطئ في الملف "Procfile" والتجربة: تحقق أنّ التطبيق سيستمر في العمل حتى لو أخفق النشر. تنبيه: على الرغم من إمكانية التراجع التلقائي، قد يخفق البناء في العالم الحقيقي. لذلك يُعد إيجاد سبب المشكلة وإصلاحها بسرعة أمرًا جوهريًا. ويبقى سجل هيروكو Heroku عادةً هو المكان الأنسب للبحث والتقصي عن المسببات: ترجمة -وبتصرف- للفصل Deployment من سلسلة Deep Dive Into Modern Web Development. اقرأ أيضًا المقال السابق: استخدام GitHub Actions لتحقيق التكامل المستمر والنشر المستمر نشر تطبيقات الويب الموجهة لبيئة الإنتاج نشر تطبيقات iOS على متجر Apple Store
  25. كيف سيبدو موقع الويب الذي تبنيه؟ يناقش هذا المقال أعمال التخطيط والتصميم التي تجري قبل كتابة الشيفرة بما في ذلك المعلومات التي سيقدّمها الموقع وخطوط الكتابة والألوان والوظيفة التي ينجزها الموقع. التخطيط قبل كل شيء لا بد من تجميع بعض الأفكار قبل أن تبدأ بأيّ شيء، أي ما الذي سيقدِّمه موقعك؟ فبإمكانك برمجة موقع الويب ليفعل أيّ شيء تقريبًا، لكن وطالما أنها تجربتك الأولى، فلا بد أن تُبقي الأمور بسيطةً قدر المستطاع، لذلك سنبدأ ببناء صفحة ويب بسيطة بعنوان وصورة وبعض المقاطع النصية، وقبل أن نبدأ لا بد من الإجابة على الأسئلة التالية: ما الموضوع الذي يدور حوله هذا الموقع؟ هل يدور حول الحيوانات الأليفة أو حول مدينة محددة أو شخصية ما. ما المعلومات التي ستقدِّمها حول هذا الموضوع؟ اكتب عنوانًا وبعض الفقرات ثم فكّر بصورة مناسبة لتعرضها على الصفحة. كيف يبدو موقعك؟ ماهو لون الخلفية؟ ما هي أنواع خطوط الكتابة المناسبة؟ ملاحظة: تحتاج المشاريع الأكثر تعقيدًا إلى تفاصيل أكثر تتعلق بالألوان وخطوط الكتابة والفراغات ما بين العناصر في الصفحة وأسلوب الكتابة الملائم وهكذا، إذ تُدعى هذه التفاصيل عادةً بدليل التصميم design guide أو نظام التصميم أو دليل المنتج، كما يمكنك الاطلاع على أمثلة عن ذلك في معرض فايرفوكس للتصاميم. رسم الخطوط الأولى لتصميمك أمسك ورقةً وقلمًا وحاول رسم شكل الموقع الذي تريده، إذ لن تجد الكثير لرسمه في الصفحة البسيطة التي سنبنيها، لكن من الأفضل أن تعتاد الأمر منذ اللحظة، إذ سيساعدك ذلك كثيرًا، ولا حاجة بالطبع إلى رسم متقن. ملاحظة: يبدأ المصممون برسم تصوراتهم عن الموقع رسمًا تقريبيًا على ورقة مهما كان معقدًا، ثم يبنون نسخةً رقميةً باستخدام محرر رسومي أو تقنيات ويب أخرى، وغالبًا ما يضم فريق ويب مصممي رسوميات ومصممي تجربة المستخدِم، إذ يضع مصممي الرسوميات (الغرافيك) التصور المرئي للموقع، بينما تقتصر مهمة مصممي تجربة المستخدِم على تصوّر تجربة المستخدِم لهذا الموقع وطبيعة تفاعله معه وتوجيه التصميم لتحقيق التجربة الأفضل. اختيار المواد المساعدة من الأفضل أن تبدأ بتجميع المحتوى الذي ستعرضه على موقعك في هذه المرحلة. النصوص لا بد أن يكون عنوان الصفحة والمقاطع النصية التي ستعرضها محضرة مسبقًا، لذا تأكد من ذلك. السمة اللونية للصفحة انتق لونًا مناسبًا باستخدام أيّ أداة لانتقاء الألوان، فعندما تنقر على اللون المطلوب، فسترى ستة محارف تشبه الرمز التالي 6600AB# عادةً، إذ يُعَدّ هذا الرمز رمزًا ست عشريًا يمثل اللون، وبعدها انسخ هذا الرمز واحفظه في مكان يسهل عليك العودة له لاحقًا. الصور استخدم محرك البحث الذي تراه مناسبًا لإيجاد الصور المناسبة لموقعك. انقر على الصورة حين تجدها لعرضها بأبعاد أكبر. انقر بالزر اليميني على الصورة (أو انقر عليها مع ضغط زر Ctrl في ماك) ثم اختر "حفظ الصورة باسم Save Image As" وانتق مكانًا مناسبًا على جهازك لحفظها فيه، كما يمكنك نسخ عنوان الصورة من شريط عنوان المتصفح وحفظه لاستخدامات لاحقة. انتبه إلى أنّ معظم الصور الموجودة على ويب محمية بحقوق نشر معيّنة، ولكن يمكنك استخدام مرشّح رخص الاستخدام من جوجل Google's license filter مثلًا إذا كنت تستخدم "صور جوجل Google Images" في البحث وذلك للتقليل من مشاكل خرق هذه الحقوق، ثم انقر على زر "أدوات Tools" ثم اختر "التراخيص الإبداعية العامة Creative Commons licenses" من قائمة "حقوق الاستخدام Usage Rights". خطوط الكتابة لاختيار خط كتابة معيّن: انتقل إلى خطوط الكتابة من جوجل Google Fonts" واعثر على الخط المناسب. انسخ أسطر الشيفرة التي يزوّدك بها جوجل إلى المحرر النصي لديك واحفظها لاستخدامات لاحقة. ترجمة -وبتصرف- للمقال ?What will your website look like. اقرأ أيضًا المقال السابق: تثبيت البرمجيات الأساسية للانطلاق في تطوير الويب أفضل 12 أداة انتقاء للألوان لمصممي الويب الألوان في تصميم الرسوميات ونظرية الألوان الحركات في تصميم الويب: لماذا نستخدمها ومتى؟ فلسفة تصميم الويب المتجاوب
×
×
  • أضف...