عبد اللطيف ايمش

المساهمون
  • المساهمات

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

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

السُّمعة بالموقع

66 Excellent

6 متابعين

آخر الزُوّار

2,390 زيارة للملف الشّخصي
  1. رأينا في الدرس السابق أكثر ثلاثة أنماط شيوعًا لتحديد مواقع العناصر في صفحات HTML عبر CSS، وهي static و relative و absolute. سننظر في هذا الدرس إلى fixed و sticky، ثم سنناقش طريقة ترتيب العناصر فوق بعضها عبر z-index. طريقة fixed لتحديد مواقع العناصر هنالك قاعدة background-attachment: fixed تُطبَّق على صور الخلفية، وأيضًا توجد قاعدة position: fixed التي تُطبَّق على العناصر؛ حيث تسمح بأن يكون موقع العنصر ثابتًا نسبةً إلى الصفحة، مما يسمح بتمرير بقية العناصر مع بقاء العنصر في مكانه. ويُطبَّق ذلك عادةً على حاويات، فمن الأمثلة الشائعة هي الترويسات والتذييلات الثابتة. وكما عند ضبط العناصر ذات القاعدة position: absolute، ستُصبِح جميع العناصر ذات القاعدة position: static تحت أيّة محتوى له القاعدة position: fixed. هذه شيفرة HTML لعنصر ثابت يظهر على يسار الصفحة: <div id="fixed-pull-tab"> <img src="red-tab.png" style="float: right" alt="LEVI’S"> <p>This is an example of an element with <code>position: fixed;</code> applied to it, for this blog’s article on the CSS property. </div> استخدمتُ الخاصية box-shadow في CSS على الصورة لكي أوضِّح أنَّ الحاوية موجودة في «طبقة» تعلو بقية المستند: div#fixed-pull-tab { width: 300px; border: 1px solid #000; padding-left: 1em; background-color: rgba(255, 255, 37, 0.7); position: fixed; left: -265px; top: 200px; } div#fixed-pull-tab p { margin-right: 70px; } div#fixed-pull-tab img { box-shadow: 4px 4px 4px rgba(0, 0, 0, 0.33); } div#fixed-pull-tab:hover { left: 0; } وكما في position: absolute، يجب أن تُستخدَم القيمة fixed بحذر، حيث تسمح هذه الخاصية بإنشاء عناصر في صفحات الويب التي تتداخل مع غيرها من محتوى الصفحة أو تغطي عليها. حالة خاصة: العناصر الثابتة الموجودة داخل حاوية في الحالات العادية، سيُزال العنصر المُطبَّق عليه القاعدة position: fixed من المستند، وأيّة معلومات تُحدِّد مكانه نسبةً إلى العنصر <body> ستصبح غير متاحة. لكن، من الممكن وضع عنصر مُطبَّق عليه القاعدة position: fixed داخل عنصر آخر ومنسوبًا إليه، وذلك إذا أُجري CSS transform عليه. فلو كانت لدينا الشيفرة الآتية: <div id="container"> <div id="fixed"></div> </div> يمكن أن يصبح العنصر الداخلي «ثابتًا» بالنسبة إلى العنصر الأب باستخدام شيفرة CSS الآتية (دون السابقات الخاصة بالمتصفحات، لغرض وضوح الشيفرة): #container { transform: translateZ(0); } #fixed { position: fixed; } هذه «الميزة» الغربية هي جزء من مواصفات CSS، وهي مدعومة في جميع المتصفحات الحديثة التي جربتها (باستثناء IE 11)، وحسب معلوماتي، أول من اكتشف ذلك هو Eric Meyer، وأتوقع أنَّ هذه الميزة نادرة الاستخدام (أغلبية المطورين يفضلون استخدام absolute مع relative) لكن لا ضير من معرفة هذه المعلومة. طريقة sticky لتحديد مواقع العناصر مرت فترةٌ أصبحت فيها العناصر «الثابتة ديناميكيًا» هي ميزة التصميم الأساسية في الموقع، فإذا مرّرتَ إلى الأسفل فسيتحرك كل شيءٍ كما هو متوقع، لكن عندما يبلغ عنصرٌ معيّن (عادةً يكون شريط القائمة، أو إعلان في بعض الأحيان) أعلى الصفحة فسيثبت مكانه، بينما ستستمر بقية عناصر المستند بالتمرير أدناه؛ وعند التمرير إلى الأعلى فسيربط العنصر نفسه مرةً أخرى بالمستند. هذا السلوك (الذي هو دمجٌ بين position: static و position: fixed) كان يُضاف إلى الصفحة باستخدام jQuery (عبر إحدى الإضافات الكثيرة الموجودة لهذا الغرض). وكالعديد من الميزات المشهورة، سينتهي بهذا الأمر إلى أن يصبح جزءًا من المواصفة الرسمية، مما يعني أننا سنتمكن من فعل ذلك باستخدام CSS بمفردها، دون إطارات عمل، أو سكربتات، أو غير ذلك… لكن في هذه الحالة، كانت (وما زالت) عملية التحويل إلى مواصفة مليئةً بالمشاكل. كيف كان يُفتَرض لهذه الميزة أن تعمل كنّا نكتب هذه الميزة كقيمة جديدة: position: sticky، وذلك مع استخدامٍ ذكيٍ للخاصية top (وهي ترمز عند استخدامها مع sticky إلى المسافة من أعلى العنصر bodyالذي بعدها سيصبح العنصر «ثابتًا» عند التمرير؛ البدائل هي الخاصياتleftوbottomوright` للتمرير في تلك الاتجاهات)، وكان ذلك كافيًا لتغطية طيفٍ واسعٍ من حالات الاستخدام؛ وبهذا ستصبح طريقة الاستخدام سهلةً جدًا: #stickytest { position: sticky; top: 0px; } وعند تطبيق ما سبق على صورة (مثلًا)، فستبدو الشيفرة كما يلي: <img src="geckofoot.jpg" alt id="stickytest"> <p>Lorem ipsum… ملاحظة: افتراضنا أنَّ الصفحة تحتوي على محتوى بعد العنصر لكي يصبح العنصر ‎#stickytest في أعلى نافذة المتصفح، وإذا لم تكن تحتوي الصفحة على عناصر أخرى، فلن يصل العنصر إلى المكان اللازم لكي «يثبت» مكانه. أنصحك بتعبئة الصفحة بكثير من النصوص لغرض التجربة. إذا جربتَ الشيفرة السابقة على نسخةٍ حديثةٍ من متصفح Firefox، فيجب أن تعمل عملًا صحيحًا. لكن عند الانتقال إلى بقية المتصفحات، فسنجد أمورًا عجيبة! 1. متصفح Safari 6.1+‎ (على الحاسوب وعلى الهاتف) يدعم القيمة sticky عبر سابقة (prefix) خاصة به. صحيحٌ أنَّ من غير الشائع أن تُطبَّق السابقة على القيمة، وليس على الخاصية. 2. هنالك أمرٌ بسيطٌ عليك الانتباه إليه ألا وهو أنَّ خاصية sticky في Safari تعمل إذا كانت العناصر لها القاعدة display: block، لذا يجب تعديل مثال الصورة السابق ليصبح: #stickytest { display: block; position: -webkit-sticky; position: sticky; top: 0px; } بالإضافة إلى: وضع عنصر sticky داخل حاوية لها القاعدة overflow: hidden سيؤدي إلى عدم تطبيق سلوك sticky. رسميًا، يجب أن يعمل sticky مع display: table، بما في ذلك خلايا الجدول، وهذا مفيدٌ عند التنقل في الجداول الطويلة مع إبقاء عناوين الأعمدة ظاهرةً. لكن للأسف لم تُطبَّق هذه الميزة في المتصفح. متصفح Chrome ينسحب من السباق ربما تجد في الويب بعض الصفحات التي تُصّر أنَّ متصفح Chrome يدعم القاعدة position: sticky كخيارٍ اختباري، وكان هذا صحيحًا… إلى أن أُلغي هذا الخيار تمامًا في Chrome 37. شعر فريق تطوير Google أنَّ إبقاء position: sticky هو تحدٍ لهم في مسعاهم في تحسين سرعة العرض في المتصفح، لهذا ألغيت هذه الميزة. وهذا يعني أنَّ علينا العودة إلى الطرق الالتفافية لإنشاء هذا السلوك في متصفح Chrome و Internet Explorer (الذي لم يدعم القيمة الجديدة قط). لحسن الحظ، هنالك بعض الخيارات المتاحة أمامنا: فلدى Filament Group حلٌّ يعتمد على jQuery، بالإضافة إلى غيره من الحلول. أفضِّل -شخصيًا- استخدام stickyfill من تطوير Oleg Korsunsky، والذي يمكن أن يعمل مع أو بدون jQuery. المثال الموجود في صفحة StickFill يُضيف سلوك sticky كفئة CSS، لكن من المرجَّح أن تُطبِّق ذلك على عنصرٍ وحيد، وإنشاء مُحدِّد يعتمد على المُعرِّف id هو أمرٌ منطقي. وفي هذا المثال، سأضيف إلى السكربت الموجود أسفل الصفحة السطرَ الآتي: Stickyfill.add(document.getElementById('stickytest')); لاحظ أنَّ قيم top و left و bottom و right للعنصر مُقاسةٌ من العنصر body، وهذا يتضمن أيّة هوامش موجودة للعنصر body. أبقِ الأمر بسيطًا صحيحٌ أنَّ position: sticky لها العديد من الميزات، لكن تسهل إساءة استخدامها. ولتفادي الكوارث التي تتعلق بواجهة المستخدم فسأنصحك باتباع ما يلي: - نظريًا، يمكن استخدام position: sticky لإبقاء العناصر ثابتةً داخل أيّ حاوية قابلة للتمرير، كما في هذا المثال؛ لكن رجاءً لا تستخدمها في أكثر من مكان في الصفحة، فلا نحتاج إلى ذلك! (إضافةً إلى أنَّ جميع الحلول الالتفافية المتوافرة لقاعدة position: sticky ستفشل في توفير هذه الميزة، وسيعمل المثال السابق في حاويةٍ في الصفحة لأنها تلك الحاوية عبارة عن عنصر iframe). - كن حذرًا للغاية عند استخدام position: sticky على شاشات الهاتف المحمول: فكل شيءٍ سيثبت مكانه سيأخذ مساحةً كبيرةً من الشاشة، مما يقلّل من المساحة الباقية لعرض محتواك. يجب أن تضبط العنصر ليكون ثابتًا sticky إذا كان ذلك ضروريًا جدًا أو كان مفيدًا للغاية، وليس لأنه «يبدو بشكلٍ جميل». تذكّر أنَّه من الأسهل للمستخدمين أن يُمرِّروا في الصفحة باللمس أعلى أو أسفل الشاشة، لذا لا تقف بطريقهم! لضمان استخدام sticky استخدامًا حكيمًا، فاكتب قاعدة ‎@media التي تبطل تأثير position: sticky على الشاشات الصغيرة: @media all and (max-width: 600px) { #stickytest { position: static !important; } } ما سبق سيُعيد العنصر إلى مكانه الطبيعي في المستند إذا كان عرض الشاشة 600 بكسل أو أقل. ستحتاج إلى كتابة قاعدة مشابهة في JavaScript باستخدام matchMedia إذا كنتَ تستعمل حلًا التفافيًا. أعد كتابة السكربت أعلاه إلى شيءٍ شبيهٍ بما يلي: var sticky = document.getElementById('stickytest'); var screencheck = window.matchMedia("(max-width: 600px)"); Stickyfill.add(sticky); if (screencheck.matches) { Stickyfill.remove(sticky); } كقاعدة عامة، يجب أن تكون العناصر «الثابتة» هي نقطة لانقطاع المحتوى، فلا «تُثبِّت» العناصر في منتصف قطعة من المحتوى، مما يجعل النص يظهر أعلى وأسفل العنصر الثابت، وهذا أمرٌ يُشتِّت القارئ كثيرًا، ويُصعِّب من قراءة النص. وشبيهًا بما سبق، حاول أن تتفادى تثبيت العناصر التي تقسم جزءًا من محتوى نصي، مما يجبر المستخدم على التمرير بسرعة أبطأ لقراءة السطر بأكمله. احرص أنَّ لا تحتل العناصر الثابتة أكثر من الحد الأدنى المتوقع لنافذة المتصفح، فلو كانت أطول من حاويتها فلن يتمكن المستخدمون من رؤية كامل محتواها ولن يستطيعوا أيضًا قراءة بقية المحتوى الموجود في الصفحة. إضافةً إلى ما سبق، ربما تريد أن تُشير إلى أنَّ المحتوى تحت العنصر الثابت سيكون مخفيًا، وربما تستعمل تأثير الشفافية مثلًا عندما يُثبَّت العنصر، كتأثير عدم الوضوح على المحتوى خلف شريط الانتقال. فكرة أخرى هي تطبيق القاعدة position: sticky على سلسلة من العناصر، مما يجعلها تظهر بعضها تلو بعض عند نقاط مُحدِّدة أثناء التمرير. وعلى الرغم من أنَّ هذه الطريقة قد تكون فعالة، لكن يُحتَمَل أن تُربِك المستخدمين، لذا نفِّذها بعد أخذ الحيطة. للأسف، لا يوجد حدث باسم stuck في JavaScript للتبليغ أنَّ أحد العناصر قد أصبح بموضعٍ ثابتٍ. لكن يمكنك الآن اختبار ذلك يدويًا (بعض الطرائق الالتفافية تستخدم فئة class خاصة بالعناصر الثابتة للتعويض عن عدم وجود الحدث stuck). ترتيب العناصر فوق بعضها عبر z-index عندما توضع العناصر في الصفحة بمكانٍ مطلق (absolute) فيمكن أن تتداخل وتغطي على المحتوى العادي، وعلى بقية العناصر التي لها القاعدة position: absolute أيضًا. لكن عندما يحدث ذلك، فكيف سيُحدِّد المتصفح ما هو العنصر الذي يجب أن يكون في الأعلى؟ افتراضيًا، يُحدِّد المتصفح ترتيب العناصر فوق بعضها عبر ترتيب ورودها في الشيفرة. أفضل طريقة لتصور ذلك هي تخيل مجموعة أوراق لعب، وبعض أوراقها موزعةٌ على الطاولة، وتلك الأوراق هي المحتوى العادي للصفحة بما في ذلك العناصر ذات القيمة static و relative و fixed. وإذا تداخلت مع أوراقٍ أخرى موضوعة مسبقًا على الطاولة (وتلك هي العناصر ذات القيمة absolute)، فسيتم ترتيب الأوراق بناءً على ترتيب سحبها (أي أنَّ العناصر الحديثة ستظهر فوق العناصر الأقدم…). ففي مثالنا في الدرس السابق [أضف رابط درس 34676-CSS-Positioning-static-relative-absolute]، ظهرت الصور بترتيبٍ معيّن في الشيفرة، فعندما تداخلت الصور كانت صورة إسخيلوس في أسفل المجموعة، ثم أفلاطون فوقها، وفي الأعلى صورة ألكيبيادس. أسهل طريقة لإنشاء «طبقات» لعناصر ذات القاعدة position: absolute هي تغيير ترتيب ورودها في الشيفرة، فمثلًا لو غيرنا الشيفرة إلى: <div id="greek-figures"> <img src="plato-bust.jpg" alt="Plato" id="plato"> <img src="aeschylus-bust.jpg" alt="Aeschylus" id="aeschylus"> <img src="alcibiades-bust.jpg" alt="Alcibiades" id="alcibiades"> </div> فستنتج الصورة الآتية: صورة أفلاطون في المنتصف تحت الصورتين الأخريتين، لأنها تأتي أولًا في الشيفرة. ستبقى الصورة في نفس المكان، لكن ترتيبها قد تغيّر. لأسبابٍ مختلفة، قد لا نستطيع تغيير ترتيب العناصر في الشيفرة (أو لا نرغب في ذلك) لكننا نريد ترتيب العناصر بترتيبٍ يختلف عن تسلسل ورودها في الشيفرة. سنُعيد الشيفرة إلى حالتها الأصلية: <div id="greek-figures"> <img src="aeschylus-bust.jpg" alt="Aeschylus" id="aeschylus"> <img src="plato-bust.jpg" alt="Plato" id="plato"> <img src="alcibiades-bust.jpg" alt="Alcibiades" id="alcibiades"> </div> إذا أردنا إعادة ترتيب العناصر، لكننا لا نريد تعديل الشيفرة، فيمكننا إضافة الخاصية z-index إلى أنماط CSS التي تتحكم في الصور: body p { text-align: justify; } div#greek-figures { float: right; width: 250px; height: 450px; margin-left: 2em; } div#greek-figures img { height: 150px; width: 150px; position: absolute; } img#aeschylus { z-index: 2; } img#plato { top: 155px; right: 15px; z-index: 1; } img#alcibiades { top: 290px; z-index: 2; } الشيفرة السابقة ستؤدي إلى إظهار نفس نتيجة المثال الأول، لكن بدون الحاجة إلى تغيير شيفرة HTML. تُعتَبَر قيمة الخاصية z-index للعنصر body هي 0، وأيّة قيمة موجبة للخاصية z-index لأي عنصر آخر ستجعله يظهر فوق العنصر <body>. العناصر ذات الخاصية z-index والمسند إليها قيمةٌ سالبةٌ ستكون أسفله. انتبه أنَّه لو كان للعنصر body لون خلفية (عبر خاصية background-color) فستختفي تلك العناصر تحته. ما هو أعلى رقم يمكن إسناده للخاصية z-index القيمة العظمى للخاصية z-index هي 2147483647 في المتصفحات الحديثة. من المستحيل أن يكون لديك أكثر من مليارَي عنصر مكدس فوق بعضه في الصفحة، لذا لا تحاول استخدام هذا الرقم عند كتابة شيفرات CSS. ترجمة –وبتصرّف– لكل من: CSS Positioning: fixed position sticky: scroll-to-top-then-fixed in pure CSS Stacking order and z-index لصاحبها Dudley Storey
  2. تمهيد «السلسلة النصية» هي مجموعة من المحارف (أي الأحرف والأرقام والرموز) التي إما أن تكون قيمة ثابتة أو قيمة لمتغير. وهذه السلاسل النصية مُشكَّلة من محارف يونيكود (Unicode) والتي لا يمكن تغيير مدلولها. ولأنَّ النص هو شكلٌ شائعٌ من أشكال البيانات الذي نستعمله يوميًا، لذا فإنَّ السلاسل النصية مهمة جدًا وتُمثِّل لُبنةً أساسيةً في البرمجة. سيستعرض هذا الدرس كيفية إنشاء وطباعة السلاسة النصية، وكيفية جمعها مع بعضها وتكرارها، وآلية تخزين السلاسل النصية في متغيرات. إنشاء وطباعة السلاسل النصية تتواجد السلاسل النصية إما داخل علامات اقتباس فردية ’ أو علامات اقتباس مزدوجة "، لذا لإنشاء سلسلة نصية، كل ما علينا فعله هو وضع مجموعة من المحارف بين أحد نوعَي علامات الاقتباس السابقَين: 'This is a string in single quotes.' "This is a string in double quotes." يمكنك الاختيار بين النوعَين السابقَين، لكن أيًّا كان اختيارك، فعليك أن تحافظ على استخدامك له في كامل برنامجك. يمكنك طباعة السلاسل النصية إلى الشاشة باستدعاء الدالة print()‎ بكل بساطة: print("Let's print out this string.") Let's print out this string. بعد أن فهمتَ كيفية تهيئة السلاسل النصية في بايثون، لنلقِ نظرةً الآن إلى كيفية التعامل مع السلاسل النصية في برامجك وتعديلها. جمع السلاسل النصية عملية الجمع (concatenation) تعني إضافة سلسلتين نصيتين إلى بعضهما بعضًا لإنشاء سلسلة نصية جديدة. نستخدم المعامل + لجمع السلاسل النصية؛ أبقِ في ذهنك أنَّ المعامل + يعني عملية الجمع عند التعامل مع الأعداد، أما عندما نستخدمه مع السلاسل النصية فيعني إضافتها إلى بعضها. لنجمع السلستين النصيتين "Sammy" و "Shark" مع بعضها ثم نطبعهما باستخدام الدالة print()‎: print("Sammy" + "Shark") SammyShark إذا أردتَ وضع فراغ بين السلسلتين النصيتين، فيمكنك بكل بساطة وضعه عند نهاية السلسلة النصية الأولى، أي بعد الكلمة “Sammy”: print("Sammy " + "Shark") Sammy Shark لكن احرص على عدم استعمال المعامل + بين نوعَين مختلفَين من البيانات، فلن نتمكن من جمع السلاسل النصية والأرقام مع بعضها، فلو حاولنا مثلًا أن نكتب: print("Sammy" + 27) فسنحصل على رسالة الخطأ الآتية: TypeError: Can't convert 'int' object to str implicitly أما إذا أردنا أن نُنشِئ السلسلة النصية "Sammy27" فعلينا حينها وضع الرقم 27 بين علامتَي اقتباس ("27") مما يجعله سلسلةً نصيةً وليست عددًا صحيحًا. سنستفيد من تحويل الأعداد إلى سلاسل نصية عندما نتعامل مع أرقام الهواتف على سبيل المثال، لأننا لن نحتاج إلى إجراء عملية حسابية على رمز الدولة ورمز المنطقة في أرقام الهواتف، إلا أننا نريدهما أن يظهرا متتابعَين. عندما نجمع سلسلتين نصيتين أو أكثر فنحن نُنشِئ سلسلةً نصيةً جديدةً التي يمكننا استخدامها في برنامجنا. تكرار السلاسل النصية هنالك أوقاتٌ نحتاج فيها إلى استخدام بايثون لأتمتة المهام، وإحدى الأمور التي يمكننا أتمتتها هي تكرار سلسلة نصية لعدِّة مرات. إذ نستطيع فعل ذلك عبر المعامل *، وكما هو الأمر مع المعامل + فإنَّ المعامل * له استخدامٌ مختلف عندما نتعامل مع أرقام، حيث يُمثِّل عملية الضرب. أما عندما نستخدمه بين سلسلةٍ نصيةٍ ورقمٍ فإنَّ المعامل * هو معامل التكرار، فوظيفته هي تكرار سلسلة نصية لأي عدد مرات تشاء. لنحاول طباعة السلسلة النصية “Sammy” تسع مرات دون تكرارها يدويًا، وذلك عبر المعامل *: print("Sammy" * 9) SammySammySammySammySammySammySammySammySammy يمكننا بهذه الطريقة تكرار السلسلة النصية لأيِّ عددٍ نشاء من المرات. تخزين السلاسل النصية في متغيرات المتغيرات هي «رموز» التي يمكننا استعمالها لتخزين البيانات في برنامج. يمكنك تخيل المتغيرات على أنها صندوقٌ فارغٌ يمكنك ملؤه بالبيانات أو القيم. السلاسل النصية هي نوعٌ من أنواع البيانات، لذا يمكننا استعمالها لملء المتغيرات. التصريح عن السلاسل النصية كمتغيرات سيُسهِّل علينا التعامل معها في برامجنا. لتخزين سلسلة نصية داخل متغير، فكل ما علينا فعله هو إسنادها إليه. سنُصرِّح في المثال الآتي عن المتغير my_str: my_str = "Sammy likes declaring strings." أصبح المتغير my_str الآن مُشيرًا إلى سلسلةٍ نصيةٍ، والتي أمسى بمقدورنا طباعتها كما يلي: print(my_str) وسنحصل على الناتج الآتي: Sammy likes declaring strings. استخدام المتغيرات لاحتواء قيم السلاسل النصية سيساعدنا في الاستغناء عن إعادة كتابة السلسلة النصية في كل مرة نحتاج استخدامها، مما يُبسِّط تعاملنا معها وإجراءنا للعمليات عليها في برامجنا. الخلاصة لقد تعلمنا في درسنا هذا أساسيات التعامل مع السلاسل النصية في لغة بايثون 3. بما في ذلك إنشاءها وطباعتها وجمعها وتكرارها، إضافةً إلى تخزينها في متغيرات، وهذه هي المعلومات الأساسية التي عليك فهمها للانطلاق في تعاملك مع السلاسل النصية في برامج بايثون 3. ترجمة -وبتصرّف- للمقال An Introduction to Working with Strings in Python 3 لصاحبته Lisa Tagliaferri
  3. يهرع الكثير من المبتدئين إلى طرائق تحديد مواقع العناصر في CSS معتقدين أنها ستحل لهم جميع المشاكل التي يواجهونها في تخطيط الصفحة، لكن هذا ليس صحيحًا بالمطلق: هنالك جوانب أخرى في CSS مسؤولةٌ عن تخطيط الصفحة. وصحيحٌ أنَّ طرائق تحديد المواقع العناصر لها دورٌ لتلعبه في تخطيط الصفحة، لكن من الأحسن أن تعرف كيف ومتى عليك استخدام مختلف أنماط تحديد المواقع، عوضًا عن محاولة تجريبها لربما «حلّت» لك مشكلتك! طريقة static لتحديد مواقع العناصر افتراضيًا، يملك كل عنصر في صفحتك القاعدة position: static مطبقةً عليه، ولهذا السبب لن نحتاج إلى التصريح عن هذه الطريقة، إلا إذا كان ذلك ضروريًا لإلغاء تأثير خاصية position أخرى قد ورثها العنصر من غيره. الكلمة static لا تعني أنَّ العنصر سيبقى في مكانه في الصفحة، وفي الواقع أرى أنَّ هذه الكلمة غير دقيقة وكان يجب استخدام كلمة أخرى (مثل fluid) بدلًا عنها. طريقة تحديد مواقع العناصر الافتراضية تعني أنَّ العناصر لن تتداخل وتظهر فوق بعضها، أي أنَّ كل عنصر «سيدفع» العناصر الأخرى بعيدًا عنه، مستجيبًا إلى قياس ودقة والنسبة بين طول وعرض الجهاز الذي يعرض صفحة الويب. لنأخذ مثالًا بسيطًا لمحتوى صفحة: <p><img src="assets/images/pericles.jpg" style="float: left;" alt="Bust of Pericles"> Pericles was a prominent statesman, orator, and naval general of Athens during the city-states's <q>Golden Age</q>, from 448BCE until his death in 429. Pericles was a promoter of the arts (particularly plays), architecture (it was under his patronage that the Parthenon was built), and the principles of democracy, but he was also an instigator of war: Pericles is widely held to be responsible for maneuvering Athens into the disastrous Peloponnesian War with Sparta.</p> حجز كل عنصرٍ –في المثال السابق– مساحةً خاصةً به، فلن تظهر الصورة فوق النص، وسيبتعد النص قليلًا عن الصورة. تغيير قياس نافذة المتصفح سيُسبِّب بتضييق عرض الصفحة، وستلتف أسطر الفقرة حول الصورة وستدفع أي محتوى أدناها إلى الأسفل. وهذا جيد، لأنَّ الصفحة ستتأقلم مع أيّ قياس للشاشة وأي نسبة عرض إلى ارتفاع وأي دقة، ولن تظهر أيّة عناصر فوق بعضها. لاحظ أنَّ قاعدة «لا شيء سيتداخل، وكل شيء سينساب حول بقية العناصر» هي المبدأ الرئيسي لطريقة position: static إلا أنَّ هنالك بعض الاستثناءات. فمثلًا سيظهر النص فوق صورة الخلفية، ويمكن أن نلغي انسيابية العناصر بتحديد عرض ثابت على عناصر div الحاوية لبقية العناصر، ويمكن أن تتداخل العناصر أو تُزاح من الصفحة إذا طبقنا هامشًا (margin) سلبيًا عليها. لكن في الحالة العامة ستُطبَّق قاعدة static كما هي. العناصر المُطبَّق عليها القاعدة position: static –سواءً بتصريحنا بذلك، أو افتراضيًا– لا يمكن أن تملك الخاصيات التي سنتحدث عنها في الأقسام التالية (وهي top و left و bottom و right). العناصر التي لها القيمة static للخاصية position يمكن أن تُحرَّك فقط بتعديل قيم الخاصيتَين margin أو padding، أو عبر تعديل موقعها في شيفرة HTML. وهذا أمرٌ مقبولٌ بين المطورين وبسيطٌ وسهل التطبيق في أغلبية التصاميم. طريقة relative لتحديد مواقع العناصر من المهم ملاحظة أنَّ تطبيق القاعدة position: relative على أحد العناصر لن يُغيّر شيئًا بمفردها، فسيبقى العنصر يسلك سلوك العناصر ذات القيمة static لخاصية position (كما في القسم السابق). لكن القيمة relative قد أعطتنا وصولًا إلى الخاصيات top و left و bottom و right. فعند تطبيق القاعدة position: relative بالإضافة إلى إحدى الخاصيات السابقة، فسيحدث أمران: سيَخرُجُ العنصر من مكانه في المستند، لكن ستبقى المساحة الفارغة المحجوزة له باقيةً (كما لو كان static). سيُزاح العنصر بمقدارٍ مساوٍ للقيم المنسدة إلى الخاصيات top و left و bottom و right، نسبةً إلى موقعه الأصلي (static). ما يزال بالإمكان تطبيق الخاصية float على العناصر ذات القاعدة position: relative. سنُعدِّل في المثال السابق ليصبح كما يلي: <p><img src="assets/images/pericles.jpg" style="position: relative; top: 2em; right: 4em;" alt="Bust of Pericles"> Pericles was a prominent statesman, orator, and naval general of Athens during the city-states's <q>Golden Age</q>, from 448BCE until his death in 429. Pericles was a promoter of the arts (particularly plays), architecture (it was under his patronage that the Parthenon was built), and the principles of democracy, but he was also an instigator of war: Pericles is widely held to be responsible for maneuvering Athens into the disastrous Peloponnesian War with Sparta.</p> كما لاحظت، ستُزاح الصورة بمقدار 2em إلى الأسفل انطلاقًا من أعلى موقعها الأصلي، و 4em من اليمين. لاحظ كيف بقيت المساحة محجوزةً للعنصر الأصلي، وكيف يتلف النص حولها، وأنَّ الصورة ستتداخل مع بعض الأسطر النصية. ربما أسهل طريقة لكي تفهم فيها position: relative هي أنَّ تتخيل أنَّها تستخدم «لإزاحة» العناصر لكنك لا ترغب بالتأثير على تخطيط بقية الصفحة. وذلك لأنَّ المساحة المعطاة إلى العنصر الأصلي ما تزال موجودةً. إذ يمكنك بكل سهولة إعطاء قيم إلى خاصيات top و left و bottom و right (يمكن أيضًا استخدام القيم السلبية) دون أن تقلق من تأثير ذلك على بقية عناصر الصفحة. طريقة absolute لتحديد مواقع العناصر المطورون الذين يملكون المعلومات الكافية في CSS لكي يقعوا في مشاكل، مع المصممين المهووسين بأن تكون تصاميمهم دقيقة جدًا، سيجنحون إلى استخدام position: absolute استخدامًا مفرطًا؛ ويجادلون قائلين «بإمكاننا وضع أي شيء في صفحة الويب في المكان الذي نريده». لكنهم للأسف يغفلون عدِّة نقط مهمة، وسيقعون حتمًا في حالتين اللتين ستؤديان إلى حدوث «متاهة» مقعدة في الصفحة. لكن دعنا أولًا نرى ماذا تفعل قاعدة position: absolute بصفحتنا. ستصبح الشيفرة كما يلي: <p><img src="assets/images/pericles.jpg" style="absolute; top: 0; left: 30px;" alt="Bust of Pericles"> Pericles was a prominent statesman, orator, and naval general of Athens during the city-states's <q>Golden Age</q>, from 448BCE until his death in 429. Pericles was a promoter of the arts (particularly plays), architecture (it was under his patronage that the Parthenon was built), and the principles of democracy, but he was also an instigator of war: Pericles is widely held to be responsible for maneuvering Athens into the disastrous Peloponnesian War with Sparta.</p> عند تطبيق قاعدة position: absolute، فستفقد الخاصتان float و margin تأثيرهما، لذا أزلتُهما. تؤدي القيمة absolute إلى نزع الصورة من مكانها في المستند تمامًا، أي ستؤخذ وتُرفَع إلى أعلى المستند الذي سيظهر تحتها. باختصار: ستسلك صفحة الويب سلوكًا مشابهًا لسلوكها كما لو أنَّ الصورة غير موجودة من الأساس. لماذا إذًا يستعمل الكثيرون position: absolute؟ لأنَّنا نستطيع تحديد موضع العنصر –عند استخدام absolute– انطلاقًا من الزاوية العليا اليسرى للعنصر الحاوي لها، وهو العنصر body في حالتنا. قد تبدو لك position: absolute جذابةً، حيث يبدو أنَّها تعدك بوضع العناصر في مكانها بدقة شديدة. وهذا مغرٍ جدًا للمصممين التقليديين الذين يصممون تصاميم لسطح المكتب أو للطباعة، والمعتادين على التحكم بمكان كل شيء في صفحات A4، والذي لا يرون داعٍ للتصميمات المتجاوبة. لكنهم يرفضون أن يلاحظوا بضع نقاط: صفحات الويب ليست كالورق ذي القياس المعياري. فالشاشات والمتصفحات والأجهزة تملك أحجام ونسب ودقة مختلفة. واستخدام absolute لوضع العناصر في الصفحة يعني افتراض مجموعة من المتغيرات عن جهاز المستخدم، مما يؤثِّر سلبًا في مرونة الويب. بعد أن تُطبِّق position: absolute على أحد العناصر، فستجد نفسك تُطبِّق نفس القاعدة على كل العناصر الأخرى. وذلك لأنَّ position: absolute سينتزع العنصر من المستند، ويجب علينا تعديل موضع بقية العناصر للتأقلم مع ذلك ولضمان عدم تداخل العناصر التي لا تريدها أن تتداخل. وهذا سيؤدي إلى إنشاء قواعد CSS معقدة والتي لن تعمل كما يجب عند إضافة محتوى جديد إلى الصفحة (أكرِّر أنَّ الويب ليس صفحةً مطبوعةً، حيث تُعدَّل المحتويات وتُضاف إضافات بين الحين والآخر، ويجب أن يكون تصميمك مرنًا كفايةً للاستجابة إلى تلك التعديلات). واستخدام position: absolute يجعل التعديلات على الصفحة تأخذ وقتًا طويلًا وجهدًا كبيرًا. لماذا نستخدم إذًا position: absolute من الأساس؟ حسنًا، إذا استخدمنا absolute استخدامًا حكيمًا، فيمكن أن نستفيد منها لتداخل العناصر المنفصلة التي كانت لتُدمَج في صورةٍ وحيدةٍ. على سبيل المثال، لنقل أنَّك تريد الإضافة على المقالة السابقة ووضع صور أخرى من العصر الذهبي لأثينا. أي لديك عدِّة صور (إسخيلوس وأفلاطون وألكيبيادس)، وتريد أن تضعها على الجانب الأيمن لمستندك، وتريدها أن تتداخل مع بعضها. أحد الحلول هي تعديل الصور باستخدام فوتوشوب، بدمجها مع بعضها في صورةٍ وحيدة، لكن هذا سيمنعك من تعديل ترتيبها ومحاذاتها والتباعد بينها لاحقًا، وعليك حينها العودة إلى ملف فوتوشوب لإجراء تعديلاتك ثم إعادة التصدير ورفع الصورة من جديد. لكن بدلًا من كل ما سبق، لنحاول الإبقاء على الصور معزولةً ونضعها داخل عنصر <div>. شيفرة HTML هي: <div id="greek-figures"> <img src="aeschylus-bust.jpg" alt="Marble bust of Aeschylus" id="aeschylus"> <img src="plato-bust.jpg" alt="Marble bust of Plato" id="plato"> <img src="alcibiades-bust.jpg" alt="Marble bust of Alcibiades" id="alcibiades"> </div> نعلم أنَّ لهذه الصور نفس الأبعاد، لذا لن نحتاج إلى تحديد خاصيات height و width لكل واحدة على حدة. وإنما سنجعل ذلك ضمن أنماط CSS: div#greek-figures { float: right; } div#greek-figures img { height: 150px; width: 150px; position: absolute; } إذا حاولتَ عرض الصفحة الآن، فستجد أنَّ عنصر <div> قد تضائل، وظهرت صورةٌ وحيدةٌ فقط وهي خارجة عن مكانها. هل لديك أيِّة فكرة عن السبب؟ …[تريّث قليلًا وفكِّر بالسبب قبل إكمال القراءة]… الجواب: لقد طُبِّقَت القاعدة position: absolute على الصورة، لذا تم انتزاعها من مكانها في الصفحة، ولم تعد تستطيع «دفع» العناصر التي حولها. أما عنصر div بعد وضع الخاصية float له فسيحاول تحديد عرضه عبر محتوياته التي بداخله؛ لكن المحتوى (أي الصور) لها القيمة absolute، فلن تُحتَسَب، والصور هي المحتوى الوحيد الموجود في العنصر <div>، أي أنَّ العنصر <div> ليس له عرض! في النهاية، ستحاول كل صورة أن تضع نفسها داخل عنصر <div> في الزاوية العليا اليسرى، وهذا يعني أنَّ الصور ستتكدس فوق بعضها. لاحظ أنَّ آخر صورة داخل العنصر div ستكون في الأعلى (وسنتحدث عن هذا الأمر في درسٍ لاحق). لنحاول الآن حلّ المشاكل السابقة بتعديل CSS: body p { text-align: justify; } div#greek-figures { float: right; width: 250px; height: 450px; margin-left: 2em; } div#greek-figures img { height: 150px; width: 150px; position: absolute; } img#plato { top: 155px; right: 15px; } img#alcibiades { top: 290px; } (وفرنا خاصية height لعنصر div لأنَّ الصور التي موضعها absolute لن تساهم في توفير ارتفاع للعنصر؛ وبدون ارتفاع فلن تجد الأسطر النصية شيئًا لتلتف حوله. أضفنا بقية الخاصيات مثل text-align والهوامش لإظهار الصفحة بشكل جميل). سيعمل ما سبق كما ينبغي، لكنك ستتكشف شيئًا غريبًا. إذا عدَّلتَ الخاصية right لصورة أفلاطون إلى left، فلن تحصل على ما كنتَ تتوقعه. فبدلًا من قياس الموضع من الحاوية، فستُنسَب الصورة نفسها إلى أكبر حاوية وهي العنصر <body>. يمكننا الالتفاف على هذه المشكلة بإضافة قاعدة في أنماط CSS: div#greek-figures { float: right; width: 250px; height: 450px; margin-left: 2em; position: relative; } وستكون النتيجة النهائية هي: إذا كان العنصر الذي له القيمة absolute للخاصية position موجودًا ضمن عنصرٍ آخر تُطبَّق عليه القاعدة position: relative، فستُقاس إحداثيات العنصر انطلاقًا من الزاوية العليا اليسرى للحاوية التي يقع فيها؛ وإلا فستُقاس الإحداثيات انطلاقًا من الزاوية العليا اليسرى من العنصر body. عمومًا، إذا كنتَ تُصرّ على استخدام position: absolute فأنصحك بوضع عدِّة عناصر (التي لها القاعدة position: absolute) في «حاوية» (إما عنصر <div> عادي أو حتى <canvas>). مما يسمح لك بنقلها مع العناصر الموجودة داخلها دون مشاكل. ترجمة -وبتصرّف- للمقالات CSS Positioning: static, the default CSS Positioning: relative, the underappreciated CSS Positioning: absolute, the overused لصاحبها Dudley Storey
  4. أصبحت تطبيقات الويب ذات الصفحة الوحيدة Single Page Apps رائجةً في هذه الفترة في تطوير الويب، فأمسى كل شخصٍ يريد أن ينُشِئ تطبيق ويب ذا صفحةٍ وحيدة. سأريك في هذا الدرس طريقةٍ سهلة لإنشاء تطبيقات الويب ذات الصفحة الوحيدة باستخدام jQuery ودون استخدام أيّة إطارات عمل مثل React أو Angular أو Vue …إلخ. لمحة لأننا نريد إنشاء تطبيق ويب ذا صفحةٍ وحيدة، فسنستخدم sammy.js للتوجيه (routing)، مكتبة Sammy هي مكتبة jQuery بمساحة تخزينية لا تتجاوز 5.2 كيلوبايت. سيبدو سكربت التوجيه المكتوب لإضافة Sammy كما يلي: var app = $.sammy(function() { this.get('#/', function() { //your function }); this.get('#about/', function() { //your function }); this.get('#contact/', function() { //your function }); }); علينا أولًا تهيئة التطبيق باستخدام ‎$.sammy وتخزين نسخة من الكائن في المتغير app. يمكننا تعريف تعليمة «توجيه» (route) في sammy بالطريقة الآتية: this.get('path/',function(){ // ... }); هنالك دالة ستُستدعى لكل عملية توجيه، والتي يمكن أن نكتب بداخلها البنية المنطقية لها، ونربط البيانات اللازمة إلى كل «صفحة»، لأن كل عملية توجيه ستؤدي إلى إظهار «صفحة» مختلفة. بعد تعريف كل تعليمات التوجيه، فسنتمكن من تشغيل تطبيق الويب ذي الصفحة الوحيدة باستدعاء الدالة run()‎ التابعة للكائن app كالتالي app.run()‎. تطبيق التدوين الذي سننشئه لتوضيح المفهوم الذي سنشرحه، فسنحاول إنشاء مثال واقعي. لنفترض أننا نريد إنشاء مدونة بسيطة، التي يوجد فيها صفحة رئيسية (أي صفحة Home) وصفحة معلومات (About). سنُنشِئ في صفحة index قائمة بالتدوينات، والضغط على أي واحدة منها سيأخذنا إلى صفحتها. يمكنك أن تجرب هذا التطبيق عمليًا هنا. سنستخدم صيغة JSON لقائمة التدوينات، وسنستخدم إضافة Sammy لعرضها. بنية الملفات - index.html <!-- main layout --> - app.js <!-- stores all our routing logic --> - css --- style.css - js --- jquery-1.11.3.min.js --- sammy.min.js --- sammy.template.js - data --- articles.json - templates <!-- the templates pages that will be injected into the main layout --> --- article.template --- article-detail.template --- about.template شيفرة HTML سنستخدم قالب HTML boilerplate لإنشاء شيفرة HTML: <!DOCTYPE html> <html> <head> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="js/jquery-1.11.3.min.js" type="text/javascript"></script> <script src="js/sammy.min.js" type="text/javascript"></script> <script src="js/sammy.template.js" type="text/javascript"></script> <link rel="stylesheet" href="css/style.css" /> <script src="app.js"></script> </head> <body> <div class="header-container"> <header class="wrapper clearfix"> <nav> <ul> <li><a href="#/">Home</a></li> <li><a href="#/about/">About</a></li> <!-- defining nav url according to route--> </ul> </nav> </header> </div> <div class="main-container"> <div class="main wrapper clearfix"> <div id="app"> <!-- template will be injected here --> </div> </div> </div> </body> </html> تعريف التوجيهات //app.js (function($) { var app = $.sammy('#app', function() { this.use('Template'); this.around(function(callback) { var context = this; this.load('data/articles.json') .then(function(items) { context.items = items; }) .then(callback); }); this.get('#/', function(context) { context.app.swap(''); $.each(this.items, function(i, item) { context.render('templates/article.template', {id: i, item: item}) .appendTo(context.$element()); }); }); this.get('#/about/', function(context) { var str=location.href.toLowerCase(); context.app.swap(''); context.render('templates/about.template', {}) .appendTo(context.$element()); }); this.get('#/article/:id', function(context) { this.item = this.items[this.params['id']]; if (!this.item) { return this.notFound(); } this.partial('templates/article-detail.template'); }); this.before('.*', function() { var hash = document.location.hash; $("nav").find("a").removeClass("current"); $("nav").find("a[href='"+hash+"']").addClass("current"); }); }); $(function() { app.run('#/about/'); }); })(jQuery); هيئنا في البداية التطبيق داخل العنصر ‎#app، حيث سنُضيف القوالب المختلفة بناءً على مسار التوجيه. سنستخدم أيضًا محرك قوالب باسم sammy template engine. this.use('Template'); سنحصل على بيانات المدونة من ملف articles.json باستخدام الدالة load()‎ التابعة لمكتبة jQuery (يمكنك أيضًا استخدام ‎$.get أو ‎$.post)، وتخزين الناتج في المتغير context. حان الآن الوقت لتعريف تعليمة التوجيه لصفحة index باستخدام ‎#/‎: this.get('#/', function(context) { context.app.swap(''); $.each(this.items, function(i, item) { context.render('templates/article.template', {id: i, item: item}) .appendTo(context.$element()); }); }); لدينا بيانات مخزنة في المتغير context، سنمرّ الآن عبر تلك البيانات عبر حلقة تكرار باستعمال الدالة ‎$.each ثم عرضها في article.template. سنستخدم أيضًا الدالة context.app.swap()‎ لكي نُفرِّغ العنصر الذي سنضع فيه المحتويات (أي ‎#app) مما فيه قبل عرض القالب. <article> <section> <a href="#/article/<%= id %>"><h2><%= item.title %></h2></a> </section> </article> سنستخدم هنا مُحرِّك القوالب (templating engine) لكي نعرض القيم القابلة للتغيير باستخدام الصيغة ‎<%= yourdata %>‎، يمكنك أيضًا إضافة رابط لصفحة تفاصيل كل مقالة باستخدام الصيغة ‎#/article/<%= id %>‎. الضغط على رابط التدوينة سيأخذنا إلى قالب article-detail.template، حيث نستطيع إظهار صورة التدوينة وملخص عنها …إلخ. وخلف الكواليس، سنُعرِّف تعليمة توجيه إلى تفاصيل التدوينة في ملف app.js، وسنحصل على مُعرِّف التدوينة كمعامل (parameter) ثم نُمرِّر البيانات إلى article-detail.template. this.get('#/article/:id', function(context) { this.item = this.items[this.params['id']]; if (!this.item) { return this.notFound(); } this.partial('templates/article-detail.template'); }); وبشكلٍ شبيه، يمكننا إنشاء صفحة ثابتة باسم «about» ونعرضها عبر قالب about.template. this.get('#/about/', function(context) { var str=location.href.toLowerCase(); context.app.swap(''); context.render('templates/about.template', {}) .appendTo(context.$element()); }); لقد نسينا أهم خطوة، ألا وهي تهيئة التطبيق باستخدام app.run()‎ حيث يمكنك تحديد مكان التوجيه الافتراضي لتطبيقك. إذا أردتَ أن تفتح صفحة about أولًا فيمكنك أن تكتب: $(function() { app.run('#/about/'); }); إذا أردتَ إجراء بعض العمليات قبل استدعاء كل تعليمة توجيه، فيمكنك استخدام الدالة before()‎. سنُجري في الأسطر الآتية تعديلاتٍ على قائمة التنقل (nav) اعتمادًا على مسار التوجيه الحالي: this.before('.*', function() { var hash = document.location.hash; $("nav").find("a").removeClass("current"); $("nav").find("a[href='"+hash+"']").addClass("current"); }); الخلاصة هذا درسٌ بسيطٌ جدًا لكي تأخذ فكرة عن كيفية عمل تطبيق ذي صفحةٍ وحيدةٍ باستخدام jQuery. يمكنك الآن المضي قدمًا وإنشاء تطبيقات ذات صفحة وحيدة. إحدى الأشياء التي عليك أن تأخذها بعين الاعتبار هي أنَّك ستحتاج إلى خادوم ويب لتشغيل التطبيق، ويمكنك أيضًا تجربة متصفح Firefox، لأنَّ متصفح Chrome يحجب طلبيات Ajax لأيّة بروتوكولات ما عدا http://‎ أو https://‎، لذا إذا كنتَ ستُشغِّل هذا السكربت على جهازك المحلي فلن يعمل لأنَّ مسارات الملفات المحلية تبدأ بالسابقة file://‎. ترجمة –وبتصرّف– للمقال Single Page Apps with jQuery Routing لصاحبه Arkaprava Majumder
  5. تمهيد سنشرح في هذا الدرس كيفية إعداد بيئة برمجية محليّة للغة بايثون 3 في توزيعة أوبنتو 16.04 أو دبيان 8. بايثون هي لغةٌ سهلة القراءة للغاية ومتنوعة ومتعددة الاستخدامات، واسمها مستوحى من مجموعة كوميدية بريطانية باسم «Monty Python»، وكان أحد الأهداف الأساسية لفريق تطوير بايثون هو جعل اللغة مرحةً وسهلة الاستخدام، وإعدادها بسيطٌ، وطريقة كتابتها مباشرة وتعطيك تقريرًا مباشرًا عند حدوث أخطاء، وهي خيارٌ ممتازٌ للمبتدئين والوافدين الجدد على البرمجة. إصدار بايثون 3 هو الإصدار الحالي من اللغة ويُعتَبَر أنَّه مستقبل بايثون. سيُرشِدُك هذا الدرس خطوةً بخطوة إلى كيفية تثبيت بايثون 3 على نظام لينكس المحلي عندك وذلك عبر سطر الأوامر، وصحيحٌ أنَّ هذا الدرس يشرح عملية التثبيت لتوزيعة أوبنتو 16.04 أو دبيان 8، إلا أنَّ المفاهيم الأساسية فيه تنطبق على أيّة توزيعة أخرى. المتطلبات المسبقة يجب أن يكون لديك حاسوبٌ يعمل بتوزيعة أوبنتو 16.04 أو دبيان 8 (أو أيّ إصدار آخر من دبيان)، وأن تكون لديك امتيازات إدارية على النظام، بالإضافة إلى اتصالٍ بالإنترنت. الخطوة الأولى: إعداد بايثون 3 سنُثبِّت ونضبط بايثون عبر سطر الأوامر، والذي هو طريقةٌ غيرُ رسوميةٍ للتعامل مع الحاسوب، فبدلًا من الضغط على الأزرار فستكتب نصًا وتعطيه للحاسوب لينفذه وسيُظهِر لك ناتجًا نصيًا أيضًا. يمكن أن يساعدك سطر الأوامر على تعديل أو أتمتة مختلف المهام التي تنجزها على الحاسوب يوميًا، وهو أداةٌ أساسيةٌ لمطوري البرمجيات، وهنالك الكثير من الأوامر التي عليك تعلمها لكي تتمكن من الاستفادة منه. هنالك مقالات في أكاديمية حسوب (كدرس مدخل إلى طرفيّة لينكس Linux Terminal) ستعلمك أساسيات سطر الأوامر، وهنالك كتاب «سطر أوامر لينكس» الذي يُعتَبر مرجعًا تفصيلًا لطريقة التعامل مع سطر الأوامر. ستجد تطبيق «Terminal» (البرنامج الذي تستعمله للوصول إلى سطر الأوامر) بالضغط على أيقونة Dash في الزاوية العليا اليسرى من الشاشة ثم كتابة «terminal» في شريط البحث، ثم الضغط على أيقونة التطبيق التي ستظهر بعدئذٍ. يمكنك بشكلٍ بديلٍ أن تضغط على Ctrl+Alt+T في لوحة المفاتيح بنفس الوقت لتشغيل تطبيق Terminal. أما على دبيان 8 فيمكنك فتح القائمة الموجودة أيضًا في الزاوية العليا اليسرى من الشاشة ثم البحث عن «terminal» في شريط البحث، ثم النقر على أيقونة التطبيق. يمكنك أيضًا أن تضغط على Ctrl+Alt+T في لوحة المفاتيح بنفس الوقت لتشغيل تطبيق Terminal. تأتي توزيعات أوبنتو 16.04 ودبيان 8 وإصدارات دبيان الأخرى مثبتةً مسبقًا مع بايثون 3 وبايثون 2. للتأكد أنَّك تملك آخر الإصدارات منها فحدِّث نظامك باستخدام apt-get: sudo apt-get update sudo apt-get -y upgrade الخيار ‎-y يعني أنَّك توافق على تثبيت جميع الحزم القابلة للتحديث، لكن قد تحتاج إلى تأكيد ذلك عند تحديث النظام وذلك اعتمادًا على الحزم التي ستُحدَّث ونسخة نظامك. بعد إكمال العملية، يمكننا التحقق من إصدار بايثون 3 المُثبّت في النظام بكتابة: python3 -V ستحصل على مخرجات في نافذة الطرفية والتي ستريك ما هو إصدار بايثون المثبّت. قد يختلف الرقم بناءً على النسخة المثبتة في توزيعتك، لكن يجب أن يكون شبيهًا بما يلي: Python 3.5.2 لإدارة الحزم البرمجية الخاصة ببايثون، فثبّت pip: sudo apt-get install -y python3-pip الأداة pip هي أداةٌ تعمل مع لغة بايثون تُثَبِّت وتدير الحزم البرمجية التي قد نحتاج إلى استخدامها في تطوير مشاريعنا. يمكنك تثبيت حزم بايثون بكتابة الأمر: pip3 install package_name حيث عليك وضع اسم الحزمة أو المكتبة التابعة لبايثون مكان package_name مثل Django لتطوير الويب أو NumPy لإجراء حسابات علمية. لذا إن شئتَ تنزيل NumPy فيمكنك تنفيذ الأمر pip3 install numpy. بعد أن انتهينا من ضبط بايثون وتثبيت pip، فيمكننا الآن إنشاء «بيئة وهمية» (virtual environment) لمشاريعنا. الخطوة الثانية: إعداد بيئة وهمية تُمكِّنك البيئات الوهمية من إنشاء مساحة معزولة في حاسوبك مخصصة لمشاريع بايثون، مما يعني أنَّ كل مشروع تعمل عليه يملك مجموعة من الاعتماديات (dependencies) والتي لن تؤثِّر على غيره من المشاريع. يوفِّر لنا ضبط بيئةٍ برمجيةٍ تحكمًا أكبر بمشاريع بايثون وإمكانية التعامل مع إصداراتٍ مختلفةٍ من حزم بايثون. وهذا مهمٌ كثيرًا عندما تتعامل مع الحزم الخارجية. يمكنك ضبط أيُّ عددٍ تشاء من البيئات الوهمية، وكل بيئة تُمثِّل مجلدًا في حاسوبك الذي فيه عددٌ من السكربتات. علينا أولًا تثبيت وحدة (module) برمجية باسم venv، وهي جزءٌ من مكتبة بايثون3 القياسية، وذلك لكي نتمكن من استخدام الأمر pyvenv الذي سيُنشِئ البيئات الوهمية لنا. لنثبِّت venv على نظامنا بكتابة: sudo apt-get install -y python3-venv سنتمكن الآن من إنشاء بيئات وهمية بعد إتمام التثبيت، لنختار ما هو المجلد الذي سنضع فيه بيئات بايثون، أو لننشِئ مجلدًا جديدًا باستخدام الأمر mkdir كما يلي: mkdir environments cd environments بعد أن انتقلتَ إلى المجلد الذي تريد احتواء البيئات فيه، فتستطيع الآن إنشاء بيئة جديدة بتنفيذ الأمر الآتي: pyvenv my_env سيُنشِئ الأمر pyvenv مجلدًا جديدًا فيه بعض الملفات التي يمكننا عرضها باستخدام الأمر ls: ls my_env bin include lib lib64 pyvenv.cfg share تعمل هذه الملفات مع بعضها لضمان أنَّ مشاريعك معزولةٌ عن بقية النظام، لكي لا تختلط ملفات النظام مع ملفات المشاريع. وهذا أمرٌ حسنٌ لإدارة الإصدارات ولضمان أنَّ كل مشروع يملك وصولًا إلى حزمٍ معيّنة التي يحتاج لها. تتوافر أيضًا Python Wheels والتي هي صيغة بناء حزمٍ لبايثون والتي يمكن أن تُسرِّع من تطوير البرامج بتقليل عدد المرات التي تحتاج فيها إلى بناءٍ (compile) للمشروع، وهي موجودةٌ في مجلد share في توزيعة أوبنتو 16.04 لكنها ستكون في دبيان 8 في أحد مجلدات lib وليس في share. عليك تفعيل البيئة لاستخدامها، وذلك بكتابة الأمر الآتي الذي سيُنفِّذ سكربت التفعيل: source my_env/bin/activate يجب أن تظهر الآن سابقةٌ (prefix) في المِحث (prompt) والتي هي اسم البيئة المستخدمة، وفي حالتنا هذه يكون اسمها my_env، وقد يكون مظهر المِحَث مختلفًا في توزيعة دبيان، وذلك اعتمادًا على الإصدار المستخدم؛ لكن يجب أن تشاهد اسم البيئة بين قوسين في بداية السطر: (my_env) sammy@sammy:~/environments$ السابقة ستسمح لك بمعرفة أنَّ البيئة my_env مفعلة حاليًا، وهذا يعني أننا سنستخدم إعدادات وحزم هذه البيئة عند إنشائنا لمشاريع جديدة. ملاحظة: يمكنك داخل البيئة الوهمية أن تستخدم الأمر python بدلًا من python3 والأمر pip بدلًا من pip3 إن شئتَ. أما إذا كنتَ ستستخدم بايثون 3 خارج البيئة الوهمية، فيجب عليك حينها استخدام python3 و pip3 حصرًا. يجب أن تكون بيئتك الوهمية جاهزةً للاستخدام بعد اتباعك للخطوات السابقة. الخطوة الثالثة: إنشاء برنامج بسيط بعد أن أكملنا ضبط بيئتنا الوهمية، لننشِئ برنامجًا بسيطًا يعرض العبارة «Hello World!‎»، وبهذا سنتحقق من أنَّ البيئة تعمل عملًا صحيحًا، وستصبح طريقة إنشاء برامج بايثون مألوفةً لديك إن كنتَ وافدًا جديدًا على اللغة. علينا أولًا تشغيل محرر ملفات نصية لإنشاء ملف جديد، وليكن المحرر nano الذي يعمل من سطر الأوامر: (my_env) sammy@sammy:~/environments$ nano hello.py بعد فتح الملف في نافذة الطرفية، فاكتب البرنامج الخاص بنا: print("Hello, World!") أغلق محرر nano بالضغط على Ctrl+x ثم اضغط على y عندما يسألك عن حفظ الملف. بعد أن يُغلَق محرر nano وتعود إلى سطر الأوامر، فحاول تشغيل البرنامج: (my_env) sammy@sammy:~/environments$ python hello.py سيؤدي برنامج hello.py الذي أنشأتَه إلى طباعة الناتج الآتي في الطرفية: Hello, World! للخروج من البيئة، فاكتب الأمر deactivate وستعود إلى مجلدك الأصلي. الخلاصة تهانينا! لقد ضبطتَ الآن بيئة تطوير للغة بايثون 3 في جهازك الذي يعمل بتوزيعة أوبنتو أو دبيان، حان الآن الوقت للتعمق بلغة بايثون وإنشاء برامج رائعة! بالتوفيق. ترجمة -وبتصرّف- للمقال How To Install Python 3 and Set Up a Local Programming Environment on Ubuntu 16.04 لصاحبته Lisa Tagliaferri
  6. أهلًا بك في هذه السلسلة التي تتحدث عن تأثيرات التمرير (Scrolling Effects)، سنستعرض في هذه السلسلة عددًا من تأثيرات التمرير وسنشرح آلية عملها وسنجرِّبها عمليًا. يمكننا الاستفادة من الحدث scroll في JavaScript لإجراء تأثيرات عند تمرير صفحة الويب؛ لكن إن فعلنا ذلك دون إتقان فالنتيجة كارثية، أما إذا أحسنا صنعنا فيمكن لتأثيرات التمرير أن تبهر الزوار وتشعرهم أنَّ موقعك مميز. هذه هي المقالة الأولى في هذه السلسلة، والتي تتضمن التأثيرات الآتية: إخفاء صورة خلفية تدريجيًا عند التمرير توضيح الصورة عند التمرير تدوير العناصر عند التمرير تأثير اختلاف المظهر parallax أما المقالة الثانية والثالثة فهي تتضمن التأثيرات الآتية: إظهار صورة الخلفية عند التمرير باستخدام CSS فقط تمرير سلس للصفحة تطبيق تأثير عدم الوضوح على المحتوى خلف شريط الانتقال إنشاء عنصر قابل للتمرير مع إمكانية وصول مخصصة من لوحة المفاتيح تأثير غروب الشمس باستخدام SVG إظهار فيديو في الخلفية صور متحركة بتأثير parallax باستخدام CSS 3D و JavaScript سأقدِّم لك في بداية كل قسم رابطًا لتجربة المثال تجربةً حيةً على المتصفح. سيسهل عليك كثيرًا أن تفهم الشرح والشيفرات بعد تجربتك للتأثير. إخفاء صورة خلفية تدريجيًا عند التمرير (تجربة حية) إذا كنتَ مِن مَن يستعملون صورًا كخلفية لكامل صفحة الويب، فاستخدام تأثير إخفاء تلك الصورة تدريجيًا عند التمرير هو أمرٌ حسن، حيث يمكّنك ذلك من استخدام أيّة صورة كخلفية دون التأثير على وضوح العناصر أو قابلية قراءة النص. ولعدم قدرة CSS على الاستجابة مباشرةً إلى موضع شريط التمرير، فنحن بحاجةٍ إلى استخدام بعض شيفرات JavaScript لمعرفة موضع التمرير لأحد العناصر. التقنية التي سأشرحها لك هنا سنستخدم فيها أنماط CSS أيضًا لإنشاء تأثير الإخفاء والتي ستُحدَّث ديناميكيًا عبر JavaScript. طريقة التفافية للتعويض عن عدم وجود الخاصية background-opacity للأسف لا يوجد لحد الآن خاصية باسم background-opacity لتحديد شفافية الخلفية؛ لكن من الممكن إنشاء التأثير عبر استخدام ميزة تعدد الخلفيات في CSS: حيث سنضع الصورة كطبقة (layer)، ثم الطبقة الثانية هي تدرجٌ لوني سنستخدم في ألوانه الشفافية alpha. لذا ستبدو شيفرة CSS كالآتي: body { background: linear-gradient(rgba(255, 255, 255, 0), rgba(255, 255, 255, 0)), url(times-square-perspective.jpg); background-repeat: no-repeat; background-attachment: fixed !important; background-size: 100% !important; background-position: center top !important; padding: 1rem; padding-top: 45%; color: #fff; } التدرج اللوني (الذي سيتم وضعه فوق الصورة، لأنَّه عُرِّفَ أولًا) غير ظاهر حاليًا، لأنَّ قيمة الشفافية alpha هي 0 للونين المشكلين للتدرج. استخدمنا الكلمة المفتاحية ‎!important للتأكّد أنَّ شيفرة JavaScript –التي سنُطبِّقها بعد قليل– لن تتمكن من إلغاء قيم خاصيات CSS السابقة. شيفرة HTML تحتوي على ما يلي: <h1>New York Stories</h1> <p>In my younger and more vulnerable years… في النهاية، علينا وضع العنصر h1 في الأعلى: h1 { text-shadow: 0 0 5px rgba(0,0,0,0.5); font-size: 4rem; color: #fff; line-height: 1; position: absolute; top: 10px; } ولكي تصبح الفقرة قابلةً للقراءة، فعلينا أن نحوِّل التدرج اللوني إلى اللون الأبيض تمامًا، مما يؤدي إلى إخفاء صورة الخلفية. تعديل التدرجات اللوني في CSS باستخدام JavaScript أضف شيفرة JavaScript الآتية في أسفل الصفحة: var nystories = document.querySelector("p").offsetTop; window.onscroll = function() { if (window.pageYOffset > 0) { var opac = window.pageYOffset / nystories; document.body.style.background = "linear-gradient(rgba(255, 255, 255, " + opac + "), rgba(255, 255, 255, " + opac + ")), url(times-square-perspective.jpg) no-repeat"; } } في الشيفرة السابقة، قيمةُ المتغير nystories هي موضع أوّل فقرة في الصفحة. عندما يبدأ المستخدم بالتمرير فستُنشِئ الشيفرة السابقة متغيرًا باسم opac الذي سيُقسِّم الموضع «الحالي» للنافذة على الموضع «البدائي» لأول فقرة. ومن ثم ستُجمَع النتيجة مع قيم rgb لألوان التدرج اللوني مكان قيمة الشفافية alpha، مما يعطي التأثير بإخفاء صورة الخلفية عندما يتم تمرير الصفحة. هذا أحد الأمثلة البسيطة عن التمرير، ما زال في جعبتنا المزيد. توضيح الصورة عند التمرير (تجربة حية) أعجبتني اللمسة الجميلة في تطبيق توتير على هواتف iPhone: عندما تمرّر إلى الأسفل في صفحة «Me» فستصبح صورة الترويسة غير واضحة وسيتم تقريبها. أرى أنَّ من المفيد تقليد هذه التقنية في متصفحات الويب، خصوصًا للصور البارزة التابعة للمقالات… شيفرة HTML بسيطة للغاية: <article> <header> <img src="placid-pond.jpg" alt> </header> … </article> وفي نفس الصفحة سنضيف شيفرة SVG: <svg xmlns="http://www.w3.org/2000/svg" width="0" height="0"> <filter id="blur"> <feGaussianBlur stdDeviation="0" /> </filter> </svg> الغرض من وضع شيفرة SVG السابقة هو إضافة تأثير عدم الوضوح (blur) في متصفح Firefox، والذي لا يدعم إلى الآن «مُرشّحات CSS» ‏(CSS Filters)؛ لاحظ أنَّ خاصية العرض والارتفاع لعنصر svg لها القيمة 0، لذا لن يؤثِّر على تخطيط الصفحة. سنضيف تأثير عدم الوضوح عبر أنماط CSS: header { overflow: hidden; font-size: 0; } article header img { width: 100%; height: auto; filter: url(#blur); } شيفرة JavaScript أكثر تعقيدًا مما سبق. يجب في البداية أن نضع جميع الشيفرات داخل دالة التي ستستدعى عندما يتم التمرير في المتصفح: window.onscroll = function (event) { } سنحتاج إلى الكثير من المعلومات داخل هذه الدالة. المتغيرات التي سنستعملها هي: var headerImg = document.querySelector("article header img"), headerImgOffset = headerImg.getBoundingClientRect(), imgTop = headerImgOffset.top, imgBottom = headerImgOffset.bottom, imgHeight = headerImg.offsetHeight, viewportHeight = document.documentElement.clientHeight, blur = document.getElementById("blur"), svgblur = blur.getElementsByTagName("feGaussianBlur")[0], topEffectStart = Math.abs((viewportHeight – imgHeight)/5); اقرأ الأسطر السابقة بتمعّن، يجب أن تبدو لك المتغيرات منطقيةً: المتغير headerImg يشير إلى صورة الترويسة الموجودة في أعلى المقالة (وللتبسيط، سأعتبر أنَّ الصفحة فيها مقالة وحيدة، وعنصر header وحيد). المتغير headerImgOffset يسمح لنا بالوصول إلى مكان وأبعاد الصورة، وفي حالتنا سنستخدم imgTop و imgBottom و imgHeight. المتغير viewportHeight يمثِّل ارتفاع نافذة المتصفح. المتغير blur يشير إلى شيفرة SVG في الصفحة، أما svgBlur فهو العنصر الذي سيسبِّب تأثير عدم الوضوح في متصفح Firefox. المتغير topEffectStart هو الفرق بين ارتفاع «إطار العرض» (viewport) الحالي وارتفاع الصورة، ومقسومًا على 5. وهذا يعني أنَّ تأثير عدم الوضوح سيبدأ فقط إذا كانت صورة الترويسة في الخُمس العلوي من نافذة المتصفح. استخدمتُ الدالة Math.abs لكي أحصل دومًا على عددٍ موجب، لأنَّ من الممكن أن تكون الصورة «أطول» من ارتفاع إطار العرض الحالي. بعد توضيح كل ما سبق، فلنضف عبارةً شرطيةً داخل الدالة: if (imgTop < topEffectStart && imgBottom > 0 ) { blurFactor = Math.abs(imgTop / 100); scaleFactor = 1+Math.abs(imgTop / 1000); headerImg.style.webkitFilter = "blur(" + blurFactor + "px)"; svgblur.setAttribute("stdDeviation", blurFactor); headerImg.style.webkitTransform = "scale(scaleFactor)"; headerImg.style.transform = "scale(scaleFactor)"; } هذا يعني إذا كان أعلى الصورة موجودٌ في أول خُمس من نافذة إطار العرض وما زالت الصورةُ معروضةً وظاهرةً للمستخدم، فافعل ما يلي: أخذ الموقع الحالي لأعلى الصورة نسبةً إلى أعلى إطار العرض، وتقسيم الناتج على 100، وتحويل الناتج إلى عدد عشري موجب، وتسمية الناتج باسم blurFactor. وبطريقةٍ مشابهة، سنأخذ الفرق، ونُقسِّمه على 1000 (لأن تغيير المقياس [scale] حساسٌ أكثر من تأثير عدم الوضوح) ومن ثم سنجمع العدد 1 مع الناتج، ونسمِّه scaleFactor. سنُطبِّق blurFactor (بعد إضافة “px” إليه) على صورة الترويسة، وذلك بوضعه كقيمة لمُرشِّح Webkit Blur. وبطريقةٍ مشابهة، سنُغيّر قيمة stdDeviation لنفس قيمة blurFactor وذلك لتطبيق التأثير في متصفح Firefox. في النهاية، سنسند قيمة scaleFactor إلى خاصية transform في CSS لصورة الترويسة. إحدى ميزات استخدام scale و blur معًا هي القدرة على إخفاء تأثير عدم الوضوح في جوانب الصورة، والتي لا يمكن إخفاؤها حتى باستخدام overflow: hidden في الحالات العادية. يمكن استخدام نفس السكربت السابق مع بعض التعديلات لكي يتم التركيز أو توضيح الصورة عندما تكون في منتصف الصفحة، مما يجذب انتباه المستخدم إليها. تدوير العناصر (المسننات) عند التمرير (تجربة حية) سأريك في هذا المثال كيفية تدوير العناصر أثناء تمرير الصفحة باستخدام JavaScript مباشرةً دون أيّة مكتبات خارجية. ضبط المسننات سيتم وضع المسننين في الصفحة باستخدام قيم id فريدة لكلٍ منهما؛ ولمّا كانا عبارةً عن رسوميات متجهة (vector shapes) فمن المعقول أن نُنشئهما باستخدام SVG: <div id="gearbox"> <img src="gear.svg" alt id="leftgear"> <img src="gear.svg" alt id="rightgear"> </div> هنالك الكثير من الخيارات لطريقة وضع المسننات: ففي أغلبية الأحيان سنستعمل موضعًا ثابتًا لها (أي position: fixed)، ومن الممكن أيضًا وضعها في مكانٍ معيّن ثم سيصبح موقعها ثابتًا عندما يتم التمرير بعدها (أي position: sticky). سأستعمل في هذا المثال flexbox لفصل العناصر، وسأستخدم الواحدات vm لقياسها؛ ويمكن أيضًا أن يكون موضع المسننات static وبالتالي ستختفي المسننات عند تمرير الصفحة. #gearbox { display: flex; justify-content: space-between; } #leftgear, #rightgear { width: 20vw; max-width: 20%; height: auto; } تدوير المسننات تدوير العناصر سهلٌ للغاية، كل ما عليك فعله هو إضافة الشيفرة الآتية في أسفل الصفحة: var leftgear = document.getElementById("leftgear"), rightgear = document.getElementById("rightgear"); window.addEventListener("scroll", function() { leftgear.style.transform = "rotate("+window.pageYOffset+"deg)"; rightgear.style.transform = "rotate(-"+window.pageYOffset+"deg)"; }); سيدور كل مسنن بنفس مقدار تمرير النافذة مقدرًا بالبكسل والذي سيحوّل إلى درجات. لتسريع أو تبطيء دوران المسننات نسبةً إلى مقدار التمرير، فيمكنك جعل window.pageYOffset جزءًا من تعبيرٍ رياضيٍ بسيط (كالقسمة أو الضرب بالرقم 2 على سبيل المثال). تفادي «تعليق» المسننات مشكلة شائعة مع أيّة تقنية التي تتعامل مع التمرير هي «تعليق» العناصر حيث ستحاول مطابقة مدخلات المستخدم. أسهل حل للتعامل مع هذه الإشكالية هو تحريك العناصر عندما يصبح المتصفح جاهزًا للتعامل مع تلك الحركة، وذلك عبر requestAnimationFrame. ولفعل ذلك سنُجعل الدالة addEventListener تستمع إلى حدث خاص (custom event): window.addEventListener("optimizedScroll", function() { … }) وقبل دالة معالجة الحدث، أضف دالةً مجهولةً (anonymous function): ;(function() { var throttle = function(type, name, obj) { var obj = obj || window; var running = false; var func = function() { if (running) { return; } running = true; requestAnimationFrame(function() { obj.dispatchEvent(new CustomEvent(name)); running = false; }); }; obj.addEventListener(type, func); }; throttle ("scroll", "optimizedScroll"); })(); طريقة سهلة لإنشاء تأثير اختلاف المظهر (تجربة حية) نَشَرَ «Adam Mustill» تقنيةً ذكيةً جدًا لإنشاء تأثير اختلاف المظهر (Parallax) عبر تعديل واحدة root em. يتساءل الكثيرون عن كيفية إنشاء تأثيرات اختلاف المظهر، وارتأيتُ أنَّ هذه الطريقة قد تكون نقطة بداية جيدة لتعلم إنشاء هذا التأثير. تأثير اختلاف المظهر (Parallax) يتطلب عادةً عمليات معالجة معقدة لعدد كبير من عناصر DOM المنفصلة، لكن التقنية التي سنستخدمها هنا تُسهِّل علينا معالجة العناصر بتغيير قياسٍ وحيد، وهي تستغل ميزة غريبة بعض الشيء لواحدة القياس em: إذا كان قياس العنصر الأب والعناصر الأبناء بواحدة em، فإن القياس النهائي للأبناء هو ناتج ضربها مع بعضها. وصحيحٌ أنَّ هذا السلوك قد يُسبِّب لنا صداعًا (وهو سببٌ وجيهٌ لاستخدام rem إن استطعنا) إلا أنَّ له ميزتين في هذه الحالة: 1. وحدة القياس em لها دعم أقوى في المتصفحات نسبةً إلى rem. 2. تتطلب em وجود عنصر أب، وهذا يعني أنَّ تأثير اختلاف المظهر يمكن أن يُطبَّق على حاوية معيّنة؛ أما في طريقة rem التي ابتكرها Adam، فإنَّ كل شيءٍ موجود في عنصر <body> سيتأثّر بالتمرير إلا إذا اتخذتَ إجراءاتٍ معينةً للحد من ذلك. ولقد حسّنتُ أيضًا من شيفرة Adam وجعلتها سطرًا وحيدًا من شيفرات JavaScript، بدلًا من عدِّة أسطر من jQuery، مما يُحسِّن من الأداء. شيفرات HTML و CSS شيفرة HTML لهذا المثال بسيطةٌ جدًا: إذ هنالك ثلاث صور وترويسة داخل عنصر <div>: <div id="parallax"> <h1>Simple EM Parallax Technique</h1> <img src="candles.jpg" alt> <img src="cherry-tree.jpg" alt> <img src="pagoda-surrounded-by-trees.jpg" alt> </div> وأنماط CSS ليست معقدةً أبدًا: #parallax { background-image: url(blurred-background-small.jpg); background-size: cover; padding-top: 62.5%; overflow: hidden; position: relative; font-size: .1em; } #parallax * { position: absolute; } #parallax img { width: 40%; height: auto; box-shadow: 0 .2em 8px 4px rgba(0,0,0,0.5); } #parallax h1 { font-size:3rem; color: #fff; z-index: 2; top: 0; text-transform: uppercase; width: 100%; text-align: center; text-shadow: 0 .2em 5px rgba(0,0,0,0.4); } #parallax img:nth-of-type(1) { left: 5%; bottom: 22em; } #parallax img:nth-of-type(2) { left: 28%; z-index: 3; bottom: 8em; } #parallax img:nth-of-type(3) { left: 55%; bottom: 12em; } الأمور المهمة التي يجب ملاحظتها هي القيمة الافتراضية الصغيرة جدًا لقياس الحاوية ‎#parallax بواحدة em، بالإضافة إلى قيم bottom لكل صورة، وأنَّ نص الترويسة مقاسٌ بواحدة rem وليس em. شيفرة JavaScript شيفرة JavaScript هي عبارة عن سطرٍ وحيدٍ فقط يجري تعديلًا على إحدى خاصيات CSS: window.onscroll = function() { if (window.pageYOffset > 0) { document.getElementById("parallax").style.fontSize = (window.pageYOffset/20)*.1+"em"; } } بشرح مبسّط: سيؤدي التمرير إلى تغيير قيمة الخاصية font-size لحاوية ‎#parallax، وهذا التغيير يعتمد على عدد البكسلات التي تم تمريرها، مقسومةً على 20، مضروبةً بقيمة ‎0.1 em‎ والتي هي حجم الخط الأصلي. التمرير إلى الأسفل سيؤدي إلى زيادة قيمة الخاصية bottom لكل صورة مما يدفعها نحو الأعلى؛ لكن الترويسة لن تتأثر لأنها مُقاسة بواحدة rem، وهي ذات خاصية z-index مناسبة مما يسمح للصور بالمرور أمامها وخلفها. الخلاصة استعرضنا التأثيرات السابقة بسرعة، بقي عليك أن تتمعّن بشيفراتها، وتحاول إيجاد استخدامات أخرى للتقنيات التي رأيتها. ترجمة وبتصرّف للمقالات: Fade A Responsive Background Image On Scroll Scroll-to-Focus Effect For Hero Images Rotate Elements on Scroll with JavaScript Easy Parallax Effects With Em لصاحبها Dudley Storey
  7. توصيف الأشخاص باستخدام metadata في HTML5

    إن كنت قد قرأت المقال السّابق الذي يشرح ماهية metadata وكيفية استخدامها فأود أن أنوّه إلى أنني لم أخترع الأمثلة في الدرس السابق من عندي تمامًا، فهنالك اصطلاحات للبيانات الوصفية لتوصيف المعلومات الخاصة بالأشخاص، ومن السهل فعل ذلك. لنلقي نظرةً أقرب. أسهل طريقة لدمج البيانات الوصفية في موقعك الشخصي تكون في صفحة About، من المرجح وجود صفحة About لديك، أليس كذلك؟ إن لم تكن لديك صفحة، فيمكنك المتابعة معي في توسعة صفحة About الآتية ببعض البنى الهيكلية. لننظر أولًا إلى الشيفرة المصدرية، قبل إضافة أيّة خاصيات لها علاقة بالبيانات الوصفية: <section> <img width="204" height="250" src="http://diveintohtml5.org/examples/2000_05_mark.jpg" alt="[Mark Pilgrim, circa 2000]"> <h1>Contact Information</h1> <dl> <dt>Name</dt> <dd>Mark Pilgrim</dd> <dt>Position</dt> <dd>Developer advocate for Google, Inc.</dd> <dt>Mailing address</dt> <dd> 100 Main Street<br> Anytown, PA 19999<br> USA </dd> </dl> <h1>My Digital Footprints</h1> <ul> <li><a href="http://diveintomark.org/">weblog</a></li> <li><a href="http://www.google.com/profiles/pilgrim">Google profile</a></li> <li><a href="http://www.reddit.com/user/MarkPilgrim">Reddit.com profile</a></li> <li><a href="http://www.twitter.com/diveintomark">Twitter</a></li> </ul> </section> أول شيء عليك فعله دائمًا هو التصريح عن نوع الاصطلاحات الذي ستستعمله، ومجال (scope) الخاصيات التي تريد إضافتها. يمكنك القيام بذلك عبر إضافة خاصيتَي itemtype و itemscope إلى العنصر الأب الذي يحتوي على بقية العناصر التي تريد توصيف البيانات فيها، وهو في حالتنا العنصر <section>. <section itemscope itemtype="http://schema.org/Person"> يمكنك الآن البدء بتعريف خاصيات البيانات الوصفية من نوع الاصطلاحات http://schema.org/Person، لكن ما هي هذه الخاصيات؟ كما هو واضح، تستطيع رؤية كامل قائمة الخاصيات بزيارة الصفحة http://schema.org/Person في متصفحك. لا تتطلب مواصفة البيانات الوصفية أن توضع قائمة الخاصيات هناك، لكنني أرى أنَّ ذلك مستحسنٌ. فلو أردت مثلًا أن تجعل المطورين يستعملون نوع اصطلاحات البيانات الوصفية الذي أنشأته، فستحتاج إلى توثيقه. ولا يوجد مكانٌ أفضل لوضع التوثيق فيه من رابط نوع الاصطلاحات نفسه، أليس كذلك؟ الخاصية الشرح name الاسم additionalName الاسم الإضافي، قد يكون الاسم الأوسط أو اللقب image رابطٌ لصورةٍ له jobTitle المُسمَّى الوظيفي (مثلًا، مدير مالي [Financial Manager]) url رابط URL لصفحة ويب، مثل الصفحة الرئيسية لمدونة ذاك الشخص affiliation المنظمة التي يرتبط بها هذا الشخص (أن يكون -على سبيل المثال- موظفًا أو طالبًا فيها) address العنوان الفيزيائي للشخص. يمكن أن يحتوي على خاصيات أخرى مثل streetAddress وaddressLocality و addressRegion و postalCode و addressCountry knows علاقة اجتماعية بين الشخص الموصوف وشخصٍ آخر أول شيء نصادفه في صفحة About السابقة هي صورةٌ موضوعةٌ ضمن عنصر <img>، ولكي نُصرِّح أنَّ الصورة الموجودة هي صورة الشخص الموصوف، فكل ما نحتاج له هو إضافة itemprop="image"‎ إلى عنصر <img>. <img itemprop="image" width="204" height="250" src="http://diveinto.html5doctor.com/examples/2000_05_mark.jpg" alt="[Mark Pilgrim, circa 2000]"> أين هي قيمة خاصية البيانات الوصفية؟ إنها موجودةٌ في خاصية src، وإذا كنتَ تتذكر من النموذج الهيكلي للبيانات الوصفية في HTML5، "قيمة" خاصية البيانات الوصفية في عنصر <img> هي محتوى الخاصية src. ولكل عنصر <img> خاصية src -وإلا فلن تُعرَض الصورة- وخاصية src تحتوي على رابط URL دائمًا، أترى؟ إذا كنت تكتب HTML بشكلٍ صحيح، فاستعمال البيانات الوصفية سهلٌ جدًا. علاوةً على ذلك، العنصر <img> ليس موجودًا لوحده في الصفحة، فهو عنصر ابن للعنصر <section>، الذي عرَّفناه مع الخاصية itemscope. تُعيد البيانات الوصفية استعمال علاقة "الأب-الابن" بين العناصر في الصفحة لتعريف مجال (scope) خاصيات البيانات الوصفية. أي أننا نقول بالعربية: "العنصر <section> يُمثِّل شخصًا، وأيّة خاصيات للبيانات الوصفية التي تجدها في العناصر التي تكون ابنًا للعنصر <section> هي خاصياتٌ تابعةٌ لذاك الشخص". يمكنك تخيل الأمر على أنَّ العنصر <section> هو الفاعل في الجملة، والخاصية itemprop تمثِّل الفعل (وهو يُشبِه: "مُصوَّرٌ في")، وقيمة خاصية البيانات الوصفية هي المفعول به في الجملة. يجب تعريف "الفاعل" مرةً واحدةً فقط، وذلك بوضع الخاصيتين itemscope و itemtype في عنصر <section> الأب. أما "الفعل" فيُعرَّف بوضع الخاصية itemprop="image"‎ في عنصر <img>. أما "المفعول به" فلا يحتاج إلى أيّة شيفرات خاصة، لأنَّ النموذج الهيكلي للبيانات الوصفية في HTML5 يقول أنَّ قيمة خاصية البيانات الوصفية في عنصر <img> هي في خاصية src. سننتقل الآن إلى القسم التالي من الشيفرة، سنشاهد ترويسة <h1> وبداية قائمة <dl>. ليس من الضروري إضافة خاصيات البيانات الوصفية إلى عنصرَي <h1> و <dl>، فلا حاجة إلى وضع خاصية من خاصيات البيانات الوصفية في كل عنصر من عناصر HTML. الغرض من البيانات الوصفية هو "توصيف" البيانات وليس الشيفرات أو الترويسات التي تحيط بها. ترويسة <h1> لا تحتوي على قيمة، فهي مجرد ترويسة. وكذلك الأمر لعنصر <dt> الذي يحتوي على السلسلة النصية "Name» التي لا تمثل خاصية، وإنما لافتة (label) فقط. <h1>Contact Information</h1> <dl> <dt>Name</dt> <dd>Mark Pilgrim</dd> أين توجد المعلومات الحقيقية؟ في عنصر <dd>، وهنالك سنحتاج إلى وضع خاصية itemprop، لكن أيّ خاصية منها؟ إنها خاصية name، وأين قيمة الخاصية؟ هي النص الموجود ضمن العنصر <dd>، لكن ألا نحتاج إلى وضع القيمة في شيفرة خاصة؟ النموذج الهيكلي للبيانات الوصفية في HTML5 يقول لا، فلا يوجد معنى خاص لعناصر <dd>، وستكون قيمة الخاصية هي النص الموجود ضمن العنصر. <dd itemprop="name">Mark Pilgrim</dd> كيف نستطيع التعبير عما سبق بالعربية؟ "اسم هذا الشخص هو Mark Pilgrim". حسنًا، لنتابع. إضافة الخاصيتين التاليتين صعبٌ قليلًا، هذه هي الشيفرة قبل إضافة البيانات الوصفية: <dt>Position</dt> <dd>Developer advocate for Google, Inc.</dd> إذا نظرتَ إلى نوع اصطلاحات Person، فستجد أنَّ النص "Developer advocate for Google, Inc.‎" يحتوي على خاصيتين: jobTitle ‏(قيمتها "Developer advocate") و affiliation‏ ( قيمتها "Google, Inc.‎")، لكن كيف تستطيع أن تُعبِّر عن ذلك عبر البيانات الوصفية؟ الجواب المختصر: لا يمكنك فعل ذلك. لا توجد طريقة في البيانات الوصفية تمكِّنك من تقسيم سلسلة نصية إلى عدِّة خاصيات. لا يمكنك القول "أول 18 محرفًا من هذه السلسلة النصية هي خاصيةُ بياناتٍ وصفية، وآخر 12 محرفًا هي خاصيةٌ أخرى". لكن هذا لا يعني أنَّ الأمر مستحيلٌ. تخيل أنك تريد أن تُنسِّق النص "Developer advocate" بنوع خطٍ مختلف عن النص "Google, Inc.‎". حسنًا، CSS لا تستطيع فعل ذلك أيضًا، لكن ماذا كنتَ ستفعل؟ ستحتاج أولًا إلى وضع كل قسم من السلسلة النصية في حاويات مختلفة، مثل <span>، ثم تُطبِّق أنماط CSS على كل عنصر <span> على حدة. يمكنك تطبيق هذه التقنية أيضًا على البيانات الوصفية، فهنالك معلومتان منفصلتان هنا: jobTitle و affiliation. إذا وضعت كل معلومة في عنصر <span>، فستستطيع أن تقول أنَّ كل عنصر <span> هو خاصيةٌ مستقلةٌ من خاصيات البيانات الوصفية. <dt>Position</dt> <dd><span itemprop="jobTitle">Developer advocate</span> for <span itemprop="affiliation">Google, Inc.<span></dd> هذا يعني: "وظيفة هذا الشخص هي "Developer advocate". هذا الشخص يعمل لدى "Google, Inc.‎"". تلك جملتان، وخاصيتا بياناتٍ وصفية. صحيحٌ أننا وضعنا مزيدًا من الشيفرات، لكننا استفدنا منها خيرَ استفادة. سنستفيد أيضًا من نفس التقنية لتوصيف معلومات العنوان، يُعرِّف نوع الاصطلاحات Person الخاصية address، التي هي بدورها عنصرٌ من عناصر البيانات الوصفية، وهذا يعني أنَّ للعنوان نوعُ اصطلاحاتٍ خاصٌ به (http://schema.org/PostalAddress)، وله خاصياتٌ متعلقةٌ به. يُعرِّف نوع الاصطلاحات PostalAddress خمسَ خاصياتٍ: streetAddress و addressLocality و addressRegion و postalCode و addressCountry. إذا كنتَ مبرمجًا، فمن المرجح أنَّك تعرف كيف تفصل النقطة بين الكائنات وخاصياتها، تخيل أنَّ العلاقة كالآتي: Person Person.PostalAddress Person.PostalAddress.streetAddress Person.PostalAddress.addressLocality Person.PostalAddress.addressRegion Person.PostalAddress.postalCode Person.PostalAddress.addressCountry لنعد إلى مثالنا. العنوان بأكمله موجودٌ في عنصر <dd> وحيد (أكرِّر مرةً أخرى أنَّ العنصر <dt> هو لافتة، ولا يلعب دورًا في إضافة معلومات إلى البيانات الوصفية). من السهل الإشارة إلى خاصية address، كل ما عليك فعله هو إضافة الخاصية itemprop في عنصر <dd>. <dt>Mailing address</dt> <dd itemprop="address"> لكن تذكَّر أنَّ خاصية address هي بدورها عنصرٌ من عناصر البيانات الوصفية، هذا يعني أننا نحتاج إلى وضع الخاصيتين itemscope و itemtype أيضًا. <dt>Mailing address</dt> <dd itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> لقد رأينا هذا من قبل، لكن للعناصر من المستوى الأول (top-level). عنصر <section> يحتوي على itemtype و itemscope، وجميع العناصر الموجودة ضمن العنصر <section> التي لديها خاصيات للبيانات الوصفية هي ضمن "مجال" (scope) نوع اصطلاحات البيانات الوصفية. لكن هذه هي أول مرة نرى فيها "تشعّب" المجالات، أي تعريف itemtype و itemscope (في عنصر <dd>) داخل مجال موجود مسبقًا (في عنصر <section>). المجالات المتشعبة تعمل تمامًا كما تعمل شجرة DOM في HTML. العنصر <dd> يحتوي على عددٍ معيّنٍ من العناصر الأبناء، ويكون مجالها هو نوع الاصطلاحات المُعرَّف في العنصر <dd>، وبعد أن ينتهي العنصر <dd> عبر وسم الإغلاق <‎/dd> فسيرجع المجال إلى نوع الاصطلاحات المُعرَّف في العنصر الأب (الذي هو <section> في حالتنا). تُعاني خاصيات العنوان من نفس المشكلة التي واجهناها عند تعريف الخاصيتين jobTitle و affiliation، لكن السلسلة النصية للعنوان أطول قليلًا، وعلينا تقسيمها إلى خمس خاصيات للبيانات الوصفية. وسنستعمل نفس الآلية التي اتبعناها سابقًا: وضع كل قطعة من المعلومات في عنصر <span>، ثم توصيف تلك المعلومات عبر خاصيات البيانات الوصفية. <dd itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> <span itemprop="streetAddress">100 Main Street</span><br> <span itemprop="addressLocality">Anytown</span>, <span itemprop="addressRegion">PA</span> <span itemprop="postalCode">19999</span> <span itemprop="addressCountry">USA</span> </dd> بالعربية: "هذا الشخص يملك عنوانًا بريديًا. اسم الشارع لذاك العنوان البريدي هو "100 Main Street"، أما البلدة (locality) فهي "Anytown"، والإقليم (region) هو "PA"، والرمز البريدي (postal code) هو "19999"، واسم الدولة هو "USA"". س: هل صيغة العنوان البريدي خاصةٌ بالولايات المتحدة؟ ج: لا. خاصيات نوع الاصطلاحات PostalAddress عامةٌ لتتمكن من وصف أي عنوان بريدي في العالم. لكن لن يكون لجميع العناوين قيمٌ لكل خاصية من الخاصيات، لكن لا بأس بهذا. وقد تتطلب بعض العناوين وضع أكثر من "سطر" واحد في خاصية معيّنة، ولا بأس بهذا أيضًا. فمثلًا، لو كان يحتوي العنوان البريدي على عنوان الشارع ورقم البناء، فسيمثِّل كلاهما الخاصية streetAddress: <p itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> <span itemprop="streetAddress"> 100 Main Street Suite 415 </span> ... </p> بقي شيءٌ أخيرٌ في صفحة About: قائمةٌ بروابط URL. لدى نوع الاصطلاحات Person خاصيةٌ لهذا الغرض اسمها url، التي يمكن أن تحتوي على أيّ نوعٍ من أنواع الروابط (المهم أن يكون "رابطًا"). ما أقصده هو أنَّ تعريف الخاصية url غير مُحدَّد، ويمكن أن تحتوي على أيّة روابط متعلقة بالشخص: مدونة، أو معرض صور، أو حساب شخصي على موقعٍ آخر مثل فيسبوك أو تويتر. من المهم أن تلاحظ أنَّ الشخص الواحد قد يمتلك أكثر من خاصية url. تقنيًا، يمكن لأي خاصية أن تتكرر، لكن إلى الآن لم نستفد من هذا. على سبيل المثال، قد يكون لديك أكثر من خاصية image تُشير إلى روابط URL لصورتين مختلفتين. أريد هنا أن أذكر أربعة روابط URL مختلفة: المدونة، وحساب Google، وحساب Reddit، وحساب تويتر. هنالك قائمة في HTML فيها أربعة روابط موجودة في أربعة عناصر <a>، كلُ واحدٍ منها موجودٌ في عنصر <li> خاص به. سنُضيف الخاصية itemprop="url"‎ إلى كل عنصر من عناصر <a>. <h1>My Digital Footprints</h1> <ul> <li><a href="http://diveintomark.org/" itemprop="url">weblog</a></li> <li><a href="http://www.google.com/profiles/pilgrim" itemprop="url">Google profile</a></li> <li><a href="http://www.reddit.com/user/MarkPilgrim" itemprop="url">Reddit.com profile</a></li> <li><a href="http://www.twitter.com/diveintomark" itemprop="url">Twitter</a></li> </ul> وفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، سيُعامَل العنصر <a> معاملةً خاصةً، فقيمة خاصية البيانات الوصفية تؤخذ من الخاصية href، وليس من المحتوى النصي للعنصر. وسيتم تجاهل المحتوى النصي لكل رابط من قِبَل مُفسِّر البيانات الوصفية. وهذا يعني -بالعربية-: "هذا الشخص لديه رابط URL في http://diveintomark.org/‎ وهذا الشخص لديه رابط URL آخر في http://www.google.com/profiles/pilgrim وهذا الشخص لديه رابط URL آخر في http://www.reddit.com/user/MarkPilgrim وهذا الشخص لديه رابط URL آخر في http://www.twitter.com/diveintomark" المقتطفات المنسقة Rich Snippets ربما تتساءل : "لماذا نفعل هذا؟" هل نُضيف البنى الهيكلية عبثًا؟ لماذا نأبه للبيانات الوصفية ونستعملها؟ هنالك نوعان رئيسيان من التطبيقات التي تستخدم HTML، وبطريقها تستخدم البيانات الوصفية أيضًا: متصفحات الويب محركات البحث أما للمتصفحات، فهنالك واجهةٌ برمجيةٌ في DOM لاستخلاص عناصر البيانات الوصفية وخاصياتها وقيم تلك الخاصيات من صفحة الويب، لكن للأسف هذه الواجهة البرمجية غير مدعومة إلا من الإصدارات الحديثة لبعض المتصفحات، لهذا اعتبر أنَّ هذا الطريق مسدودٌ إلى أن تدعم جميع المتصفحات هذه الواجهة البرمجية. مستهلكٌ آخر لشيفرات HTML هو محركات البحث. ماذا يمكن لمحركات البحث فعله مع خاصيات البيانات الوصفية التي تتحدث عن شخصٍ ما؟ تخيل هذا: بدلًا من عرض عنوان الصفحة ومُلخَّص عن محتواها، فسيعرض محرِّك البحث بعض المعلومات الهيكلية الموجودة فيها، مثل الاسم الكامل، والمُسمى الوظيفي، والشركة التي يعمل بها، والعنوان، وربما سيعرض أيضًا صورةً مُصغَّرةً له. هل جذب ذلك انتباهك؟ يدعم محرك البحث Google البيانات الوصفية كجزءٍ من برنامج "المقتطفات المنسقة" (Rich Snippets)، فعندما يُفسِّر عنكبوت البحث في Google صفحتك ويجد خاصيات للبيانات الوصفية التي تتطابق مع نوع الاصطلاحات http://schema.org/Preson، فسيحاول تفسير تلك الخاصيات ويُخزِّن قيمها بجانب بقية بيانات الصفحة. لدى Google أداةٌ رائعةٌ لكي ترى كيف "يرى" Google خاصيات البيانات الوصفية في صفحتك، واختبارها على صفحة About التي نعمل عليها سيُعطي النتيجة: الشكل 1: معلومات البيانات الوصفية كما تُظهِرها أداة اختبار البيانات المنظّمة كل البيانات الوصفية موجودٌ هنا: خاصية image من <img src>، جميع روابط URL من قائمة عناصر <a href>، وحتى كائن العنوان (مذكورٌ في "address") والخاصيات الخمس المتعلقة به. الآن، كيف يستعمل Google كل هذه المعلومات؟ الأمر نسبيٌ، فلا توجد قواعد مُلزِمَة لكيفية عرض خاصيات البيانات الوصفية، ولا أيُّها سيُعرَض، وحتى لا توجد قواعد تحكم إذا كانت ستُعرَض هذه الخاصيات أم لا. إذا بحث أحدهم عن "Mark Pilgrim" ورأى Google أنَّ صفحة "About" تستحق الظهور في نتائج البحث، وقرر Google أنَّ خاصيات البيانات الوصفية الموجودة في تلك الصفحة تستحق أن تُعرَض، فعندها ستبدو نتيجة البحث مشابهةً لما يلي: الشكل 2: مثالٌ عن نتيجة البحث عن صفحة فيها بياناتٌ وصفيةٌ أول سطر "About Mark Pilgrim" هو عنوان الصفحة الموجود في عنصر <title>، ولكن هذا ليس أمرًا مثيرًا للاهتمام؛ لأن محرك Google يفعل هذا لكل صفحة، لكن السطر الثاني مليءٌ بالمعلومات المأخوذة مباشرةً من البيانات الوصفية التي أضفناها إلى الصفحة. "Anytown PA" هو جزءٌ من العنوان البريدي، الموصوف عبر نوع الاصطلاحات http://schema.org/PostalAddress، أما "Developer advocate" و "Google, Inc.‎" هما الخاصيتان من نوع الاصطلاحات http://schema.org/Person (الخاصية jobTitle و affiliation على التوالي وبالترتيب). هذا رائع! لا تحتاج إلى أن تكون شركةً كبيرةً تُبرِمُ اتفاقياتٍ خاصةً مع شركات محركات البحث لتخصيص طريقة عرض نتيجة البحث. كل ما تحتاج له هو عشر دقائق وبعض خاصيات HTML لكي توصِّف فيها بياناتك التي ستنشرها في صفحتك. س: فعلتُ كل ما قلتَه لي، لكن لم تتغير طريقة عرض نتائج البحث عن صفحتي في Google، ما الخطب؟ ج: "لا تضمن Google أنَّ الشيفرة الموجودة في أيّة صفحة أو موقع ستُستخدَم في نتائج البحث"، لكن بغض النظر أنَّ محرك Google قرر ألّا يستعمل البيانات الوصفية في صفحتك، فقد يستعملها محركُ بحثٍ آخر. فمعيار البيانات الوصفية (Microdata) هو معيارٌ مفتوحٌ يستطيع أيُّ شخصٍ توظيفه -كما هي بقية HTML5-. من واجبك توفير أكبر قدر من البيانات تستطيع تقديمه. ثم اترك الأمر للآخرين لكي يُقرِّروا ماذا يفعلون معها. ربما يفاجئوك! ترجمة -وبتصرّف- لجزء من فصل "Microdata" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  8. مدخل إلى البيانات الوصفية (microdata) في HTML5

    هنالك أكثر من 100 عنصر في HTML5، بعضها هيكليٌ تمامًا وبعضها مجردُ حاويةٍ لواجهةٍ برمجيةٍ. وعبر تاريخ HTML، تجادل كاتبو المعايير حول العناصر التي يجب تضمينها في اللغة، فهل يجب أن تحتوي HTML على عنصر <figure>؟ أو عنصر <person>؟ ماذا عن عنصر <rant>؟ اُتخِذَت القرارات، وكُتِبَت المعايير، وطوَّر المطورون تطبيقاتهم، وأضاف صانعو المتصفحات الميزات لمتصفحاتهم، ودُفِعَت عجلة تطوير الويب إلى الأمام. من المؤكَّد أنَّ HTML لن تستطيع إرضاء الجميع، إذ لا يستطيع أيُّ معيارٍ فعل هذا. لم تصل بعض الأفكار المقترحة إلى المستوى المطلوب، فمثلًا، لا يوجد عنصر <person> في HTML5 (وكذلك الأمر لعنصر <rant>)؛ لا يوجد شيءٌ يمنعك من إضافة عنصر <person> إلى صفحات الويب التي تكتبها، لكنها لن تكون سليمةً بنيويًا، ولن تعمل بشكلٍ متماثل في جميع المتصفحات، وقد تتعارض مع معايير HTML المستقبلية إن أضافتها لاحقًا. حسنًا، إن لم يكن الحل كامنًا في إنشاء عناصر جديدة، فماذا على مطوِّر الويب الذي يحب اتباع القواعد الهيكلية أن يفعل؟ كانت هنالك محاولاتٌ لتوسعة الإصدارات القديمة من HTML. أشهر طريقة هي microformats، التي تستعمل الخاصيتين class و rel في HTML. خيارٌ آخر هو RDFa، التي صُمِّمَت لتعمل في XHTML لكنها صُدِّرَت لاحقًا إلى HTML أيضًا. لدى Microformats و RDFa نقاطُ قوةٍ وضعف. إذ تأخذان طريقًا مختلفًا تمامًا لتحقيق نفس الهدف: توسعة صفحات الويب بإضافة بُنى هيكلية جديدة لا تُمثِّل جزءًا من أساس لغة HTML. لا أنوي أن أقلب هذا الدرس إلى حربٍ بين الصيغ؛ وإنما أريد أن أُركِّز على خيارٍ ثالثٍ طوِّرَ بعد تعلم الدروس من microformats و RDFa، وصُمِّمَ ليندمج جيدًا مع HTML5: إنه "البيانات الوصفية" (microdata). ما هي البيانات الوصفية؟ كل كلمة في الجملة الآتية مهمة، لذا انتبه جيدًا إليها: حسنًا، ماذا تعني الجملة السابقة العجيبة؟ لنبدأ من نهايتها إلى بدايتها. تتمحور البيانات الوصفية (microdata) حول "أنواع الاصطلاحات المخصصة" (custom vocabularies). تخيّل أنَّ جميع عناصر HTML5 هي نوعٌ وحيدٌ من الاصطلاحات. وهذا النوع يستطيع تمثيل "قسم" (section) أو "مقالة" (article)، لكنه لا يستطيع تضمين عناصر لتمثيل "شخص" أو "حدث". فإذا أردت تمثيل "شخص" في صفحة الويب، فعليك تعريف نوع اصطلاحاتٍ خاص بك. تسمح لك البيانات الوصفية (microdata) بذلك، حيث يستطيع أيُّ شخصٍ أن يُعرِّف اصطلاحات microdata خاصة به ويبدأ بتضمين خاصياته (properties) في صفحات الويب التي يطورها. النقطة الثانية التي عليك أن تعرفها عن البيانات الوصفية أنَّه تعمل وفق ثنائيات "الاسم/القيمة". فكل نوع اصطلاحات يُعرِّف مجموعةً من الخاصيات التي لها أسماءٌ معيّنة. على سبيل المثال، قد يتضمن نوع الاصطلاحات "person" خاصياتٍ مثل name و image. ولتضمين خاصية من خاصيات البيانات الوصفية (microdata) في صفحة الويب، عليك وضع اسم الخاصية في مكانٍ معيّن. واعتمادًا على العنصر الذي تضع فيه خاصيتك، هنالك قواعد حول كيفية استخلاص قيمة الخاصية (سنأتي على ذكرها في القسم التالي). بالإضافة إلى الخاصيات التي لها أسماء، تعتمد البيانات الوصفية كثيرًا على مفهوم "المجال" (scope). أبسط طريقة تستطيع تخيّل المجالات في البيانات الوصفية هي أن تتخيل علاقة "الأب-الابن" بين عناصر DOM. العنصر <html> يحتوي عادةً على عنصرين: <head> و <body>. العنصر <body> يحتوي عادةً على عدِّة أبناء وقد يحتوي كلٌ منها على أبناء خاصة به. على سبيل المثال، قد تحتوي صفحة الويب على عنصر <h1> موجودٌ ضمن عنصر <hgroup> موجودٌ داخل عنصر <header> الموجود داخل عنصر <body>. وقد يحتوي جدولٌ ما على خلية <td> موجودة ضمن <tr> الموجود ضمن <table> (الذي يتفرّع من <body>). تُعيد البيانات الوصفية استخدام هذه البنية الهيكلية لشجرة DOM لتوفير طريقة لقول "جميع الخاصيات ضمن هذا العنصر مأخوذةٌ من نوع الاصطلاحات المُحدَّد". وهذا يسمح لك باستخدام أكثر من نوع من أنواع الاصطلاحات في البيانات الوصفية في نفس الصفحة. تستطيع أيضًا أن تضع نوعًا من أنواع اصطلاحات البيانات الوصفية ضمن نوعٍ آخر، وذلك عبر إعادة استخدام البنية الهيكلية لشجرة DOM (سأريك عدِّة أمثلة عن تداخل أنواع الاصطلاحات في دروسٍ قادمة). تحدثتُ سابقًا عن موضوع DOM، لكن دعني أستفيض قليلًا فيه. مهمة البيانات الوصفية هي إضافةُ مزيدٍ من الهيكلية إلى البيانات الظاهرة في صفحة الويب. ليس الغرض من البيانات الوصفية أن تكون صيغةُ بياناتٍ تعملُ بمفردها، وإنما هي مكمِّلةٌ للغة HTML وتعتمد عليها. وكما سترى في القسم التالي، تعمل البيانات الوصفية بأفضل صورة ممكنة عندما تستعمل HTML استعمالًا سليمًا، لكن اصطلاحات HTML غير كافية للتعبير عن كل ما نريده، لذلك أتت البيانات الوصفية (microdata) للتحكم الدقيق في البُنى الهيكلية للبيانات الموجودة في شجرة DOM. إذا كانت البيانات التي تحاول توصيفها غير موجودةٍ في شجرة DOM، فربما عليك أن تتراجع وتعيد التفكير فيما إذا كانت البيانات الوصفية هي الحل الصحيح لمشكلتك. هل أصبحت هذه الجملة واضحةً الآن؟ "توصِّف البيانات الوصفية شجرة DOM بثنائياتٍ على شكل "الاسم/القيمة" آتيةٌ من أنواع اصطلاحاتٍ مُخصَّصة". أرجو ذلك، ولنرى تطبيقاتٍ عمليةً عليها في بقية هذا السلسلة. النموذج الهيكلي للبيانات الوصفية تعريف نوع اصطلاحات البيانات الوصفية الخاص بك سهلٌ. تحتاج أولًا إلى مجال أسماء (namespace) الذي هو URL. رابط URL لمجال الأسماء يمكن أن يُشير إلى صفحة ويب موجودة، لكن هذا ليس ضروريًا. لنقل أنَّك تريد إنشاء نوع اصطلاحات لوصف "شخص ما". إذا كنتُ أملك النطاق schema.org فسأستعمل رابط URL الآتي http://schema.org/Person كمجال أسماءٍ لنوع اصطلاحات البيانات الوصفية. هذه طريقة سهلة لإنشاء مُعرِّف فريد عالمي: اختر رابط URL في نطاقٍ تملكه. سأحتاج إلى تعريف بعض الخاصيات في نوع الاصطلاحات، لنبدأ بثلاث خاصيات أساسية: name (اسمك الكامل) image (رابطٌ لصورةٍ لك) url (رابطٌ لموقعٍ يتعلق بك، مثل مدونتك أو حسابك على Google) بعض الخاصيات السابقة هي روابط URL، وبعضها الآخر مجرد نص بسيط. وتربُط كلُ واحدةٍ منها نفسها بنوعٍ معيّن من الشيفرات، وحتى قبل أن تبدأ بالتفكير عن البيانات الوصفية أو الاصطلاحات أو إلى ما هنالك… تخيّل أنَّ لديك صفحة لحساب المستخدم أو صفحة "About"، من المحتمل أنَّك ستضع اسمك كترويسة (ربما في عنصر <h1>)، أما صورتك ففي عنصر <img> ذلك لأنَّك تريد أن يراها زوار صفحتك، وستضع أيّة روابط URL مرتبطة بك في عناصر <a> ذلك لأنَّك تريد أن يتمكن زوار صفحتك من النقر عليها. ولنقل أيضًا أنَّ كامل معلوماتك الشخصية موجودةٌ في عنصر <section> لفصلها عن بقية محتويات الصفحة. إذًا: <section> <h1>Mark Pilgrim</h1> <p><img src="http://www.example.com/photo.jpg" alt="[me smiling]"></p> <p><a href="http://diveintomark.org/">weblog</a></p> </section> النموذج الهيكلي للبيانات الوصفية هو ثنائيات على شكل "الاسم/القيمة". يُعرَّف اسم الخاصية التي تتبع لبيانات الوصفية (مثل name أو image أو url في مثالنا) دومًا ضمن عنصر HTML. ثم تؤخذ قيمة تلك الخاصية من شجرة DOM للعنصر. ولأغلبية عناصر HTML، تكون قيمة الخاصية هي المحتوى النصي للعنصر، لكن هنالك عددٌ لا بأس به من الاستثناءات. العنصر القيمة <meta> الخاصية content <audio> الخاصية src <embed> <iframe> <img> <source> <video> <a> الخاصية href <area> <link> <object> الخاصية data <time> الخاصية datetime جميع العناصر الأخرى المحتوى النصي "إضافة البيانات الوصفية" إلى صفحتك هي مسألة إضافة بعض الخاصية إلى عناصر HTML التي لديك. أول شيء عليك فعله هو التصريح عن نوع الاصطلاحات الذي ستستخدمه، وذلك عبر الخاصية itemtype، أما الشيء الثاني الذي عليك دائمًا فعله هو التصريح عن مجال (scope) نوع الاصطلاحات، وذلك عبر الخاصية itemscope. جميع البيانات التي نريد هيكلتها في المثال الآتي موجودةٌ في عنصر <section>، لذا سنُعرِّف الخاصيتين itemtype و itemscope في العنصر <section>. <section itemscope itemtype="http://schema.org/Person"> اسمك هو أول المعلومات الموجودة ضمن عنصر <section>، وهو موجودٌ ضمن عنصر <h1>، وعنصر <h1> لا يملك أيّ معنى خاص في النموذج الهيكلي للبيانات الوصفية في HTML5، لذلك سيُصنَّف تحت بند "جميع العناصر الأخرى" حيث تكون قيمة الخاصية هي المحتوى النصي الموجود ضمن العنصر (سيعمل ما سبق بشكلٍ مماثل إن كان اسمك موجودًا ضمن عنصر <p> أو <div> أو <span>). <h1 itemprop="name">Mark Pilgrim</h1> السطر السابق يقول بالعربية: "هذه هي خاصية name لنوع الاصطلاحات http://schema.org/Person، وقيمة تلك الخاصية هي Mark Pilgrim". الخاصية التالية هي خاصية image، التي من المفترض أن تكون رابط URL، ووفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، "قيمة" خاصية البيانات الوصفية الموجودة في العنصر <img> هي قيمة الخاصية src، لكن لاحظ أنَّ رابط URL لصورتك الشخصية موجودٌ في خاصية <img src>، فكل ما علينا فعله هو التصريح أنَّ العنصر <img> يُمثِّل خاصية image. <p> <img itemprop="image" src="http://www.example.com/photo.jpg" alt="[me smiling]"> </p> السطر السابق يقول بالعربية: "هذه هي قيمة خاصية image لنوع الاصطلاحات http://schema.org/Person، وقيمة الخاصية هي http://www.example.com/photo.jpg". في النهاية، الخاصية url هي رابط URL أيضًا، ووفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، "قيمة" خاصية البيانات الوصفية الموجودة في العنصر <a> هي قيمة الخاصية href، وهذا يتوافق توافقًا مثاليًا مع الشيفرة الموجودة عندك؛ فكل ما تحتاج له هو أن تقول أنَّ عنصر <a> الموجود مسبقًا يُمثِّل الخاصية url: <a itemprop="url" href="http://diveintomark.org/">dive into mark</a> السطر السابق يقول بالعربية: "هذه هي قيمة خاصية url لنوع الاصطلاحات http://schema.org/Person، وقيمة الخاصية هي http://diveintomark.org/‎". إذا كانت شيفراتك مختلفةً قليلًا فلن تُشكِّل لك مشكلةً بكل تأكيد. يمكنك إضافة خاصيات البيانات الوصفية وقيمتها إلى أيّ شيفرة من شيفرات HTML، حتى الشيفرات من القرن العشرين التي كانت تستعمل الجداول لإنشاء تخطيط الصفحة. وبغض النظر عن أنني لا أنصحك بتاتًا بكتابة مثل هذه الشيفرات، لكنها للأسف ما تزال شائعةً، وما يزال بإمكاننا إضافة خاصيات البيانات الوصفية إليها. <table> <tr><td>Name<td>Mark Pilgrim <tr><td>Link<td> <a href=# onclick=goExternalLink()>http://diveintomark.org/</a> </table> أضف خاصية itemprop في خلية الجدول التي تحتوي على الاسم لإنشاء الخاصية name. خلايا الجدول لا تملك أيّ معنى خاص في النموذج الهيكلي للبيانات الوصفية في HTML5، لذلك ستكون قيمة الخاصية هي المحتوى النصي الموجود ضمن الخلية. <tr> <td>Name<td itemprop="name">Mark Pilgrim إضافة الخاصية url أصعب بقليل، حيث لا تستعمل الشيفرة السابقة العنصر <a> استعمالًا سليمًا، فبدلًا من وضع رابط للصفحة الهدف في خاصية href، فستستعمل JavaScript والخاصية onclick لاستدعاء دالة (غير معروضة هنا) التي تستخلص رابط URL ثم تنتقل إليه. ولكي تُصاب بالغثيان، لنقل أنَّ تلك الدالة ستفتح الرابط في نافذة منبثقة صغيرة دون شريط تمرير. ألم يكن الويب مسليًا في القرن الماضي؟ على أيّة حال، ما يزال متوجبًا عليك إضافة خاصية البيانات الوصفية، لكن عليك أن تكون مبدعًا قليلًا. لا يمكنك استخدام عنصر <a> إذ أنَّ الرابط الهدف ليس موجودًا في خاصية href، ولا توجد طريقةٌ تستطيع فيها تجاوز القاعدة التي تقول "في العنصر <a>، ابحث عن قيمة خاصية البيانات الوصفية في href"، لكنك تستطيع إضافة عنصر ليحتوي الفوضى السابقة، وتُضيف الخاصية url إليه. <table itemscope itemtype="http://schema.org/Person"> <tr><td>Name<td>Mark Pilgrim <tr><td>Link<td> <span itemprop="url"> <a href=# onclick=goExternalLink()>http://diveintomark.org/</a> </span> </table> ولعدم وجود قاعدة خاصة تنطبق على العنصر <span>، فستُستعمَل القاعدة الافتراضية "قيمة الخاصية هي المحتوى النصي الموجود ضمن العنصر". "المحتوى النصي" لا يعني "جميع الشيفرات داخل العنص" (كالتي تحصل عليها عبر خاصية innerHTML في DOM على سبيل المثال)، وإنما تعني "النص فقط". وفي هذه الحالة يكون المحتوى النصي لعنصر <a> الموجود ضمن العنصر <span> هو http://diveintomark.org/‎. الخلاصة يمكنك إضافة خاصية البيانات الوصفية إلى أي شيفرة. وإذا كنتَ تستعمل HTML بشكلٍ صحيح، فستجد أنَّ إضافة البيانات الوصفية أسهل وأيسر فيما لو كانت شيفرة HTML مشوهةً، لكنك تستطيع إضافة البيانات الوصفية إليها على أيّة حال. ترجمة -وبتصرّف- لفصل "Microdata" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  9. يشيع في بيئات الخواديم الوهمية (VPS) تواجد عدد من البرامج الصغيرة التي تريدها أن تعمل عملًا متواصلًا، سواءً كانت سكربتات صدفة (shell scripts) صغيرة، أو تطبيقات Node.js، أو أيّة تطبيقات كبيرة أخرى. ستكتب عادةً سكربت init لكل برنامج من تلك البرامج، لكن سرعان ما ستستهلك هذه الطريقة وقتًا لإدارتها، ولن يسهل للمستخدمين الجدد استعمالها. Supervisor هو مدير للعمليات الذي يجعل إدارة عدد من البرامج التي تعمل عملًا متواصلًا أمرًا هينًا بتوفير واجهة موحدة يمكنك خلالها مراقبة تلك البرامج والتحكم بها. سنفترض في درسنا هذا أنَّك معتادٌ على استعمال سطر الأوامر وتثبيت الحزم وإجراء بعض أمور الإدارة البسيطة على الخادوم. التثبيت تثبيت Supervisor على توزيعتَي أوبنتو ودبيان سهلٌ للغاية، إذ تتوفر حزمٌ مبنيةٌ مسبقًا في مستودع كلا التوزيعتين. نفِّذ الأمر الآتي بحساب الجذر لتثبيت حزمة Supervisor: apt-get install supervisor سيُشغّل "عفريت" (daemon) خدمة supervisor مباشرةً بعد إكمال التثبيت، لأنَّ الحزم التي ثبتها تحتوي على سكربت init الذي سيُشغِّل Supervisor بعد إعادة تشغيل النظام. يمكنك التأكد من ذلك بتنفيذ الأمر: service supervisor restart يمكننا الآن أن نتعلم كيف نستطيع إضافة البرامج بعد أن ثبتنا Supervisor على نظامنا. إضافة برنامج تضاف البرامج الجديدة إلى Supervisor عبر ملفات الضبط، التي تُعلِم Supervisor ما هو الملف التنفيذي الذي عليه تشغيله، وما هي متغيرات البيئة (environmental variables) التي يجب ضبطها، وآلية التعامل مع المخرجات. ملاحظة: جميع البرمجيات التي تعمل ضمن Supervisor يجب أن تُشغَّل في الأمامية ("foreground" وأحيانًا تسمى "non-daemonising"). فلو كان البرنامج -افتراضيًا- يشتق العملية fork ويشغل نفسه بالخلفية، فيجب أن تعود إلى دليل ذاك البرنامج لكي تعرف ما هو الخيار المستخدم لتفعيل هذا النمط، وإلا فلن يستطيع Supervisor تحديد حالة البرنامج تحديدًا صحيحًا. لغرض هذا الدرس، سنفترض وجود سكربت صَدَفة (shell script) الذي نريد أن نشغله بشكلٍ دائم والمحفوظ في المسار ‎/usr/local/bin/long.sh ويبدو كالآتي: #!/bin/bash while true do # Echo current date to stdout echo `date` # Echo 'error!' to stderr echo 'error!' >&2 sleep 1 done chmod +x /usr/local/bin/long.sh من الناحية العملية، السكربت السابق عديم الجدوى، لكنه يسمح لنا بتغطية أساسيات ضبط Supervisor. ملفات ضبط البرمجيات في Supervisor موجودةٌ في مجلد ‎/etc/supervisor/conf.d، يوضع عمومًا كل برنامج في ملف خاص به ذي اللاحقة ‎.conf مثالٌ عن ملف الضبط للسكربت الخاص بنا الذي سنحفظه في المسار ‎/etc/supervisor/conf.d/long_script.conf سيبدو كما يلي: [program:long_script] command=/usr/local/bin/long.sh autostart=true autorestart=true stderr_logfile=/var/log/long.err.log stdout_logfile=/var/log/long.out.log سننظر الآن لوظيفة وأهمية كل سطر من الأسطر السابقة، وسأخبرك بالتعديلات التي قد ترغب بإجرائها عند إضافة ملفات ضبط لبرامجك: [program:long_script] command=/usr/local/bin/long.sh يبدأ ملف الضبط بتعريف برنامج اسمه "long_script" ويُحدِّد المسار الكامل للبرنامج. autostart=true autorestart=true السطران السابقان يُحدَّدان السلوك التلقائي الافتراضي للسكربت في مختلف الحالات. الخيار autostart يخبر Supervisor أنَّ البرنامج يجب أن يُشغَّل عند إقلاع النظام. ضبط هذه القيمة إلى false سيستوجب تشغيلًا يدويًا للبرنامج بعد كل إعادة إقلاع للنظام. الخيار autorestart يُحدِّد كيف على Supervisor أن يتعامل مع البرنامج في حال توقف عن العمل، وهنالك ثلاثة خيارات: false: إخبار Supervisor ألّا يعيد تشغيل البرنامج إطلاقًا بعد توقفه عن العمل true: إخبار Supervisor أن يعيد تشغيل البرنامج دومًا في حال توقف عن العمل unexpected: إخبار Supervisor أن يُعيد التشغيل في حال انتهى تنفيذ البرنامج بعد حدوث خطأ (افتراضيًا: أي قيمة ما عدا 0 و 2، وهذه القيمة هي قيمة حالة الخروج [exit status] التي يرسلها البرنامج بعد انتهاء تنفيذه). stderr_logfile=/var/log/long.err.log stdout_logfile=/var/log/long.out.log السطران الأخيران يُعرِّفان المسار الافتراضي لملفَي السجل الخاصَين بالبرنامج. وكما هو واضح من اسمَي الخيارين: سيُعاد توجيه مجرى الخرج القياسي (stdout) ومجرى الخطأ القياسي (stderr) إلى المسارَين الموجودَين في الخيارَين stdout_logfile و stderr_logfile على التوالي وبالترتيب. يجب أن يكون المجلد المُحدَّد موجودًا قبل تشغيل البرنامج، إذ لن يحاول Supervisor أن يُنشِئ أيّة مجلدات غير موجودة. الضبط الذي أنشأناه هو أقل ضبط ممكن لكي يعمل Supervisor. يحتوي التوثيق على خياراتِ ضبطٍ أخرى كثيرة لتخصيص كيفية تنفيذ البرنامج. بعد إنشاء وحفظ ملف الضبط، سنتمكن من إعلام Supervisor عن برنامجنا الجديد عبر الأمر supervisorctl. علينا أولًا أن نطلب من Supervisor أن يعيد قراءة ملفات الضبط الموجودة في مجلد ‎/etc/supervisor/conf.d عبر الأمر: supervisorctl reread ثم نُتبِعُ ذلك بالطلب منه أن يتصرف وفقًا لما تغيّر بالأمر: supervisorctl update عندما تُجري أيّة تعديلات على أي ملف ضبط لأي برنامج، فعليك تنفيذ الأمرين السابقين لتأخذ التعديلات مجراها. يجب أن يعمل برنامج الآن، ويمكنك التحقق من ذلك بالنظر إلى ناتج ملف السجل: $ tail /var/log/long.out.log Sat Jul 20 22:21:22 UTC 2013 Sat Jul 20 22:21:23 UTC 2013 Sat Jul 20 22:21:24 UTC 2013 Sat Jul 20 22:21:25 UTC 2013 Sat Jul 20 22:21:26 UTC 2013 Sat Jul 20 22:21:27 UTC 2013 Sat Jul 20 22:21:28 UTC 2013 Sat Jul 20 22:21:29 UTC 2013 Sat Jul 20 22:21:30 UTC 2013 Sat Jul 20 22:21:31 UTC 2013 إدارة البرامج من المؤكد أنَّه سيحين وقتٌ تريد فيه أن توقف أو تعيد تشغيل أو ترى حالة برامجك المُشغَّلة. لدى برنامج supervisorctl -الذي استخدمناه في الأعلى- نمطٌ تفاعليٌ نستطيع عبره إعطاء أوامر للتحكم بالبرامج. أدخِل supervisorctl دون وسائط للدخول إلى النمط التفاعلي: $ supervisorctl long_script RUNNING pid 12614, uptime 1:49:37 supervisor> سيعرض supervisorctl (عند تشغيله) حالة وزمن تشغيل جميع البرامج، وذلك متبوعٌ بإظهار مِحَث (prompt) لإدخال الأوامر. أدخِل الأمر help لإظهار جميع الأوامر التي نستطيع استخدامها: supervisor> help default commands (type help ): ===================================== add clear fg open quit remove restart start stop update avail exit maintail pid reload reread shutdown status tail version يمكنك استخدام الأوامر start و stop و restart متبوعةً باسم البرنامج الذي تريد تشغيله أو إيقافه أو إعادة تشغيله على التوالي: supervisor> stop long_script long_script: stopped supervisor> start long_script long_script: started supervisor> restart long_script long_script: stopped long_script: started يمكنك استخدام الأمر tail لمشاهدة أحدث التغيرات التي طرأت في سجلات الخرج القياسي والخطأ القياسي في برنامجنا: supervisor> tail long_script Sun Jul 21 00:36:10 UTC 2013 Sun Jul 21 00:36:11 UTC 2013 Sun Jul 21 00:36:12 UTC 2013 Sun Jul 21 00:36:13 UTC 2013 Sun Jul 21 00:36:14 UTC 2013 Sun Jul 21 00:36:15 UTC 2013 Sun Jul 21 00:36:17 UTC 2013 supervisor> tail long_script stderr error! error! error! error! error! error! Error! يمكننا عرض حالة البرامج عبر الأمر status للتأكد من وضعها بعد إجراء أيّة تعديلات على إحداها: supervisor> status long_script STOPPED Jul 21 01:07 AM وفي النهاية، يمكنك الخروج من النمط التفاعلي بالضغط على Ctrl-C أو بكتابة quit في المِحَث: supervisor> quit هذا كل ما في الأمر! لقد تعلمتَ أساسيات إدارة البرامج دائمة التشغيل عبر Supervisor، ولن يصعب عليك فعل المِثل لبقية برامجك. إذا كانت لديك أيّة استفسارات، فاسألها في التعليقات. ترجمة -وبتصرّف- للمقال How To Install and Manage Supervisor on Ubuntu and Debian VPS.
  10. مدخل إلى html5 النماذج (Forms) في HTML5

    كلنا نعرف نماذج الويب، أليس كذلك؟ أنشِئ <form>، وبعض عناصر <input type="text"‎>، وربما عنصر <input type="password"‎> وإنهِ النموذج بزر <input type="submit"‎>. معلوماتك السابقة منقوصة، إذ تُعرِّف HTML5 ثلاثة عشر نوع إدخالٍ جديدٍ التي تستطيع استعمالها في نماذجك. لاحظ أنني ذكرتُ كلمة "تستطيع" بصيغة المضارع، أي أنَّك تستطيع استخدامها الآن دون أيّة أمور التفافية أو إضافات مُخصَّصة. لكن لا تتحمس كثيرًا؛ فلم أكن أقصد أنَّ جميع تلك الميزات الرائعة مدعومة في كل متصفح؛ ما أقصده هو أنَّ تلك النماذج ستعمل بشكلٍ رائع في المتصفحات الحديثة، لكنها ستبقى تعمل في المتصفحات القديمة (بما في ذلك IE6) وإن لم يكن سلوكها مماثلًا لسلوكها في المتصفحات الحديثة. النص البديل placeholder IE Fiefox Safari Chrome Opera iPhone Android 10+ 3.7+ 4.0+ 4.0+ 11.0+ 4.0+ 2.3+ أول تحسين لنماذج الويب أتت به HTML5 هو القدرة على ضبط نص بديل (placeholder text) في حقل الإدخال. "النص البديل" هو النص الذي سيُعرَض داخل حقل الإدخال لطالما كان حقل الإدخال فارغًا، وسيختفي النص البديل بعد بدء الكتابة في الحقل. من المرجَّح أنَّك شاهدت نصًا بديلًا من قبل، فمتصفح Firefox فيه نصٌ بديلٌ في شريط العنوان مكتوبٌ فيه "Search Bookmarks and History" (النص البديل في الإصدارات الحديثة هو "Search" فقط): وعندما تهمُّ بكتابة أيّ شيءٍ فيه، فسيختفي النص البديل: هذه آلية تضمين النص البديل في نماذج الويب الخاصة بك: <form> <input name="q" placeholder="Search Bookmarks and History"> <input type="submit" value="Search"> </form> ستتجاهل المتصفحاتُ التي لا تدعم النص البديل الخاصيةَ placeholder ببساطة. انظر إلى الجدول أعلاه لمعرفة ما هي المتصفحات التي تدعم النص البديل. س: هل يمكنني وضع وسوم HTML في خاصية placeholder؟ أريد أن أضيف صورةً أو أن أغيِّرَ الألوان. ج: لا يمكن أن تحتوي خاصية placeholder إلا على نصٍ بسيط، ولا يُسمَح بوضع وسوم HTML فيها. لكن هنالك إضافات CSS تسمح لك بتنسيق النص البديل في بعض المتصفحات. التركيز التلقائي على الحقول autofocus IE Fiefox Safari Chrome Opera iPhone Android 10+ 4.0+ 5.0+ 5.0+ 10.1+ . 3.0+ يمكن لمواقع الويب استخدام JavaScript للتركيز على حقلٍ للإدخال في نموذج الويب تلقائيًا. على سبيل المثال، الصفحة الرئيسية لمحرك البحث Google ستُركِّز تلقائيًا (auto-focus) على حقل البحث لكي تستطيع البدء في كتابة عبارة البحث مباشرةً؛ وعلى الرغم من أنَّ هذا الأمر ملائمٌ للكثيرين، لكنه قد يُزعِج المستخدمين المتقدمين أو أولي الاحتياجات الخاصة؛ فلو ضغطتَ على زر المسافة (space) متوقعًا أن تُمرَّر الصفحة إلى الأسفل، فستفاجأ بعدم التمرير، لأنَّ مؤشر الكتابة موجودٌ في حقلٍ من حقول النموذج (سيُكتَب فراغ في ذاك الحقل بدلًا من التمرير). وإن ضغطت على حقل إدخالٍ مختلف أثناء تحميل الصفحة وبدأت الكتابة فيه، فسيأتي سكربت التركيز التلقائي (بعد اكتمال تحميل الصفحة) و"يُساعِدُك" وينقل التركيز إلى حقل الإدخال الأصلي، مما يجعلك تكتب في حقلٍ مختلف، ويقطع عليك «سلسلة أفكارك». ولمّا كان التركيز التلقائي يُنفَّذ عبر JavaScript، فمن الصعب التعامل مع كل الحالات الغريبة؛ وليس هنالك منقذٌ لِمَن يريدون تعطيل ميزة التركيز التلقائي. لحل هذه المشكلة، وفَّرَت HTML5 خاصية autofocus لجميع عناصر التحكم في نماذج الويب. عمل الخاصية autofocus واضحٌ من اسمها: نقل التركيز إلى حقل إدخال مُعيّن في أقرب فرصة ممكنة عند تحميل الصفحة. ولكن لمّا كانت هذه الخاصية موجودة في HTML وليست مُطبَّقة عبر JavaScript، فسيكون سلوكها متماثلًا في جميع مواقع الويب وعلى جميع المتصفحات. وسيتمكن مطورو المتصفحات (أو مطورو الإضافات) من منح المستخدمين إمكانية تعطيل التركيز التلقائي تمامًا. هذا مثالٌ عن كيفية التركيز التلقائي عبر الخاصية autofocus: <form> <input name="q" autofocus> <input type="submit" value="Search"> </form> المتصفحات التي لا تدعم الخاصية autofocus ستتجاهلها تمامًا. انظر إلى الجدول أعلاه لتعرف ما هي المتصفحات التي تدعم خاصية autofocus. هل تريد أن تعمل ميزة التركيز التلقائي في جميع المتصفحات، وليس تلك التي تدعم HTML5 فقط؟ يمكنك الاستمرار في استخدام سكربت التركيز التلقائي، لكن عليك إجراء تعديلين بسيطين: أضف الخاصية autofocus إلى شيفرة HTML اكتشف إذا كان المتصفح يدعم الخاصية autofocus، وشغِّل سكربت التركيز التلقائي إن لم يكن يدعم المتصفح الخاصية autofocus داخليًا. <form name="f"> <input id="q" autofocus> <script> if (!("autofocus" in document.createElement("input"))) { document.getElementById("q").focus(); } </script> <input type="submit" value="Go"> </form> ضبط التركيز التلقائي في أقرب فرصة ممكنة تنتظر العديد من صفحات الويب إلى أن يقع الحدث window.onload لكي تضبط التركيز؛ لكن الحدث window.onload لا يُفعَّل إلا بعد تحميل جميع الصور؛ وإذا حوَت صفحتك على الكثير من الصور، فمن المحتمل أن السكربت الذي ستستخدمه سيؤدي إلى إعادة التركيز على حقل الإدخال المُعيّن بعد أن يبدأ المستخدم تفاعله مع قسمٍ آخر في صفحتك. هذا هو سبب كره المستخدمين المتقدمين لسكربتات التركيز التلقائي. وضعنا سكربت التركيز التلقائي في المثال الموجود في القسم السابق بعد حقل الإدخال مباشرةً؛ وهذا حلٌ مثاليٌ لمشكلتنا، لكن قد ترى أنَّ وضع شيفرة JavaScript في منتصف الصفحة هو أمرٌ سيئ (أو قد لا يسمح لك السند الخلفي [back-end] بذلك)؛ فإن لم تستطع وضع السكربت في منتصف الصفحة، فعليك أن تضبط التركيز أثناء وقوع حدث مخصص مثل ‎$(document).ready()‎ في jQuery بدلًا من window.onload. <head> <script src=jquery.min.js></script> <script> $(document).ready(function() { if (!("autofocus" in document.createElement("input"))) { $("#q").focus(); } }); </script> </head> <body> <form name="f"> <input id="q" autofocus> <input type="submit" value="Go"> </form> تُفعِّل مكتبة jQuery الحدث الخاص ready في أقرب فرصة تتوفر فيها شجرة DOM للصفحة، أي أنها تنتظر إلى أن ينتهي تحميل النص في الصفحة، لكنها لن تنتظر تحميل جميع الصور فيها. لكن هذا ليس حلًا مثاليًا، فإن كانت الصفحة كبيرةً جدًا أو كان الاتصال بطيئًا للغاية، فقد يبدأ المستخدم تفاعله مع الصفحة قبل تنفيذ سكربت التركيز التلقائي؛ إلا أنَّ هذا الحل أفضل بكثير من انتظار وقوع الحدث window.onload. إذا كنت قادرًا على إضافة تعبير JavaScript وحيد في شيفرة صفحتك، فهنالك حلٌ وسطٌ بين الأمرين. يمكنك استخدام الأحداث الخاصة في jQuery لتعريف حدث خاص بك، ولنقل أنَّ اسمه هو autofocus_ready. ثم تستطيع تفعيل هذا الحدث يدويًا بعد أن تُتاح خاصية autofocus مباشرةً. <head> <script src=jquery.min.js></script> <script> $(document).bind('autofocus_ready', function() { if (!("autofocus" in document.createElement("input"))) { $("#q").focus(); } }); </script> </head> <body> <form name="f"> <input id="q" autofocus> <script>$(document).trigger('autofocus_ready');</script> <input type="submit" value="Go"> </form> هذا الحل مثاليٌ مثل الحل الأول، حيث يضبط التركيز إلى الحقل المُحدَّد في أقرب فرصة ممكنة، وذلك أثناء تحميل بقية نص الصفحة. لكنه ينقل تنفيذ التسلسل المنطقي لتطبيقك (التركيز على حقل الإدخال) من جسم الصفحة إلى ترويستها. يعتمد المثال السابق على مكتبة jQuery، لكن مفهوم الأحداث الخاصة ليس مقتصرًا على jQuery فحسب، فلدى مكتبات JavaScript الأخرى مثل YUI و Dojo إمكانياتٌ مشابهة. الخلاصة هي: من المهم ضبط التركيز التلقائي ضبطًا سليمًا. يُفضَّل أن تدع المتصفح يضبط التركيز التلقائي عبر خاصية autofocus في حقل الإدخال الذي تريد التركيز تلقائيًا عليه إذا كان ذلك ممكنًا. إذا كنتَ تريد حلًا للمتصفحات القديمة، فاكتشف أولًا دعم المتصفح للخاصية autofocus لكي تتأكد أنَّ السكربت الذي كتبتَه سيُنفَّذ على المتصفحات القديمة فقط. حاول ضبط التركيز التلقائي في أقرب فرصة ممكنة، حاول مثلًا أن تضع سكربت التركيز في شيفرة HTML بعد حقل الإدخال مباشرةً. فإن لم تستطع، فاستعمل مكتبة JavaScript تدعم الأحداث المُخصَّصة، وفعِّل الحدث المُخصَّص مباشرةً بعد شيفرة النموذج؛ وإن لم يكن ذلك ممكنًا، فاعتمد على حدثٍ مثل الحدث ‎$(document).ready()‎ في مكتبة jQuery. لا تنتظر الحدث window.onload لكي يضبط التركيز تحت أيّ ظرفٍ كان. عناوين البريد الإلكتروني لفترةٍ تجاوزت العقد من الزمن، احتوت نماذج الويب على عدِّد قليلٍ من الحقول، وكان أكثرها شيوعًا: نوع الحقل شيفرة HTML ملاحظات مربع تأشير (checkbox) "input type="checkbox يمكن تفعيله أو تعطيله زر انتقاء (radio button) "input type="radio يمكن تجميعه مع حقول أخرى حقل كلمة مرور input type="password"‎ يُظهِر نقطًا بدلًا من المحارف التي تكتبها قائمة منسدلة <select> </select> - مُنتقي الملفات "input type="file يُظهِر مربع حوار "اختيار ملف" زر الإرسال "input type="submit - نص عادي ‎input type="text"‎‎ يُمكن حذف الخاصية type تعمل جميع أنواع الحقول السابقة في HTML5، فلو أردت "التحديث إلى HTML5" (ربما بتغيير نوع المستند DOCTYPE)، فلن تحتاج إلى إجراء أيّة تعديلات على نماذج الويب عندك، والفضل بذلك يعود إلى توافقية HTML5 مع الإصدارات التي تسبقها. لكن HTML5 أضافت ثلاثة عشر نوعًا جديدًا من الحقول، ولأسبابٍ ستتضح لك بعد قليل، لا يوجد سببٌ يمنعك من استعمالها الآن. أول أنواع المدخلات الجديدة مخصصٌ لعناوين البريد، ويبدو كما يلي: <form> <input type="email"> <input type="submit" value="Go"> </form> أوشكتُ على كتابة جملةٍ مطلعها: "أما في المتصفحات التي لا تدعم type="email"‎..." لكنني تداركتُ نفسي وتوقفت. لكن ما السبب؟ لأنني لستُ متأكدًا من ماذا يعني عدم دعم المتصفح للحقل type="email"‎، حيث «تدعم» جميع المتصفحات type="email"‎‎، لكنها قد لا تفعل شيئًا خاصًا لها (سترى بعد قليل بعض الأمثلة عن المعاملة الخاصة لهذا الحقل)؛ لكن المتصفحات التي لا تتعرف على type="email"‎ ستعامله على أنَّه type="text"‎ وسيظهر كحقلٍ نصيٍ عاديٍ. لا تسعني الكلمات للتعبير عن مدى أهمية ما سبق، لأنَّ في الويب ملايين النماذج التي تسألك أن تدخِل عنوان بريدك الإلكتروني، وجميعها تستعمل <input type="text"‎> وستشاهد مربعًا نصيًا، ثم تُدخِل فيه عنوان بريدك، وانتهى. ثم أتت HTML5 التي أضافت type="email"‎، فهل سترتبك المتصفحات؟ لا، يُعامِل كل متصفح على وجه هذا الكوكب القِيم غير المعروفة لخاصية type على أنها type="text"‎، بما في ذلك IE 6. لذلك تستطيع استعمال type="email"‎ حالًا دون القلق حول دعم المتصفحات. لكن ماذا يعني أنَّ المتصفح "يدعم" الحقل type="email"‎؟ حسنًا، قد يعني هذا عدِّة أشياء. لم تُحدِّد مواصفة HTML5 أيّة توصية حول الواجهة الرسومية التي تظهر للمستخدم لأنواع المدخلات الجديدة. ستعرضه أغلبية متصفحات سطر المكتب مثل Safari و Chrome و Opera و Firefox كحقلٍ نصيّ؛ ولهذا لن يُلاحِظ مستخدمو موقعك الفرق (إلى أن يُحاولوا إرسال النموذج). ثم ها قد أتت الهواتف المحمولة… ليس للهواتف المحمولة لوحة مفاتيح فيزيائية، فكل "الكتابة" تتم بالضغط على لوحة مفاتيح ظاهرة على الشاشة التي تُعرَض عند الحاجة لها (عند التركيز على حقل من حقول أحد النماذج في صفحة الويب على سبيل المثال). تتعرف متصفحات الهواتف المحمولة الذكية على العديد من أنواع المدخلات الجديدة في HTML5، وتُجري تعديلات ديناميكية على لوحة المفاتيح الظاهرة على الشاشة لكي تُلائم نوع المدخلات. مثلًا، عناوين البريد الإلكتروني هي نصوص، صحيح؟ بالطبع، لكنها نوع خاص من النصوص، فمثلًا يحتوي كل عنوان بريد إلكتروني (نظريًا) على رمز @ وعلى نقطة (.) واحدة على الأقل؛ ومن غير المحتمل أن يحتوي على فراغات؛ لذا لو كنتَ تستعمل هاتف iPhone وحاولت الكتابة في عنصر <input type="email‎"‎>، فستظهر لوحة مفاتيح تحتوي على زر مسافة أصغر من المعتاد، بالإضافة إلى أزرار مخصصة لمحرفَي @ و . . لا توجد سلبيات لتحويل جميع الحقول التي تُمثِّل عناوين البريد الإلكتروني إلى type="email"‎ في الحال. فلن يلاحظ ذلك أحدٌ إلا مستخدمي الهواتف المحمولة، الذين أظن أنَّهم لن ينتبهوا لذلك أيضًا. لكن من سيلاحظ ذلك سيبتسم بصمت ويشكرك في قلبه لأنَّك سهلت عليه الأمر قليلًا. عناوين الويب عناوين مواقع الويب -التي يُسمّيها مهووسو المعايير "URLs"، إلا بعض المتحذلقين الذين يدعونها "URIs"- هي نوعٌ آخرٌ من النص المُخصَص؛ البنية العامة لعناوين الويب مرتبطة بمعايير الإنترنت ذات الصلة. إذا طلب منك أحدهم كتابة عنوان لموقع ويب في نموذج، فسيتوقعون منك كتابة شيءٍ مثل "http://www.google.com/‎" وليس "125 Farwood Road"؛ ومن الشائع استخدام الخط المائل / في العناوين (الخط المائل مذكورٌ ثلاث مرات في عنوان صفحة Google الرئيسية)؛ وينتشر استخدام النقط . أيضًا، لكن يُمنَع وضع فراغات في العنوان. لدى جميع عناوين الويب لاحقة للنطاق مثل "‎.com" أو "‎.org". تعرض أغلبية متصفحات الويب لسطح المكتب الحديثة حقل type="url"‎ كحقلٍ نصيّ عادي، لذلك لن يُلاحظ مستخدمو موقعك ذلك إلى أن يحاولوا أن يُرسِلوا النموذج. ستُعامِل المتصفحات التي لا تدعم HTML5 الحقل type="url"‎ كحقل type="text"‎ تمامًا، فلا بأس من استعمال هذا الحقل في صفحات الويب الحديثة عند الحاجة. ستُعدِّل الهواتف الذكية من طريقة عرض لوحة المفاتيح كما في حقل عنوان البريد الإلكتروني، لكن لوحة المفاتيح في هذه المرة ستُخصَّص لتسهيل إدخال عناوين الويب. ففي هواتف iPhone سيُزال زر المسافة تمامًا وسيُستعاض عنه بنقطة وخط مائل وزر "‎.com" (يمكنك الضغط مطولًا على زر "‎.com" للاختيار بين اللاحقات الشهيرة الأخرى مثل "‎.org" أو "‎.net")؛ وستُخصِّص هواتف أندرويد لوحة مفاتيحها بشكلٍ مشابه. إدخال الأرقام طلب إدخال الأرقام أصعب من طلب كتابة عنوان بريد إلكتروني أو موقع ويب؛ لأنَّ الأرقام معقدة أكثر مما تظن. اختر رقما ما بسرعة. -1؟ لا، كنت أقصد رقمًا بين 1 و 10. ‎7½‎؟ لا، ليس رقمًا كسريًا. π؟ لماذا تختار الأرقام العجيبة؟! الفكرة التي أود إيصالها هي أنَّك لا تسأل عن "رقمٍ ما"، فمن المحتمل أنَّك ستطلب من المستخدم إدخال رقم في مجال معيّن، ولا تريد إلا نوعًا محدَّدًا من الأرقام ضمن ذلك المجال، وقد تريد استبعاد الأعداد الكسرية أو العشرية، أو أن تسمح بإدخال الأرقام التي تقبل القسمة على 10. ستسمح لك HTML5 بكل هذا. <input type="number" min="0" max="10" step="2" value="6"> لنتحدث عن الخاصيات السابقة كلًا على حدة (يمكنك المتابعة مع المثال الحي إن شئت). الخاصية type="number"‎ تعني أنَّ الحقل يقبل الأرقام. الخاصية min="0"‎ تُحدِّد القيمة الدنيا المقبولة لهذا الحقل. الخاصية max="10"‎ تُحدِّد القيمة القصوى المقبولة. الخاصية step="2"‎ مجتمعةً مع قيمة الخاصية min ستُعرِّف ما هي الأرقام المسموحة في المجال: 0 و 2 و 4 وهكذا إلى أن تصل إلى قيمة الخاصية max. الخاصية value="6"‎ تُحدِّد القيمة الافتراضية. يُفترَض أن تكون هذه الخاصية مألوفةً لديك، فهي نفس الخاصية التي تستعملها دومًا لتحديد قيم حقول النموذج (ذكرتُ هذه النقطة هنا لكي أذكِّرك أنَّ HTML5 مبنية على إصدارات HTML السابقة، فلا حاجة أن تعيد تعلم كل الأمور التي تعرفها من قبل!). هذه هي الشيفرة الخاصة بحقل الأرقام. ابقِ في ذهنك أنَّ جميع الخاصيات السابقة اختيارية. فإذا كانت لديك قيمة دنيا للمجال المقبول لكن دون وجود حد أقصى للأرقام، فيمكنك ضبط خاصية min وعدم ضبط الخاصية max. الخطوة الافتراضية هي 1، ويمكنك عدم ذكر الخاصية step إلا إذا كانت قيمة الخطوة عندك مختلفةً عن 1. تستطيع إسناد سلسلة نصية فارغة إلى الخاصية value إن لم تكن هنالك قيمةٌ افتراضيةٌ، أو بإمكانك حذف الخاصية تمامًا. لكن HTML5 لا تقف عند هذا الحد، حيث توفِّر لك دوال JavaScript للتحكم بهذا الحقل: الدالة (input.stepUp(n تزيد قيمة الحقل بمقدار n. الدالة (input.stepDown(n تنقص من قيمة الحقل مقدار n. الخاصية input.valueAsNumber تُعيد القيمة الحالية للحقل كعدد ذي فاصلةٍ عشرية (تذكَّر أنَّ الخاصية input.value تُعيد سلسلةً نصيةً دومًا). هل صَعُبَ عليك تخيل شكل هذا الحقل؟ حسنًا، طريقة عرض هذا الحقل عائدة تمامًا لمتصفحك، ويدعم مختلف مُصنِّعي المتصفحات هذا الحقل بطرائق مختلفة. فعلى هواتف iPhone -التي يصعب فيها كتابة المدخلات عمومًا- سيُحسِّن المتصفح من لوحة المفاتيح مرةً أخرى لكتابة الأرقام. أما في نسخة سطح المكتب من متصفح Opera، سيظهر نفس الحقل type="number"‎ كعنصر "spinbox" الذي يملك أسهمًا صغيرةً للأعلى والأسفل تستطيع الضغط عليها لتغيير القيمة. تحترم المتصفحات قيم الخاصيات min و max و step، لذلك ستكون قيمة ذاك الحقل مقبولة دومًا، فلو وصلت إلى القيمة القصوى، فسيُعطَّل زر السهم العلوي ولن تستطيع زيادة الرقم الموجود. وكما في بقية حقول الإدخال التي شرحناها سابقًا في هذا الدرس، المتصفحات التي لا تدعم type="number"‎ ستُعامِل الحقل وكأنَّه type="text"‎، وستظهر القيمة الافتراضية في الحقل النصي (لأنها مُخزَّنة في الخاصية value)، لكن سيتجاهل المتصفح الخاصيات الأخرى مثل min و max؛ لكنك تستطيع إنشاء spinbox بنفسك، أو قد تستعمل مكتبة JavaScript تحتوي على هذا العنصر؛ لكن تذكَّر أن تتحقق من دعم المتصفح لهذا الحقل أولًا، على سبيل المثال: if (!Modernizr.inputtypes.number) { //لا يوجد دعم لحقول type=number //ربما تجرِّب Dojo أو مكتبة JavaScript أخرى } تحديد الأرقام عبر المزلاج هنالك آليةٌ أخرى لتمثيل المدخلات الرقمية، فمن المحتمل أنَّك رأيك "مزلاجًا" (slider) من قبل يُشبِه: يمكنك وضع المزلاج في نماذج HTML5 أيضًا، والشيفرة الخاصة به شبيهة جدًا بحقل spinbox: <input type="range" min="0" max="10" step="2" value="6"> جميع الخاصيات المتوفرة مماثلة لحقل type="number"‎ (أي min و max و step و value)، ولها نفس المعنى. الفرق الوحيد هو في واجهة الاستخدام؛ فبدلًا من وجود حقل لكتابة الرقم، ستعرض المتصفحات الحديثة الحقل type="range"‎ كمزلاج، بينما ستعرضه المتصفحات القديمة التي لا تدعم HTML5 كحقل type="text"‎، لذا لا يوجد سبب يمنعك من البدء باستخدامه مباشرةً. منتقي التاريخ Date picker لم تتضمن HTML 4 حقلًا لاختيار التاريخ، لكن مكتبات JavaScript تداركت الأمر (Dojo و jQuery UI و YUI و Closure Library) لكن هذه الحلول كانت تتطلب الخوض في مكتبة JavaScript التي تدعم حقل مُنتقي التاريخ (date picker). أضافت HTML5 أخيرًا حقلًا لانتقاء التاريخ دون الحاجة إلى كتابته يدويًا عبر JavaScript؛ وفي الواقع، أضفات ستة حقول: واحد للتاريخ (date) وآخر للشهر (month) وآخر للأسبوع (week) وآخر للوقت (time) وآخر للتاريخ والوقت (date + time) وآخر للتاريخ والوقت لكن دون ذكر المنطقة الزمنية (date + time – timezone). لكن للأسف، هذا الحقل غير مدعوم من أغلبية المتصفحات. إذ يدعمه متصفح Opera منذ الإصدار التاسع و Chrome من الإصدار 20. هذه هي طريقة عرض متصفح Opera لحقل <input type="date"‎>: وإذا أردت من المستخدم انتقاء الوقت والتاريخ، فهنالك حقل <input type="datetime"‎>: أما لو كنت تحتاج إلى الشهر والسنة فقط (ربما تريد إدخال تاريخ انتهاء البطاقة الائتمانية)، فهنالك حقل <input type="month"‎>: ويتوفر أيضًا حقلٌ لانتقاء أسبوع معيّن في السنة –وإن لم يكن ذلك شائعًا– عبر الحقل <input type="week"‎>: أخيرًا وليس آخرًا، يمكنك اختيار الوقت عبر الحقل <input type="time"‎>: من المحتمل أن تدعم المتصفحات حقول الإدخال السابقة تباعًا، لكن كما في حقل type="email"‎ وغيره، ستُعرَض هذه الحقول كحقولٍ نصيةٍ بسيطةٍ في المتصفحات التي لا تدعم الحقل type="date"‎ وأخوته. يمكنك ببساطة أن تستعمل الحقل <input type="date"‎> وأخوته لتوفِّر مُنتقي التاريخ لمستخدمي متصفحَي Opera و Chrome وتنتظر دعم بقية المتصفحات. أو أن تعتمد حلًا عمليًا هو استعمال <input type="date"‎> ثم تكتشف إن كان المتصفح يدعم مُنتقي التاريخ، ثم تستعمل حلًا برمجيًا إن لم يكن يدعمه (مثل Dojo و jQuery UI و YUI و Closure Library أو حلًا آخر). <form> <input type="date"> </form> ... <script> var i = document.createElement("input"); i.setAttribute("type", "date"); if (i.type == "text") { //لا يوجد دعم لمنتقي التاريخ :-( //استخدام مكتبة Dojo/jQueryUI/YUI/Closure لإنشاء واحد //ثم استبدل حقل <input> ديناميكيًا } </script> حقول البحث حسنًا، وظيفة هذا الحقل واضحة من اسمه، لكن قد نحتاج إلى شرح آلية تطبيقه في المتصفحات. البحث لا يكون فقط في محركات البحث مثل Google أو Yahoo!‎، فمن الممكن أن يكون حقل البحث في أيّ صفحة وفي أيّ موقع؛ فهنالك واحدٌ في موقع أمازون، وآخر في موقع CNN، ويتواجد أيضًا في أغلبية المدونات. لكن ما هو الوسم المستخدم لتلك الحقول؟ <input type="text"‎>، مثل بقية حقول النص الموجودة في الويب. لنحاول تصحيح الأمر… <form> <input name="q" type="search"> <input type="submit" value="Find"> </form> ما رأيك بتجربة حقل <input type="search"‎> في متصفحك. قد لا تلاحظ أيّة اختلافات بينه وبين الحقل النصي العادي؛ لكن إن كنتَ تستعمل Safari على نظام Mac OS X، فسيبدو الحقل كما يلي: هل لاحظت الفرق؟ لدى حقل البحث زوايا مدورة! أعلم أنَّك لا تستطيع احتواء نفسك من الفرح، لكن انتظر، فهنالك المزيد! عندما تبدأ الكتابة في حقل type="search"‎، فسيضع المتصفح زر "x" صغير في الجانب الأيمن من الحقل؛ ويؤدي الضغط عليه إلى حذف محتويات الحقل (متصفح Chrome، الذي يتشارك مع Safari في البنية الداخلية، له نفس السلوك السابق). الغرض من التعديلات البسيطة السابقة هي إعطاء حقول البحث شكلًا وسلوكًا شبيهًا بحقول البحث في تطبيقات Mac OS X المكتبية مثل iTunes. يستعمل موقع Apple.com الحقل <input type="search"‎> لحقل البحث في الموقع لإعطائه شكلًا مألوفًا لمستخدمي Mac، لكن ذلك ليس خاصًا بنظام Mac فقط؛ إذ أنَّه شيفرة HTML فحسب، وبهذا يستطيع كل متصفح على كل منصة (أو نظام تشغيل) أن يعرض الحقل بشكلٍ مشابه لعناصر الواجهة الرسومية الخاصة بالمنصة. وكما هو الحال في بقية أنواع الحقول، المتصفحات التي لا تتعرف على حقل type="search"‎ ستعامِله كأنَّه type="text"‎؛ فلا يوجد سببٌ يمنعك من استخدام type="search"‎ في حقول البحث حالًا. منتقي الألوان تُعرِّف HTML5 حقل <input type="color"‎> الذي يسمح لك باختيار لونٍ ما ويُعيد التمثيل الست عشري للون المُختار؛ تأخرت المتصفحات في دعم هذا الحقل، حيث يدعمه Opera منذ الإصدار 17، و Firefox منذ الإصدار 29، و Chrome منذ الإصدار 20، وما زلنا في انتظار دعم بقية المتصفحات له. يندمج هذا الحقل جيدًا مع منتقي الألوان الموجود في نظامَيّ ويندوز و Mac، أما في لينُكس فهو يعرض مُنتقي ألوان أساسي. وهو يُعيد قيمة ست عشرية للون RGB الذي يمكن استخدامه في أي مكان يقبل ألوان CSS (جرِّب مُنتقي الألوان في متصفحك). التحقق من صحة مدخلات المستخدم IE Fiefox Safari Chrome Opera iPhone Android 10+ 4.0+ 5.0+ 10.0+ 9.0+ 4.1+ 4.0+ تحدثت في هذا الفصل عن عددٍ من حقول الإدخال الجديدة وبعض الميزات المحدثة مثل التركيز التلقائي لحقلٍ من حقول النموذج، لكنني لم أذكر ما أعتبره أهم جزء من النماذج الحديثة في HTML5: التحقق التلقائي من صحة مدخلات المستخدم. خذ على سبيل المثال مشكلةً شائعةً هي إدخال عنوان بريد إلكتروني في نموذج ويب؛ ربما ستجري تحققًا من مدخلات المستخدم من طرف العميل عبر JavaScript، متبوعًا بتحققٍ من جهة الخادوم عبر PHP أو Python أو أيًّا كانت لغة البرمجة التي تستعملها. لن يُشكِّل التحقق من مدخلات المستخدم عبر HTML5 بديلًا عن التحقق من جهة الخادوم، لكن من المحتمل أن تشكِّل بديلًا عن سكربتات JavaScript التي تستعملها. هنالك مشكلتين كبيرتين في التحقق من البريد الإلكتروني عبر JavaScript: عددٌ كبيرٌ جدًا من زوار موقعك (حوالي 10% تقريبًا) يُعطِّلون JavaScript في متصفحهم ستفشل في التحقق من صحة البريد الإلكتروني أنا آسف لقول هذا، لكنك ستفشل في ذلك. فعملية تحديد فيما إذا كانت سلسلة نصية ما هي عنوان بريد إلكتروني معقدةٌ بشكلٍ لا يُصدَّق. فكلما أمعنت النظر في الأمر، لوجدت مدى تعقيده. هل ذكرتُ لتوي أنَّ الأمر معقدٌ جدًا جدًا؟ أليس من الأسهل إلقاء ذاك الحِمل والصداع الناتج عنه على عاتق المتصفح؟ لقطة الشاشة السابقة مأخوذة من متصفح Opera 10، إلا أنَّ إمكانية التحقق من حقول النماذج متوفرة منذ الإصدار 9. ولدى Firefox 4 و Chrome 10 آليةٌ مشابهة. كل ما عليك فعله هو ضبط الخاصية type إلى "email". وعندما يحاول المستخدم إرسال (submit) نموذج فيه حقل <input type="email"‎>، فسيتحقق المتصفح تلقائيًا من عنوان البريد الإلكتروني حتى لو كانت JavaScript معطَّلةً في المتصفح. توفِّر HTML5 أيضًا تحققًا من عناوين الويب المُدخَلة في حقول <input type="url"‎>، والأرقام المدخلة في حقول <input type="number"‎>؛ ستؤخذ قيم الخاصية min و max بعين الاعتبار عند التحقق من الأرقام، فلن تسمح لك المتصفحات بإرسال النموذج إذا أدخلتَ رقمًا كبيرًا أكبر من الحد الأقصى. لا حاجة إلى وضع شيفرات لتفعيل التحقق من المدخلات في HTML5؛ حيث تكون مفعَّلة افتراضيًا، إلا أنَّك تستطيع تعطيلها عبر وضع الخاصية novalidate. <form novalidate> <input type="email" id="addr"> <input type="submit" value="Subscribe"> </form> الحقول المطلوبة required IE Fiefox Safari Chrome Opera iPhone Android 10+ 4.0+ 5.0+ 10.0+ 9.0+ 4.1+ 4.0+ التحقق من مدخلات المستخدم في HTML5 ليس محدودًا بنوع الحقل، إذ تستطيع أيضًا أن تُشير إلى أنَّ بعض الحقول "مطلوبةٌ"؛ يجب تحديد قيم للحقول المطلوبة قبل أن ترسِل النموذج. شيفرة الحقول المطلوبة بسيطةٌ جدًا: <form> <input id="q" required> <input type="submit" value="Search"> </form> يمكنك تجربة حقل <input required> في متصفحك. قد تُغيّر بعض المتصفحات الشكل الافتراضي للحقول المطلوبة. وإذا حاول المستخدم إرسال النموذج دون تعبئة الحقل المطلوب، فسيظهر إشعار يخبر المستخدم أنَّ من الضروري إدخال قيمة في الحقل وعدم تركه فارغًا (الصورة الآتية من متصفح Chrome): ترجمة -وبتصرّف- للفصل "HTML5 Forms" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  11. مدخل إلى html5 التخزين المحلي (Local Storage) في HTML5

    كانت البرمجيات المكتبية تتفوق على تطبيقات الويب بإمكانية تخزين المعلومات محليًا تخزينًا دائمًا؛ حيث يوفِّر نظام التشغيل عادةً طبقةً وسيطةً لتخزين وقراءة بيانات خاصة بالتطبيق مثل الإعدادات وحالة التشغيل، وقد تُخزَّن هذه القيم في سجل النظام (registry) أو ملفات ini أو ملفات XML أو في مكانٍ آخر وفقًا للتقاليد المُتبَعة في نظام التشغيل؛ أما لو احتاج التطبيق المكتبي إلى تخزينٍ محليٍ أكثر تعقيدًا من مجرد تخزين البيانات على شكل "المفتاح/القيمة"، فيمكنك أن تُضمِّن قاعدة البيانات الخاصة بتطبيقك، أو أن تبتكر صيغة ملفات للتخزين، أو غيره ذلك من الحلول. لكن على مرِّ التاريخ، لم تملك تطبيقات الويب هذا الامتياز، وعلى الرغم من ابتكار الكعكات (Cookies) في بدايات الويب لكن كان الغرض منها هو التخزين المحلي لكميةٍ قليلةٍ من البيانات، إلا أنَّ هنالك ثلاثة أسباب تمنعنا من استخدامها لهذا الغرض: ستُضمَّن الكعكات في كل طلبية HTTP، مما يؤدي إلى حدوث بطء في تطبيق الويب بسبب نقل نفس البيانات مرارًا وتكرارًا دون داعٍ ستُضمَّن الكعكات في كل طلبية HTTP، وهذا يعني إرسال البيانات دون تشفير عبر الإنترنت (إلا إذا كان يُخدَّم تطبيق الويب عندك عبر طبقة SSL) المساحة التخزينية للكعكات محدودة إلى حوالي 4 كيلوبايت من البيانات، وهي كافية لإبطاء تطبيقك (انظر أعلاه)، لكنها ليست كافية لتخزين شيءٍ مفيدٍ ما نحتاج له حقًا هو: مساحة تخزينية كبيرة موجودة على جهاز العميل يمكن أن تبقى حتى بعد تحديث الصفحة لن تُنقَل طوال الوقت إلى الخادوم جميع المحاولات -قبل HTML5- لتحقيق ما سبق كانت غير مرضية لمختلف الأسباب. لمحة تاريخية عن محاولات تخزين البيانات محليا قبل HTML5 لم يكن هنالك سوى متصفح Internet Explorer في بدايات الويب، أو على الأقل هذا ما حاولت مايكروسوفت إيهام العالم به، ولتحقيق هذه الغاية، وكجزءٍ من الحرب الكبرى الأولى للمتصفحات، ابتكرت مايكروسوفت ميزاتٍ كثيرة ووضعتها في متصفحها -Internet Explorer- الذي أنهى تلك الحرب. واحدة من تلك الميزات تُسمى "DHTML Behaviors" وكان أحد خصائصها يُدعى userData. تسمح ميزة userData لصفحات الويب أن تُخزِّن 64 كيلوبايت كحد أقصى لكل نطاق (domain)، وذلك عبر هيكلية تعتمد على XML (أما النطاقات الموثوقة، مثل مواقع إنترانت [intranet]، فتستطيع تخزين 10 أضعاف الكمية؛ وكانت 640 كيلوبايت في ذاك الوقت أكثر من كافية). لم يوفِّر IE أي مربع حوار لأخذ إذن المستخدم، ولم تكن هنالك إمكانية لزيادة كمية البيانات التي يمكن تخزينها محليًا. في عام 2002، أضافت شركة Adobe ميزةً في Flash 6 التي اكتسبت الاسم "Flash cookies"، لكن هذه الميزة كانت معروفةً ضمن بيئة Flash بالاسم Local Shared Objects؛ باختصار، تسمح هذه الميزة لكائنات Flash أن تُخزِّن 100 كيلوبايت من البيانات كحد أقصى لكل نطاق. طوَّر Brad Neuberg نموذجًا أوليًا لجسرٍ يربط تقنية Flash بلغة JavaScript أسماه AMASS (اختصار للعبارة AJAX Massive Storage System)، لكنه كان محدودًا بسبب بعض المشكلات في تصميم صيغة Flash. لكن في 2006، ومع مجيء ExternalInterface في Flash 8، أصبح من الممكن بسهولة وسرعة الوصول إلى الكائنات المشتركة المخزنة محليًا (Local Shred Objects أو اختصارًا LSOs) من JavaScript؛ ولهذا السبب أعاد Brad كتابة AMASS ودمجها مع Dojo Toolkit تحت الاسم dojox.storage. وبهذا مَنَحَ Flash كل نطاق 100 كيلوبايت من التخزين المحلي "مجانًا"، وستُطلَب موافقة المستخدم عند كل زيادة في تخزين البيانات (1 ميغابايت، 10 ميغابايت، وهكذا). في عام 2007، أصدرت Google إضافة Gears، التي هي إضافة مفتوحة المصدر للمتصفحات غرضها هو توفير إمكانيات إضافية إليها (تحدثنا سابقًا عن Gears في سياق /توفير واجهة برمجية لتحديد الموقع الجغرافي لمتصفح IE/). توفِّر Gears واجهة برمجية (API) للوصول إلى قاعدة بيانات SQL مدمجة فيها مبنيةٌ على محرك قواعد البيانات SQLite. يمكن لإضافة Gears تخزين كمية غير محدودة من البيانات لكل نطاق في جداول قاعدة بيانات SQL بعد أخذ إذن المستخدم. في تلك الأثناء، أكمل Brad Neuberg وآخرون مشوارهم في تطوير dojox.storage لتوفير واجهة موحَّدة لمختلف الإضافات، وبحلول 2009 أصبح بمقدور dojox.storage أن تكتشف دعم (وتوفر واجهة موحدة) لبرمجية Adobe Flash و Gears و Adobe AIR والنموذج الأولي من التخزين المحلي في HTML5 الذي كان مُطبَّقًا في الإصدارات القديمة من Firefox فقط. عندما تنظر إلى تلك الحلول، فستكتشف أنَّ جميعها كان خاصًا بمتصفح معيّن أو كان يتبع لإضافة خارجية. وعلى الرغم من الجهود البطولية لتوحيد تلك الاختلافات (dojox.storage) إلا أنَّ تلك الحلول تملك واجهات برمجية مختلفة جذريًا عن بعضها، ولكلٍ منها حدود قصوى لمقدار المساحة التخزينية المتوفرة، ولكلٍ منها تجربة مستخدم مختلفة. هذه هي المشكلة التي أتت HTML5 لحلها: توفير واجهة برمجية معيارية، ومطبَّقة في جميع المتصفحات، دون الحاجة إلى استخدام إضافات خارجية. مدخل إلى التخزين المحلي في HTML5 ما أشير إليه على أنَّه "التخزين المحلي في HTML5"‏ (HTML5 Storage) هو مواصفة باسم "Web Storage" التي كانت جزءًا من معيار HTML5، لكنها انقسمت وأصبحت معيارًا مستقلًا لأسباب ليست مهمة. بعض الشركات المسؤولة عن المتصفحات تطلِق عليها الاسم "التخزين المحلي" (Local Storage) أو "تخزين DOM" ‏(DOM Storage). ازداد تعقيد موضوع التسميات خصوصًا بعد ظهور عدد من المعايير الجديدة التي سأناقشها في نهاية هذا الدرس. إذًا، ما هو التخزين المحلي في HTML؟ بشكل مبسّط: هو طريقة تتمكن صفحات الويب من خلالها أن تُخزِّن البيانات على شكل "المفتاح/القيمة" محليًا داخل متصفح الويب في حاسوب العميل. ومثل الكعكات، ستبقى البيانات موجودةً حتى بعد إغلاقك للسان الصفحة في المتصفح، أو إغلاق المتصفح. لكن على عكس الكعكات، لن تُرسَل البيانات تلقائيًا إلى خادوم الويب البعيد؛ وعلى النقيض من كل المحاولات السابقة لتوفير ميزة التخزين المحلي، هذه الميزة موجودة داخليًا في متصفحات الويب، لذلك ستكون متاحة للاستخدام حتى لو لم تتوفر إضافاتٌ خارجيةٌ للمتصفح. ما هي المتصفحات التي تدعمها؟ حسنًا، التخزين المحلي في HTML5 مدعومٌ من أغلبية المتصفحات، وحتى القديمة منها. IE Firefox Safari Chrome Opera iPhone Android 8.0+ 3.5+ 4.0+ 4.0+ 10.5+ 2.0+ 2.0+ تستطيع الوصول إلى التخزين المحلي في HTML5 في شيفرات JavaScript عبر الكائن localStorage الموجود في الكائن العام window؛ لكن قبل أن تستخدمها، عليك أن تكتشف دعم المتصفح لها. function supports_html5_storage() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } } لكن بدلًا من كتابة الدالة السابقة يدويًا، يمكنك استخدام Modernizr لاكتشاف دعم التخزين المحلي في HTML5. if (Modernizr.localstorage) { // window.localStorage متوفرة! } else { // لا يوجد دعم للتخزين المحلي :( // ربما تجرب dojox.storage أو مكتبة أخرى } استخدام التخزين المحلي في HTML5 يعتمد التخزين المحلي في أساسه على تخزين البيانات على شكل "مفتاح/قيمة". أي أنَّك تُخزِّن البيانات في مفتاح له اسم مُميِّز، ثم تستطيع الحصول على تلك البيانات مرةً أخرى باستخدام نفس المفتاح. ذاك المفتاح هو سلسلة نصية، ويمكن أن تكون البيانات المُخزَّنة من أي نوع تدعمه لغة JavaScript بما في ذلك السلاسل النصية والقيم المنطقية (true و false) أو الأعداد الصحيحة أو الأعداد العشرية؛ لكن في الواقع، ستُخزَّن البيانات كسلسلة نصية، وهذا يعني أنَّه لو لم تكن القيمة المُخزَّنة نصيةً فستحتاج إلى استعمال دوال مثل parseInt()‎ أو parseFloat()‎ لكي تحوِّل البيانات التي حصلت عليها إلى نوع البيانات الذي تريده. interface Storage { getter any getItem(in DOMString key); setter creator void setItem(in DOMString key, in any data); }; سيؤدي استدعاء الدالة setItem()‎ مع تمرير مفتاح موجود مسبقًا إلى إعادة الكتابة فوق القيمة السابقة دون إشعار. وسيؤدي استدعاء الدالة getItem()‎ مع تمرير مفتاح غير موجود إلى إعادة null بدلًا من رمي استثناء (throw an exception). وكما هو الحال مع بقية الكائنات في JavaScript، يمكنك أن تُعامِل الكائن localStorage على أنَّه مصفوفة ترابطية (associative array). فبدلًا من استخدام الدالتين getItem()‎ و setItem()‎، تستطيع بكل بساطة أن تستعمل الأقواس المربعة (التي تستعملها للوصول إلى عناصر المصفوفات). يمكن على سبيل المثال أن نُعيد كتابة هذه الشيفرة: var foo = localStorage.getItem("bar"); localStorage.setItem("bar", foo); var foo = localStorage["bar"]; localStorage["bar"] = foo; هنالك دوالٌ أخرى لحذف قيمة مرتبطة بمفتاح معيّن، ولحذف كل ما هو مُخزَّنٌ محليًا (وهذا يعني حذف كل المفاتيح والقيم معًا). interface Storage { deleter void removeItem(in DOMString key); void clear(); }; لن يؤدي استدعاء الدالة removeItem()‎ مع تمرير مفتاح غير موجود إلى فعل أي شيء. وأخيرًا، هنالك خاصية للحصول على العدد الكلي للقيم المُخزَّنة محليًا، ودالة للحصول على اسم كل مفتاح عبر تمرير فهرسه المكاني*. interface Storage { readonly attribute unsigned long length; getter DOMString key(in unsigned long index); }; لو استدعيتَ الدالة key()‎ مع فهرس لا يقع بين 0 - 1 فستُعيد الدالة null. تتبّع التغييرات في مساحة التخزين المحلي إذا أردت أن تتبَّع التغييرات في مساحة التخزين (storage area) برمجيًا، فعليك أن تستعمل الحَدَث storage، الذي يُفعَّل (fired) في الكائن العام window في كل مرة تُستدعى فيها الدالة setItem()‎ أو removeItem()‎ أو clear()‎ وتجري تلك الدالة تغييرًا ما. فعلى سبيل المثال، لو أعدتَ ضبط قيمة موجودة مسبقًا وكانت القيمة الجديدة مساوية للقيمة القديمة، أو استدعيت الدالة clear()‎‎ لكن لم تكن هنالك أيّة قيم في مساحة التخزين، فلن يُفعَّل الحدث storage، لعدم تغيّر شيء في مساحة التخزين. الحدث storage مدعوم في كل متصفح يدعم الكائن localStorage، وهذا يتضمن Internet Explorer 8، لكن IE 8 لا يدعم الدالة المعيارية من W3C لمراقبة الأحداث addEventListener (لكنها أُضيفت في نهاية المطاف في IE 9)؛ ولهذا، إذا أردت مراقبة تفعيل الحدث storage فعليك أن تكتشف ما هي آلية الأحداث التي يدعمها المتصفح أولًا (إذا فعلتَ هذا من قبل مع الأحداث الأخرى، فيمكنك تخطي هذه الفقرة والانتقال إلى آخر القسم. مراقبة الحدث storage مماثلة تمامًا لعملية مراقبة الأحداث الأخرى التي سبقَ وأن راقبتَها؛ وإذا كنتَ تُفضِّل استخدام jQuery أو مكتبة JavaScript أخرى لتسجيل دوال مراقبة الأحداث، فتستطيع فعل ذلك مع الحدث storage أيضًا). if (window.addEventListener) { window.addEventListener("storage", handle_storage, false); } else { window.attachEvent("onstorage", handle_storage); }; ستُستدعى الدالة handle_storage مع تمرير كائن من نوع StorageEvent، عدا في متصفح Internet Explorer حيث يُخزَّن الكائن في window.event. function handle_storage(e) { if (!e) { e = window.event; } } سيكون المتغير e -عند هذه النقطة- كائنًا من نوع StorageEvent، الذي لديه الخاصيات المبيّنة في الجدول الآتي: الخاصية النوع الشرح key سلسلة نصية مفتاح القيمة التي أُضيفَت أو حُذِفَت أو عدِّلَت oldValue أي نوع القيمة (التي كُتِبَ فوقها)، أو null إذا أُضيف عنصرٌ جديد newValue أي نوع القيمة الجديدة، أو null إن حُذِفَ عنصرٌ ما url* سلسلة نصية الصفحة التي تحتوي على الدالة التي أجرت هذا التغيير ملاحظة: كان اسم الخاصية url الأصلي هو uri، وذلك لأنَّ بعض المتصفحات امتلكت هذه الخاصية قبل تغيير مواصفة التخزين المحلي. لأكبر قدر من التوافقية، عليك أن تتحقق من وجود الخاصية url، فإن لم تكن موجودًا فتحقق من قيمة الخاصية uri. لا يمكن إلغاء الأحداث في الحدث storage. فلا توجد طريقة من داخل الدالة handle_storage تستطيع إيقاف تغيير ما من الحدوث. بكل بساطة، هذه طريقة لكي يخبرك المتصفح: "هذا ما حصل لتوِّه، لا يمكنك فعل أي شيء تجاهه؛ كل ما أستطيع فعله هو إخبارك ما الذي حدث". المحدوديات في المتصفحات الحالية في حديثي عن اللمحة التاريخية عن محاولات تخزين البيانات محليًا باستخدام إضافات خارجية، حرصتُ على ذكر محدوديات كل تقنية من تلك التقنيات، مثل محدودية المساحة التخزينية. لكنني لم أذكر شيئًا عن محدوديات التخزين المحلي في HTML5 المعياري. سأعطيك الأجوبة أولًا ثم سأشرحها. الأجوبة هي -بترتيبها حسب الأهمية-: "5 ميغابايت"، و"QUOTA_EXCEEDED_ERR" و "لا". "5 ميغابايت" هي المساحة التي يُسمَح لكل موقع بالحصول عليها افتراضيًا، وهذه القيمة متساوية -على غير العادة- بين المتصفحات، على الرغم من أنَّها مذكورة في مواصفة التخزين المحلي في HTML5 على أنَّها "اقتراح". ابقِ في ذهنك أنَّك تُخزِّن سلاسل نصية، ولا تخزِّن البيانات بصيغتها الأصلية، فلو كنت تُخزِّن الكثير من الأعداد الصحيحة (integers) أو العشرية (floats)، فسيكون الفرق في طريقة تمثيل البيانات مؤثرًا، إذ يُخزَّن كل رقم من عدد عشري كمحرف (character)، وليس بالتمثيل التقليدي لعدد عشري. "QUOTA_EXCEEDED_ERR" هو الاستثناء (exception) الذي سيُرمى (thrown) عندما تتجاوز حد 5 ميغابايت. أما "لا" فهو الجواب على السؤال البدهي الذي سيخطر ببالك: "هل يمكنني طلب المزيد من المساحة التخزينية من المستخدم؟" إلى حد الساعة، لا تدعم أيّة متصفحات أي آلية يتمكن خلالها مطورو الويب من طلب المزيد من المساحة التخزينية. لكن بعض المتصفحات (مثل Opera أو Firefox) تسمح للمستخدم أن يتحكم بالحدّ الأقصى للتخزين المحلي، لكن هذا منوطٌ بالمستخدم تمامًا، ولا يمكنك -كمطوِّر ويب- الاعتماد على ذلك لبناء تطبيقك. مثال عملي عن استخدام التخزين المحلي لنأخذ مثالًا عمليًا عن التخزين المحلي في HTML. هل تتذكر لعبة الضامة التي بنيناها في الدرس الذي يتحدث عن canvas؟ هنالك مشكلة صغيرة مع هذه اللعبة: ستخسر تقدّمك في اللعبة عندما تُغلِق نافذة المتصفح. لكن باستخدام التخزين في HTML5، سنستطيع حفظ التقدّم محليًا داخل المتصفح. هذا مثالٌ حيّ للعبة بعد التعديل. حرِّك بعض القطع، ثم أغلق لسان الصفحة (أو المتصفح)، ثم أعد فتح الصفحة. فإذا كان يدعم متصفحك التخزين المحلي، فيجب أن تتذكر الصفحة السابقة خطواتك التي أجريتها في اللعبة، بما في ذلك عدد الخطوات التي تحركت بها، ومكان كل قطعة على رقعة اللعب، وحتى آخر قطعة قمتَ بتحديدها. ما هي الآلية التي اتبعناها لفعل ذلك؟ سنستدعي الدالة الآتية في كل مرّة يطرأ فيها تغيير داخل اللعبة: function saveGameState() { if (!supportsLocalStorage()) { return false; } localStorage["halma.game.in.progress"] = gGameInProgress; for (var i = 0; i < kNumPieces; i++) { localStorage["halma.piece." + i + ".row"] = gPieces[i].row; localStorage["halma.piece." + i + ".column"] = gPieces[i].column; } localStorage["halma.selectedpiece"] = gSelectedPieceIndex; localStorage["halma.selectedpiecehasmoved"] = gSelectedPieceHasMoved; localStorage["halma.movecount"] = gMoveCount; return true; } كما لاحظت، تستعمل الدالة السابقة الكائن localStorage لتخزين أنَّ المستخدم قد بدأ اللعب (المفتاح gGameInProgress، الذي هو قيمة منطقية [Boolean]). ثم ستدور حلقة for على جميع القطع (المتغير gPieces، الذي هو مصفوفة في لغة JavaScript) ثم يحفظ رقم السطر والعمود لكل قطعة؛ ثم تحفظ الدالة بعض المعلومات الإضافية عن اللعبة، بما في ذلك القطعة التي تم تحديدها (القيمة gSelectedPieceIndex، التي هي رقمٌ صحيح [integer])، وفيما إذا كانت القطعة في منتصف سلسلة من القفزات (القيمة gSelectedPieceHasMoved، التي هي قيمة منطقية)، والعدد الكلي للحركات التي قام بها اللاعب (القيمة gMoveCount، التي هي عدد صحيح). وعند تحميل الصفحة، وبدلًا من الاستدعاء التلقائي للدالة newGame()‎ التي ستُعيد ضبط جميع المتغيرات إلى قيم مُحدَّدة مسبقًا، فسنستدعي الدالة resumeGame()‎. التي تتحقق -باستخدام التخزين المحلي في HTML5- فيما إذا كانت هنالك نسخة محفوظة من اللعبة مُخزَّنةٌ محليًا؛ فإن وُجِدَت، فستُستعاد تلك القيم باستخدام الكائن localStorage. function resumeGame() { if (!supportsLocalStorage()) { return false; } gGameInProgress = (localStorage["halma.game.in.progress"] == "true"); if (!gGameInProgress) { return false; } gPieces = new Array(kNumPieces); for (var i = 0; i < kNumPieces; i++) { var row = parseInt(localStorage["halma.piece." + i + ".row"]); var column = parseInt(localStorage["halma.piece." + i + ".column"]); gPieces[i] = new Cell(row, column); } gNumPieces = kNumPieces; gSelectedPieceIndex = parseInt(localStorage["halma.selectedpiece"]); gSelectedPieceHasMoved = localStorage["halma.selectedpiecehasmoved"] == "true"; gMoveCount = parseInt(localStorage["halma.movecount"]); drawBoard(); return true; } أهم فكرة في هذه الدالة هي تطبيق التحذير الذي ذكرته لك سابقًا في هذا الدرس، وسأكرره هنا: "ستُخزَّن البيانات كسلسلة نصية، وهذا يعني أنَّه لو لم تكن القيمة المُخزَّنة نصيةً فستحتاج إلى تحويل البيانات التي حصلت عليها إلى نوع البيانات الذي تريده". فعلى سبيل المثال، القيمة التي تُحدِّد فيما إذا كانت هنالك لعبة قيد اللعب (gGameInProgress) هي قيمة منطقية، وفي الدالة saveGameState()‎ خزَّنا القيمة دون أن نلقي بالًا لنوعها: localStorage["halma.game.in.progress"] = gGameInProgress; لكن في دالة resumeGame()‎ علينا أن نُعامِل القيمة التي أخذناها من التخزين المحلي كسلسلةٍ نصيةٍ كالآتي: gGameInProgress = (localStorage["halma.game.in.progress"] == "true"); وبشكلٍ مشابه، يُخزَّن عدد الخطوات في gMoveCount كعددٍ صحيحٍ؛ فلقد خزَّناها ببساطة في الدالة saveGameState()‎: localStorage["halma.movecount"] = gMoveCount; لكن في دالة resumeGame()‎ علينا أن نحوِّل القيمة إلى عدد صحيح باستخدام الدالة parseInt()‎ الموجودة في JavaScript: gMoveCount = parseInt(localStorage["halma.movecount"]); مستقبل التخزين المحلي في تطبيقات الويب على الرغم من أنَّ الماضي كان مليئًا بالطرق الالتفافية، لكن الوضع الراهن للتخزين المحلي في HTML5 مشرقٌ، فهنالك واجهة برمجية (API) جديدة قد وُضِعَ لها معيارٌ وطبِّق هذا المعيار في جميع المتصفحات الرئيسية على مختلف المنصات والأجهزة. فهذا أمرٌ لا تراه كل يوم كمطوِّر ويب، أليس كذلك؟ لكن ألا تتطلع إلى أكثر من "5 ميغابايت من الثنائيات على شكل "مفتاح/قيمة"؟ حسنًا، هنالك عدد من الرؤى التنافسية لمستقبل التخزين المحلي. إحدى تلك الرؤى هي اختصارٌ تعرفه بالتأكيد: SQL. أطلقَت Google في عام 2007 إضافة Gears المفتوحة المصدر التي تعمل على مختلف المتصفحات والتي احتوت على قاعدة بيانات مُضمَّنة فيها مبنية على SQLite. أثَّر هذا النموذج الأولي لاحقًا على إنشاء مواصفة Web SQL Database، والتي كانت تعرف رسميًا باسم "WebDB" التي توفر طبقةً للوصول إلى قاعدة بيانات SQL، سامحةً لك بالقيام بأشياء شبيهة بما يلي عبر JavaScript (لاحظ أنَّ الشيفرة الآتية حقيقية وتعمل على أربعة متصفحات): openDatabase('documents', '1.0', 'Local document storage', 5*1024*1024, function (db) { db.changeVersion('', '1.0', function (t) { t.executeSql('CREATE TABLE docids (id, name)'); }, error); }); كما لاحظت، ما يهم في الشيفرة السابقة هو السلسلة النصية التي مررتها إلى الدالة executeSql، ويمكن أن تحتوي تلك السلسلة النصية على أيّة تعليمات SQL مدعومة، بما في ذلك SELECT و UPDATE و INSERT و DELETE. الأمر هنا شبيهٌ ببرمجة قواعد البيانات بلغةٍ مثل PHP، إلا أنَّك تقوم بذلك عبر JavaScript! طُبِّقت مواصفات Web SQL Database من أربعة متصفحات ومنصات. IE Firefox Safari Chrome Opera iPhone Android . . 4.0+ 4.0+ 10.5+ 3.0+ 2.0+ وبكل تأكيد، لو سبق وأن استخدمت أكثر من مُحرِّك لقواعد البيانات في حياتك، فأنت تعلم أنَّ "SQL" هي مصطلح تسويقي أكثر من كونها معيارًا متكاملًا (قد يقول البعض أنَّ HTML5 كذلك، لكن لا تأبه بقولهم). حسنًا، هنالك معيار للغة SQL (يسمى SQL-92) لكن لا يوجد خادوم قواعد بيانات في العالم يتوافق تمامًا مع ذاك المعيار. فهنالك نسخة SQL لقواعد بيانات Oracle، ونسخة أخرى لقواعد MSSQL، ونسخة أخرى لقواعد بيانات MySQL، وأخرى لقواعد بيانات PostgreSQL، ولا ننسَ نسخة SQL لقواعد بيانات SQLite. وحتى كل منتج من تلك المنتجات يُضيف ميزات SQL جديدة على مرّ الزمن، وبهذا يكون قولنا "نسخة SQL لقواعد بيانات SQLite" ليس كافيًا لتحديد ما نتحدث عنه بدقّة. فعليك أن تقول "نسخة SQL التي تأتي مع قواعد بيانات SQLite ذات الإصدار X.Y.Z". كل ما سبق أدى إلى الإعلان الآتي، التي يقبع الآن في أعلى صفحة مواصفة Web SQL Database: وعلى ضوء هذا، سأعرِّفك على رؤية تنافسية أخرى لتخزينٍ محليٍ متقدم وثابت لتطبيقات الويب: Indexed Database API المعروفة رسميًا باسم "WebSimpleDB" التي اشتهرت باسم "IndexedDB". تحتوي IndexedDB على ما يُسمى "مخزن الكائنات" (object store)، الذي يتشارك مع قاعدة بيانات SQL في الكثير من المفاهيم؛ فهنالك "قواعد بيانات" (databases) فيها "سجلات" (records)، ويملك كل سجل عددًا من "الحقول" (fields)، وكل حقل له نوع بيانات معيّن، الذي يُعرَّف عند إنشاء قاعدة البيانات. تستطيع أيضًا تحديد مجموعة فرعية من السجلات، ثم تعرضها عبر "مؤشر" (cursor)، ويتم التعامل مع التغييرات على مخزن الكائنات عبر "التحويلات" (transactions). إذا سبق وأن برمجتَ قليلًا بأي نوع من أنواع قواعد بيانات SQL ، فمن المرجح أن تبدو المصطلحات السابقة مألوفةً لديك. الفرق الرئيسي هو أنَّ "مخزن الكائنات" ليس لديه "لغة استعلام بنيوية"، لا تستطيع كتابة عبارات مثل "SELECT * from USERS where ACTIVE = 'Y'‎" لكنك تستطيع استخدام الدوال التي يوفرها مخزن الكائنات لفتح "مؤشر" (cursor) في قاعدة البيانات "USERS"، ثم تمر عبر السجلات، وتستبعد سجلات المستخدمين غير النشيطين، ثم تستخدم دوالًا للوصول إلى قيم كل حقل في السجلات المتبقية. دعم IndexedDB موجودٌ في Firefox منذ الإصدار 4.0 (صرَّحَت Mozilla أنَّها لن تدعم Web SQL Database في متصفحها)، وChrome منذ الإصدار 11، وحتى Internet Explorer أصبح يدعم IndexedDB منذ الإصدار 10. ترجمة -وبتصرّف- لفصل "Local Storage" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  12. سأقدِّم لك أداةً رائعةً للتصميم: أرأيت؟ ألم ترَ؟ إنها الفراغات البيضاء! حسنًا، ربما مزاحي لم يكن في محلّه، أعترف بذلك. أنا آسف. صحيحٌ أنَّ تعريفي للفراغات البيضاء لك لم يكن مثاليًا، لكن الفراغات البيضاء هي من أهم الأدوات في تصميم الويب، ولكن يغفل عنها الناس معظم الوقت. أعلمُ أنَّ من الممتع العمل على عناصر أخرى من تصميم الويب، مثل سمة الألوان، أو الخطوط، أو التخطيط العام للصفحة… خصوصًا عندما تُنشِئ مشروعًا لعميلٍ ما (ملاحظة شخصية: لا أظن أنَّ هنالك عميلًا في العالم يلقي بالًا لطريقة تحسين الفراغات البيضاء لموقعه الجديد!). ومع ذلك، قد تستفيد من الفراغات البيضاء خيرَ استفادة إن استطعت استخدامها استخدامًا صحيحًا. وسأتطرّف في هذا الدرس إلى آلية فعل ذلك. لماذا الفراغات البيضاء مهمة في تصاميم الويب بدايةً، الفراغات البيضاء ليست مفهومًا جديدًا في عالم التصميم، إذ استعمِلَت لقرون كالأداة رقم 1 لإعطاء تركيز على العناصر المهمة في التصميم. والأمر سيانٌ في يومنا هذا. على سبيل المثال، إذا كانت لديك لوحةٌ جميلةٌ وتريد أن تُظهِر اهتمامك بها، فإن أفضل طريقة هي: (1) وضع إطار حولها، و (2) ألّا تضع أيَّ شيءٍ آخر على ذاك الجدار (انظر الصورة أدناه). وبشكلٍ مشابه، أفضل طريقة لإعطاء أولوية لأحد عناصر صفحة الويب هي عدم وضع أي شيء حوله، وكلما قلَّت الأشياء التي حول ذاك العنصر، كلما كان ذلك أفضل. ما رأيك أن ترى مثالًا بدلًا من إطالة الكلام (الصورة خيرٌ من ألف كلمة). هذا موقعٌ تدخل إليه يوميًا: يستعمل موقع Google هذا المظهر منذ سنوات، وربما أصبح شكله مألوفًا، لهذا قد لا تفكّر في تصميم الصفحة كثيرًا. لكن دعنا نتوقف قليلًا ونفكِّر كم أنَّ من السهل أن تضع Google بعض الأشياء الإضافية في الصفحة. إذ يستطيعون تضمين الأخبار (من Google News) وسيكون بعض الأشخاص سعداء بذلك. ويستطيعون أيضًا تضمين مربع لبريد Gmail لكي يتمكن الجميع من التحقق من الرسائل التي تأتيهم على بريدهم مباشرةً. أو أن يضعوا مربعًا لتقويم Google، وهلم جرًا… الاحتمالات والإمكانيات غير محدودة، إلا أنَّ Google قرروا عدم وضع أيّا من تلك الاحتمالات في الصفحة الرئيسية؛ إذ قرروا أنَّ يضعوا حقل البحث فقط (بالإضافة إلى الشعار، وبعض الأشياء في الركن العلوي الأيمن إن سجَّلتَ دخولك). لكن لماذا؟ لماذا وضعوا حقل البحث فقط؟ الجواب بسيطٌ للغاية، وإنّي واثقٌ أنَّك تعرفه مسبقًا: بوضع حقل البحث في الصفحة بمفرده، فسيظهر الغرض من الصفحة جليًا أمامك. فسيعلم زائر الصفحة بعد دخوله إليها مباشرةً ما الغرض منها وكيف يستعملها، فلا يضيع وقته بمحاولة «فهم» ما الذي يجري في الصفحة. وهذا يتوافق تمامًا مع هدف Google الرئيسي. يريدونك أن تستعمل محرك البحث الخاص بهم، وتُشكِّل الفراغات البيضاء أحد الأشياء التي يستعملها مطورو Google لكي يحثوك على فعل ذلك. لتلخيص ما سبق، يمكن للفراغات البيضاء المُستعمَلة استعمالًا صحيحًا أن: تساعد في حثّ المستخدم على القيام بأمرٍ معيّن تساعد على التركيز على أحد العناصر تساعد في توضيح الغرض من الموقع تعطي تركيزًا على الأشياء المهمة تجعل الأشياء التي ليست مهمة جدًا بأولوية منخفضة تساعد الزائر على المسح البصري للصفحة وتقرير ما الذي يهمه مباشرةً سنتحدث الآن عن كيفية استعمال الفراغات البيضاء بفعالية، بعد بأخذ كل ما سبق بعين الاعتبار. كيف تستعمل الفراغات البيضاء بفاعلية في تصاميم الويب لنتحدث عن الفراغات البيضاء من ناحية منهجية التعامل معها، وبعض المعلومات التقنية الأساسية لكيفية إنشاءها (لكنا لن نشرح الأدوات والطرق المستعملة لذلك). أولا: استعمل الفراغات البيضاء لتدعيم الهدف الأساسي من موقعك حسنًا، هنالك هدفٌ من إنشاء كل صفحةٍ أو كل موقعٍ على الويب. ومن الممكن أن يكون هنالك هدفٌ وحيدٌ لكل صفحات الموقع، أو أن يكون لكل صفحةٍ هدفٌ خاصٌ بها. أيًّا كان، يمكنك أن تستعمل الفراغات البيضاء لجعل تلك الأهداف واضحةً كالبدر في الليلة الصافية. على سبيل المثال، لنلقِ نظرةً على الصفحة الرئيسية لموقع Bigcommerce: من الواضح أنَّ الهدف الرئيسي من الصفحة هو إقناعك -أي الزائر- أن تُسجِّل تجريبيًا في Bigcommerce. وفي الواقع، لا يوجد أيٌ شيءٍ في الصفحة سوى عنوان بخطٍ كبير الذي يحاول إقناعك، بالإضافة إلى فراغات بيضاء كبيرةٍ حوله. حسنًا، تُمثِّل الصورة مثالًا عن المنتج، لكنها موجودةٌ في مركز الصفحة، مما يزيد في إضفاء الأهمية على العنوان. لم يسبق لي التّعامل مع Bigcommerce، إلا أنني متأكدٌ أنَّهم يستطيعون إضافة أشياءٍ كثيرةٍ في الصفحة الرئيسية؛ إلا أنَّهم قرروا عدم فعل ذلك. لماذا؟ لتدعيم الغرض الرئيسي من الصفحة. ثانيا: استعمل الفراغات البيضاء للحث على القيام بإجراء معين هذه إحدى قواعد التصميم الجيد للويب: افترض دومًا أنَّ الزائر لا يعرف ماذا عليه أي يفعل في الخطوة القادمة. أنَّى لهم أن يعلموا؟ لا تنسَ أنهم لم يصمموا أو يبرمجوا الصفحة، وإنما أتو لتوهم إليها … ربما عن طريق الخطأ! استعمل الفراغات البيضاء لمساعدة الزوار ليعلموا ماذا عليهم أن يفعلوه. الفكرة بسيطة: إذا لم يكن هنالك الكثير من الأشياء في الصفحة فيمكن للزائر أن يتفاعل مع الأشياء القليلة المتبقية في الصفحة. هذا مثالٌ من Crazy Egg: قد نعتبر أنَّ الصفحة السابقة فارغة تقريبًا، إذ أنَّ التصميم شبيهٌ جدًا بتصميم صفحة Google الرئيسية؛ لكنه يوضِّح ماذا يستطيع أن يفعل المستخدم. حتى لو لم تتعامل مع Crazy Egg من قبل، فيمكنك أن تعرف ماذا عليك أن تفعل بسرعة. حيث يوجد حقل إدخالٍ فيه تلميحة ("Your website URL") وزر يقول "Show Me My Heatmap". الغرض من هذه الصفحة واضحٌ ألا وهو حثّ المستخدم على اتخاذ إجراء، وتساعد الفراغات البيضاء بذلك، وتجعل الصفحة أكثر "نظافةً". ثالثا: ليس من الضروري أن تكون الفراغات البيضاء "بيضاء" ربما هذه اللحظة مناسبة لكي أصحِّحَ لبسًا في المفاهيم: ليس من الضروري أن يكون لون الفراغات البيضاء هو اللون الأبيض. بأبسط الكلمات، الفراغات البيضاء تعني عدم وجود أي عناصر ذات محتوى، ولا تعني أنَّ تلك الفراغات لونها أبيض. بهذا الخصوص، تستطيع استخدام مختلف العناصر كفراغات بيضاء. يمكنك أن تستعمل الألوان التي تحبها، على سبيل المثال Marshall: أو يمكنك أن تستعمل خلفية مشوشة (blurred) أو نقوش. مثالٌ من Zapier: في النهاية، يمكنك أن تخاطر باستخدام صور غير مشوشة لطالما كان هنالك تباينٌ كبيرٌ بينها وبين محتوى الصفحة. يمكنك رؤية مثالٍ عمليٍ عنها في موقع Grammarly: رابعا: استخدم الفراغات البيضاء في القسم العلوي للصفحة شاعت في الآونة الأخيرة ما نسميه أقسام hero‏ (hero sections) أو صور hero ‏(hero images) التي هي الأقسام التي تظهر في أول الصفحة والتي تسترعي انتباه الزوار. ترويسة الموقع أو قسم hero هو المكان الملائم لاستخدام الفراغات البيضاء. حيث ستجد أنَّه من السهل جدًا استعمال الفراغات البيضاء في تلك الأماكن، وستعطيك مكانًا رائعًا للتحدث فيه عن خدمتك التي تقدمها. الذي أقصده هو أنَّ هذا القسم في أعلى الصفحة وسيراه كل زائر… حيث لا يمرر إلى أسفل الصفحة إلا القليلون، بينما سيرى جميع الزوار القسم العلوي منها. لذا ركِّز على هذا وتأكّد أن تستغل الفراغات البيضاء جيدًا في هذا القسم. على سبيل المثال، إحدى صفحات موقع Teespring: سنرى الكثير من العناصر في القسم الذي يلي قسم hero مع فراغات بيضاء قليلة. إلا أنَّ المنطقة العلوية تقوم بعملها على أتم وجه بدعم بتدعيم الهدف الرئيسي من الصفحة وتوضيح الأمور للزائر. خامسا: استخدم الفراغات البيضاء للتلاعب بالمشاعر هنالك الكثير من الأدوات بحوزتك إن كنت تسعى خلف التأثير عاطفيًا على الزائر فيمكنك أن تستعمل الألوان، أو صورة جيدة، أو قد تستفيد من الفراغات البيضاء! جميع العناصر السابقة لها دورٌ عندما يأتي الأمر إلى إنشاء استجابة عاطفية، لكن الفراغات البيضاء تضفي لمسةً من «الدراما» إلى الموقف. فبنفس الطريقة التي تستطيع الفراغات البيضاء تدعيم هدف الصفحة، ستستطيع أن تدعِّم العاطفة التي تود التأثير عليها عند الزائر. لننظر إلى هذا المثال من Todoist: Todoist هو أداة لإنشاء قائمة بالمهام. لكن باستخدامهم للمسافات البيضاء استخدامًا جيدًا حول العنوان الرئيسي، جعلوا الصور بارزةً وأضفت بعض المشاعر الإيجابية. فبدلًا من عرض صورة للمنتج نفسه، عرضوا صورةً لشخصٍ سعيدٍ يبدو أنَّه يستمتع بيومه، بعد أن أنهى مجموعةً من المهام الموكلة إليه. سادسا: حارب نزعة المصمم لملء الفراغات حسنًا، نحن البشر نحب أن نملأ الفراغات؛ فعندما نرى مكانًا فارغًا، سنفكر -لا إراديًا- كيف نستطيع أن نملأه. لكن هذه العقلية -الطبيعية جدًا- قد تسبب مشكلةً للمصمم. فعلى عكس رفوف المكتبة، ليس عليك في تصميم الويب أن تطمح إلى ملء جميع الفراغات. لذا ابدأ هكذا: ضع هدفًا للصفحة واختر عنصرًا وحيدًا يستطيع تحقيق ذاك الهدف؛ الذي يمكن أن يكون «عنوانًا رئيسيًا + دعوةً إلى إجراءٍ ما». ضع ذاك العنصر في منتصف التصميم. ضع فراغات حوله. أضف عناصر أخرى حول تلك الفراغات. سابعا: فكر بما تستطيع حذفه، لا بما تستطيع إضافته هذه النقطة مرتبطةٌ بالنقطة التي تسبقها؛ لكن بالمقلوب. بعد أن تنتهي من تصميمك، قد تشعر أنَّ هنالك أشياءً كثيرةً هنا وهناك، وستبدأ بالتفكير بالعناصر التي تستطيع حذفها من الصفحة والتي لا تؤثر تأثيرًا سلبيًا على الهدف الرئيسي. فكلما قللت العناصر، كلما كان ذلك أفضل. ويمكنك أن تعتبر أنَّ تصميمك كاملٌ إن لم يبقَ شيءٌ تستطيع حذفه، لا إضافته. ثامنا: كلما كبر حجم الخط، كلما احتجت إلى فراغ أكبر العناوين الكبيرة أصبحت "موضة" في هذه الأيام، وخصوصًا في عصر التصميمات المسطحة والعقلية السائدة المؤيدة لها. لكن الخطوط الكبيرة تحتاج إلى مساحة للتنفس؛ فإن لم تكن هنالك فراغات كافية حول العناوين الضخمة في تصميمك، فستخسر قوتها وستبدو كأنها صعبة القراءة… بغض النظر أنَّها لم تعد فعالةً. لذا ستكون القاعدة كالآتي: استخدم نصًا كبيرًا إذا ما استطعت توفير فراغات بيضاء كبيرة حوله. انظر هذا المثال من Dior: لاحظ حجم الخط الكبير جدًا للعنوان، وكمية الفراغات البيضاء التي حوله. الفراغات البيضاء على الهواتف المحمولة؟ كن حذرًا هنا. صحيحٌ أنَّ القواعد والمبادئ العامة تنطبق على تصاميم مواقع الهواتف المحمولة، ومن المحتمل أن تستفيد من الفراغات البيضاء فيها؛ إلا أنَّ هنالك حدًا دقيقًا فاصلًا بين "الاستخدام الجيد للفراغات البيضاء" وبين "ترك فراغات كثيرة بين العناصر". يكون الحد الفاصل عادةً هو عدِّة بكسلات في أحد الاتجاهات، ومن السهل جدًا أن تنتقل من تجربةٍ رائعةٍ للمستخدم إلى واجهةٍ صعبة التصفح نتيجةً لوجود الكثير من الفراغات. أرى أن تحاول جعل كل كتلة من المحتوى المهم (مثل العنوان + عبارة لحث المستخدم على اتخاذ إجراء) تملأ شاشة الهاتف بأكملها، وإن كانت هذه المهمة صعبةً في بعض الأحيان. على سبيل المثال، انظر إلى هذا التصميم من Evernote عندما يُعرَض على هاتفٍ محمول: ستُعرَض الكتلة الرئيسية من المحتوى على كامل الشاشة، ومن ثم سيبدأ المستخدم بالتمرير إلى الأسفل ليرى ماذا تحتوي بقية الصفحة. النقطة الصعبة هنا هي أنَّه عليك أن تتعامل مع أجهزةٍ مختلفةٍ، وفي حين أنَّك تستطيع اختبار التصميم على أكثر الأجهزة شيوعًا، إلا أنَّه من المستحيل أن يبدو تصميمك بشكلٍ صحيح على كل الأجهزة. أضف إلى ذلك أنَّك تريد أن يُعرَض عرضًا سليمًا على الحواسيب أيضًا؛ مما يزيد التعقيد كثيرًا. الرسالة الرئيسية التي أريد أن أوصلها لك هنا هي أن تبقى حذرًا، والزم المبادئ العامة لآلية استغلال الفراغات البيضاء دون الإفراط باستخدامها، وسيكون الأمر على ما يرام. ترجمة -وبتصرّف- للمقال The Importance of Whitespace in Web Design لصاحبه Karol K.
  13. تحديد الموقع الجغرافي هو آلية معرفة مكان وجودك في هذا العالم ومشاركة تلك المعلومات (اختياريًا) مع الأشخاص الذين تثق بهم؛ وهنالك أكثر من طريقة لمعرفة أين أنت: إما باستخدام عنوان IP الخاص بك، وإما عبر اتصال الشبكة اللاسلكية، أو عبر برج التغطية الخلوية الذي يتصل به هاتفك، أو عبر شريحة GPS التي تحسب مكانك نسبةً إلى خطوط الطول (longitude) والعرض (latitude) من المعلومات التي ترسلها الأقمار الاصطناعية من السماء. س: يبدو لي أنَّ تحديد الموقع الجغرافي مرعبٌ. هل أستطيع تعطيله؟ ج: يقلق المستخدمون من انتهاك الخصوصية عندما نتحدث عن مشاركة موقعك الفيزيائي مع خادوم ويب بعيد. تقول واجهة تحديد الموقع الجغرافي البرمجية (API) "أنَّ على المتصفحات عدم إرسال معلومات الموقع الحالي إلى مواقع الويب دون إذن المستخدم"؛ أي بكلامٍ آخر، السماح بمشاركة الموقع الجغرافي منوطٌ بك، فإن شئتَ سمحتَ بمشاركته، وإلا فلا. واجهة تحديد الموقع الجغرافي البرمجية تسمح واجهة تحديد الموقع الجغرافي البرمجية (geolocation API) لك بمشاركة موقعك الحالي مع مواقع الويب الموثوقة. ستتوفر إحداثيات الطول والعرض للصفحات عبر JavaScript، التي بدورها سترسِل تلك المعلومات إلى خادوم الويب البعيد الذي سيُجري عمليات عجيبة متعلقة بالموقع الجغرافي مثل العثور على شركة محلية أو إظهار موقعك على خريطة. كما سترى في الجدول الآتي، تُدعَم واجهة تحديد الموقع الجغرافي من أغلبية متصفحات الحاسوب والهواتف المحمولة؛ وهنالك دعمٌ لبعض المتصفحات القديمة باستخدام مكتبات خارجية، التي سنأتي على ذكرها لاحقًا في هذا الدرس. IE Firefox Safari Chrome Opera iPhone Android 9.0+ 3.5+ 5.0+ 5.0+ 10.6+ 3.0+ 2.0+ بجانب دعم واجهة تحديد الموقع الجغرافي القياسية، هنالك عدد من الواجهات البرمجية الخاصة بهواتف معيّنة، التي سنغطي شرحها لاحقًا في هذا الدرس. أريني الشيفرة تتمحور واجهة تحديد الموقع الجغرافي البرمجية حول خاصية جديدة في كائن navigator العام: navigator.geolocation. أبسط استخدام لواجهة تحديد الموقع الجغرافي هي كالآتي: function get_location() { navigator.geolocation.getCurrentPosition(show_map); } لكن ليست في الشيفرة السابقة أيّة آليات للتحقق من دعم المتصفح أو التعامل مع الأخطاء أو خياراتٍ أخرى؛ ويجب عادةً أن يتضمن تطبيق الويب اثنين مما سبق. يمكنك استخدام Modernizr للتحقق من دعم واجهة تحديد الموقع الجغرافي البرمجية: function get_location() { if (Modernizr.geolocation) { navigator.geolocation.getCurrentPosition(show_map); } else { //لا يوجد دعم لتحديد الموقع؛ ربما تجرب استخدام Gears؟ } } ما الذي تريد فعله إن لم يكن تحديد المواقع مدعومًا منوطٌ بك؛ وسأشرح كيفية استخدام مكتبة Gears بعد قليل، لكنني سأتحدث أولًا عمّا يحدث أثناء استدعاء الدالة getCurrentPosition()‎. كما ذكرتُ سابقًا في بداية هذا الدرس، لن يُجبِرَك المتصفح على إعطاء موقعك الفيزيائي إلى الخادوم البعيد، ولكن تختلف طريقة فعل ذلك من متصفح إلى آخر؛ فسيؤدي استدعاء الدالةgetCurrentPosition() ‎ في متصفح Firefox إلى إظهار "شريط معلومات" في أعلى نافذة المتصفح، الذي يبدو كالآتي: الشكل 1: شريط المعلومات الذي يُظهِره متصفح Firefox عند محاولة الوصول إلى الموقع الفيزيائي. هنالك الكثير من الأشياء المضمَّنة في ذاك الشريط؛ أنت كمستخدمٍ للمتصفح: سيتم إخبارك أنَّ موقع ويب يحاول معرفة موقعك الفيزيائي سيتم إخبارك ما هو موقع الويب الذي يحاول معرفة موقعك الفيزيائي ستتمكن من الذهاب إلى صفحة المساعدة "Location-Aware Browsing" التي تشرح لك ما الذي يجري (النسخة المختصرة من القصة هي أنَّ Google ستوفر الموقع وستخزن بياناتك بما يتوافق مع اتفاقية الخصوصية لخدمة تحديد المواقع الخاصة بها) ستستطيع أن تسمح بمشاركة موقعك الجغرافي ستتمكن من عدم السماح بمشاركة موقعك الجغرافي ستتمكن من إخبار متصفح أن يتذكر اختيارك (سواءً كنت تريد مشاركة موقعك الجغرافي أم لا) لكي لا تشاهد شريط المعلومات مرةً أخرى لكن هنالك المزيد! هذا الشريط: لا يمنعك من التبديل بين ألسنة (tabs) المتصفح أو بين نوافذه خاص بالصفحة وسيختفي بمجرد تبديلك إلى لسان أو نافذة أخرى ثم سيظهر مرةً ثانية عند عودتك إلى اللسان الأصلي. لا يمكن لمواقع الويب تجاوزه أو الالتفاف عليه يمنع مشاركة الموقع الجغرافي مع خادوم الويب أثناء انتظاره لجوابك (إن كنتَ تريد المشاركة أم لا) لقد رأيتَ شيفرة JavaScript التي تؤدي إلى إظهار شريط المعلومات السابق، وفيها دالة تؤدي إلى استدعاء دالةٍ أخرى (التي سميتُها show_map)، وستُنفَّذ الدالة getCurrentPosition()‎ مباشرةً لكن هذا لا يعني أنَّك تستطيع الوصول إلى بيانات موقع المستخدم؛ فأول مرة تضمن فيها حصولك على تلك البيانات هي داخل الدالة التي ستُستدَعى؛ التي تبدو كالآتي: function show_map(position) { var latitude = position.coords.latitude; var longitude = position.coords.longitude; // لنُظهِر خريطة أو شيئًا آخر مفيدًا } تأتي الدالة السابقة مع معامل (parameter) وحيد، الذي هو كائنٌ له خاصيتان: coords و timestamp. خاصية timestamp بسيطة، فهي الوقت والتاريخ الذي حُسِبَ فيه الموقع (لا يمكنك توقع متى سيُحسَب الموقع لأن ذلك يحدث بشكلٍ غير متزامن. فربما سيأخذ المستخدم بعض الوقت لقراءة شريط المعلومات والموافقة على مشاركة الموقع الجغرافي، وقد تستغرق الأجهزة ذات شريحة GPS بعض الوقت للاتصال بأقمار GPS الاصطناعية، ...إلخ.). أما الكائن coords فلديه خاصيات مثل latitude و longitude الواضح من اسمها أنَّها إحداثيات الموقع الفيزيائي للمستخدم. يوضِّح هذا الجدول خاصيات الكائن position: الخاصية النوع ملاحظات coords.latitude double عدد عشري coords.longitude double عدد عشري coords.altitude double أو null مترًا فوق المجسم المرجعي للأرض coords.accuracy double بواحدة المتر coords.altitudeAccuracy double أو null بواحدة المتر coords.heading double أو null درجات باتجاه عقارب الساعة من الشمال الحقيقي coords.speed double أو null بواحدة متر/ثانية timestamp DOMTimeStamp مثل الكائن Date()‎ من المضمون وجود ثلاث خاصيات من الخاصيات السابقة (coords.latitude و coords.longitude و coords.accuracy) أما البقية فيمكن أن يعيدوا القيمة null اعتمادًا على قدرات جهازك وعلى قدرات خادوم تحديد المواقع الذي تتعامل معه. ستُحسَب الخاصيتان heading و speed اعتمادًا على موقع المستخدم السابق إذا كان متوفرًا. التعامل مع الأخطاء موضوع تحديد الموقع الجغرافي معقدٌ بعض الشيء، ويحتمَل أن تأخذ الأمور منحى خاطئ. ذكرتُ سابقًا ناحية "موافقة المستخدم"؛ فلو أراد تطبيق الويب الحصول على الموقع الفيزيائي للمستخدم لكن المستخدم لم يرغب في إعطائه للتطبيق، فلن تحصل عليه وسيُطبَّق ما يقوله المستخدم دائمًا. كيف يبدو التعامل مع الأخطاء في الشيفرات؟ عليك أن تُمرِّر وسيطًا ثانيًا إلى الدالة ()getCurrentPosition هو الدالة التي ستُستدَعى عند حدوث خطأ. navigator.geolocation.getCurrentPosition(show_map, handle_error) إن حدث أيّ خطأٍ فستُستدعى الدالة المُحدَّدة مع تمرير الكائن PositionError إليها. يوضِّح الجدول الآتي خاصيات الكائن PositionError: الخاصية النوع ملاحظات code short قيمة عددية message DOMString الرسالة الموجودة في هذه الخاصية ليست موجهة للمستخدم النهائي. ستكون قيمة الخاصية code واحدة من القيم الآتية: PERMISSION_DENIED: إذا ضغط المستخدم على زر "Don't Share" أو منع وصول الوصول إلى موقعه بطريقةٍ أو بأخرى. POSITION_UNAVAILABLE: إذا توقفت الشبكة عن العمل أو في حال عدم التمكن من الوصول إلى الأقمار الاصطناعية. TIMEOUT: إذا كانت الشبكة تعمل لكنها تأخذ وقتًا طويلًا لحساب موقع المستخدم الفيزيائي؛ لكن بكم يُقدَّر "الوقت الطويل"؟ سأريك كيفية تعريف تلك القيمة في القسم التالي. function handle_error(err) { if (err.code == 1) { // لم يسمح المستخدم بالحصول على الموقع الجغرافي! } } س: هل تعمل واجهة تحديد الموقع الجغرافي في المحطة الفضائية الدولية، أو على القمر، أو على الكواكب الأخرى؟ ج: تقول مواصفات تحديد المواقع أنَّ "نظام الإحداثيات الجغرافية المستخدم في هذا الصدد هو نظام الإحداثيات الجيوديزية العالمي [WGS84]. بقية أنظمة الإحداثيات غير مدعومة". تدور المحطة الفضائية الدولية حول الأرض، لذلك يمكن وصف موقع رواد الفضاء على المحطة بإحداثيات طول وعرض وارتفاع عن الأرض، لكن يتمحور نظام الإحداثيات الجيوديزية العالمي حول الأرض، ولا يمكن استخدامه لتعيين مواقع على القمر أو بقية الكواكب. الخيارات المتاحة أمامك تدعم بعض الهواتف المحمولة -مثل iPhone وهواتف أندرويد- طريقتين لتحديد مكانك. تحسب أول طريقة موقعك بناءً على قربك من عدِّة أبراج تغطية مملوكة من شركة الاتصالات الخلوية المُشترِك فيها؛ هذه الطريقة سريعة ولا تحتاج إلى شريحة GPS فيزيائية، لكنها تعطي فكرة عامة عن موقعك، ويمكن تعيين الدقة بناءً على عدد أبراج التغطية في موقعك، فقد تكون على مستوى المباني السكنية، أو على نطاق كليو متر من مكانك. تستعمل الطريقة الثانية شريحة GPS في هاتفك لتبادل المعلومات مع أقمار GPS الاصطناعية التي تدور حول الأرض. يمكن تحديد موقعك عبر GPS بدقة كبيرة (عدِّة أمتار)، لكن من سلبيات هذه الطريقة هي الاستهلاك الكبير للطاقة من شريحة GPS، لذا ستُعطِّل الهواتف المحمولة هذه الشريحة إلى أن يتم الاحتياج إليها؛ وهذا يعني أنَّ هنالك تأخير عند تشغيل الشريحة ريثما يُهيَّأ الاتصال مع أقمار GPS الاصطناعية. إذا سبق لك واستخدمت Google Maps على هاتفٍ ذكيٍ مثل iPhone أو هواتف أندرويد، فستشاهد تطبيقًا لكلا الطريقتين السابقتين: ستشاهد أولًا دائرةً كبيرةً تُحدِّد موقعك تقريبيًا (وذلك بالبحث عن أقرب برج تغطية)، ثم دائرة أصغر (بحساب الموقع بناءً على عدِّة أبراج تغطية)، ثم نقطة وحيدة دقيقة (التي هي إحداثيات موقعك الفيزيائي بناءً على المعلومات الآتية من أقمار GPS الاصطناعية). السبب وراء ذكري لهذه المعلومات هي أنَّك لا تحتاج دومًا إلى دقة عالية، فإن كنت تبحث عن قائمة بدور عرض الأفلام التي بالجوار، فلا تلزمك إلا معرفة الموقع العام للمستخدم؛ إذ لا توجد دور عرض سينمائية كثيرة حتى في المدن المزدحمة، وستذكر -على أيّة حال- أكثر من دار عرض. أما على الكفة الأخرى، إذا كان تطبيقك يُعطي توجيهات لسائق السيارة في الوقت الحقيقي، فيجب أن تعرف موقع المستخدم الفيزيائي بدقة لكي تستطيع أن تقول "انعطف نحو اليمين بعد 20 مترًا" (أو ما شابه ذلك). يمكن تمرير وسيط (argument) ثالث اختياري إلى دالة getCurrentPosition()‎ هو كائن PositionOptions؛ وهنالك ثلاث خاصيات يمكنك ضبطها في كائن PositionOptions، وكل تلك الخاصيات اختيارية، إذ تستطيع أن تضبطها جميعًا أو أن لا تضبط أيًّا منها، وهي مبيّنة في الجدول الآتي: الخاصية النوع القيمة الافتراضية ملاحظات enableHighAccuracy Boolean false قد تُسبِّب القيمة true بطئًا timeout long (لا توجد قيمة افتراضية) القيمة بواحدة الميلي ثانية maximumAge long 0 القيمة بواحدة الميلي ثانية وظيفة خاصية enableHighAccuracy مماثلة لاسمها، إن كانت قيمتها true وكان يدعمها الجهاز ووافق المستخدم على مشاركة موقعه الفيزيائي مع التطبيق، فسيحاول الجهاز توفير الموقع الفيزيائي بدقة. هنالك أذونات منفصلة للتحديد الدقيق وغير الدقيق للموقع الجغرافي في هواتف iPhone وأندرويد؛ لذا من الممكن أن يفشل استدعاء الدالة getCurrentPosition()‎ مع ضبط الخاصية enableHighAccuracy:true، لكن قد ينجح استدعاؤها مع ضبط الخاصية enableHighAccuracy:false. تُحدِّد خاصية timeout كم ملي ثانية على تطبيق الويب أن ينتظر الحصول على الموقع الفيزيائي، لكن لا يبدأ المؤقت إلا بعد أن يوافق المستخدم على إعطاء إحداثيات موقعك الفيزيائي؛ فليس الغرض من هذه الخاصية قياس سرعة ردة فعل المستخدم، وإنما قياس سرعة الشبكة. تسمح خاصية maximumAge للجهاز بأن يُجيب مباشرةً بنسخة محفوظة من الإحداثيات. على سبيل المثال، لنقل أنَّك استَدعيتَ getCurrentPosition()‎ لأول مرة، ثم وافق المستخدم على إعطاء موقعه الجغرافي وانتهت عملية حساب الموقع الفيزيائي في الساعة ‎10:00 AM تمامًا؛ وبعد دقيقة واحدة (أي 10:01‎ AM) استدعيتَ الدالة getCurrentPosition()‎ مرةً أخرى مع ضبط خاصية maximumAge إلى 75000. navigator.geolocation.getCurrentPosition( success_callback, error_callback, {maximumAge: 75000}); أنت تقول أنَّه لا يهمك موقع المستخدم في لحظة استدعاء الدالة، وإنما ستقبل بمعرفة أين كان المستخدم منذ 75 ثانية مضت (75000 ميلي ثانية)؛ لكن الجهاز يعرف أين كان المستخدم منذ 60 ثانية (60000 ملي ثانية)، لأنه حسب موقعه في أول مرة استدعيتَ فيها الدالة getCurrentPosition()‎؛ وبالتالي لن يحتاج الجهاز إلى إعادة حساب موقع المستخدم الحالي، إذ سيَستخدِم نفس المعلومات التي أرسلها أول مرة: أي نفس إحداثيات الطول والعرض، ونفس الدقة، ونفس بصمة الوقت (timestamp أي 10:00‎ AM). عليك أن تفكِّر في مدى الدقة المطلوبة قبل أن تسأل المستخدم عن موقعه، وتضبط الخاصية enableHighAccuracy وفقًا لذلك. وإذا كنت تريد معرفة موقع المستخدم أكثر من مرة، فعليك التفكير في العمر الأقصى للمعلومات التي تستطيع الاستفادة منها، وتضبط الخاصية maximumAge وفقًا لذلك. أما إن أردت معرفة موقع المستخدم بشكلٍ دائم، فلن تكون الدالة getCurrentPosition()‎‎ مناسبةً لك، وعليك حينها استخدام watchPosition()‎. تملك دالة watchPosition() نفس بنية الدالة getCurrentPosition()‎، إذ يمكنها استدعاء دالتين، إحداهما ضرورية وتستخدم إن نجحت عملية الحصول على الموقع، وأخرى اختيارية غرضها هو التعامل مع الأخطاء؛ ويمكنها -أي الدالة- أن تقبل تمرير كائن PositionOptions اختياريًا الذي يملك الخاصيات ذاتها التي تعلمتها منذ قليل. الاختلاف في أنَّ الدالة التي ستُستدَعى ستُنفَّذ في كل مرة يتغير فيها موقع المستخدم، ولن تحتاج إلى محاولة الحصول على الموقع يدويًا، فسيُحدِّد جهازك الفاصل الزمني الأمثل لتحديث الموقع وسيستدعي الدالة عند كل تغيّر لموقع المستخدم. يمكنك الاستفادة من هذا لتحديث موضع مؤشر على الخريطة، أو توفير تعليمات عن المكان الذي عليك زيارته لاحقًا، أو أيّ شيءٍ تريده. تُعيد الدالة watchPosition()‎ رقمًا عليك تخزينه في مكانٍ ما، فلو أردت إيقاف عملية مراقبة تغيّر موقع المستخدم، فعليك استدعاء الدالة clearWatch()‎ مُمرِّرًا إليها ذاك الرقم، وسيتوقف الجهاز عن إرسال تحديثات الموقع إلى دالتك. يعمل ما سبق تمامًا كالدالتين setInterval()‎ و clearInterval()‎ في JavaScript إن استخدمتهما من قبل. ماذا عن متصفح IE؟ لم يكن يدعم متصفح Internet Explorer قبل الإصدار التاسع واجهة تحديد المواقع البرمجية من W3C التي شرحتها من قبل، لكن لا تقنط! Gears هي إضافة مفتوحة المصدر للمتصفحات من Google التي تعمل على ويندوز ولينُكس و Mac OS X والهواتف العاملة بنظامَي Windows Phone وأندرويد. حيث مهمتها توفير ميزات للمتصفحات القديمة، وإحدى الميزات التي توفرها Gears هي واجهة برمجية لتحديد المواقع إلا أنَّها ليس مماثلة لواجهة W3C البرمجية، لكنها تخدم نفس الغرض. لمّا كنّا نتحدث هنا عن المنصات القديمة، فمن الجدير بالذكر أنَّ عددًا من أنظمة الهواتف المحمولة القديمة لها واجهات برمجية خاصة بها لتحديد المواقع، حيث توفر هواتف BlackBerry و Nokia وpalm و OMTP BONDI واجهات برمجية خاصة بها؛ التي تختلف بالطبع عن Gears والتي تختلف بدورها عن W3C. مكتبة geo.js geo.js هي مكتبة JavaScript مفتوحة المصدر مرخَّصة برخصة MIT التي تسهِّل التعامل مع واجهة W3C البرمجية وواجهة Gears البرمجية والواجهات البرمجية التي توفرها أنظمة الهواتف القديمة. عليك أن تُضمِّن عنصرَي <script> في أسفل صفحتك لكي تستخدمها (يمكنك أن تضع العنصرين في أي مكان في الصفحة، لكن وضعهما في عنصر <head> سيُبطِّئ من تحميل الصفحة، فلا تفعل ذلك). أول سكربت هو gears_init.js الذي يُهيِّئ إضافة Gears إن وجِدَت، أما السكربت الثاني فهو geo.js. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Dive Into HTML5</title> </head> <body> ... <script src="gears_init.js"></script> <script src="geo.js"></script> </body> </html> ستتمكن من تحديد الموقع الآن بغض النظر عن الواجهة البرمجية المدعومة في المتصفح. if (geo_position_js.init()) { geo_position_js.getCurrentPosition(geo_success, geo_error); } لنُقسِّم ما سبق ونشرح كل سطرٍ على حدة. ستحتاج أولًا إلى استدعاء دالة init()‎، التي تُعيد true إن وجِدَ دعمٌ لإحدى واجهات تحديد المواقع البرمجية. if (geo_position_js.init()) { لن تعثر الدالة init()‎ على الموقع الجغرافي، وإنما تتحقق من أنَّ الوصول إلى الموقع ممكنٌ. عليك أن تستدعي الدالة getCurrentPosition()‎ للحصول على الموقع. geo_position_js.getCurrentPosition(geo_success, geo_error); ستؤدي الدالة getCurrentPosition()‎ إلى جعل المتصفح يطلب من المستخدم إذنه للحصول على موقعه الفيزيائي ومشاركته. إن كان الوصول إلى الموقع الجغرافي موفرًا من إضافة Gears فسيظهر مربع حوار يسألك إن كنت تثق بموقع الويب لكي يحصل على موقعك. أما إذا كان يدعم المتصفح تحديد المواقع داخليًا، فسيظهر مربع حوار ذو شكلٍ مختلف. على سبيل المثال، يدعم Firefox واجهة تحديد الموقع الجغرافي البرمجية داخليًا، فلو حاولت الحصول على الموقع الجغرافي فيه، فسيظهر شريط معلومات في أعلى الصفحة يسأل المستخدم إن كان يريد مشاركة موقعه الجغرافي مع موقع الويب. تأخذ الدالة getCurrentPosition()‎ وسيطين هما الدالتان اللتان ستُستدعيا، فإن نجحت الدالة getCurrentPosition() في الحصول على موقع المستخدم -أي أنَّه أعطى إذنًا للوصول إلى الموقع الجغرافي، واستطاعت واجهة تحديد الموقع الجغرافي البرمجية تعيين الموقع- فستُستدعى الدالة الأولى، التي تكون في هذه المثال geo_success. geo_position_js.getCurrentPosition(geo_success, geo_error); تأخذ تلك الدالة وسيطًا وحيدًا يحتوي على معلومات الموقع الفيزيائي: function geo_success(p) { alert("Found you at latitude " + p.coords.latitude + ", longitude " + p.coords.longitude); } وإن لم تستطع الدالة getCurrentPosition()‎ معرفة موقع المستخدم -إما أن يكون المستخدم قد رفض إعطاء الإذن، أو لفشل تعيين الموقع من الواجهة البرمجية لسببٍ من الأسباب- فستُستدعى الدالة الثانية، التي تكون في مثالنا geo_error. geo_position_js.getCurrentPosition(geo_success, geo_error); لا تأخذ تلك الدالة أيّة وسائط: function geo_error() { alert("Could not find you!"); } لا تدعم مكتبة geo.js الدالة watchPosition()‎، عليك أن تطلب الدالة getCurrentPosition()‎ بشكلٍ متواصل إن أردت الحصول على تحديث فوري لموقع المستخدم. مثال متكامل سأشرح لك مثالًا يستخدم مكتبة geo.js للوصول إلى موقعك وعرض خريطة لما حولك. ستُستدعى الدالة geo_position_js.init()‎ عند تحميل الصفحة لمعرفة فيما إذا كانت تتوفر ميزة تحديد الموقع الجغرافي بأي شكلٍ من الأشكال التي تدعمها geo.js. فإن كانت مدعومةً فسيظهر رابط يمكن للمستخدم النقر عليه لإظهار موقعه الجغرافي؛ يستدعي هذه الرابط الدالة lookup_location()‎ الظاهرة هنا: function lookup_location() { geo_position_js.getCurrentPosition(show_map, show_map_error); } إذا أعطى المستخدم موافقته على تحديد الموقع، وكانت الخدمة الخلفية (backend service) قادرة على تحديد الموقع، فستستدعي مكتبة geo.js أول دالة التي هي show_map()‎ مع وسيط وحيد الذي هو loc حيث يُمثِّل خاصية coords التي تحتوي إحداثيات الطول والعرض ودقة القياس (لا يستخدم هذا المثال معلومات دقة القياس). تستعمل بقية الدالة show_map()‎ واجهة Google Maps البرمجية لإظهار الخريطة. function show_map(loc) { $("#geo-wrapper").css({'width':'320px','height':'350px'}); var map = new GMap2(document.getElementById("geo-wrapper")); var center = new GLatLng(loc.coords.latitude, loc.coords.longitude); map.setCenter(center, 14); map.addControl(new GSmallMapControl()); map.addControl(new GMapTypeControl()); map.addOverlay(new GMarker(center, {draggable: false, title: "You are here (more or less)"})); } أما لو لم تستطع geo.js تحديد موقعك، فستُستدعى الدالة الثانية show_map_error()‎. function show_map_error() { $("#live-geolocation").html('Unable to determine your location.'); } مصادر إضافية W3C geolocation API مكتبة Gears مكتبة geo.js
  14. سنلقي في هذا الدرس نظرةً إلى آلية التعامل مع الأخطاء و الإشارات أثناء تنفيذ السكربتات. يُقاس الفرق بين البرمجة الجيدة والتعيسة عادةً بمدة استقرار البرنامج ومرونته، أي قابلية تعامل البرنامج مع الحالات التي لا تسير فيها الأمور على ما يرام. حالة الخروج تتذكر من دروسنا السابقة أنَّ كل برنامج مكتوب بطريقة جيدة سيُعيد حالة خروج عندما ينتهي تنفيذه؛ فإذا انتهى تنفيذ البرنامج بنجاح، فستكون حالة الخروج مساويةً للصفر، وإن كانت حالة الخروج مساوية لقيمة مختلفة عن الصفر، فهذا يدلّ أنَّ البرنامج قد واجهة مشكلةً ما منعته من إتمام مهمته. من المهم جدًا التحقق من حالة خروج البرامج التي تستدعيها في سكربتاتك، ومن المهم أيضًا أن تُعيد سكربتاتك حالة خروج مفيدة عندما تنتهي. لقد مرّ عليّ مدير أنظمة يونكس قد كتب سكربتًا لنظام إنتاجي يحتوي على السطرين الآتيين: # Example of a really bad idea cd $some_directory rm * هذه طريقة سيئة جدًا لكتابة البرامج، لأنها تعمل بشكلٍ جيد لو لم يحدث أيّ خطأ. فالسطر الأول ينقل مجلد العمل الحالي إلى المسار الموجود في المتغير ‎$some_directory ثم يحذف جميع الملفات الموجودة في ذاك المجلد؛ وهذه هي الحالة الطبيعية لتنفيذ السكربت، لكن ماذا يحدث لو لم يكن المسار الموجود في المتغير ‎$some_directory موجودًا؟ سيفشل -في هذه الحالة- تنفيذ الأمر cd ثم سيُنفِّذ السكربتُ الأمرَ rm في مجلد العمل الحالي، وهنا ستقع الكارثة! بالمناسبة، واجه سكربت مدير الأنظمة البائس هذه المشكلة ودمَّر جزءًا كبيرًا مهمًا من النظام الإنتاجي؛ لا تدع ذلك يحدث لك! مشكلة السكربت السابق أنَّه لم يتحقق من حالة خروج الأمر cd قبل الاستمرار وتنفيذ الأمر rm. التحقق من حالة الخروج هنالك عدِّة طرائق تستطيع فيها الحصول على حالة الخروج والتصرف وفقًا لها. أول طريقة هي معاينة محتويات متغير البيئة ‎$?‎، الذي يحتوي على حالة خروج آخر أمر مُنفَّذ. يمكنك رؤية ذلك في المثال الآتي: $ true; echo $? 0 $ false; echo $? 1 تذكَّر أنَّ الأمرَين true و false لا يفعلان شيئًا سوى الانتهاء بحالة خروج تساوي 0 و 1 على التوالي وبالترتيب. وعبر استخدامهما سنرى كيف يُعيد المتغير ‎$?‎ حالة الخروج لآخر أمر مُنفَّذ. نستطيع كتابة السكربت بهذه الطريقة بعد التحقق من حالة الخروج: # Check the exit status cd $some_directory if [ "$?" = "0" ]; then rm * else echo "Cannot change directory!" 1>&2 exit 1 fi تفحصنا محتوى الأمر cd، وإن كان لا يساوي الصفر، فسيطبع رسالة خطأ في مجرى الخطأ القياسي (standard error stream) وسينتهي تنفيذه مع ضبط حالة الخروج إلى 1. صحيح أنَّ النسخة السابقة تقدِّم حلًا لمشكلتنا، إلا أنَّ هنالك طرائق أفضل ستقلل مقدار الكتابة التي نحتاج لها. سنستخدم في المثال الآتي العبارة الشرطية if مباشرةً، لأنَّها تحقق من حالة خروج الأمر الذي يليها ثم تتصرف وفقًا لذلك. يمكننا إعادة صياغة السكربت كالآتي: # A better way if cd $some_directory; then rm * else echo "Could not change directory! Aborting." 1>&2 exit 1 fi تحققنا هنا أنَّ تنفيذ الأمر cd قد نجح، وبعد ذلك سينُفّذ الأمر rm؛ أو ستظهر رسالة خطأ فيما عدا ذلك وينتهي البرنامج بحالة خروج تساوي 1، مما يشير إلى حدوث مشكلة أثناء التنفيذ. دالة خروج عند حدوث خطأ لمّا كنّا نتحقق من الأخطاء مرارًا وتكرارًا في برامجنا، فمن المعقول أن نكتب دالةً لإظهار الأخطاء، مما يوفِّر علينا بعض الكتابة ويُنمّي إحساس الكسل لدينا :-) . # An error exit function error_exit() { echo "$1" 1>&2 exit 1 } # Using error_exit if cd $some_directory; then rm * else error_exit "Cannot change directory! Aborting." fi معاملات التحكم: "و" AND وَ "أو" OR يمكننا تبسيط السكربت كثيرًا باستخدام معاملَي التحكم "AND" و "OR"، وسأقتبس الفقرة الآتية من صفحة دليل bash لشرح آلية عملهما: سنستعمل الأمرَين true و false مجددًا لكي نشاهد آلية التعامل مع AND و OR عمليًا: $ true || echo "echo executed" $ false || echo "echo executed" echo executed $ true && echo "echo executed" echo executed $ false && echo "echo executed" $ باستخدام هذه التقنية، يمكننا إعادة كتابة نسخة مختصرة أكثر من السكربت السابق: # Simplest of all cd $some_directory || error_exit "Cannot change directory! Aborting" rm * إن لم يكن إنهاء البرنامج مطلوبًا، فيمكننا كتابة السكربت بالصيغة الآتية: # Another way to do it if exiting is not desired cd $some_directory && rm * صحيحٌ أننا أخذنا جميع الاحتياطات الممكنة في مثال cd، إلا أنني أود أن أشير أنَّ الشيفرة يمكن أن تتعرض للمشاكل البرمجية الشائعة، خصوصًا إذا كُتِبَ اسم المتغير الذي يحتوي على مسار المجلد الذي نريد حذف محتوياته بشكلٍ خاطئ. ففي هذه الحالة ستضع الصَدَفة قيمةً فارغةً بدلًا من اسم المتغير وسينتج تنفيذ الأمر cd، لكنه سينتقل إلى مجلد المنزل للمستخدم الحالي، ثم سيحذف كل ما فيه! تحسين دالة الخروج عند حدوث خطأ هنالك عددٌ من التحسينات التي نستطيع إجراءها على الدالة error_exit، أود أن أضع اسم البرنامج في رسالة الخطأ كي أوضِّح من أين تأتي رسالة الخطأ؛ وذلك مهمٌ خصوصًا في السكربتات الكبيرة والمعقدة التي تُستدعى فيها سكربتات داخل سكربتات… لاحظ أيضًا تضمين متغير البيئة LINENO الذي سيساعد في تحديد السطر الذي حدثت فيه المشكلة. #!/bin/bash # A slicker error handling routine # I put a variable in my scripts named PROGNAME which # holds the name of the program being run. You can get this # value from the first item on the command line ($0). PROGNAME=$(basename $0) error_exit() { # ---------------------------------------------------------------- # Function for exit due to fatal program error # Accepts 1 argument: # string containing descriptive error message # ---------------------------------------------------------------- echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 exit 1 } # Example call of the error_exit function. Note the inclusion # of the LINENO environment variable. It contains the current # line number. echo "Example of error with line number and message" error_exit "$LINENO: An error has occurred." استخدام الحاضنات ({}) داخل الدالة error_exit هو مثالٌ عن "توسعة المعاملات" (parameter expansion)، يمكنك وضع حاضنات حول اسم المتغير (كما في {‎${PROGNAME) إذا أردت عزل المتغير عمّا حوله من نصوص. يُفضِّل البعض وضع الحاضنات حول كل متغير، وهذا الأمر منوطٌ بك. الشيء الآخر الذي أريد الإشارة إليه هو {"‎${1:- "Unknown Error الذي يعني أنَّه لو لم يكن المعامل 1 (أي ‎$1) مُعرَّفًا، فضع السلسلة النصية "Unknown Error" مكانه. يمكن إجراء الكثير من عمليات معالجة النصوص باستخدام توسعة المعاملات. الإشارات Signals لا تستطيع اعتبار أنَّ الأخطاء هي المُسبِّب الوحيد لإنهاء البرنامج على نحوٍ غير متوقع، فعليك أن تحتاط من الإشارات (signals) أيضًا. ليكن لديك المثال الآتي: #!/bin/bash echo "this script will endlessly loop until you stop it" while true; do : # Do nothing done بعد أن تُشغِّل هذا السكربت، فسيبدو وكأنَّه علّق، لكنه -مثل أغلبية البرامج التي تُعلِّق- قد دخل في حلقة تكرار ولم يخرج منها. فالسكربت في مثالنا ينتظر أن يُعيد الأمر true حالة خروج لا تساوي الصفر، وهذا لن يحدث أبدًا. وسيستمر تنفيذ السكربت إلى أن تُرسِل الصَدَفة bash إشارةً له لإيقافه. يمكنك إرسال إشارة بالضغط على Ctrl+c التي تُسمى SIGINT (مختصرة من SIGnal INTerrupt). إنهاء البرامج التي تستقبل إشارات بشكل سليم حسنًا، يمكن أن تأتي إشارة وتؤدي إلى إنهاء تنفيذ السكربت، لكن لماذا نهتم بذلك؟ لا يؤدي إنهاء السكربت عبر الإشارات في العديد من الحالات إلى مشاكل، لكنها تحتاج إلى معالجة في بعض الحالات. لننظر إلى مثالٍ آخر: #!/bin/bash # Program to print a text file with headers and footers TEMP_FILE=/tmp/printfile.txt pr $1 > $TEMP_FILE echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE fi يُعالِج السكربت السابق ملفًا نصيًا مُمَرَّرًا كوسيط في سطر الأوامر عبر الأمر pr الذي يُخزِّن الناتج في ملف مؤقت (temporary file)، ثم سيسأل المستخدم عمّا إذا كان يريد طباعة الملف، فلو وافق المستخدم عبر كتابة y فسيُمرَّر الملف المؤقت إلى الأمر lpr للطباعة (يمكنك وضع الأمر less بدلًا من lpr إذا لم تكن لديك طابعة موصولة بحاسوبك). عليّ أن أعترف لك أنَّ السكربت السابق فيه العديد من المشاكل التصميمية؛ وعلى الرغم من استخدامه لاسم الملف المُمرَّر عبر سطر الأوامر، لكنه لا يتحقق أنَّ القيمة فارغة، ولا يتحقق إن كان الملف موجودًا فعلًا. لكن الفكرة التي أريد التركيز عليها هي أنَّه عندما ينتهي تنفيذ السكربت، فسيُبقي خلفه ملفًا مؤقتًا. من المستحسن أن نحذف الملف المؤقت ‎$TEMP_FILE عند انتهاء تنفيذ السكربت، ويتم هذا بسهولة بإضافة السطر الآتي في نهاية السكربت: rm $TEMP_FILE قد تظن أننا حللنا المشكلة، لكن ماذا سيحدث لو ضغط المستخدم على Ctrl+c عندما تظهر الرسالة "Print file? [y/n]:‎" سينتهي تنفيذ السكربت عند الأمر read ولن يُنفَّذ الأمر rm أبدًا. سنحتاج إذًا إلى طريقة لمعالجة الإشارات مثل SIGINT عندما يضغط المستخدم على Ctrl+c. لحسن الحظ، توفِّر bash طريقةً لتنفيذ الأوامر فيما إذا استقبِلَت إشارةٌ ما. الأمر trap يسمح لك الأمر trap بتنفيذ أمر عندما يستقبل سكربتك إشارةً. يعمل هذا الأمر كالآتي: trap arg signals حيث signals هي قائمة بالإشارات التي تريد «اعتراضها» أو معالجتها، و arg هو الأمر التي سيُنفَّذ عندما تُستقبَل واحدة من الإشارات المُحدَّدة. يمكننا التعامل مع الإشارات في سكربت الطباعة السابق كما يلي: #!/bin/bash # Program to print a text file with headers and footers TEMP_FILE=/tmp/printfile.txt trap "rm $TEMP_FILE; exit" SIGHUP SIGINT SIGTERM pr $1 > $TEMP_FILE echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE fi rm $TEMP_FILE أضفنا الأمر trap الذي سيُنفِّذ الأمر rm $TEMP_FILE إذا استقبل السكربت إحدى الإشارات المذكورة، التي هي أكثر الإشارات التي ستواجهها، لكن هنالك المزيد منها التي تستطيع ذكرها أيضًا. انظر ناتج الأمر trap -l لرؤية القائمة الكاملة. يمكنك أيضًا ذكر الإشارات بأرقامها بدلًا من أسمائها. الإشارة 9 التي لا ترحم! هنالك إشارة لا تستطيع التعامل معها داخل السكربت: إشارة SIGKILL أو الإشارة رقم 9. ستُنهي النواة أيّ عملية تُرسَل لها هذه الإشارة مباشرةً دون العودة إلى البرنامج أو السماح له بإجراء أيّ عملية لمعالجة الإشارة. ولأنه هذه الإشارة تُنهي البرامج التي تُعلِّق أو لا تستجيب، فربما تظن أنَّها أسهل طريقة لإنهاء برنامج ما. وقد ترى الأمر الآتي عندما يأتي ذكر الإشارة SIGKILL: kill -9 وعلى الرغم من أنَّ هذه الإشارة تُتِمُّ عملها بسرعة وسهولة، لكن تذكَّر أنَّ البرنامج لن يستطيع معالجة هذه الإشارة، ولا بأس في ذلك في بعض الحالات؛ لكن قد يُسبِّب مشاكل في بعضها الآخر. حيث تُنشِئ بعض البرامج المعقدة (وحتى بعض البرامج غير المعقدة) ملفات اسمها lock files لمنع تشغيل عدِّة نسخ من نفس البرنامج في نفس الوقت. فعندما تُرسَل إشارة SIGKILL إلى برنامج يستخدم lock files، فلن يكون لديه فرصة لحذف ذاك الملف؛ وسيؤدي وجود ذاك الملف إلى منع تشغيل البرنامج إلى أن يُحذَف يدويًا. استعمل SIGKILL كملاذٍ أخيرٍ لك. دالة clean_up صحيحٌ أنَّ الأمر trap حلّ المشكلة، لكننا لاحظنا بعض المحدوديات؛ خصوصًا أنَّه لا يقبل إلا سلسلةً نصيةً وحيدةً تحتوي الأمر الذي سيُنفَّذ عندما تُستقبَل الإشارة. يمكنك الالتفاف على هذه المحدودية بوضع ; بين عدِّة أوامر، إلا أنَّ هذه الطريقة بشعة المظهر. فمن الأفضل إنشاء دالة ستُستدعى عندما ينتهي تنفيذ السكربت. أُسميّ هذه الدالة عادةً clean_up. #!/bin/bash # Program to print a text file with headers and footers TEMP_FILE=/tmp/printfile.txt clean_up() { # Perform program exit housekeeping rm $TEMP_FILE exit } trap clean_up SIGHUP SIGINT SIGTERM pr $1 > $TEMP_FILE echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE fi clean_up يمكن استعمال دالة clean_up في البُنى التي تهتم بمعالجة الأخطاء. فيجب على أيّة حال أن "تُنظِّف" ما خلّفه السكربت بعد انتهائه (لأي سببٍ كان). هذه هي النسخة النهائية من برنامجنا التي حسّنّا فيها معالجة الأخطاء والإشارات: #!/bin/bash # Program to print a text file with headers and footers # Usage: printfile file # Create a temporary file name that gives preference # to the user's local tmp directory and has a name # that is resistant to "temp race attacks" if [ -d "~/tmp" ]; then TEMP_DIR=~/tmp else TEMP_DIR=/tmp fi TEMP_FILE=$TEMP_DIR/printfile.$$.$RANDOM PROGNAME=$(basename $0) usage() { # Display usage message on standard error echo "Usage: $PROGNAME file" 1>&2 } clean_up() { # Perform program exit housekeeping # Optionally accepts an exit status rm -f $TEMP_FILE exit $1 } error_exit() { # Display error message and exit echo "${PROGNAME}: ${1:-"Unknown Error"}" 1>&2 clean_up 1 } trap clean_up SIGHUP SIGINT SIGTERM if [ $# != "1" ]; then usage error_exit "one file to print must be specified" fi if [ ! -f "$1" ]; then error_exit "file $1 cannot be read" fi pr $1 > $TEMP_FILE || error_exit "cannot format file" echo -n "Print file? [y/n]: " read if [ "$REPLY" = "y" ]; then lpr $TEMP_FILE || error_exit "cannot print file" fi clean_up الطريقة المثالية لإنشاء ملفات مؤقتة هنالك عدِّة إجراءات يمكننا اتخاذها لتأمين الملف المؤقت الذي استخدمه السكربت. من تقاليد نظام يونكس استخدام المجلد ‎/tmp لتخزين الملفات المؤقتة التي تستعملها البرامج. يمكن للجميع الكتابة إلى ذاك المجلد، وقد يُسبِّب ذلك لك بعض المخاوف الأمنية. تجنّب كتابة الملفات في مجلد ‎/tmp إن كان ذلك ممكنًا. التقنية المُستحسنة هي كتابة الملفات إلى مجلد محلي مثل ‎~/tmp (أي مجلد tmp الموجود في مجلد المنزل للمستخدم المُنفِّذ للسكربت)؛ وإن كان لا بُد، فعليك اتخاذ بعض الخطوات للتأكد أن أسماء الملفات المُخزَّنة في مجلد ‎‎/tmp غير متوقعة؛ حيث تسمح أسماء الملفات المتوقعة للمخترقين أن يُنشِئوا وصلات رمزية إلى ملفات أخرى التي يريدون منك أن تكتب فوقها. الاسم الجيد للملف المؤقت هو الاسم الذي يسمح لك بمعرفة مَن الذي كتب الملف، ولكنه ليس متوقعًا بشكلٍ كامل. استخدمنا السطر الآتي في السكربت أعلاه لإنشاء اسم الملف المؤقت ‎$TEMP_FILE: TEMP_FILE=$TEMP_DIR/printfile.$$.$RANDOM سيحتوي المتغير ‎$TEMP_DIR على ‎‎/tmp أو ‎~/tmp اعتمادًا على توفر المجلد ~/tmp، ومن الشائع تضمين اسم البرنامج في اسم الملف، وهذا هو سبب وضعنا للكلمة "printfile". ثم استخدمنا متغير الصَدَفة $$ لتضمين مُعرِّف العملية (PID) الخاص بالبرنامج. وهذا سيُحدِّد تمامًا ما هي العملية المسؤولة عن الملف. وبالطبع لا نستطيع أن نعتبر أنَّ رقم العملية كافٍ لجعل اسم الملف غير متوقع؛ لهذا أضفنا متغير الصَدَفة ‎$RANDOM لتضمين رقم عشوائي في اسم الملف. وبالآلية السابقة، أنشأنا اسمًا لملفٍ مؤقتٍ سهلُ التعرف وغير متوقع. خاتمة حسنًا، لقد أنهينا هذه السلسلة، أرجو أن تكون قد وجدتها مفيدةً ومسلية في آن واحد. وأنصحك بإكمال مسيرتك في سطر الأوامر وسكربتات الصَدَفة بقراءة كتاب سطر أوامر لينُكس لمترجمه عبد اللطيف ايمش. ترجمة -وبتصرّف- للمقالين Errors And Signals And Traps (Oh My!) - Part 1 و Errors And Signals And Traps (Oh, My!) - Part 2 لصاحبهما William Shotts.
  15. تعرفنا في الدروس السابقة على صيغ ترميز الفيديو والصوت و كيفية ترميز مقاطع الفيديو باستخدام عدة صيغ. حسنًا، يُفترَض أنَّ هذه السلسلة عن HTML، لذا أين الشيفرات البرمجية؟ تمنحك HTML5 طريقتين لتضمين الفيديو في صفحة الويب، وكلا الطريقتين تستخدمان العنصر <video>. وإذا كان لديك ملف فيديو وحيد، فيمكنك ببساطة إضافة رابط له في خاصية src، وهذا شبيه ٌجدًا بطريقة إدراج صورة بالوسم ‎<img src="...">‎. <video src="pr6.webm"></video> تقنيًا، هذا كل ما تحتاج له؛ ولكن كما في عنصر <img> عليك دومًا أن تضيف الخاصيتين width و height في وسوم <video>. يمكن أن تكون الخاصيتان width و height مطابقتان لعرض وارتفاع الفيديو الذي رمَّزتَه. لا تقلق إن كان أحد بُعدَي الفيديو أصغر من القيمتين المُحدَّدتين، لأن المتصفح سيضع الفيديو في منتصف المربع الناتج عن وسم <video>، ولهذا لن يتشوه المقطع. <video src="pr6.webm" width="320" height="240"></video> افتراضيًا، لن يُظهِر الوسم <video> أي نوع من عناصر التحكم بالتشغيل، يمكنك إنشاء عناصر التحكم الخاصة بك باستخدام HTML و CSS و JavaScript؛ فالعنصر <video> يملك دوال مثل play()‎ و pause()‎ ويمكن القراءة والكتابة إلى خاصيات مثل currentTime، وهنالك أيضًا خاصيات أخرى يمكن القراءة منها والكتابة عليها مثل volume و muted؛ لذا تستطيع أن تبني واجهة عناصر التحكم كيف ما تشاء. إن لم تُرِد بناء واجهة عناصر التحكم يدويًا، فيمكنك أن تخبر المتصفح أن يُظهِر عناصر التحكم المدمجة فيه؛ وذلك بتضمين الخاصية controls في وسم <video>. <video src="pr6.webm" width="320" height="240" controls></video> هنالك خاصيتان اختياريتان إضافيتان أريد أن أذكرهما قبل الإكمال: preload و autoplay؛ دعني أشرح لك فائدتهما. تخبر الخاصية preload المتصفح أنَّك تريد البدء بتنزيل ملف الفيديو في أقرب فرصة بعد انتهاء تحميل الصفحة، وهذا شيءٌ منطقي إذا كان الغرض من الصفحة هو مشاهدة مقطع الفيديو؛ أما على الكفة الأخرى، إذا كان المقطع ثانويًا ولن يشاهده إلا القلة، فيمكنك أن تضبط الخاصية preload إلى none كي تخبر المتصفح بذلك لتقليل استهلاك التراسل الشبكي. هذا مثالٌ عن مقطع فيديو يبدأ بالتنزيل (لكن لن يُشغَّل) بعد انتهاء تنزيل الصفحة: <video src="pr6.webm" width="320" height="240" preload></video> هذا مثالٌ عن مقطع فيديو لن يبدأ بالتنزيل عند انتهاء تحميل الصفحة: <video src="pr6.webm" width="320" height="240" preload="none"></video> وظيفة الخاصية autoplay واضحة من اسمها: تخبر المتصفح أنَّك تحبِّذ تنزيل ملف الفيديو بعد انتهاء تحميل الصفحة، وترغب في أن يبدأ تشغيل المقطع تلقائيًا في أقرب وقتٍ ممكن. بعض الأشخاص يحبون هذا الأمر، وبعضهم يكرهونه؛ لكن دعني أشرح لماذا من المهم وجود هذه الخاصية في HTML5. بعض الأشخاص يريدون بدء تشغيل مقاطع الفيديو في صفحاتهم تلقائيًا حتى لو كان ذلك سيُزعِج زوار موقعهم. إن لم تُعرِّف HTML5 طريقةً معياريةً لبدء تشغيل مقاطع الفيديو فسيلجأ المطورون إلى استخدام JavaScript لفعل ذلك (على سبيل المثال، عبر استدعاء الدالة play()‎ أثناء الحدث window.load)؛ وهذا يُصعِّب مهمة تعطيل هذه الخاصية على الزوار، إذ يمكن استخدام إضافة إلى المتصفح (أو كتابة واحدة إن لزم الأمر) مهمتها هي تجاهل خاصية autoplay، مما يُعطِّل تشغيل الفيديو التلقائي. هذا مثالٌ عن مقطع فيديو سيبدأ تنزيله وتشغيله في أقرب فرصة ممكنة بعد انتهاء تحميل الصفحة: <video src="pr6.webm" width="320" height="240" autoplay></video> هذا هو سكربت Greasemonkey الذي تستطيع تثبيته على متصفحك ليمنع التشغيل التلقائي لفيديو HTML5؛ حيث يستخدم خاصية autoplay في كائن DOM (المكافئة لخاصية autoplay في شيفرة HTML) لتعطيل التشغيل (disable_video_autoplay.user.js): // ==UserScript== // @name Disable video autoplay // @namespace http://diveintomark.org/projects/greasemonkey/ // @description Ensures that HTML5 video elements do not autoplay // @include * // ==/UserScript== var arVideos = document.getElementsByTagName('video'); for (var i = arVideos.length - 1; i >= 0; i--) { var elmVideo = arVideos[i]; elmVideo.autoplay = false; } تمهل قليلًا… إذا كنتَ تتابع معي منذ بداية هذا الفصل، فأنت تعلم أنَّ لدينا ثلاثة ملفات فيديو بدلًا من واحد! هنالك ملف ‎.ogv الذي أنشأته باستخدام Firefogg أو ffmpeg2theora، وهنالك آخر ‎.mp4 أنشأته باستخدام HandBrake، والثالث هو ملف ‎.webm الذي أنشأته عبر ffmpeg. توفر HTML5 طريقةً لإضافة روابط لجميع الملفات السابقة: العنصر <source>. يمكن أن يحتوي كل عنصر <video> على أكثر من عنصر <source>؛ وسيقرأ المتصفحُ قائمةَ عناصر source بالترتيب وسيُشغِّل أول مقطع يستطيع تشغيله. وهذا يطرح سؤالًا آخر: كيف يعلم المتصفح أيَّ مقطعٍ يستطيع تشغيله؟ حسنًا، أسوأ الاحتمالات هي تحميل كل مقطع من تلك المقاطع ومحاولة تشغيله؛ وهذا إهدارٌ لتراسل البيانات. تستطيع أن تسهل الأمر على المتصفح (وتوفر قدرًا لا بأس به من تراسل البيانات) إذا أخبرتَ المتصفح معلوماتٍ عن كل مقطع، وذلك باستخدام الخاصية type في عنصر <source>. <video width="320" height="240" controls> <source src="pr6.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'> <source src="pr6.webm" type='video/webm; codecs="vp8, vorbis"'> <source src="pr6.ogv" type='video/ogg; codecs="theora, vorbis"'> </video> لنُقسِّم الشيفرة السابقة ليسهل فهمها. يُحدِّد العنصر <video> عرض وارتفاع مقطع الفيديو، لكنه لا يُحدِّد رابطًا لملف الفيديو؛ أما داخل عنصر <video> فهنالك ثلاثة عناصر <source>، كل عنصر <source> يُشير إلى ملف فيديو وحيد (باستخدام خاصية src)، ويُعطي معلومات أيضًا حول صيغة الفيديو (في خاصية type). تبدو خاصية type معقدةً، وهي كذلك! فهي مجموعةٌ من ثلاثة أقسام من المعلومات: صيغة الحاوية، ومرماز الفيديو، ومرماز الصوت. لنبدأ من الأسفل إلى الأعلى: لملفات ‎.ogv تكون صيغة الحاوية هي Ogg المُمثَّلة هنا بالعبارة video/ogg (بكلامٍ تقني، هذا هو نوع MIME لملفات Ogg)، ومرماز الفيديو هو Theora، ومرماز الصوت هو Vorbis. تبيّن أنَّ خاصية type بسيطة، عدا كون شكلها مشوه، لأن القيمة نفسها تتضمن علامات اقتباس، وهذا يعني أنَّ عليك استخدام نوع آخر من علامات الاقتباس لإحاطة القيمة كلها. <source src="pr6.ogv" type='video/ogg; codecs="theora, vorbis"'> صيغة WebM مشابهة لما سبق، لكن نوع MIME مختلف (video/webm بدلًا من video/ogg) ومرماز فيديو مختلف (vp8 بدلًا من theora) مذكورٌ ضمن المعامل (parameter) المسمى codecs. <source src="pr6.webm" type='video/webm; codecs="vp8, vorbis"'> أما فيديو H.264 فهو أكثر تعقيدًا؛ تذكر ما قلته عند شرح فيديو H.264 وصوت AAC أنها تأتي بأنماط (profiles) مختلفة؟ لقد رمَّزنا الفيديو بنمط "baseline" والصوت بنمط «low-complexity) ثم وضعناهما في حاوية MPEG-4. كل هذه المعلومات موجودة في خاصية type. <source src="pr6.mp4" type='video/mp4; codecs="avc1.42E01E, mp4a.40.2"'> الفائدة التي نجنيها من وراء مشقة تحديد نوع مقطع الفيديو هي أنَّ المتصفح سيتحقق من خاصية type أولًا ليرى إن كان بإمكانه تشغيل ملف فيديو معيّن؛ وإن قرر المتصفح أنَّه لا يستطيع تشغيل فيديو معيّن، فلن ينزِّل الملف، ولا أيَّ جزءٍ منه؛ وبهذا لن تستهلك تراسل البيانات، وسيشاهد زوار موقعك الفيديو الذي يريدونه بشكلٍ أسرع. إذا كنت تتبع التعليمات الواردة سابقًا في هذا الفصل لترميز مقاطع الفيديو، فيمكنك أن تنسخ وتلصق قيم خاصية type من المثال السابق، وإلا فعليك أن تكتبها يدويًا بنفسك. أنواع MIME تكشف عن وجهها القبيح هنالك قطعٌ كثيرةٌ لأحجية تشغيل الفيديو على الويب، ولقد ترددت كثيرًا في طرح هذا الموضوع، لكنه مهم لأنَّ الضبط الخاطئ لخادوم الويب سيؤدي إلى مشاكل لا تنتهي عندما تحاول معرفة سبب تشغيل مقاطع الفيديو على جهازك المحلي بينما لا تستطيع ذلك على الخادم الإنتاجي. إذا واجهتك هذه المشكلة فاعلم أنَّ المُسبِّب الرئيسي لها هو أنواع MIME في أغلبية الحالات. ذكرتُ أنواع MIME في درس لمحة تاريخية، لكن ربما تخطيت تلك الفقرة ولم تعرها اهتمامك، هذه خلاصة الموضوع: يجب أن تُخدَّم ملفات الفيديو بنوع MIME الملائم لها. ما هو "نوع MIME الملائم"؟ لقد رأيته سابقًا: هو جزءٌ من قيمة الخاصية type في العنصر <soruce>، لكن ضبط الخاصية type في شيفرات HTML ليس كافيًا، فعليك أيضًا أن تتأكد أنَّ خادم الويب يُضمِّن نوع MIME المناسب في ترويسة Content-Type في HTTP. إذا كنتَ تستخدم خادم ويب أباتشي (Apache) أو أي خادم آخر مُشتَق منه، فيمكنك استخدام تعليمة AddType في ملف httpd.conf (أو apache2.conf في الإصدارات الحديثة منه) الذي يُعدِّل الضبط لكل الموقع، أو في ملف ‎.htaccess الذي يُعدِّل الضبط للمجلد الذي تُخزِّن فيه مقاطع الفيديو (إذا كنتَ تستخدم خادم ويب آخر فانظر إلى توثيق ذاك الخادم لترى كيف يمكنك ضبط ترويسة Content-Type في HTTP لأنواع ملفات معيّنة). AddType video/ogg .ogv AddType video/mp4 .mp4 AddType video/webm .webm أول سطر مما سبق لمقاطع الفيديو في حاوية Ogg، والسطر الثاني لمقاطع الفيديو في حاوية MPEG-4، والسطر الثالث لمقاطع الفيديو في WebM. اضبطها مرةً ودعها تعمل عملها؛ لكن إن نسيت ضبطها فلن تعمل جميع مقاطع الفيديو المُحدَّدة في بعض المتصفحات، حتى لو وضعتَ نوع MIME في خاصية type في شيفرات HTML. ماذا عن متصفح IE؟ يدعم متصفح Internet Explorer عنصر <video> في HTML5، ويدعم فيديو H.264 وصوت AAC في حاوية MPEG-4، مثل متصفح Safari وهاتف iPhone. لكن ماذا عن الإصدارات القديمة من Internet Explorer؟ مثل IE8 وما دونه؟ أغلبية الأشخاص الذين يستخدمون Internet Explorer لديهم إضافة Adobe Flash مثبتةً على جهازهم. الإصدارات الحديثة من Adobe Flash (بدءًا من الإصدار 9.0.60.184) تدعم فيديو H.264 وصوت AAC في حاوية MPEG-4، مثل متصفح Safari و iPhone. فبمجرد ترميزك لمقطع الفيديو بمرماز H.264 لمتصفح Safari، ستستطيع تشغيله في مشغل فيديو يعتمد على تقنية Flash، وذلك في حال اكتشفت أنَّ أحد زوارك لا يملك متصفحًا متوافقًا مع فيديو HTML5. FlowPlayer هو مشغِّل فيديو مبني على تقنية Flash مفتوح المصدر ومرخَّص برخصة GPL (تتوفر رخص تجارية له). لا يَعرِف مشغِّل FlowPlayer أيّ شيءٍ عن عنصر <video>، ولن يحوِّل وسم <video> بشكلٍ سحري إلى كائن Flash، لكن HTML5 مصممة تصميمًا جيدًا لكي تتعامل مع هذه الحالات، لأنك تستطيع تضمن عنصر <object> ضمن عنصر <video>؛ وستتجاهل المتصفحات التي لا تدعم الفيديو في HTML5 العنصر <video> وستُحمِّل العنصر <object> الموجود ضمنه بدلًا عنه؛ الذي سيُشغِّل مقطع الفيديو باستخدام FlowPlayer؛ أما المتصفحات التي تدعم الفيديو في HTML5 فستعثر على مقطع فيديو (عبر العنصر soruce) تستطيع تشغيله، ثم ستتجاهل العنصر <object> تمامًا. آخر جزء من مفتاح حل الأحجية هو أنَّ HTML5 تقول بوجوب تجاهل جميع العناصر (ما عدا عناصر <source>) التي تكون "أبناءً" (children) للعنصر <video> تمامًا، وهذا يسمح لك باستخدام عنصر video في HTML5 في المتصفحات الحديثة مع توفير حل للمتصفحات القديمة عبر تقنية Flash دون الحاجة إلى كتابة سكربتات JavaScript معقدة. مثال متكامل هذا تطبيقٌ للتقنيات التي تعلمناها سابقًا في الدرس السابق حول ترميز مقاطع الفيديو؛ قمت بتضمين فيديو WebM، ولقد رمَّزتُ الفيديو المصدري إلى ثلاث صيغ باستخدام هذه الأوامر: ## Theora/Vorbis/Ogg you@localhost$ ffmpeg2theora --videobitrate 200 --max_size 320x240 --output pr6.ogv pr6.dv ## H.264/AAC/MP4 you@localhost$ HandBrakeCLI --preset "iPhone & iPod Touch" --vb 200 --width 320 --two-pass --turbo --optimize --input pr6.dv --output pr6.mp4 ## VP8/Vorbis/WebM you@localhost$ ffmpeg -pass 1 -passlogfile pr6.dv -threads 16 -keyint_min 0 -g 250 -skip_threshold 0 -qmin 1 -qmax 51 -i pr6.dv -vcodec libvpx -b 204800 -s 320x240 -aspect 4:3 -an -f webm -y NUL you@localhost$ ffmpeg -pass 2 -passlogfile pr6.dv -threads 16 -keyint_min 0 -g 250 -skip_threshold 0 -qmin 1 -qmax 51 -i pr6.dv -vcodec libvpx -b 204800 -s 320x240 -aspect 4:3 -acodec libvorbis -ac 2 -y pr6.webm سنستخدم العنصر <video> في الشيفرة النهائية، مع وجود عنصر <object> لمشغل Flash إن لم يدعم المتصفح العنصر <video>: <video id="movie" width="320" height="240" preload controls> <source src="pr6.webm" type='video/webm; codecs="vp8, vorbis"' /> <source src="pr6.ogv" type='video/ogg; codecs="theora, vorbis"' /> <source src="pr6.mp4" /> <object width="320" height="240" type="application/x-shockwave-flash" data="flowplayer-3.2.1.swf"> <param name="movie" value="flowplayer-3.2.1.swf" /> <param name="allowfullscreen" value="true" /> <param name="flashvars" value='config={"clip": {"url": "http://wearehugh.com/dih5/pr6.mp4", "autoPlay":false, "autoBuffering":true}}' /> <p>Download video as <a href="pr6.mp4">MP4</a>, <a href="pr6.webm">WebM</a>, or <a href="pr6.ogv">Ogg</a>.</p> </object> </video> ستتمكن من تشغيل مقطع الفيديو السابق على أي متصفح أو جهاز بدمج شيفرات HTML5 مع مشغل Flash كما في المثال السابق. عناصر تحكم بتشغيل الفيديو معدة مسبقا: VideoJS MediaElement.js Kaltura HTML5 Video & Media JavaScript Library ترجمة -وبتصرّف- لفصل "Video" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim