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

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

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

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

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

  • عدد الأيام التي تصدر بها

    63

كل منشورات العضو عبد اللطيف ايمش

  1. إحدى أفضل ميزات الشبكات متعددة المواقع في ووردبريس تكمن بإمكانية سماحك للمستخدمين بإنشاء مواقع خاصة بهم، سواءً مجانًا أو لقاء أجر. السماح للمستخدمين بالتسجيل في شبكتك وإنشاء موقع خاص بهم هو أمرٌ بسيطٌ جدًا، كل ما عليك فعله هو تفعيل هذا الخيار في إعدادات الشبكة، لكن يمكنك إجراء تحسينات على هذه العملية وجعل تجربة المستخدم أفضل. هذا هو الدرس الخامس من سلسلة الشبكات متعددة المواقع في ووردبريس. ستتعلم في هذه السلسلة كل ما تحتاج له لكي تُنشِئ شبكتك، وتضيف المواقع إليها أو تسمح للمستخدمين بذلك، بالإضافة إلى إدارة الشبكة. وستتعلم كيف تتأكد أنَّ شبكتك آمنة وأداؤها عالٍ، وكيف يمكنك أن تُنشِئ مجتمعًا ناجحًا من المستخدمين والمواقع. ستتعلم في هذا الدرس كيفية ضبط عملية التسجيل في الموقع، ثم سنتحدث عن كيف سيتمكن المستخدمون من إنشاء مواقع خاصة بهم، وسنستكشف كيف نتمكن من جعل تلك العملية أبسط، وكيفية تخصيص رسائل التسجيل؛ ثم سننظر حول كيفية جعل عملية إنشاء موقع أسهل عبر استخدام إحدى الإضافات. قبل أن نبدأ في هذا الدرس، أنصحك أن تراجع الدروس السابقة في السلسلة، التي ستوفر لك خلفيةً صلبةً عن ميزة تعدد المواقع في ووردبريس وكيفية ضبطها، وإنشاء المواقع والمستخدمين على الشبكة، وتثبيت القوالب والإضافات، وكيفية ضبط إعدادات الشبكة. عندما تنتهي من الاطلاع على ما سبق، فسنبدأ درسنا بإجراء الخطوة الأساسية الأولى: تفعيل تسجيل المواقع. تفعيل تسجيل المواقع تسجيل المواقع هو ميزة من ميزات تعدد المواقع التي تُشكِّل جزءًا من ووردبريس، لذا كل ما عليك عمله هو تفعيلها؛ وذلك عبر صفحة إعدادات الشبكة في لوحة التحكم. اذهب إلى صفحة «Settings > Network Settings» (الإعدادات > إعدادات الشبكة) في لوحة تحكم مدير الشبكة، لكي ترى ما يلي: القسم الثاني الموجود في الصفحة السابقة هو قسم «Registration Settings» (إعدادات التسجيل). يكون تسجيل المستخدمين والمواقع معطلًا افتراضيًا، وعليك تغيير ذلك. انتقِ إحدى الخيارات الأخرى التي تناسب احتياجاتك. هذه هي الخيارات المتاحة أمامك، وما الذي تفعله: التسجيل معطّل: يمكن لمدراء الشبكات أو المواقع فقط إنشاء المستخدمين الجدد، ويمكن لمدراء الشبكة فقط إنشاء مواقع جديدة (أي أنَّ تسجيل المواقع من قِبل المستخدمين مُعطَّلٌ). السماح بتسجيل عضويات: يمكن للآخرين إنشاء حساب مستخدم على شبكتك لكنهم لن يستطيعوا إنشاء موقع جديد (وهذا شبيهٌ بتسجيل المستخدمين في مواقع ووردبريس المفردة). السماح للأعضاء المتصلين بتسجيل مواقع جديدة: يمكن للأشخاص الذين يستطيعون تسجيل الدخول أن يُنشِئوا المواقع. استخدم هذا الخيار إن لم تكن تريد أن يُنشِئ الآخرون حسابات مستخدمين ومواقع خاصة بهم، لكنك تريد أن يكون تسجيل المواقع مقتصرًا على المستخدمين الذين أعددتهم بنفسك. هذا الخيار مفيدٌ للأنظمة المغلقة مثل شبكة من المواقع للمجتمع أو لشركةٍ ما. السماح بتسجيل مواقع وعضويات: يمكن للآخرين إنشاء مواقع وحسابات مستخدمين في نفس الوقت (أو يمكنهم إنشاء موقع باستخدام عضوية موجودة مسبقًا). في هذه المرحلة من سلسلتنا التعليمية، سنسمح للمستخدمين بإنشاء حسابات المستخدمين ومواقع خاصة بهم، لذا سنختار الخيار الرابع: «السماح بتسجيل مواقع وعضويات» (Both sites and user accounts can be registered). بعد أن تفعل ذلك، فلننظر إلى إعدادات الضبط التي تلي ما سبق: إشعار التسجيل: أبقِ هذا الحقل مُفعَّلًا لكي تحصل على تنبيه في كل مرة يحاول فيها شخصٌ ما إنشاء حساب مستخدم أو موقعًا جديدًا. إذا كانت شبكتك حديثةً، فأنصحك بتفعيل هذا الخيار لكي تستطيع أن تكتشف الأشخاص المزعجين؛ وعندما تنمو شبكتك فسيصبح من غير الملائم أن تستقبل رسائل بريدية طوال الوقت، وفي هذه الحالة ستحتاج إلى إضافة لمكافحة المستخدمين المزعجين، وسننظر في هذا الأمر بالتفصيل في آخر جزء من سلسلتنا، الذي سيكون عن إدارة الشبكة. إضافة مستخدمين جدد: إذا كانت شبكتك عامة، أو كنت تريد أن تسمح لمدراء المواقع بإضافة مستخدميهم، فيجب عليك تفعيل هذا الحقل؛ لكن إن كنت تريد تحكمًا أكبر بعملية إنشاء حسابات المستخدمين، فاترك هذا الحقل معطلًا. أحب عادةً أن أعطي مدراء المواقع عندي إمكانية تسجيل مستخدمين جدد لمواقعهم، لذا أنصحك بتفعيل هذا الحقل. الأسماء الممنوعة: تُضيف ووردبريس قائمةً بأسماء المدونات الممنوعة افتراضيًا، لكنك تستطيع الإضافة عليها. هذه هي الأسماء التي ستكون اختصاراتٍ للمواقع الجديدة. إذا أردتَ أن تُضيف المزيد منها، فاكتبها في الحقل وافصل بينهما بفراغ. ربما تود تضمين اسم علامتك التجارية، أو أيّة أسماء لصفحاتٍ أنشأتها في موقعك. نطاقات البريد الإلكتروني المسموحة: إذا كانت شبكتك تابعةً لمنظمةٍ يتشارك الأعضاء فيها بنطاق بريدهم الإلكتروني، فيمكنك أن تستعمل هذا الحقل لكي تمنع أي شخصٍ لا يملك عنوانًا بريديًا مسموحًا من إنشاء موقع. اكتب اسم النطاق دون رمز @، فلو كان اسم النطاق الخاص بك هو microsoft.com (على سبيل المثال)، فكتابة microsoft.com في ذاك الحقل تؤدي إلى تحديد عملية تسجيل المواقع للأشخاص الذين يملكون عنوانًا بريديًا من الشكل name@microsoft.com. نطاقات البريد الإلكتروني الممنوعة: استخدم هذا الحقل لذكر نطاقات البريد الإلكتروني التي لا تريد أن يتمكن مستخدموها من التسجيل في الموقع. وهذا يعني لو أنَّك أضفتَ microsoft.com هنا، فلن يتمكن أي شخصٍ يملك عنوانًا بريديًا من الشكل name@microsoft.com من إضافة موقع أو تسجيل حساب مستخدم. هذا مفيدٌ إذا واجهت محاولات تسجيل مزعجة من حسابات تستعمل نفس نطاق البريد الإلكتروني. بعد أن تضبط الإعدادات اللازمة لتفعيل تسجيل المواقع وإضافة أيّة أسماء أو نطاقات محظورة… فمرِّر إلى أسفل الصفحة واضغط على رز «Save Changes» (حفظ التغييرات). بقية الصفحة السابقة تتعلق بكيفية تخصيص عملية التسجيل، والتي سأشرحها بعد أن ننظر إلى طريقة إنشاء المستخدمين لمواقعَ خاصةٍ بهم. تسجيل موقع وحساب مستخدم بعد أن تُفعِّل تسجيل المواقع، سيتمكن المستخدمون من فعل ذلك بزيارة صفحة wp-signup.php في موقعك. فلو كان رابط موقعك هو http://mynetwork.com، فعليهم زيارة الصفحة http://mynetwork.com/wp-signup.php. حسنًا، لا أتوقع أن يعلم المستخدمون أنَّ بإمكانهم التسجيل دون أن نخبرهم بذلك! لذا ما عليك فعله الآن هو وضع نوع من أنوع الروابط لصفحة التسجيل. يمكنك فعل ذلك بإحدى الطرق الآتية: إضافة رابط إلى قامة التنقل الرئيسية إضافة زر يدعو المستخدمين إلى التسجيل إضافة رابط في التذييل أو الشريط الجانبي إضافة عدد من الروابط في الصفحة الرئيسية (أو أيّة صفحات أخرى مناسبة) استخدام ودجت للتسجيل (سنأتي عليها لاحقًا). اعتمادًا على حاجاتك وطبيعة مستخدمين، فربما يكون من المناسب استخدام أكثر من تقنية من التقنيات السابقة. لكن لتبسيط الأمر، سنبدأ بإضافة رابط إلى قائمة التنقل. إضافة رابط لصفحة التسجيل في قائمة التنقل يمكنك فعل هذا في صفحة «Menus» (قوائم) في لوحة تحكم المواقع أو عبر صفحة «التخصيص» في الموقع الرئيسي في شبكتك. لنستخدم صفحة التخصيص. اختر موقعك الرئيسي من قائمة «My Sites» (مواقعي) ثم اذهب إلى «Appearance > Customize» (مظهر > تخصيص) لفتح صفحة التخصيص: اختر لسان «Menus» (قوائم) من على الجانب الأيسر (على الجانب الأيمن في الواجهة العربية)، ثم اختر قائمة الرئيسية، أو إذا لم يملك موقعك قائمةً بعد، فأنشِئ واحدةً بالضغط على زر «Add a menu» (أضف قائمة). تأكد أنَّ قائمتك موجودةٌ في المكان الأساسي (Primary location) في قالبك (أو أيًا كان المصطلح المستخدم في القالب للدلالة على القائمة الرئيسية): بعد أن تفعل ذلك، فستتمكن من إضافة عناصر بالضغط على زر «Add items» (أضف عناصر)؛ ثم اختر «Custom Links» (روابط مخصصة)، ثم في حقل رابط URL اكتب http://mynetwork.com/wp-signup.php حيث mynetwork.com هو اسم نطاق موقعك؛ واكتب النص الذي سيظهر في القائمة في حقل «Link Text» (نص الرابط). يمكنك أن ترى ذلك في الصورة الآتية: اضغط على زر «Add to Menu» (أضف للقائمة) ثم اضغط على زر «Save & Publish» (حفظ ونشر) في الزاوية العليا اليسرى (الزاوية العليا اليمنى في الواجهة العربية). هذا سيحفظ القائمة التي فيها رابط لصفحة التسجيل: تسجيل موقع الآن، وبعد أن أعطيت مستخدميك رابطًا لتسجيل موقعهم، فكل ما سيحتاجون له هو الضغط على الرابط لرؤية صفحة التسجيل: ولمّا كنتَ مديرًا للشبكة، فسترى رسالةً في الأعلى، التي لن يراها بقية المستخدمين. وسترى إشعارًا من بقية المواقع التي تكون عضوًا فيها، وبعض المعلومات الأخرى. لنسجل خروجنا ونحاول إنشاء موقع جديد وحساب مستخدم جديد. سجل الخروج من حسابك كمدير للشبكة ثم حدِّث الصفحة (أو استخدم الرابط الموجود في قائمة التنقل لفتحها): يمكنك أن ترى حقلين يجب تعبئتهما للبدء: اسم المستخدم عنوان البريد الإلكتروني يجب أن يكون كلاهما فريدًا، فلو حاول أحدٌ التسجيل باسم مستخدم أو بريد إلكتروني مُسجَّل مسبقًا على الشبكة، فسيحتاج ذلك الشخص إلى تسجيل الدخول ومن ثم إنشاء موقع جديد (أي لا يمكن إنشاء حسابين بنفس الاسم أو البريد الإلكتروني). ملاحظة: بعد أن يملأ المستخدم هذين الحقلين، فعليه الاختيار بين تسجيل موقع أو تسجيل حساب مستخدم. إذا اختار تسجيل موقع، فسيُنشَأ حساب مستخدم جديد تلقائيًا، ولا حاجة لإنشائه يدويًا. إذا أبقى المستخدم على الخيار الافتراضي «Gimme a site!‎» (أريد موقعًا!) ثم ضغط على زر «Next» (التالي)، فسينتقل إلى صفحةٍ تسأله عن معلوماتٍ عن موقعه: سيحتاج هنا إلى توفير رابط مختصر (slug) لموقعه، الذي سيُملَأ تلقائيًا باسم المستخدم الذي اختاره، وسيُطلَب منه أيضًا توفير عنوان للموقع؛ ويجب أن يُحدِّد إن كان يريد أن تُفهرِس محركات البحث هذا الموقع. سأغيّر رابط الموقع وعنوانه، وسأعطِّل فهرسة الموقع الجديد، وذلك لأني لستُ مستعدًا لعرضه على الزوار بعد. لا تنسَ أنَّ مدراء المواقع يستطيعون تغيير هذا الضبط في إعدادات الموقع بعد إنشائه، وذلك بالذهاب إلى «Settings > Reading» (الإعدادات > قراءة). في النهاية، اضغط على زر «Signup» (تسجيل) لإنشاء الموقع. ملاحظة: إن بدا لك عدم تغيّر أي شيء في بادئ الأمر، فاصبر؛ فإذا ضغطتَ مرتين على زر التسجيل فستحصل على رسالة خطأ. سيحصل المستخدم على رسالةٍ مفادها أنَّ موقعه أصبح جاهزًا تقريبًا: لكن لأسبابٍ أمنية، يجب أن يضغط المستخدم على رابطٍ سيُرسَل له عبر البريد الإلكتروني قبل أن يعمل الموقع. تحقق من صندوق البريد الوارد في البريد الإلكتروني الذي استعملتَه للتسجيل؛ حيث ستحصل على رسالة إلكترونية شبيهة بالرسالة الآتية: اضغط على ذاك الرابط لتذهب إلى صفحة فيها معلومات الدخول الخاصة بك: يمكنك الآن زيارة موقعك أو تسجيل الدخول كمدير للموقع باستخدام الروابط الموجودة. ستُرسِل ووردبريس رسالةً إلى المستخدم على البريد الإلكتروني فيها تفاصيل الموقع الجديد: هذا هو الموقع الخاص بالمستخدم، مع قالب Twenty Sixteen المفعل افتراضيًا: تخصيص عملية التسجيل ما رأيته الآن هو العملية الافتراضية لتسجيل موقع، لكن إن أردت أن تُخصِّص بعض الأمور، فهذه هي الخيارات المتاحة أمامك: تخصيص رسالة الترحيب التي تصل على البريد (ثاني رسالة) عبر صفحة ضبط الشبكة تخصيص رسالة الترحيب للمستخدمين الجدد الذين لم يسجلوا موقعًا عبر صفحة ضبط الشبكة تخصيص المحتوى الافتراضي للمواقع الجديدة (أول منشور، وأول صفحة، وأول تعليق) عبر صفحة ضبط الشبكة تخصيص نموذج التسجيل عبر إضافة لنبدأ بتخصيص رسالة الترحيب التي تصل على البريد للمواقع الجديدة وللمستخدمين الجدد. تخصيص رسائل الترحيب الإلكترونية اذهب إلى «Settings > Network Settings» (الإعدادات > إعدادات الشبكة) ثم مرِّر إلى الأسفل إلى أن تصل إلى قسم «New Site Settings» (إعدادات الموقع الجديد): ابدأ بتعديل البريد الإلكتروني الذي سيُرسَل إلى الأشخاص الذين نجحوا بتفعيل موقعهم الجديد، وذلك بتعديل الرسالة الموجودة في حقل «Welcome Email» (رسالة الترحيب بأصحاب المواقع). لاحظ أنَّ هنالك بعض الأكواد الخاصة مكتوبة بأحرفٍ كبيرة التي من المستحسن الإبقاء عليها: USERNAME: اسم مستخدم مالك الموقع SITE_NAME: عنوان الشبكة (وليس الموقع الجديد) BLOG_URL: عنوان URL للموقع الجديد (مع شرطة مائلة في نهايته) PASSWORD: كلمة مرور المستخدم الجديد LOGINLINK: رابط لكي يسجِّل المستخدم دخوله إلى الشبكة أو إلى الموقع الذي أُضيف إليه من قِبل مدير موقع هذا هو النص الذي سأستعمله، الذي أرى أنَّه ملائم أكثر لموقعي: لنعدل الآن البريد الذي سيصل للمستخدمين الجدد الذين لم يضيفوا موقعًا جديدًا. هذه الرسالة تتضمن نصًا مشابهًا للرسالة السابقة، لكن دون أيّة إشارات إلى إدارة الموقع. عدِّل هذه الرسالة في حقل «Welcome User Email» (رسالة الترحيب بالأعضاء الجدد): بعد أن تُعدِّل هذه الرسائل، اضغط على زر «Save Changes» (حفظ التغييرات) في أسفل الصفحة لحفظ تعديلاتك. تخصيص المحتوى الافتراضي افتراضيًا، تملأ ووردبريس كل موقع على الشبكة بمنشور وصفحة وتعليق، وذلك بطريقةٍ شبيهةٍ بالمواقع المفردة. يمكنك أن تُعدِّل ذلك المحتوى. أرى شخصيًا أنَّ المحتوى الافتراضي غير مفيد أبدًا، لهذا أحب أن أعدِّله. لفعل ذلك، اذهب إلى «Settings > Network Settings» (الإعدادات > إعدادات الشبكة) ثم مرِّر إلى الأسفل إلى أن تصل إلى قسم «New Site Settings» (إعدادات الموقع الجديد) ثم تخطى الحقلين الخاصين برسائل البريد الذين عملنا عليهما في الفقرة السابقة. يمكنك استخدام شيفرات HTML في هذه الحقول لإضافة تعدادات أو روابط أو صور أو غير ذلك. كن مبدعًا! عدلتُ المنشور الافتراضي في شبكتي لكي يوفر مزيدًا من المعلومات حول كيفية عمل المنشورات والصفحات والتعليقات: وعندما يُنشِئ أحدهم موقعًا في شبكتي، فسيبدأ بموقع يحتوي منشورًا وصفحةً وتعليقًا كالتي ضبطتُها: تخصيص صفحة التسجيل إن لم تكن سعيدًا بصفحة التسجيل الافتراضية، فيمكنك تخصيصها عبر إضافة. هذه هي بعض الخيارات المطروحة أمامك: استخدام إضافة تسجيل (هذه قائمة بأفضل 20 إضافة للتسجيل وتسجيل الدخول) لإضافة ودجت (widget) إلى قائمة الودجات في موقعك على الشريط الجانبي أو في التذييل، أو في منطقة خاصة بالودجات ضمن سياق المحتوى الرئيسي إن كان متوفرًا للقالب الذي تستعمله. استخدام إضافة للنماذج مثل Gravity Forms، التي تسمح لك بإنشاء نموذج للتسجيل/تسجيل الدخول الذي تتم من خلاله عملية التسجيل. ستحتاج إلى مكوِّن User Registration لإضافة Gravity Forms لكي تربط نموذجك بعملية التسجيل. ويمكنك أن تستعمل مكوِّن Paypal لإضافة خيار الدفع كجزء من عملية التسجيل. استخدام إضافة لتسجيل العضويات مثل Pro Sites، التي تسمح لك ببيع المواقع مع خيارات خاصة. لننظر إلى كيفية إنشاء نموذج تسجيل خاص باستخدام Gravity Forms. ملاحظة: إضافة Gravity Forms هي إضافة مدفوعة، وستحتاج إلى رخصة مطوِّر لاستخدام مكوِّن التسجيل. إن لم تكن ميزانيتك تسمح بذلك، فاستخدم صفحة التسجيل الافتراضية في ووردبريس. تخصيص نموذج التسجيل باستخدام Gravity Forms يجب أن تكون الإضافة مثبتةً ومفعّلةً في موقعك الرئيسي. يمكنك رؤية ذلك واضحًا في لقطة الشاشة الآتية: الخطوة التالية هي إنشاء نموذج. اذهب إلى «Forms > New Form» لإنشاء نموذجك. ستحتاج إلى الحقول الآتية على الأقل: اسم المستخدم البريد الإلكتروني عنوان الموقع يمكنك إضافة حقول أخرى مثل الاسم الأول والكنية ورابط الموقع، التي يمكن أن تُستعمَل أثناء عملية تسجيل المستخدم. هذا هو النموذج الذي سأعتمده: نموذجٌ بسيطٌ جدًا كما ترى، خذ وقتك لإجراء أيّة تعديلات تلائمك. بعد أن تنتهي من هذه الخطوة، عليك أن تربط هذا النموذج بعملية تسجيل المستخدم. اذهب إلى «Forms > User Registration» ثم اضغط على زر «Add New». اختر الإجراء الذي تريده لنموذجك أولًا، والذي هو «Create User» ثم اختر النموذج الذي أنشأتَه منذ قليل من القائمة. ثم ستظهر حقولٌ إضافية: اختر الحقول لنموذج التي تريد أن تستخدمها لإتمام عملية التسجيل من القوائم المنسدلة. ثم مرِّر إلى الأسفل إلى قسم «Network Options» وفعِّل خيار «Create Site»: مما سيُظهِر حقولًا إضافية متعلقة بعملية إنشاء الموقع. اختر الحقول الموافقة من القوائم المنسدلة كما مرَّ معنا سابقًا. إذا قررتَ في هذه المرحلة أنَّ عليك تعديل أمرٍ ما في النموذج أو إضافة حقول أخرى، فلا حرج في ذلك، احفظ ما عدَّلتَه في هذه الصفحة، ثم ارجع إلى النموذج وعدِّل ما تشاء ثم عُد إلى هذه الصفحة وأكمل التحرير. مرِّر إلى الأسفل لتصل إلى «Additional Options» وفعِّل خيار «User Activation»؛ وهذا سيؤدي إلى تفعيل التأكد من المستخدم عبر البريد الإلكتروني وسيُحسِّن من مستوى الحماية في شبكتك. في النهاية، اضغط على زر «Save» لحفظ ما تم تعديله في هذه الصفحة. كل ما عليك فعله الآن هو إضافة النموذج إلى موقعك، تعطيك إضافة Gravity Forms ودجت تستطيع أن تستعملها في الشريط الجانبي أو في التذييل، أو يمكنك إضافتها إلى صفحتك الرئيسية. بشكلٍ بديل، يمكنك إنشاء صفحة جديدة الغرضُ منها هو إظهار نموذج التسجيل، والتي تُمثِّل نسخةً مُحسَّنةً من صفحة wp-signup الأصلية. لكني سأضيف هنا النموذج إلى الشريط الجانبي. يمكنك إضافة الودجات عبر صفحة الودجات أو عبر تخصيص القالب. سأستخدم هنا صفحة تخصيص القالب. اذهب إلى «Appearance > Customize» (مظهر > تخصيص) ثم اختر «Widgets» (ودجات)، ثم اختر منطقة الودجات التي تريد التعامل معها، ثم اضغط على زر «Add a Widget» (أضف ودجت)، ثم اختر ودجة «Forms»: ثم اختر النموذج الذي تريد استخدامه، ثم عدِّل عنوانه: في النهاية، اضغط على «Save & Publish» (حفظ ونشر)؛ وسترى نموذج التسجيل في موقعك: حاليًا، صفحتي الرئيسية تعرض التدوينات التي أكتبها، وهذا ليس مفيدًا أبدًا؛ لذا سأعدِّل الصفحة الرئيسية إلى صفحة ثابتة لحثّ الزوار على التسجيل. هذه هي النسخة المُحسّنة: يمكنني إضافة هذا النموذج إلى صفحتي الرئيسية أو إلى صفحةٍ أخرى إن شئت –بالإضافة إلى منطقة الودجات– وذلك باستخدام زر لإضافة نماذج Gravity Forms في صفحة تحرير المنشورات والصفحات. يمكن الآن لمستخدمي الشبكة أن يصلوا إلى نموذج يُمكِّنهم من إنشاء موقع خاص بهم. أستطيع أيضًا تثبيت مكوِّن Paypal لإضافة Gravity Forms لكي أجعل المستخدمين يدفعون لقاء التسجيل. أو يمكن استخدام إضافة Pro Sites، التي تسمح لك بضبط عدِّة خيارات متعلقة بعضوية الشبكة وبإنشاء المواقع. شبكة ووردبريس تحب المستخدمين! السماح بإنشاء المواقع من قِبل المستخدمين في شبكة ووردبريس بسيطٌ جدًا وذلك بتفعيل أحد الخيارات في إعدادات الشبكة. لكن، كما تعلم، جعل تلك العملية «صديقة» للمستخدم هو أمرٌ معقدٌ بعض الشيء. أنت تريد أن تَحُثَّ أكبر قدر ممكن من المستخدمين على التسجيل وإنشاء المواقع، ويمكن فعل ذلك بوضع روابط واضحة لصفحة التسجيل، ويمكنك أيضًا إضافة نموذج تسجيل مُخصَّص باستخدام إضافة Gravity Forms كما شرحنا أعلاه. في الجزء التالي من هذه السلسلة، سننظر إلى استخدامٍ آخر لشبكات ووردبريس لاستضافة المواقع الشخصية أو مواقع العملاء، وسنتعلم كيفية تسخير قدرات شبكات ووردبريس لاستضافة عدِّة مواقع لمستخدميك، وكيف تجعل سير العمل سلسًا قدر الإمكان. وسننظر أيضًا إلى كيفية ربط أسماء النطاقات كي نستعمل نسخة ووردبريس وحيدة لاستضافة عدد كبير من المواقع كلٌ على نطاقٍ خاصٍ به. ترجمة –وبتصرّف– للمقال WordPress Multisite Masterclass: Site and User Registration لصاحبته Rachel McCollin.
  2. دفع عجلة تطوير الويب تعني جعله أفضل للمستخدمين والمطورين على حدٍ سواء، وهذا يعني محاولة حلّ المشكلات التي تواجه الويب في الوقت الراهن؛ وهذا ينطبق أيضًا على جعل المواقع متوافقة مع اتجاه RTL بسهولة ويسر. قبل أن نكمل مشوارنا في الحديث عن المواضيع المتقدمة عن RTL، لنبدأ درسنا بمثالٍ عمليٍ بسيط لتتذكر فيه كيف نقلب الاتجاه إلى RTL بطريقةٍ صحيحة: سنكمل حيث انتهينا في درسنا السابق، وحان الوقت الآن لنتعمّق في بعض المواضيع المتقدمة المتعلقة بتطوير واجهات من اليمين إلى اليسار. شرحنا في الدرس الأول الأساسيات، وسنشرح هنا بعض الخاصيات المُحدَثَة التي أتت حديثًا على الويب والتي ستعطينا فكرةً عن مستقبل الويب. التقنيات الحديثة الأساس الذي تستند عليه الواجهات ذات الاتجاه من اليمين إلى اليسار (RTL) هو خاصيات CSS، وهذه الخاصيات تتطور مع الوقت كما هو حال بقية مكوّنات الويب. سأذكر هنا بعض خاصيات CSS التي يمكنك تجربتها على الفور في النسخ الحديثة من المتصفحات (من المحتمل أن تتغير هذه المعلومات بعد صدور تحديثات جديدة للمتصفحات). محاذاة النص: تعودنا في الماضي على تحديد المحاذاة الأفقية للنص عبر text-align: left أو right؛ أما الآن، فلدينا القيمتان start و end. فعلى سبيل المثال text-align: start ستعطي نتيجةً مختلفةً بناءً عمّا إذا كان اتجاه المحتوى من اليمين إلى اليسار أم من اليسار إلى اليمين. القيمة start تُشير إلى اليسار في اتجاه LTR وإلى اليمين في اتجاه RTL، والعكس صحيح للقيمة end. مثالٌ يستخدم left|right: #content { text-align: left: } html[dir="rtl"] #content { text-align: right; } أما لو استعملنا start|end: #content { text-align: start; } لا حاجة إلى استعمال قاعدة خاصة باتجاه RTL باستخدامنا للقيمتين start|end. دعم المتصفح: Chrome 1.0+‎، و Safari 3.1+‎، و Firefox 3.6+‎، وأندرويد، و iOS، غير مدعومة في متصفح Internet Explorer في الوقت الراهن. خاصيات padding و margin و border: هنالك تحسيناتٌ أُدخِلَت على تلك الخاصيات. أصبحت كل واحدة منها تقبل لاحقةً إضافيةً هي ‎-inline-start أو ‎-inline-end، فمثلًا يمكننا أن نكتب padding-inline-end أو margin-inline-start. تعطي هذه الكلمات المحجوزة الجديدة نفس تأثير start و end في محاذاة النص: الخاصية padding-inline-start تماثِل padding-left في اتجاه LTR وتماثِل padding-right في اتجاه RTL. أما margin-inline-end تعطي نفس تأثير margin-right في اتجاه LTR و margin-left في اتجاه RTL. مثالٌ يستعمل left|right: #content { padding-left: 12px: margin-right: 20px; } html[dir="rtl"] #content { padding-left: 0; padding-right: 12px; margin-left: 20px; margin-right: 0px; } نفس المثال لكن باستعمال start|end: #content { padding-inline-start: 12px: margin-inline-end: 20px; } أكرِّر أنَّه لا حاجة إلى قاعدة خاصة باتجاه RTL؛ فستصبح خاصية margin-inline-end وكأنها margin-right في اتجاه LTR و margin-left في اتجاه RTL. دعم المتصفحات: Firefox 41+‎ فقط. تحديد الموقع المطلق (absolute position) للعناصر: الخاصيتان left وright أساسيتان لتحديد الموقع المطلق، لكن قريبًا سترى استخدامًا لخاصياتٍ أذكى، ألا وهي offset-inline-startوoffset-inline-end. يجدر بالذكر أنَّك تستطيع استعمال offset-block-startوoffset-block-end بدلًا من top و bottom على التوالي وبالترتيب. مثالٌ يستعمل left|right: #content { position: absolute; left: 5rem; } html[dir="rtl"] #content { left: auto; right: 5rem; } نفس المثال لكن باستعمال start|end: #content { offset-inline-start: 5rem; } أكرر مرةً أخرى أنَّ مفهوم start|end وفَّر علينا كتابة بعض الشيفرات في ملف CSS. دعم المتصفحات: Firefox 41+‎ فقط. تحديد موقع العناصر باستخدام float: استعملنا float: left و float: right لوقتٍ طويل، لكنك ستتمكن قريبًا من استعمال float: inline-start و float: inline-end. خاصية inline-start ترتِّب عنصرك إلى اليسار في اتجاه LTR وإلى اليمين في اتجاه RTL. مثالٌ يستعمل left|right: #content { float: left; } html[dir="rtl"] #content { float: right; } نفس المثال لكن باستعمال start|end: #content { float: inline-start; } دعم المتصفحات: Firefox 44. Web Components (مكوِّنات الويب): وهي عناصر الواجهة الرسومية القابلة لإعادة الاستخدام. إذا تعاملت مع web components من قبل فستعلم أنَّ أنماط CSS الخاصة بها موجودة ضمن شجرة DOM فرعية (shadow DOM)، لذا لن تتمكن افتراضيًا من الحصول على اتجاه الصفحة التي تستعمل تلك المكوِّنة عبر html[dir="rtl"]، لكن شجرة DOM الفرعية (shadow DOM) تتضمن محدِّدًا جديدًا اسمه ‎:host-context()‎ الذي تستطيع استخدامه لتحديد عنصر في شجرة DOM الأساسية (host DOM). لنحاول تبسيط الأمر عبر مثال. لنقل أنَّ لديك عنصرًا داخل شجرة DOM فرعية اسمه back. كنت ستستخدم html[dir="rtl"] .back {}‎ في حال لم تكن داخل شجرة DOM فرعية، لكنك لا تستطيع استخدام هذا المُحدِّد من داخل مكوِّن (component). المُحدِّد المكافئ للمحدِّد السابق الذي تستطيع استخدامه داخل مكوِّن هو: .back:host-context(html[dir="rtl"]) { } ليس من الواضح ما مدى دعم هذا النوع من المُحدِّدات، لكن يمكنك استخدام هذه الطريقة الالتفافية. لا ترغب باستخدام الطرق الالتفافية لكنك تريد أن تجرِّب اتجاه RTL في web component في المتصفحات التي تدعمها مثل فيرفكس (بعد تفعيل الراية dom.webcomponents.enabled) ومتصفح Google Chrome؟ يمكنك فعل ذلك عبر Mutation Observers بمراقبة الخاصية document.documentElement.dir. ستبدو شيفرة المراقبة شبيهةً بالمثال الآتي: var dirObserver = new MutationObserver(updateShadowDir); dirObserver.observe(document.documentElement, { attributeFilter: ['dir'], attributes: true }); لا تنسَ أن تُهيّئ دالتك لأول مرة بعد تحميل الصفحة، بعد شيفرة المراقبة مباشرةً: updateShadowDir(); ستبدو دالة updateShadowDir()‎ كالآتي: function updateShadowDir() { var internal = myInnerElement.shadowRoot.firstElementChild; if (document.documentElement.dir === 'rtl') { internal.setAttribute('dir', 'rtl'); } else { internal.removeAttribute('dir'); } }; كل ذلك عبر JavaScript. سيملك أول «ابن» (first child) في المكوِّنة خاصية dir قابلة للتغيير، وستعتمد أنماط CSS عليه كمُحدِّد بدلًا من عنصر html. ولنقل أنَّ ذاك العنصر هو span، لذا سيكون مُحدِّدك شبيهًا بما يلي: span[dir="rtl"] rest-of-the-selectors-chain { … } أعلم أنَّ الأمر يبدو معقدًا، لكن –لحسن الحظ– المستقبل مشرق، لذا لنلقِ نظرةً على ما تُخبّؤه W3C لنا في المستقبل بما يخص اتجاه RTL. مستقبل اتجاه RTL في الويب إعلام الخادوم باتجاه النص: في الوقت الراهن نفقد قيمة اتجاه الصفحة (أو اتجاه عناصر النموذج) عند إرسال نموذج يحتوي على حقول إدخال نصية. أتت الخواديم بطرقٍ خاصةٍ بها لكي تُحدِّد اتجاه النص الُمرسَل إليها. لحل هذه المشكلة ولإضافة معلومات حول اتجاه النص المُرسَل، ابتكرت W3C خاصيةً جديدةً ووضعتها في مواصفة نماذج HTML5 اسمها dirname، التي ستُضيفها المتصفحات في المستقبل القريب. وكما ذَكَرَ المعيار، «تضمين خاصية dirname في حقول النموذج يُفعِّل إرسال اتجاه العنصر، ويعطي اسم الحقل الذي يحتوي على هذه القيمة أثناء عملية الإرسال. إذا حُدِّدت هذه الخاصية، فيجب ألّا تكون قيمتها فارغةً». بعبارة أخرى: إضافة هذه الخاصية إلى حقول <input> أو <textarea> يؤدي إلى تمرير اتجاهها مع معلومات GET أو POST التي ستُرسَل إلى الخادوم. لننظر إلى مثالٍ عملي. ليكن لدينا هذا النموذج: <form action="result.php" method="post"> <input name="comment" type="text" dirname="comment.dir"/> <button type=submit>Send!</button> </form> ستحتوي عبارة POST التي ستُرسَل إلى الخادوم على حقلٍ اسمه comment، وحقلٍ آخر اسمه comment.dir. وإذا كتب المستخدم الكلمة «Hello» في الحقل النصي وأرسل النموذج، فستكون النتيجة هي: comment=Hello&comment.dir=ltr أما إذا كتب «مرحبا» فستكون النتيجة هكذا: comment=%D9%85%D8%B1%D8%AD%D8%A8%D8%A7&comment.dir=rtl يمكنك قراءة المواصفة من هنا. ملاحظة: لكي تُعرَض المحارف العربية عرضًا صحيحًا في عناوين URL في متصفحك، فيجب أن تُرمَّز المحارف بترميز UTF-8، وكل حرف عربي يستعمل 4 محارف مفصولة فيما بينهما برمز %. على سبيل المثال، لو كان لديك هذا الرابط؛ فسيحوله متصفحك عندما تزوره إلى هذه الصّفحة طرق أفضل لكيفية التعامل مع الخلفيات والصور في RTL هل سئمتَ من استخدام transform: scaleX(-1) لعكس صور الخلفية؟ قدّمَت W3C كلمةً محجوزةً جديدةً في CSS اسمها rtlflip التي تُستعمل كما يلي: background-image: url(backbutton.png) rtlflip; ستقلل عليك هذه الكلمة المحجوزة من عبء قلب جميع صورك عبر خاصية transform: scaleX(-1) يدويًا. من الممكن استعمالها أيضًا في قاعدة صور القوائم كما يلي: list-style-image:url('sprite.png#xywh=10,30,60,20') rtlflip; تقول صفحة المواصفة أيضًا أنَّ هذه الكلمة المحجوزة يمكن أن تستعمل في جميع الطرق الممكنة لتحديد الصور في CSS3. على سبيل المثال: url و sprite و image-list و linear-gradient و radial-gradient. الواجهة البرمجية للتدويل إليكم بعض الأجزاء غير المشهورة المتعلقة باتجاه RTL في الواجهة البرمجية للتدويل (Internationalization APIs) في JavaScript تدعم الواجهة البرمجية للتدويل عددًا كبيرًا من اللغات مع مشتقاتها؛ فمثلًا لا تدعم هذه الواجهة البرمجية اللغة العربية فحسب، وإنما تدعم «العربية-مصر» و «العربية-تونس» وقائمة طويلة بالاشتقاقات الأخرى. استخدام الأرقام العربية المشرقية بدلًا من الأرقام العربية الغربية: تُستخدم الأرقام العربية المشرقية (١٢٣) في بعض البلدان بدلًا من الأرقام العربية الغربية (123). تسمح لنا واجهة التدويل البرمجية بتحديد متى نريد إظهار ١٢٣ ومتى نظهر 123. console.log(new Intl.NumberFormat('ar-EG').format(1234567890)); السطر السابق يقول أنَّنا نريد تنسيق الرقم 1234567890 بصيغة الأرقام التي تستعملها مصر (ar-EG). الناتج: ١٬٢٣٤٬٥٦٧٬٨٩٠ console.log(new Intl.NumberFormat('ar-TN').format(1234567890)); نقول هنا أننا نريد إظهار الرقم بالصيغة المعتمدة في تونس؛ ولمّا كانت تونس تستعمل الأرقام العربية الغربية، فسيكون الناتج: 1234567890 إظهار التواريخ بالتقويم الهجري بدلًا من الميلادي: هناك صفحة google.com/ramadan خاصة بإظهار معلومات حول شهر رمضان. وأحد أهم المعلومات الموجودة هي رقم اليوم في الشهر الهجري (القمري). تستخدم Google طريقة التفافية للحصول على هذه المعلومات. لكنك تستطيع الحصول على نفس المعلومات عبر الواجهة البرمجية للتدويل بسطرٍ وحيد بدلًا من مكتبة JavaScript كاملة. هذا مثال: console.log(new Intl.DateTimeFormat("fr-FR-u-ca-islamicc").format(new Date())); السطر السابق يعني أنَّنا نريد أن يكون التاريخ الحالي بالتقويم الهجري وأن يُعرَض بالأرقام العربية الغربية (fr-FR). الناتج هو: 16/12/1436 console.log(new Intl.DateTimeFormat("ar-SA-u-ca-islamicc").format(new Date())); نفس الأمر هنا، لكن التنسيق لمحليّة (locale) الخاصة بالسعودية التي تستعمل الأرقام العربية المشرقية. الناتج: ١٦‏/١٢‏/١٤٣٦ الخلاصة رأينا في هذا الدرس بعض التقنيات المتقدمة لدعم اتجاه RTL، وأخذنا فكرةً عن مستقبل دعم RTL من معايير الويب. ترجمة -وبتصرّف- للمقال Building RTL-Aware Web Apps & Websites: Part 2 لصاحبه أحمد نفزاوي
  3. فعّلنا في الدرس السابق شبكة المواقع في ووردبريس، وضبطتنا بعض الخيارات، وسنشرح في هذا الدرس إدارة المستخدمين والقوالب والإضافات. إدارة المستخدمين كما ذكرنا في الدرس السابق، يمكنك إضافة المستخدمين إلى موقعٍ معيّن عبر لسان Users (أعضاء) في صفحة Sites (المواقع) في لوحة تحكم مدير الشبكة. يمكنك أيضًا إضافة مستخدمين إلى الشبكة وتعديل بياناتهم عبر صفحة Users (أعضاء) في لوحة التحكم. هذه الصفحات تعمل بشكلٍ مشابهٍ لمواقع ووردبريس المفردة، ما عدا جزئية واحدة هي أنَّ هنالك معلومات عن المواقع التي يملك هذا المستخدم حسابًا فيها. سترى في الصورة الآتية مستخدمي الموقع الخاص بي، بالإضافة إلى حساب مدير الموقع الذي أنشأته للتو: سترى أيضًا عمودًا إضافيًا يُظهِر ما هي المواقع التي أُضيف إليها ذاك المستخدم. يمكنك استخدام هذه الصفحة للوصول إلى صفحة المستخدم، أو حذف المستخدم –ضع الفأرة فوق اسم المستخدم، ثم اضغط على رابط «حذف». حذف المستخدم لن يؤدي إلى حذف المواقع التي أنشأها– إذا كان لديك مستخدمٌ أنشأ حسابًا مزعجًا (spam) ومدونة لذاك الغرض، فعليك حينها حذف المستخدم والموقع. لإضافة مستخدم، اضغط على زر «Add New» (أضف جديد)، الذي يعمل بطريقة مشابهة لطريقة إضافة مستخدمين في المواقع المفردة. وإذا أردت إضافة ذاك المستخدم إلى موقع، فيمكنك فعل ذلك في صفحة «المواقع» في لوحة التحكم في لسان «Sites > Users» (المواقع > أعضاء). تثبيت القوالب والإضافات لا يمكن تثبيت القوالب والإضافات إلا من مدير الشبكة، إذ لا يستطيع مدراء المواقع فعل ذلك. لننظر إلى كيفية القيام بذلك، وكيف تتمكن بعدها من تفعيل أو تمكين تلك القوالب أو الإضافات لمواقع الشبكة. تثبيت وتفعيل القوالب بعد أن تُثبِّت قالبًا، فعليك تمكينه للمواقع الموجودة في شبكتك بإحدى الطريقتين الآتيتين: إما أن تُمكِّنه للمواقع كلًا على حدة، أو أن تُمكِّنه لجميع مواقع الشبكة. يمكن أن يُفعَّل القالب على موقعٍ ما (بوساطة مدير الشبكة أو مدير الموقع) إذا كان ذاك القالب مُمكَّنًا لكل مواقع الشبكة أو لذاك الموقع بالتحديد. هذه هي طريقة تثبيت قالب وتمكينه على الشبكة: في لوحة تحكم مدير الشبكة، اذهب إلى «Themes > Add New» (قوالب > أضف جديد)، ثم ثبِّت القالب بالطريقة المعتادة. في صفحة تثبيت القالب، اضغط على رابط «تفعيل في الشبكة» بشكلٍ بديل، يمكنك تمكين استخدام قالب مثبت مسبقًا بالذهاب إلى صفحة «Themes» (قوالب) ثم الضغط على رابط «تفعيل في الشبكة» الظاهر في أسفل القالب. يمكنك أيضًا أن تُمكِّن قالبًا لموقعٍ وحيد، وهذا مفيد إذا كانت تحتوي شبكتك على العديد من المواقع التي يجب أن يكون لكلٍ منها قالبٌ خاصٌ به؛ وهذا يكون عندما تستضيف شبكتك مواقع عملائك. تمكين القوالب لكل موقع على حدة يعني أنَّ بقية القوالب لن تكون متاحةً للمواقع التي لم تُمكِّن ذاك القالب فيها، لذا لن يستطيع مدراء المواقع تفعيل القالب الخاطئ دون قصد. هذه طريقة فعل ذلك: في لوحة تحكم الشبكة، اضغط على Sites (مواقع) لمشاهدة جميع المواقع التي عندك. مرر الفأرة فوق اسم الموقع الذي تريد تمكين القالب له ثم اضغط على رابط Edit (تحرير) الذي سيظهر. اضغط على لسان Themes (قوالب) لعرض صفحة تعديل ضبط القوالب لهذا الموقع: اضغط على رابط Enable (تفعيل). اذهب الآن إلى لوحة تحكم مدير الموقع واضغط على «Appearance > Themes» (مظهر > قوالب)، وسترى القالب الذي مكّنته موجودًا في القوالب القابلة للتفعيل: تثبيت وتفعيل الإضافات يجب أيضًا على مدير الشبكة تثبيت الإضافات، لكنها تعمل بشكلٍ مختلف عن القوالب، إذ لا تستطيع أن تُفعِّل إضافةً ما لموقعٍ معيّن، وإنما يمكنك أن تُفعِّل الإضافة لكل المواقع الموجودة على الشبكة، أو أن تثبِّتها لكي يتمكن مدراء المواقع من تفعيلها. تفعيل الإضافات على كل الشبكة مفيدٌ إذا كتبتَ أو نزلتَ إضافةً توفِّر ميزةً تريد لكل مواقع شبكتك أن تملكها، على سبيل المثال، لو أردت تفعيل إضافة للتخزين المؤقت (caching) أو إضافة لتحسين SEO. هنالك بعض الإضافات التي يمكن أن تُفعَّل لكامل الشبكة فقط، على سبيل المثال، أُثبِّت على شبكة عملائي إضافة Snapshot لأخذ نسخ احتياطية لكامل مواقع الشبكة بشكلٍ دوري. هذه هي طريقة تثبيت وتفعيل إضافة: في لوحة تحكم مدير الشبكة، اذهب إلى «Plugins > Add New» (إضافات > أضف جديد) وثبِّت الإضافة كما كنتَ تفعل في ما مضى. في صفحة تثبيت الإضافة، اضغط على رابط «تفعيل في الشبكة». ستعمل الإضافة الآن على جميع المواقع في الشبكة، ولن يتمكن مدراء المواقع من تعطيلها. لكن ماذا لو أردت أن تُفعِّل إضافةً ما في موقعٍ معيّن؟ يمكنك فعل ذلك بتثبيت الإضافة كمدير للشبكة، ثم تفعيلها لذاك الموقع. في لوحة تحكم مدير الشبكة، اذهب إلى «Plugins > Add New» (إضافات > أضف جديد) وثبت الإضافة كما تعودت. سأنزِّل إضافة WooCommerce كما هو ظاهر في الصورة التالية. ربما لا تريد تفعيل إضافة WooCommerce لكل المواقع في شبكتك إلا إذا كانت الشبكة مخصصة لمالكي المتاجر: في صفحة تثبيت الإضافة، اضغط على رابط العودة إلى صفحة الإضافات. اذهب إلى لوحة تحكم مدير الموقع الذي تريد تفعيل الإضافة فيه واضغط على رابط «إضافات» في شريط الإدارة. اضغط على رابط «تفعيل» الظاهر تحت اسم الإضافة التي تريد تفعيلها، وذلك كما في مواقع ووردبريس العادية. يجب أن تُفعَّل الإضافة الآن، كما هو ظاهر في لقطة الشاشة الآتية: ضبط إعدادات الشبكة آخر مجموعة من الصفحات التي عليك أن تستعملها كمدير للشبكة هي صفحة إعدادات الشبكة. هنالك صفحتان هما: تهيئة الشبكة (Network Setup) التي تُظهِر ما رأيتَه أثناء تثبيتك للشبكة مع الشيفرة التي ستحتاج إلى إضافتها إلى ملفَي wp-config.php و ‎.htaccess. يمكنك الرجوع إلى هذه الصفحة إذا حدثت معك مشاكل في المستقبل وعليك أن تُعدِّل الملفين السابقين. إعدادات الشبكة (Network Settings) التي تعطيك إمكانية تعديل ضبط شبكتك. للوصول إلى صفحة إعدادات الشبكة، اذهب إلى «Settings > Network Settings» (الإعدادات > إعدادات الشبكة) للدخول إلى صفحة الضبط الآتية: الإعدادات التي يمكنك تخصيصها هي: عنوان الشبكة البريد الإلكتروني لمدير الشبكة إعدادات التسجيل: هل تريد أن يتمكن المستخدمون من تسجيل حسابات و/أو مواقع، أم أن يُضيف مدراءُ المواقع المستخدمين، وما هي نطاقات البريد الإلكتروني وأسماء المواقع المحجوبة. إعدادات المواقع الجديدة: محتوى رسالة البريد الإلكتروني الترحيبية لمدراء المواقع والمستخدمين والصفحة الرئيسية، والمنشور والتعليق الافتراضي الذي سيُنشَأ على المواقع الجديدة. إعدادات الرفع: أنواع الملفات المسموحة والحجم المسموح الأقصى للملف المرفوع. إعدادات اللغة: اللغة الافتراضية للموقع. إعدادات القائمة: تفعيل أو تعطيل قائمة الإضافات لمدراء المواقع. يمكنك تعطيل هذه القائمة لمنع تفعيل أو تعطيل الإضافات. سنعود إلى هذه الصفحة وسنشرحها بالتفصيل لاحقًا في السلسلة عندما نتعلم كيف نضبط عملية تسجيل المستخدمين وإنشاء المواقع. تفعيل وضبط شبكة متعددة المواقع أسهل مما تتخيل أصبحتَ الآن تعرف كل ما يلزمك لتهيئة شبكة متعددة المواقع وضبطها، بما في ذلك إضافة مواقع جديدة، وتثبيت وتفعيل القوالب والإضافات. في المرحلة القادمة من هذه السلسلة، سنتعلم عن عملية إنشاء الموقع، وسنُفعِّل إمكانية إنشاء المواقع من قِبل المستخدمين وضبط العملية لتحسين تجربة المستخدم. سنفعل بعض هذه الأمور عبر صفحات الضبط وبعضها عبر كتابة بعض الشيفرات أو استخدام الإضافات. ترجمة –وبتصرّف– للمقال WordPress Multisite Masterclass: Activation and Configuration لصاحبته Rachel McCollin.
  4. أهلًا بك مجددًا في دورتنا التدريبية، سنشرح في هذا الدرس من السلسلة كيفية تفعيل ميزة تعدد المواقع وتشغيل شبكتك. سأشرح لك عملية التثبيت، ثم سأريك كيف تُنشِئ موقعًا في شبكتك، وتضيف المستخدمين، وتُثبِّت القوالب والإضافات وتضبط خيارات الشبكة. سأشرح لك في هذه السلسلة كل ما تحتاج معرفته لإنشاء شبكتك الخاصة، وطريقة إضافة مواقع إليها، أو السماح للمستخدمين بإضافة مواقعهم الخاصة، وكيفية إدارة الشبكة. ستتعلم أيضًا كيف تتأكد أنَّ شبكتك آمنة وأنَّ أداءها عالٍ، وكيف تُنشِئ مجتمعًا ناجحًا من المستخدمين والمواقع. قبل أن نبدأ في هذا الجزء من السلسلة التعليمية، أنصحك أن تقرأ الدرسين الأولين: مدخل إلى ووردبريس مُتعدّد المواقع أمور إضافية يجب أن تعرفها قبل أن تُطلق أول شبكة لك على ووردبريس مُتعدّد المواقع اللذان يوفران تقديمًا إلى ميزة تعدد المواقع في ووردبريس. أقرأتهما من قبل؟ رائع! فلنبدأ. ما الذي ستحتاج له لكي تتابع دروسنا في هذه السلسلة، فستحتاج إلى ما يلي: نسخة ووردبريس مُثبَّتة. قد تكون هذه النسخة موجودةً عندك من قبل أو أن تكون قد ثبتها حديثًا. أنا أرى أن تعمل على نسخةٍ جديدةٍ لكي تحصل على أغلبية الخيارات ولكي تتفادى خطر تعطيل موقعك. لا تفعل ذلك على موقعٍ إنتاجي قبل أن تأخذ نسخةً احتياطيةً أولًا! محرر شيفرات برمجية لتعديل ملفَي wp-config.php و ‎.htaccess الخاصَين بموقعك. سأستخدم محرر Coda لنظام ماك الذي يتضمن عميل FTP أيضًا؛ يمكنك اختيار أي محرر يعجبك. إن لم يتضمن محررك عميل FTP مُضمَّن داخله، فستحتاج إلى عميل FTP خارجي ليسمح لك بتنزيل ورفع ملفاتك إلى موقعك، مثل برنامج FileZilla. إذا كنت تُفضِّل أن تعمل على موقعٍ محلي بدلًا من موقعٍ يعمل على خادومٍ بعيد، فيمكن أن تستعمل نسخة ووردبريس مثبتة على خادوم محلي مثل MAMP أو ما شابهه (ولن تحتاج حينها إلى استخدام FTP لكن ذلك لن يغنيك عن محرر الشيفرات). سأفترض أنَّك معتادٌ على التعامل مع واجهة ووردبريس وأنَّك لا تخشى أن تُعدِّل الملفات مباشرةً، على الرغم من أنَّنا لن نفعل ذلك كثيرًا، لذا لا تقلق! تفعيل ميزة تعدد المواقع على نسخة ووردبريس هذه الميزة لا تتطلب تنزيل وتثبيت برمجيات جديدة لأنها جزءٌ لا يتجزأ من ووردبريس، لكن عليك تفعيلها لكي تجعلها تعمل. قبل أن تبدأ قبل أن تُفعِّل ميزة تعدد المواقع، فعليك أن تُقرِّر فيما إذا كنت تريد استخدام النطاقات الفرعية أو المجلدات الفرعية لمواقع شبكتك. هذه تذكرة سريعة ذكرناها سابقًا تبيّن لك معناها: استخدام النطاقات الفرعية يعني أنَّ لكل موقع رابط URL مثل http://site1.yournetwork.com، إذا كنت تخطط للسماح للآخرين بإنشاء مواقعهم الخاصة، فعليك أن تتأكد أنَّك تستطيع استخدام النطاقات الفرعية غير المحدودة في نطاقك الأساسي (عليك أن تراجع ذلك مع استضافتك). استخدام المجلدات الفرعية يعني أنَّ لكل موقع رابط URL مثل http://yournetwork.com/site1، لا يمكنك انتقاء هذا الخيار على موقعٍ موجودٍ مسبقًا والذي تحاول تحويله إلى شبكة لأن ذلك قد يسبب مشاكل مع روابط URL الموجودة مسبقًا في موقعك. إذا كنتَ تُفعِّل ميزة تعدد المواقع على موقعٍ محلي (أي موقع يعمل على حاسوبك المحلي)، فلن يُسمَح لك باستخدام النطاقات الفرعية عندما تحاول ضبط الشبكة، وإنما عليك استخدام المجلدات الفرعية. ضبط النطاقات الفرعية ملاحظة: لو أردت استعمال المجلدات الفرعية، فتجاوز هذه الفقرة. إذا كنت تستخدم النطاقات الفرعية وتتوقع أن تُنشَأ الكثير من المواقع على شبكتك، أو أنَّ المستخدمين سيُنشؤون مواقع خاصة بهم، فلن تعمل النطاقات الفرعية عملًا سليمًا إلا إذا كنت قد ضبطتَ ما يسمى «wildcard subdomains». أما إذا كنت تنوي ربط المواقع في شبكتك بنطاقات خارجية، أو لم تكن تمانع أن تضبط كل نطاق فرعي يدويًا عندما تُنشِئ موقعًا جديدًا، فحينها لن تحتاج إلى تفعيل wildcard subdomains. يمكنك أن تضبط النطاقات الفرعية عبر cPanel، التي يجب أن يعطيك مزود الاستضافة وصولًا إليها (راجع الدعم الفني إن لم تكن متأكدًا). ثم عبر cPanel اذهب إلى «Domains > Subdomains» لكي ترى الصفحة المسؤولة عن ضبط النطاقات الفرعية. هذه لقطة شاشة لموقعٍ ضبطتُ فيه wildcard subdomains: لضبط نطاق فرعي معيّن، عليك أن تضيفه إلى حقل «Subdomain» ثم الضغط على Create، فلو أضفتَ موقعًا إلى شبكتك له الرابط http://new-site.yournetwork.com فأضف new-site كقيمة لذاك الحقل. بشكلٍ بديل، يمكنك أن تضبط wildcard subdomains وذلك بكتابة رمز النجمة * في حقل «Subdomain» ثم اضغط على Create، وبهذا فلن تحتاج إلى إنشاء أيّة نطاقات فرعية يدويًا. ملاحظة: بعض شركات الاستضافة الرخصية لا يسمحون بتفعيل wildcard subdomains وبعضهم لا يعطيك وصولًا إلى لوحة cPanel. إذا وقعت في هذه الحالة، فأنصحك أن تبحث عن شركة استضافة أفضل، لكن إن لم تستطع فاطلب منهم أن يضبطوا النطاقات الفرعية لك. تفعيل ميزة تعدد المواقع ها قد أصبحتَ جاهزًا لتفعيل ميزة تعدد المواقع! إن لم تُثبِّت ووردبريس، فثبتها كالمعتاد، إما باستخدام سكربت يوفره مزود خدمة الاستضافة، أو بتنزيل ووردبريس وتثبيتها يدويًا على خادومك أو جهازك المحلي. افتح ملف wp-config.php الذي يجب أن تجده في المجلد الذي ثبتت ووردبريس فيه، ثم ابحث عن السطر الآتي: /* That's all, stop editing! Happy blogging. */ أو عن السطر الآتي (في النّسخة العربية): /* هذا هو المطلوب، توقف عن التعديل! نتمنى لك التوفيق. */ قبل ذلك السطر مباشرةً، افتح سطرًا جديدًا واكتب فيه المحتويات الآتية: define('WP_ALLOW_MULTISITE', true ); احفظ ملف wp-config.php. الخطوة الآتية هي زيادة لوحة تحكم ووردبريس وتثبيت ميزة تعدد المواقع. في لوحة تحكم ووردبريس، اذهب إلى «Tools > Network Setup» (أدوات > تهيئة الشبكة). ستنتقل إلى صفحة لإنشاء شبكة المواقع، وعليك إدخال أو اختيار ما يلي: تثبيت على مسار فرعي / نطاق فرعي. اختر ما تشاء إن كان متاحًا. عنوان الشبكة. هذا الحقل مملوء مسبقًا ويمكنك تعديله إن شئت، وتستطيع تعديله لاحقًا في ضبط الشبكة. البريد الإلكتروني لمدير الشبكة. إن كان عنوان البريد مختلفًا عن الذي أدخلتَه مسبقًا، فعدِّله. اضغط على زر «Install» (تنصيب). ستؤخذ بعد ذلك إلى صفحة لتفعيل الشبكة، التي ستظهر فيها بعض الشيفرات التي عليك إضافتها إلى ملفَي wp-config.php و ‎.htaccses: افتح ملفَي wp-config.php و ‎.htaccess وعدلهما لكي يتوافقا مع الشيفرات الموجودة في الصفحة السابقة. إن لم تعثر على ملف ‎.htaccess على خادومك، فقد يكون السبب هو أنَّ الملفات المخفية غير ظاهرة: عدِّل خيارات مُحرِّرك النصي إذا كنت تستعمله للوصول إلى تلك الملفات. احفظ كلا الملفين. يجب أن تكون ميزة الشبكة متعددة المواقع مثبتة الآن، وعليك تسجيل الدخول مرةً أخرى لكي ترى لوحة التحكم: يمكنك الآن البدء بإضافة مواقع وإضافات وقوالب والمزيد… إنشاء موقع ستملك شبكتك موقعًا وحيدًا في البداية، ألا وهو الموقع الأساسي، الذي هو الموقع الذي بدأت باستخدامه قبل تفعيل ميزة الشبكة متعددة المواقع. يمكنك إضافة مواقع أخرى بنفسك –كمدير للشبكة– أو يمكنك تفعيل إمكانية إنشاء المستخدمين لمواقع خاصة بهم. سنتعلم كيفية تفعيل إنشاء مواقع للمستخدمين لاحقًا في هذه السلسلة، لذا سأريك هنا كيف تفعل ذلك بنفسك. اذهب إلى لوحة تحكم مدير الشبكة بالضغط على «My Sites > Network Admin» (مواقعي > إدارة الشبكة) من على شريط الإدارة في الأعلى. اذهب إلى «Sites > Add New» (المواقع > أضف جديد). اكتب عنوان الموقع (إما كمجلد فرعي أو كنطاق فرعي)، واسم الموقع، والبريد الإلكتروني لمدير الموقع، كما هو ظاهر في الصورة أدناه: ثم اضغط على زر «Add Site» (أضف موقعًا) وستُنشِئ ووردبريس موقعًا لك. إذا استخدمتَ عنوان بريدك الإلكتروني الخاص بمدير الشبكة، فستتمكن من رؤية الموقع عندما تمرر الفأرة فورق رابط «My Sites» (مواقعي) في شريط الإدارة أعلى الشاشة. أما لو لم تكن المدير، فيمكنك أن تراه في لوحة تحكم إدارة الشبكة. اضغط على «Sites > All Sites» (المواقع > كافة المواقع) لرؤية جميع المواقع في الشبكة. سترى في الصورة الآتية الموقع الجديد مذكورًا بالإضافة إلى الموقع الأساسي: لاحظ أنَّ رابط URL لجميع مواقعي طويلٌ بعض الشيء لأنني ثبتتُ الشبكة متعددة المواقع في مجلدٍ فرعي في موقعي الرئيسي؛ ربما الروابط الخاصة بك أقصر. تعديل وضبط المواقع يمكنك كمدير للشبكة أن تُعدِّل المواقع إما عبر لوحة التحكم الخاصة بها، أو عبر قسم Sites (المواقع) في لوحة تحكم مدير الشبكة؛ وهنالك بعض الأمور التي يمكنك فعلها من صفحة Sites (المواقع) فقط، مثل تفعيل قالب لموقعٍ ما. تعديل حالة موقع من صفحة Sites (المواقع) في لوحة تحكم مدير الشبكة، يمكنك تغيير حالة كل موقع من مواقع الشبكة؛ الخيارات المتاحة أمامك هي: تعطيل. هذا ما سيحدث لموقعٍ على الشبكة لو قام مدير الموقع بحذفه. لن يتمكن مدير الموقع من الوصول إليه ولا يمكن للآخرين زيارته، لكنك –كمدير للشبكة– تستطيع فعل ذلك. تستطيع أن ترى في الصورة الآتية موقعًا معطّلًا ظاهرًا باللون الأحمر في لوحة تحكم مدير الشبكة. صحيحٌ أنَّه مُعلَّم على أنه «محذوف»، إلا أنه لم يُحذَف بعد، وإنما معطّل فقط. يمكنك الضغط على زر «Activate» (تفعيل) لتفعيل الموقع مرةً أخرى. أرشفة. تأثير الأرشفة مشابه لتعطيل الموقع، عدا أنَّ هنالك رسالة خطأ ستظهر عندما يحاول الآخرون زيارة الموقع. لن يتمكن مدير الموقع أو الزوار من الوصول إلى الموقع، لكن يمكنك فعل ذلك بصفتك مديرًا للشبكة وذلك من صفحة Sites (المواقع) في لوحة تحكم مدير الشبكة. مزعج. هذا الخيار لا يحذف الموقع، لكنه لن يكون متاحًا للوصول لأي شخص بما في ذلك مدير الشبكة. هذه هي الخطوة النهائية قبل الضغط على زر الحذف. حذف. هذا الخيار يحذف الموقع، مع جميع جداول قاعدة البيانات وجميع الملفات المرفوعة والمرتبطة معه. استخدم هذا الخيار بحذر! لاختيار أي شيء مما سبق، ضع الفأرة فوق اسم الموقع في صفحة Sites ثم اضغط على الرابط الموافق. معلومات الموقع في لوحة تحكم مدير الشبكة، اذهب إلى Sites (المواقع)، ثم مرر الفأرة فوق أحد المواقع ثم اضغط على Edit (تحرير)؛ وهذا سيأخذك إلى ألسنة (tabs) لتحرير الموقع، وستبدأ افتراضيًا بلسان Info: يمكنك هنا أن تستعرض وتعدِّل المعلومات الأساسية المرتبطة بهذا الموقع، مثل حالته ورابط URL. أحيانًا أُنشِئ موقعًا وأرتكب خطأ إملائيًا عندما أكتب رابط URL الخاص به، فإن حدث هذا معك، فهذا هو المكان الملائم لتصحيحه. مستخدمي الموقع يمكنك إضافة وتعديل مستخدمي الموقع بالضغط على لسان Users (أعضاء): سترى هنا كل مستخدمي الموقع، استخدم هذه الصفحة لإضافة مستخدم موجود مسبقًا في شبكتك إلى هذا الموقع، أو لإضافة مستخدم جديد؛ لكن أيًّا تكن الخيارات التي ستعتمدها، إلا أنَّك ستحتاج إلى تحديد دور (أو «رتبة») المستخدم؛ والتي ستكون رتبة المستخدم لهذا الموقع، وليس للشبكة ككل. قوالب الموقع اضغط على لسان Themes (قوالب) لتفعيل القوالب لمواقع معينة. هذا مفيدٌ إن كنت تدير شبكةً لمواقعك أو لمواقع عملاءك، وتريد أن يملك كل موقعٍ قالبًا خاصًا به. وبهذا الطريقة، لن تُفعِّل أنت (أو مدراء تلك المواقع) بالغلط القالب غير المخصص للموقع. إذا كان مستخدموك ينُشؤون مواقعهم الخاصة، فربما تريد توفير قوالب إضافية لهم، وهذا ما يمكنك فعله بتمكين تلك القوالب للشبكة (سنتحدث عن المزيد من هذا الموضوع قريبًا). يمكنك أن ترى قالبين مثبتين على الشبكة لكن غير مفعلين في لقطة الشاشة الآتية. القوالب المُفعّلة لكامل الشبكة لا تظهر هنا، حيث لا حاجة إلى تفعليها للمواقع كلًا على حدة لأنها مفعلة من قبل. لتفعيل قالب لموقعٍ ما، اضغط على رابط Enable (تفعيل) الظاهر تحت اسمه. ثم ستظهر في صفحة القوالب في لوحة تحكم الموقع. إعدادات الموقع يمنحك لسان Settings (إعدادات) وصولًا إلى المزيد من إعدادات الضبط للمواقع الموجودة على شبكتك: لديك هنا وصولٌ لجميع الخيارات المتعلقة بالموقع، أميل إلى تفادي التعديل على هذه الصفحة وأفضِّل استخدام لوحة تحكم مدير الموقع لكل موقع، لكن من الجيد وجود هذه الصفحة إذا تعذر الوصول إلى لوحة التحكم الخاصة بالموقع لسببٍ من الأسباب (مثلًا، النطاق لا يعمل) وتريد أن تُعدِّل في الإعدادات لكي يعمل الموقع مجددًا. إذا كنتَ تنوي تعديل الإعدادات الموجودة في هذه الصفحة، فاحذر أن تُعيد الكتابة فوق الإعدادات التي أجراها مدير الموقع. ترجمة –وبتصرّف– للمقال WordPress Multisite Masterclass: Activation and Configuration لصاحبته Rachel McCollin.
  5. أهلًا بك مجددًا في الدرس الثاني من سلسلة تعلم الاستفادة من ميزة تعدد المواقع في ووردبريس. شرحنا في الدرس السابق بعض الأمور الأساسية التي عليك أن تضعها بعين الاعتبار عند التعامل مع شبكة من المواقع، وسنكمل ذلك في درسنا هذا. النطاقات اختلافٌ آخر في الشبكة متعددة المواقع هو النطاقات (domains) المستخدمة لكل موقع على الشبكة، إذا يملك كلٌ منها رابطًا خاصًا به، وإما أن يكون ذلك عبر نطاق فرعي (subdomain) أو عبر وضع الموقع في مجلد فرعي ضمن نطاق الموقع الرئيسي. حيث لدينا الحالتين الآتيتين: استخدام النطاقات الفرعية يعني أنَّ لكل موقع رابط URL مثل http://site1.yournetwork.com، إذا كنت تخطط للسماح للآخرين بإنشاء مواقعهم الخاصة، فعليك أن تتأكد أنَّك تستطيع استخدام النطاقات الفرعية غير المحدودة في نطاقك الأساسي (عليك أن تراجع ذلك مع استضافتك). استخدام المجلدات الفرعية يعني أنَّ لكل موقع رابط URL مثل http://yournetwork.com/site1، لا يمكنك انتقاء هذا الخيار على موقعٍ موجودٍ مسبقًا والذي تحاول تحويله إلى شبكة لأن ذلك قد يسبب مشاكل مع روابط URL الموجودة مسبقًا في موقعك. إذا كنتَ تُفعِّل ميزة تعدد المواقع على موقعٍ محلي (أي موقعٍ يعمل على حاسوبك المحلي، راجع مقالة كيفية تنصيب ووردبريس محليا باستخدام MAMP)، فلن يُسمَح لك باستخدام النطاقات الفرعية عندما تحاول ضبط الشبكة، وإنما عليك استخدام المجلدات الفرعية. إذا كنتَ تُفعِّل ميزة تعدد المواقع على تثبيت ووردبريس قديم نسبيًا، فلن يُسمَح لك باستخدام المجلدات الفرعية، وعليك حينها أن تستعمل النطاقات الفرعية؛ هذا لأن موقعك قد يحتوي على منشورات أو صفحات ذات روابط قد تتعارض مع روابط المجلدات الفرعية للمواقع الجديدة التي ستُنشَأ على الشبكة. لكي تتفادى المشاكل في الروابط، فستتغير بنية الروابط الدائمة (paramlinks) في موقعك الرئيسي عندما تُفعِّل الشبكة متعددة المواقع الموجودة ضمن مجلدات فرعية؛ فلو كان لديك منشورٌ في http://yoursite.com/name-of-my-post فسيُنقَل إلى http://yoursite.com/blog/name-of-my-post. أُكرِّر أنَّ الأمر يحدث لمنع حدوث تضارب بين أسماء المنشورات والمجلدات الفرعية التي تُمثِّل المواقع الأخرى في شبكتك. سنشرح النطاقات وكيفية ربطها بالتفصيل في درسٍ لاحقٍ في هذه السلسلة. حالات استخدام شبكة متعددة المواقع يمكن توظيف الشبكات متعددة المواقع لعدِّة أغراض، بعضها مفيدٌ للمجتمع، وبعضها مفيدٌ للشركات. وهي تتضمن: إدارة مواقعك الخاصة استضافة المواقع للعملاء استضافة أكثر من موقع لمنظمة واحدة دفع الآخرين لإنشاء مواقعهم دعم المجتمع لننظر إلى كل حالة على حدة. إدارة مواقعك الخاصة أتحدث إلى الكثير من مستخدمي ومطوري ووردبريس الذي يملكون عددًا كبيرًا من المواقع. ربما يدعمون فيها المشاريع الشخصية، أو ربما لأغراض تجارية أخرى، أو ربما لكي يستعرضوا تقنياتٍ مختلفة. مثلًا، شبكة المواقع الموجودة في rachelmccollin.co.uk تحتوي على عشرات المواقع التي أنشأتها لغرض استعراض مختلف التقنيات التي أكتب عنها في الدروس والكتب والمقالات. أغلبية تلك المواقع صغيرة وفيها إضافة أو قالب واحد مفعل الذي يستعمل الشيفرات الموجودة في الدروس. والأخرى كبيرة لتضم كتابًا كاملًا. إبقاء هذه المواقع مرتبطةً بشبكة يساعدني كثيرًا عندما أحتاج إلى تحديث إضافة أو قالب أو حتى ترقية نسخة ووردبريس نفسها. الكثير من القوالب التي أستعملها هي «قوالب أبناء» (child themes) للقالب الافتراضي في ووردبريس (Twenty Sixteen)، ولا أحتاج سوى إلى تخزين وتحديث نسخة واحدة من قالب Twenty Sixteen. إذا كنت من الأشخاص الذين يحبون إنشاء مواقع شخصية، فميزة تعدد المواقع ستساعدك كثيرًا في إدارتها. وتستطيع أيضًا أن تستعمل نطاقًا مختلفًا لكل موقع، وحينها لن يعلم أحدٌ أنَّك تستعمل شبكةً من المواقع. استضافة المواقع للعملاء استعمال ميزة تعدد المواقع لاستضافة مواقع العملاء هي طريقةٌ ذات كفاءةٍ عالية وتستعمل مساحةً تخزينيةً أقل على الخادوم. تطور الشركة التي أعمل بها مواقع للشركات الصغيرة وتستضيفها. بالطبع لا نستضيف كل مواقع عملائنا في شبكةٍ واحدة، لأن الأمر سيصبح فوضويًا قليلًا؛ لكن نسبةً لا بأس بها من المواقع التي نستضيفها هي مواقع صغيرة فيها أقل من 20 صفحة. وجميع تلك المواقع تستعمل «قوالب أبناء» لنفس القالب التطويري (الذي برمجتُه بنفسي)، فلو أردتُ أن أُحدِّث القالب التطويري فسأحتاج إلى تحديثه مرةً واحدةً على الشبكة، والأمر سيانٌ للإضافات. هذا يجعل عملية التحديث أسهل وأسرع وأكثر كفاءة. لا تنسَ أنَّه بالإمكان استخدام نطاقات مختلفة لكل موقع، وبهذا لن يعلم أحدٌ أنَّ كل تلك المواقع مستضافةٌ على شبكةٍ واحدة. باستخدام إضافات مثل Support System و Snapshot، أتمكن من استخدام الشبكة متعددة المواقع لتحسين جودة الخدمة التي أقدمها لعملائي. إضافة Snapshot هي إضافة متوافقة مع الشبكات متعددة المواقع التي تسمح لي بضبط كل خيارات النسخ الاحتياطي من مكانٍ واحد؛ وإضافة Support System تسمح لي باستقبال والاستجابة إلى طلبات الدعم من العملاء. إذا كنتَ تستعمل شبكةً، فعليك توخي الحذر كما لو كنت تُجري تحديثًا، ولا تنسَ أن تختبر الإضافات والتحديثات على موقعٍ تطويري أولًا! أملك نسخةً محليةً من شبكتي، التي أُفعِّل فيها أيّة تحديثات لأختبرها قبل أن أُفعِّلها على الشبكة الإنتاجية. هذه المواقع للعملاء، لذا توخى الحذر معها! استضافة أكثر من موقع لمنظمة واحدة تخيل أنَّك مدير مواقع الويب لمنظمة كبيرة تعمل في بلدانٍ كثيرة. ربما لديك مواقع منفصلة لكل دولة من الدول، تخيل كم سيؤلمك رأسك جراء إدارتها جميعًا! أو ربما لديك عدِّة مواقع بنفس اللغة لكن لمختلف الأقسام. الشبكة متعددة المواقع تجعل من السهل الحصول على مواقع منفصلة لكن يمكن إدارتها من مكانٍ وحيد. إذا كانت جميع مواقعك تستعمل «قالب ابن» لقالب رئيسي وحيد فعليك أن تخزنه وتُحدِّثه بمكان واحد فقط. وإذا كنت تستعمل نفس الإضافات في كل مواقعك، فستستفيد من الشبكة متعددة المواقع خير استفادة؛ وكما أنَّك تستطيع أن تنشر نفس المحتوى في أكثر من موقع، ويمكنك إنشاء مجتمع من مستخدمي منظمتك، وتستطيع أن تُنشِئ موقعًا يُستعمَل كقالب لكي تستفيد منه عندما تُنشِئ موقعًا جديدًا لفرعٍ جديدٍ في دولةٍ ما أو لقسمٍ ما. جني الأموال عبر الشبكة متعددة المواقع قد تستفيد من الشبكة متعددة المواقع لدعم شركتك وجني الأموال. إذا كنت قد ضبطتَ شبكتك لكي تسمح للآخرين بإنشاء مواقعهم، فهنالك مختلف الأنواع لتقاضي الأموال عبر الشبكة، وهذا يتضمن: الدفع مقابل إنشاء موقع الدفع مقابل ميزات مُحسَّنة الدفع مقابل إضافات أو قوالب معينة الدفع للدعم الفني والخدمات الإضافية الدفع لقاء الحصول على نطاق منفصل نحن نستعمل بعض تلك الطرق في موقع Edublogs؛ إذ أنَّ إنشاء الموقع مجانيٌ، لكن إن كنت تريد ميزاتٍ متقدمة وإضافات أخرى، فعليك أن تدفع لقاء ذلك. موقع WordPress.com يعمل بشكلٍ مشابه. دعم المجتمع ليس الغرض من الشبكة متعددة المواقع هو جني الأموال فحسب، وإنما هي أداةٌ رائعة لدعم المجتمعات، حيث تسمح بإنشاء شبكة من المواقع التي يديرها المستخدمون الذين يشكلون جزءًا من المجتمع. وقد يكون هذا ناديًا أو مجموعةً من الأشخاص ذوي الاهتمامات المشتركة. لا تسمح الشبكة المتعددة المواقع لأعضاء مجتمع بإنشاء المواقع فقط، لكنها تسمح لهم أيضًا بالتواصل مع بعضهم بعضًا، بالإضافة إلى متابعة منشوراتهم، ومشاركة المحتوى. وهي توفر موردًا ممتازًا للمعلومات لأعضاء مجتمعك وتجعلهم يبقون دومًا على اتصال. للشبكات متعددة المواقع الكثير من الاستخدامات والميزات بعد أن قرأت أول درسين من هذه السلسلة، فيجب أن تتكون لديك صورة عن أهمية ومدى فائدة خاصية الشبكات متعددة المواقع في ووردبريس. سنشرح في الأجزاء القادمة من هذه السلسلة بعض الجوانب العملية لإنشاء وإدارة شبكة مواقع ووردبريس؛ بما في ذلك طريقة تفعيل ميزة تعدد المواقع وضبط الشبكة، وتفعيل وتحسين طريقة إنشاء المواقع من قِبل المستخدمين، وربط النطاقات مع مواقع في الشبكة، وطريقة إنشاء مجتمع، وإدارة شبكتك. ترجمة –وبتصرّف– للمقال WordPress Multisite Masterclass: Getting Started لصاحبته Rachel McCollin.
  6. تعلمنا في الدرسين السابقين كيف نوصِّف الأشخاص والمنظمات، وسنتناول في هذا الدرس الأحداث والمراجعات توصيف الأحداث تحدث بعض المناسبات في أوقاتٍ معيّنة، ألن يكون من الجميل أن تستطيع إخبار محركات البحث متى ستقع تلك المناسبات تحديدًا؟ هنالك طريقةٌ لفعل هذا في HTML5. لنبدأ بإلقاء نظرة على الجدول الزمني الخاص بالمحاضرات والكلمات التي سألقيها. <article> <h1>Google Developer Day 2009</h1> <img width="300" height="200" src="http://diveintohtml5.org/examples/gdd-2009-prague-pilgrim.jpg" alt="[Mark Pilgrim at podium]"> <p> Google Developer Days are a chance to learn about Google developer products from the engineers who built them. This one-day conference includes seminars and “office hours” on web technologies like Google Maps, OpenSocial, Android, AJAX APIs, Chrome, and Google Web Toolkit. </p> <p> <time datetime="2009-11-06T08:30+01:00">2009 November 6, 8:30</time> – <time datetime="2009-11-06T20:30+01:00">20:30</time> </p> <p> Congress Center<br> 5th května 65<br> 140 21 Praha 4<br> Czech Republic </p> <p><a href="http://code.google.com/intl/cs/events/developerday/2009/home.html">GDD/Prague home page</a></p> </article> جميع المعلومات التي تخص الحدث موجودةٌ في عنصر <article>، وهو المكان الذي نريد وضع خاصيتي itemtype و itemscope فيه. <article itemscope itemtype="http://schema.org/Event"> رابط URL لنوع الاصطلاحات Event هو http://schema.org/Event، الذي يحتوي أيضًا على جدولٍ يصف خاصيات هذه النوع من الاصطلاحات؛ التي ذكرتُ بعضها في هذا الجدول. الخاصية الشرح name اسم الحدث url رابط لصفحة تفاصيل الحدث location الموقع الذي سيُجرى فيه الحدث الذي يمكن أن يُمثَّل بنوع الاصطلاحات PostalAddress أو Place description وصفٌ قصيرٌ للحدث startDate تاريخ بدء فعاليات الحدث بصيغة ISO endDate تاريخ انتهاء فعاليات الحدث بصيغة ISO duration المدة الزمنية لفعاليات الحدث بصيغة ISO image صورة تعبيرية متعلقة بالحدث اسم الحدث موجودٌ في عنصر <h1>، ووفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، عنصر <h1> ليس له معالجةٌ خاصةٌ، وستكون قيمة خاصية البيانات الوصفية هي المحتوى النصي للعنصر <h1>، لذا كل ما نحتاج له هو إضافة الخاصية itemprop لكي نُصرِّح أنَّ هذا العنصر يحتوي على اسم الحدث. <h1 itemprop="name">Google Developer Day 2009</h1 نقول بالعربية: «اسم هذا الحدث هو Google Developer Day 2009». يحتوي الحدث على صورة، التي يمكن توصيفها باستخدام الخاصية image، وكما تتوقع، تُعرَض الصورةُ عبر عنصر <img>، وكما في خاصية image في نوع الاصطلاحات Person، الصورة في نوع الاصطلاحات Event هي رابط URL، ولأن النموذج الهيكلي للبيانات الوصفية يقول أنَّ قيمة الخاصية الموجودة في العنصر <img> هي قيمة خاصية src، فكل ما علينا فعله هو إضافة الخاصية itemprop إلى العنصر <img>. <img itemprop="image" width="300" height="200" src="http://diveintohtml5.org/examples/gdd-2009-prague-pilgrim.jpg" alt="[Mark Pilgrim at podium]"> نقول بالعربية: «إحدى الصور المتعلقة بهذا الحدث موجودٌ في رابط http://diveintohtml5.org/examples/gdd-2009-prague-pilgrim.jpg» يأتي بعد الصورة وصفٌ مختصرٌ للحدث، وهو فقرةٌ نصيةٌ من الكلام العادي. <p itemprop="description">Google Developer Days are a chance to learn about Google developer products from the engineers who built them. This one-day conference includes seminars and “office hours” on web technologies like Google Maps, OpenSocial, Android, AJAX APIs, Chrome, and Google Web Toolkit.</p> المعلومة التي تلي الوصف جديدةٌ علينا. تقع الأحداث عمومًا في تواريخ مُحدَّدة وتبدأ وتنتهي في أوقاتٍ معيّنة. يجب أن نضع الأوقات والتواريخ في HTML5 في عنصر <time>، وهذا ما فعلناه. لذا سيُصبِح السؤال الآن: كيف سنتمكن من إضافة خاصيات البيانات الوصفية إلى عناصر <time> السابقة؟ بالنظر مرةً أخرى إلى النموذج الهيكلي للبيانات الوصفية في HTML، سنلاحظ أنَّ هنالك معالجةٌ خاصةٌ للعنصر <time>. فقيمة خاصية البيانات الوصفية هي قيمة خاصية datetime في العنصر <time>. لكن مهلًا، أليست الصيغة المعيارية لخاصيات البيانات الوصفية startDate و endDate هي ISO، مَثَلُهَا كمَثَلِ خاصية datetime في العنصر <time>. وأكرر مرةً أخرى كيف تتوافق ةتتناغم البُنى الهيكلية من أساس HTML مع البُنى الهيكلية التي نُضيفها من نوع الاصطلاحات الخاص بالبيانات الوصفية. عملية توصيف تواريخ بداية ونهاية الحدث عبر البيانات الوصفية سهلةٌ وتتم كالآتي: استخدام HTML استخدامًا صحيحًا في المقام الأول (باستخدام عناصر <time> للوقت والتاريخ)، ومن ثم إضافة خاصية itemprop <p> <time itemprop="startDate" datetime="2009-11-06T08:30+01:00">2009 November 6, 8:30</time> &ndash; <time itemprop="endDate" datetime="2009-11-06T20:30+01:00">20:30</time> </p> بالعربية: «هذا الحديث يبدأ في November 6, 2009 الساعة 8:30 صباحًا، ويستمر إلى November 6, 2009 الساعة 20:30 (بتوقيت مدينة براغ المحلي، GMT+1)». ثم ستأتي خاصية location، التي تقول عنها صفحة تعريف نوع الاصطلاحات Event أنها قد تكون من نوع PostalAddress أو Place. وفي حالتنا، سيُقام الحدث في مكانٍ متخصصٍ بالمؤتمرات، وهو Congress Center في مدينة براغ. وإمكانية توصيفه على أنَّه «مكان» (Place) ستسمح لنا بتضمين اسمه بالإضافة إلى عنوانه. لنبدأ بالتصريح أنَّ العنصر <p> الذي يحتوي على العنوان هو خاصية location لنوع الاصطلاحات Event، وهذا العنصر يحتوي على خاصياتٍ تابعةٍ لنوع الاصطلاحات http://schema.org/Place. <p itemprop="location" itemscope itemtype="http://schema.org/Place"> ثم سنُعرِّف اسم المكان بوضعه في عنصر وإضافة الخاصية itemprop إليه. <span itemprop="name">Congress Center</span><br> وبسبب قواعد المجالات (scoping rules) في البيانات الوصفية، خاصية itemprop=”name”‎ السابقة تُعرِّف اسم المكان في نوع الاصطلاحات Place وليس في نوع الاصطلاحات Event. وذلك لأنَّ العنصر <p> السابق قد صرَّح عن بداية خاصيات المكان (Place)، ولمّا يُغلَق عنصر <p> بعدُ عبر وسم <‎/p>. أيّة خاصيات نُعرِّفها والتي تَتبَع للبياناتِ الوصفيةِ تكونُ من خصائصِ آخرِ نوعِ اصطلاحاتٍ دخلَ المجالَ. يمكن أن تتداخل أنواع الاصطلاحات مثل المكدس (stack). لم نحذف إلى الآن آخر عنصر من المكدس، وهذا يعني أننا ما زلنا ضمن مجال نوع الاصطلاحات Place. في الحقيقة، سيلزمنا إضافة نوع اصطلاحات ثالث إلى المكدس: عنوانٌ (Address) للمكان (Place) الذي سيُقام به الحدث (Event). <span itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> مرةً أخرى، علينا أن نضع كل جزء من العنوان كخاصية بيانات وصفية مستقلة، لذلك علينا أن نُضيف عددًا من عناصر <span> لكي نضع خاصيات itemprop فيها (إذا رأيتني أشرح الأمور بسرعة هنا، فأنصحك بالعودة إلى الأقسام السابقة وقراءة كيفية توصيف العنوان للأفراد وكيفية توصيف العنوان للمنظمات). <span itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> <span itemprop="streetAddress">5th května 65</span><br> <span itemprop="postalCode">140 21</span> <span itemprop="addressLocality">Praha 4</span><br> <span itemprop="addressCountry">Czech Republic</span> </span> لا توجد خاصيات إضافية للعنوان، لذا لنغلق عنصر <span> الذي بدأ المجال الخاص بنوع الاصطلاحات Address. </span> علينا الآن إضافة الخاصية geo لتمثيل الموقع الفيزيائي للحدث. سنستخدم نفس نوع الاصطلاحات (GeoCoordinates) الذي استعملناه في القسم السابق لتحديد موقع المنظمة. سنحتاج إلى عنصر <span> لكي يعمل كحاوية، والذي يملك الخاصتين itemtype و itemscope. وضمن العنصر <span> سنضع عنصرَي <meta>، واحدٌ لتحديد إحداثيات العرض (الخاصية latitude) والآخر لتحديد إحداثيات الطول (الخاصية longitude). <span itemprop="geo" itemscope itemtype="http://schema.org/GeoCoordinates"> <meta itemprop="latitude" content="50.047893" /> <meta itemprop="longitude" content="14.4491" /> </span> بعد إغلاقنا لعنصر <span> الذي يحوي خاصيات الموقع الجغرافي، سنعود إلى مجال Place، لكن لم تعد هنالك أيّة خاصيات لإضافتها إلى Place، لذا سنُغلِق العنصر <p> الذي بدأ مجال نوع الاصطلاحات Place، وبهذا سنعود إلى تعريف الخاصيات التابعة لنوع الاصطلاحات Event. </p> وأخيرًا وليس آخرًا، بقيت الخاصية url، التي يجب أن تكون مألوفةً لك. تُضاف روابط URL المتعلقة بالأحداث بنفس طريقة إضافة الروابط للأشخاص وطريقة إضافة روابط للمنظمات. إذا كنتَ تستعمل HTML استعمالًا صحيحًا (أي وضع الروابط في عنصر <a href>)، فآلية التصريح أنَّ الروابطَ تُمثِّلُ خاصيةَ url في البيانات الوصفية بسيطةٌ جدًا وذلك بإضافة خاصية itemprop إلى تلك العناصر فقط. <p> <a itemprop="url" href="http://code.google.com/intl/cs/events/developerday/2009/home.html"> GDD/Prague home page </a> </p> </article> يجدر بالذكر أنَّك تستطيع توصيف أكثر من حدث في نفس الصفحة. المقتطفات المنسقة من جديد! وفقًا لأداة اختبار البيانات المنظمة من Google، المعلومات التي تحصل عليها عناكب محرك البحث من صفحة الحدث السابقة هي: كما لاحظتَ، جميع البيانات التي وصَّفناها موجودةٌ هنا. لاحظ كيف تُظهِر أداة اختبار البيانات الهيكلية تشعّب أنواع الاصطلاحات؛ هذا التمثيل الرسومي سيساعدك كثيرًا على تخيّل الوضع. هذا رسمٌ توضيحيٌ للطريقة المتوقعة لتمثيل الصفحة في نتائج بحث Google (أكرر مرةً أخرى أنَّ هذا مجرد مثال، فقد يُغيّر Google طريقة تنسيق نتائج البحث في أيّ وقت، ولا توجد هنالك ضمانة أنَّ Google ستُفسِّر البيانات الوصفية في صفحتك من الأساس!). بعد عنوان الصفحة والمُقتطف المولَّد تلقائيًا، سيبدأ محرك Google باستعمال البيانات الوصفية التي أضفناها إلى الصفحة لعرض جدول المحتويات. لاحظ طريقة تنسيق التاريخ «Fri, Nov 6». لم ترد هذه السلسلة النصية في أيّ مكانٍ في عناصر HTML أو خاصيات البيانات الوصفية. لكننا استخدمنا سلسلتين نصيتين هما التاريخ بصيغةٍ معياريةٍ (‎2009-11-06T08:30+01:00 و ‎2009-11-06T20:30+01:00). أخذ Google هاذين التاريخين وعَرِفَ أنَّهما في نفس اليوم، وقرر عرض تاريخٍ وحيدٍ بصيغةٍ قراءتها أسهل. انظر الآن إلى العنوان الفيزيائي. اختار Google أن يعرض اسم المكان + البلدة (locality) + الدولة، لكنه لم يعرض عنوان الشارع المُفصَّل. أصبح هذا ممكنًا لأننا قسّمنا العنوان إلى خمسِ خاصياتٍ مستقلةً –streetAddress وaddressLocality و addressRegion و postalCode و addressCountry– استفاد Google من هذا لعرض عنوانٍ مختصرٍ للمكان. قد يختار مستهلكونَ آخرونَ للمعلوماتِ التي توفرُها عبر البيانات الوصفية استخدامها بطرائقَ مختلفةٍ، إذ سيقررون ما الذي سيَعرضوه وما الذي لن يعرضوه. لا يوجد خيارٌ صائب وخيارٌ خاطئ هنا. عليكَ أن توفرَ أكبرَ قدرٍ من البيانات الهيكلية، وبأكبر دقةٍ ممكنةٍ، واترك الباقي على الآخرين ليفسروه كيفما يشاؤون. توصيف المراجعات هذا مثالٌ آخر عن كيفية جعل الويب (وربما نتائج البحث) أفضل عبر استخدام البيانات الوصفية: مراجعات (reviews) الشركات والمنتجات. هذه مراجعةٌ قصيرةٌ كتبتُها لأحد مطاعم البيتزا المُفضَّلة لدي (بالمناسبة، هذا المطعم حقيقي). لننظر أولًا إلى الشيفرة الأصلية قبل إضافة البيانات الوصفية: <article> <h1>Anna’s Pizzeria</h1> <p>★★★★☆ (4 stars out of 5)</p> <p>New York-style pizza right in historic downtown Apex</p> <p> Food is top-notch. Atmosphere is just right for a “neighborhood pizza joint.” The restaurant itself is a bit cramped; if you’re overweight, you may have difficulty getting in and out of your seat and navigating between other tables. Used to give free garlic knots when you sat down; now they give you plain bread and you have to pay for the good stuff. Overall, it’s a winner. </p> <p> 100 North Salem Street<br> Apex, NC 27502<br> USA </p> <p>— reviewed by Mark Pilgrim, last updated March 31, 2010</p> </article> هذه المراجعة موجودةٌ ضمن عنصر <article>، الذي علينا وضع خاصيتي itemtype و itemscope فيه. رابط URL لمجال أسماء نوع الاصطلاحات الذي سنستعمل هو http://schema.org/Review. <article itemscope itemtype="http://schema.org/Review"> ما هي الخاصيات الموجودة في نوع الاصطلاحات Review؟ أنا ممتنٌ لسؤالك. الخاصية الشرح itemReviewed اسم العنصر الذي ستتم مراجعته. يمكن أن يكون منتجًا أو خدمةً أو شركةً… إلخ. reviewRating التقييم الذي أعطاه المُراجِع للعنصر. يُمثَّل بنوع الاصطلاحات Rating ‏(http://schema.org/Rating) author اسم الشخص الذي كتب المراجعة datePublished تاريخ نشر المراجعة بصيغة ISO name عنوان مختصر للمراجعة reviewBody الوصف الذي كتبه المُراجِع للعنصر أولُ خاصيةٍ بسيطةٌ: itemReviewed هي نصٌ عاديٌ، وقيمتها موجودةٌ في عنصر <h1>، لذا سنضع تلك الخاصية في ذاك العنصر. <h1 itemprop="itemReviewed">Anna’s Pizzeria</h1> سأتجاوز حاليًا قسم التقييم وسأعود إليه لاحقًا في النهاية. الخاصيتان التاليتان سهلتان وبسيطتان. خاصية name هي عنوانٌ مختصرٌ للمراجعة، وخاصية reviewBody هي النص الذي كتبه المراجع لوصف العنصر المُراجَع. <p itemprop="name">New York-style pizza right in historic downtown Apex</p> <p itemprop="reviewBody"> Food is top-notch. Atmosphere is just right for a “neighborhood pizza joint.” The restaurant itself is a bit cramped; if you’re overweight, you may have difficulty getting in and out of your seat and navigating between other tables. Used to give free garlic knots when you sat down; now they give you plain bread and you have to pay for the good stuff. Overall, it’s a winner. </p> يجب أن يكون توصيف العنوان وإحداثيات الموقع الجغرافي مألوفًا لديك الآن (إذا لم تكن تتابع معنا منذ بداية هذه الدروس، فراجع آلية توصيف عنوان شخص، وآلية توصيف عنوان منظمة، وآلية توصيف إحداثيات الموقع الجغرافي من الدروس السابقة). <p itemprop="contentLocation" itemscope itemtype="http://schema.org/Place"> <span itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> <span itemprop="streetAddress">100 North Salem Street</span><br> <span itemprop="addressLocality">Apex</span>, <span itemprop="addressRegion">NC</span> <span itemprop="postalCode">27502</span><br> <span itemprop="addressCountry">USA</span> </span> <span itemprop="geo" itemscope itemtype="http://schema.org/GeoCoordinates"> <meta itemprop="latitude" content="50.047893" /> <meta itemprop="longitude" content="14.4491" /> </span> </p> في آخر سطرٍ مشكلةٌ شائعة: يحتوي على معلومتين في عنصرٍ وحيدٍ. اسم الشخص الذي كتب المراجعة هو Mark Pilgrim، وتاريخ المراجعة هو March 31, 2010. كيف نستطيع توصيف هاتين الخاصيتين المستقلتين؟ ضعهما في عنصرَين منفصلين وضع خاصية itemprop في كلٍ عنصرٍ. يجدر بنا في هذا المثال أن نضع التاريخ في عنصر <time>، لكي يوفر لنا طريقةً سليمةً لوضع خاصية itemprop المناسبة. لكن اسم المُراجِع يمكن أن يُحتوى في عنصرٍ ليس له قيمةٌ دلاليةٌ مثل <span>. <p>— <span itemprop="author">Mark Pilgrim</span>, last updated <time itemprop="datePublished" datetime="2010-03-31"> March 31, 2010 </time> </p> </article> حسنًا، لنتحدث عن التقييمات. أصعب جزء في توصيف المراجعة هو التقييم. افتراضيًا، التقييمات في نوع الاصطلاحات Rating تكون على مقياس من 1 إلى 5، حيث 1 هو «سيء جدًا» و5 هو «رائع». لكنك ما تزال قادرًا على توصيف التقييم إذا كنت تستخدم مقياسًا مختلفًا، لكن دعنا نناقش المقياس الافتراضي أولًا. <p>★★★★☆ (<span itemprop="reviewRating">4</span> stars out of 5)</p> إذا كنتَ تستعمل مقياسًا افتراضيًا من 1 إلى 5، فالخاصية الوحيدة التي عليك توصيفها هي التقييم نفسه (4 في مثالنا). لكن ماذا لو كنتَ تستعمل مقياسًا مختلفًا؟ كل ما عليك فعله هو التصريح عن حدود المقياس الذي تستعمل. على سبيل المثال، إذا أردت أن تستعمل مقياسًا من 0 إلى 10، فما يزال عليك التصريح عن خاصية itemprop=”rating”‎ لكن بدلًا من إعطاء قيمة التقييم مباشرةً، سيتوجب عليك استخدام نوع http://schema.org/Rating لتعيين أقل قيمة وأعلى قيمة في مقياسك المُخصَّص بالإضافة إلى تحديد قيمةٍ للتقييم الفعلي ضمن ذاك المقياس. <p itemprop="reviewRating" itemscope itemtype="http://schema.org/Rating"> ★★★★★★★★★☆ (<span itemprop="ratingValue">9</span> on a scale of <span itemprop="worstRating">0</span> to <span itemprop="bestRating">10</span>) </p> بالعربية: «هذا المنتج الذي أكتب مراجعةً عنه يملك تقييمًا قيمته 9 في مقياسٍ من 0 إلى 10». هل ذكرتُ لك أنَّ البيانات الوصفية المُضافة للمراجعات قد تؤثر على نتائج البحث؟ هذه هي البيانات التي تستيطع أداة اختبار البيانات المنظّمة استخلاصها من مراجعتنا: وهذه صورةٌ توضيحيةٌ لكيف يمكن أن تبدو المراجعة عند عرضها في نتائج البحث: أليس هذا مُبهرًا؟ مصادر إضافية مصادر عن البيانات الوصفية (Microdata): مواصفة Microdata في HTML5 صفحة Microdata في ويكيبيديا مصادر عن المقتطفات المنسقة: صفحة Rich Snippets أداة اختبار البيانات المنظمة موقع schema.org الذي يحتوي معلوماتٍ عن أنواع الاصطلاحات المختلفة التي ذكرناها في هذا دروس هذه السلسلة. ترجمة -وبتصرّف- لفصل «Microdata» من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال السابق: توصيف المنظمات/الشركات باستخدام microdata في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  7. البيانات الوصفية ليست محدودةً لنوع اصطلاحاتٍ وحيد. صفحة «About» التي أنشأناها في الدرس السابق جيدة، لكن من المرجح أنَّ لديك صفحةً واحدةً اسمها «About»، ولكنك متعطشٌ للمزيد؟ لنتعلم كيف نوصِّف المنظمات والشركات. هذه نموذجٌ لصفحة شركةٍ ما، لنلقِ نظرةً على شيفرة HTML دون البيانات الوصفية. <article> <h1>Google, Inc.</h1> <p> 1600 Amphitheatre Parkway<br> Mountain View, CA 94043<br> USA </p> <p>650-253-0000</p> <p><a href="http://www.google.com/">Google.com</a></p> </article> وصفٌ قصيرٌ ومنظَّم، فجميع المعلومات حول هذه المنظمة موجودةٌ ضمن عنصر <article>، فلنبدأ هناك. <article itemscope itemtype="http://schema.org/Organization"> وكما فعلنا عند توصيف الأشخاص في الدرس السابق، سنحتاج إلى ضبط الخاصيتين itemscope و itemtype في العنصر الحاوي لبقية العناصر، وهو في حالتنا العنصر <article>. خاصية itemtype تُصرِّح ما هو نوع الاصطلاحات التي تستعملها (في هذه الحالة http://schema.org/Organization)، وخاصية itemscope تُصرِّح أنَّ كل الخاصيات التي تضبطها للعناصر الأبناء للعنصر الحالي ترتبط بنوع الاصطلاحات هذا. إذًا، ماذا يوجد في نوع الاصطلاحات Organization؟ بضع خاصياتٍ بسيطة، التي يكون بعضها مألوفًا لديك. الخاصية الشرح name اسم المنظمة (على سبيل المثال: «Initech») url رابط URL لصفحة ويب، مثل الصفحة الرئيسية للمنظمة address العنوان الفيزيائي للمنظمة، يمكن أن يحتوي على خاصيات أخرى مثل streetAddress وaddressLocality و addressRegion و postalCode و addressCountry telephone رقم هاتف المنظمة geo تحديد الإحداثيات الجغرافية لموقع المنظمة، ويملك خاصيتين دائمًا: latitude و longitude أول قطعة من الشيفرات في عنصر <article> هي <h1>. يحتوي عنصر <h1> على اسم الشركة، ولهذا سأضيف خاصية itemprop="name"‎ إليه مباشرةً. <h1 itemprop="name">Google, Inc.</h1> ووفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، عناصر <h1> ليس لها معالجة خاصة، وستكون قيمة خاصية البيانات الوصفية هي المحتوى النصي للعنصر. أي أننا قلنا بالعربية: «اسم المنظمة هو "Google, inc.‎"». المعلومة التالية التي نريد توصيفها هي العنوان. توصيف عنوان المنظمة مماثل تمامًا لتوصيف عنوان شخصٍ ما. أضف أولًا خاصية itemprop="address"‎ إلى العنصر الذي يحتوي على العنوان (العنصر <p> في هذه الحالة). وهذا يُصرِّح أنَّ هذه هي خاصية address للمنظمة، لكن ماذا عن خاصيات العنوان نفسه؟ سنحتاج إلى الخاصيتين itemtype و itemscope لكي نقول أنَّ عنصر العنوان هذا له خاصياتٌ تابعة له. <p itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> في النهاية، علينا وضع كل قطعة من المعلومات في عنصر <span> لكي نتمكن من وضع الخاصية الملائمة (streetAddress وaddressLocality و addressRegion و postalCode و addressCountry) في كل عنصر من تلك العناصر. <p itemprop="address" itemscope itemtype="http://schema.org/PostalAddress"> <span itemprop="streetAddress">1600 Amphitheatre Parkway</span><br> <span itemprop="addressLocality">Mountain View</span>, <span itemprop="addressRegion">CA</span> <span itemprop="postalCode">94043</span><br> <span itemprop="addressCountry">USA</span> </p> بالعربية: «هذه المنظمة تملك عنوانًا بريديًا. اسم الشارع لذاك العنوان البريدي هو "1600 Amphitheatre Parkway"، أما البلدة (locality) فهي "Mountain View"، والإقليم (region) هو "CA"، والرمز البريدي (postal code) هو "94043"، واسم الدولة هو "USA"». الخطوة التالية هي توصيف رقم الهاتف للمنظمة، بُنيةُ أرقامِ الهواتفِ معقدةٌ بعض الشيء، والصيغة المُحدَّدة لها خاصةٌ بكل دولة (والأمر أسوأ إذا أردت الاتصال بدولةٍ أخرى). لدينا في مثالنا رقمٌ هاتفيٌ من الولايات المتحدة، وهو مكتوبٌ بصيغةٍ ملائمةٍ للاتصال من أي مكان داخل الولايات المتحدة. <p itemprop="telephone">650-253-0000</p> (في حال لم تنتبه، لقد خرج نوع الاصطلاحات Address من المجال [scope] عندما أُغلِق العنصر <p>. لقد عدنا الآن إلى تعريف الخاصيات لنوع الاصطلاحات Organization.) إذا كنت تريد وضع أكثر من رقم هاتف –ربما واحد للزبائن في الولايات المتحدة وآخر للزبائن من بقية دول العالم– فيمكنك فعل ذلك. إذ يمكن تكرار أي خاصية من خاصيات البيانات الوصفية أكثر من مرة. كل ما عليك فعله هو التأكد أنَّ لكل رقمٍ عنصرُ HTML خاصٌ به مفصولٌ عن أيّة لافتة وضعتَها له. <p> US customers: <span itemprop="telephone">650-253-0000</span><br> UK customers: <span itemprop="telephone">00 + 1* + 6502530000</span> </p> ووفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، عناصر <p> أو عناصر <span> ليس لها معالجةٌ خاصةٌ، وستكون قيمة خاصية telephone هي المحتوى النصي للعنصر. لا يحاول نوع الاصطلاحات Organization أن يُقسِّم ويُصنِّف مختلف أجزاء رقم الهاتف، فخاصية telephone هي نصٌ عاديٌ. حيث تستطيع وضع رمز المنطقة بين قوسين، أو أن تستعمل الفراغات بدلًا من الشرطات للفصل بين الأرقام. وإذا حاول العميل الذي يُفسِّر البيانات الوصفية أن يُفسِّر رقم الهاتف، فآلية ذلك عائدةٌ تمامًا إليه. لدينا بعد ذلك خاصيةٌ نألفها: url. ومثل روابط URL المتعلقة بشخصٍ ما، يمكن لروابط URL أن تتعلق بمنظمة. وقد تكون هذه الروابط هي الصفحة الرئيسية للشركة، أو صفحة التواصل، أو صفحة المنتجات، أو أيّة صفحة أخرى. بعبارةٍ أخرى، إذا كان لديك رابط URL عن أو من أو يتعلق بالمنظمة، فوصِّفه بخاصية itemprop="url"‎. <p><a itemprop="url" href="http://www.google.com/">Google.com</a></p> ووفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، عنصر <a> له معالجةٌ خاصةٌ، فقيمة خاصية البيانات الوصفية هي قيمة الخاصية href، وليس المحتوى النصي للرابط. بالعربية: «لدى هذه المنظمة رابط URL الآتي http://www.google.com/‎». لكن الخاصية لا تُحدِّد مزيدًا من المعلومات عن هذا الارتباط، ولا تُضمِّن السلسلة النصية للرابط Google.com». في النهاية، أريد الحديث عن الموقع الجغرافي. لا أقصد هنا الواجهة البرمجية لتحديد الموقع الجغرافي؛ وإنما أقصد كيفية توصيف الموقع الجغرافي للمنظمات باستخدام البيانات الوصفية. لحد هذه اللحظة، كل الأمثلة التي رأيناها ركَّزَت على توصيف البيانات المرئية. بعبارةٍ أخرى، لديك عنصر <h1> فيه اسم الشركة، لذا ستُضيف خاصية itemprop إلى عنصر <h1> لتُصرِّح أنَّ النص (المرئي) الموجود في تلك الترويسة هو اسم المنظمة. أو ربما لديك عنصر <img> الذي يُشير إلى صورةٍ ما، وتستطيع إضافة الخاصية itemprop لعنصر <img> لكي تُصرِّح أنَّ تلك الصورة (المرئية) هي صورةٌ لشخصٍ ما. أما في هذا المثال، لن تكون معلومات الموقع الجغرافي على هذا النحو؛ فلا يوجد نصٌ مرئيٌ يُعطي إحداثيات الطول والعرض (بدقة أربعة أرقام عشرية!) لموقع المنظمة. وفي الواقع، الصفحة التي نعمل عليها لا تحتوي على معلومات عن الموقع الجغرافي إطلاقًا. وحتى لو وُجِدَ رابطٌ إلى خرائط Google، لكنه لا يحتوي على إحداثيات الطول والعرض (يحتوي على معلومات مشابهة في صيغةٍ خاصةٍ بخرائط Google). لكن حتى لو افترضنا وجود رابط URL لإحدى خدمات الخرائط التي تضع إحداثيات خطوط الطول والعرض كوسائط في رابط URL، فلا توجد طريقة في البيانات الوصفية لفصل أجزاء URL عن بعضها؛ فلا تستطيع أن تقول أنَّ «أول وسيطٍ في طلبية URL هو إحداثيات الطول والوسيط الثاني هو إحداثيات العرض وبقية وسائط الطلبية غير مهمة بالنسبة إلينا». توفِّر HTML5 طريقةً لتوصيف البيانات غير المرئية للتعامل مع الحالات الخاصة مثل هذه الحالة. لا تَستعمل هذه التقنية إلا كملاذٍ أخيرٍ لك. فلو كانت هنالك طريقةٌ لعرض البيانات التي تريد توصيفها، فافعل ذلك. أرى أنَّ البيانات المخفية التي تستطيع «الآلات» قراءتها فقط «ستتعفن» بسرعة. وهذا يعني أنَّ أحدهم سيأتي عاجلًا أم آجلًا وسيُحدِّث النص المرئي الموجود في الصفحة لكنه سينسى تحديث البيانات غير المرئية. هذا يحدث بوتيرة كبيرة أكثر مما تتوقع، وأنا واثقٌ أنَّه سيحدث لك أيضًا. لكن مع هذا، هنالك وضع معلومات للموقع الجغرافي لكنه لا يريد أن تعم الفوضى في الواجهة بوجود زوجين من الأرقام غير المفهومة ذات ست خانات. الخيار الوحيد الذي أمامك هنا هو البيانات المخفية. كل ما تستطيع فعله هنا هو وضع البيانات المخفية بعد النص المرئي الذي يصفها مباشرةً، لعل ذلك يُذكِّر الشخص الذي أتى ليحدث النص المرئي لكي يُحدِّث البيانات المخفية بعده مباشرةً. في هذا المثال، أنشأنا عنصر <span> ضمن عنصر <article> الذي يحوي بقية خاصيات المنظمة، ثم وضعنا بيانات الموقع الجغرافي المخفية داخل عنصر <span> (هذه هي الطريقة التنظيمية لنوع الاصطلاحات Organization، راجع صفحة http://schema.org/Organization لمزيدٍ من المعلومات). <span itemprop="areaServed" itemscope itemtype="http://schema.org/Place"> <span itemprop="geo" itemscope itemtype="http://schema.org/GeoCoordinates"> <meta itemprop="latitude" content="37.4149" /> <meta itemprop="longitude" content="-122.078" /> </span> </span> </article> معلومات الموقع الجغرافي مُعرَّفةٌ في نوع الاصطلاحات الخاص بها، مثل «عنوان» (Address) الشخص أو المنظمة، وبالتالي، سيحتاج عنصر <span> إلى ثلاث خاصيات: itemprop="geo"‎ يقول أنَّ هذا العنصر يُمثِّل خاصية geo للمنظمة التي يتبع لها itemtype="http://schema.org/GeoCoordinates"‎ يُحدِّد أيُّ نوعِ اصطلاحاتٍ ستخضع له خاصيات هذا العنصر itemscope يقول أنَّ هذا العنصر هو عنصرٌ حاوٍ يملك نوع اصطلاحات خاص به (مُحدَّدٌ في خاصية itemtype). جميع الخاصيات الموجودة ضمن هذا العنصر هي خاصياتٌ لنوع الاصطلاحات http://schema.org/GeoCoordinates، وليس لنوع الاصطلاحات للعنصر الرئيسي http://schema.org/Organization السؤال المهم في هذا المثال هو: «كيف تستطيع توصيف البيانات غير المرئية؟» يمكنك استخدام العنصر <meta>. لم تكن في السابق تستطيع استخدام العنصر <meta> إلا داخل ترويسة صفحتك؛ أما في HTML5، فتستطيع استخدام العنصر <meta> في أيِّ مكانٍ، وهذا ما سنفعله تمامًا. <meta itemprop="latitude" content="37.4149" /> ووفقًا للنموذج الهيكلي للبيانات الوصفية في HTML5، عنصر <meta> له معالجةٌ خاصة، فقيمة خاصية البيانات الوصفية هي قيمة الخاصية content؛ ولأن هذه الخاصية لا تُعرَض أبدًا، فهي مثاليةٌ لضبط عددٍ غيرِ محدودٍ من البيانات المخفية. لكن ستزداد المسؤولية الملقاة على عاتقك هنا، حيث عليك التأكد أنَّ المعلومات المخفية ستبقى متوافقةً مع ما حولها من النص المرئي. لا يوجد دعمٌ مباشرٌ لنوع الاصطلاحات Organization في المُقتطفات المنسقة في Google، لذا لا أملك نتيجةَ بحثٍ جميلة لعرضها لك. لكن لها تأثيرٌ على الأحداث (events) والمراجعات (reviews)، اللتان تدعمهما المقتطفات المنسقة في Google (تقبل بعض خاصياتهما أن يكون نوع الخاصية Organization). الشكل 1: معلومات البيانات الوصفية كما تُظهِرها أداة اختبار البيانات المنظّمة ترجمة -وبتصرّف- لجزء من فصل «Microdata» من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: كيفية توصيف الأحداث والمراجعات باستخدام microdata على HTML5 المقال السابق: توصيف الأشخاص باستخدام metadata في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  8. إن كنت قد قرأت المقال السّابق الذي يشرح ماهية 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. اقرأ أيضًا المقال التالي: توصيف المنظمات/الشركات باستخدام microdata في HTML5 المقال السابق: مدخل إلى البيانات الوصفية (microdata) في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  9. إذا كنتَ من مطوري ووردبريس، فستُتحمّس للاستفادة من ميزة تعدد المواقع (Multisite)، ولهذا السبب قدمنا لك هذه الدورة التدريبية المؤلفة من عشر مقالات كي تحترف هذه الميزة المفيدة. ميزة تعدد المواقع لها فائدة كبيرةٌ في أنها ستساعدك على إنشاء شبكة من المواقع لتلبية مختلف الاحتياجات ولمختلف الأغراض، والتي تستطيع تخصيصها لتسهيل بعض الأمور لمستخدميك ولجعل شبكتك من المواقع تعمل بكفاءة عالية… ستتعلم في هذه الدورة كل شيء ستحتاج له كي تُنشِئ شبكتك الخاصة، وتضيف المواقع إليها أو تسمح للمستخدمين بإضافة مواقع إلى الشبكة وإدارتها. وستتعلم كيف تحافظ على أمان شبكتك وتضمن أداءها أداءً عاليًا وكيف تُنشِئ مجتمعًا ناجحًا من المستخدمين والمواقع. سنبدأ بأخذ فكرة عامة عن ميزة تعدد المواقع في هذا الجزء، وسأشرح لك فيه: تمهيد إلى تعدد المواقع: ما يمكنك أو لا يمكنك فعله ميزات الشبكات متعددة المواقع الفرق بين شبكة مُنشَأة عبر تعدد المواقع، وبين المواقع المفردة حالات استخدام تعدد المواقع لنبدأ جولتنا! مدخل إلى تعدد المواقع في ووردبريس خاصية تعدد المواقع هي جزءٌ من أساس ووردبريس، إلا أنها كانت نظامًا منفصلًا يدعى «WPMU» الذي دُمِجَ مع أساس ووردبريس في الإصدار 3.0. هذا يعني أنَّك تستطيع تفعيل تعدد المواقع في أي موقع يستخدم ووردبريس بكل سهولة، حتى لو كان موقعك يعمل منذ أشهر أو سنوات. لكن هنالك اختلافٌ أساسيٌ بين إضافة ميزة تعدد المواقع على موقعٍ موجودٍ مسبقًا وبين إضافته على موقعٍ جديد؛ وهذا الاختلاف مرتبط بالنطاقات الفرعية والمجلدات الفرعية. سنشرح ذلك بالتفصيل لاحقًا عندما نلقي نظرةً على البنية الهيكلية لشبكة متعددة المواقع. تسمح ميزة تعدد المواقع لك بإنشاء شبكة من المواقع، وهذا يعني أنَّ هنالك تثبيت ووردبريس وحيد، وتستطيع عبره أن تحصل على أي عدد تريده من المواقع. أنا أعي ما أقول، تستطيع أن تحصل على أي عدد من المواقع! فموقع WordPress.com هو شبكةٌ متعددة المواقع يُشغِّل حوالي 10% من مواقع الويب، وموقع Edublogs هو شبكةٌ متعددة المواقع الذي يُشغِّل أكثر من ثلاث ملايين مدونة تعليمية. ربما لديك تصور أنَّ هنالك بعض الإجراءات الإضافية عندما يأتي الأمر إلى إدارة مثل هذه الشبكات الضخمة، مثل توزيع وسائط التخزين على أكثر من خادوم؛ لكن الفكرة الأساسية هي استخدام ووردبريس كشبكة متعددة المواقع. لن تتعلم في هذه الدورة التدريبية كيف تُنشِئ وتدير شبكات ضخمة مثل Edubolgs –هذا يحتاج إلى أعوام من الخبرة والتجربة– لكنك ستتعلم كيف تُنشِئ شبكتك الخاصة من المواقع التي يمكنك استخدامها لأغراضٍ عدّة. هذا هو تعريف «تعدد المواقع» من دليل ووردبريس للمطورين: حسنًا، الشبكة متعددة المواقع هي ميزة من ميزات ووردبريس التي تسمح لك بإنشاء شبكة خاصة بك من المواقع، ويمكن أن يُدار كل موقع من تلك المواقع بشكلٍ منفصل، وأن يشير إليه نطاقٌ خاصٌ به لكي يستطيع الزوار الوصول إليه، ولن يستطيعوا أن يميّزوا أنَّ موقعك جزءٌ من شبكةٍ ما من المواقع. ستُسهِّل هذه الميزة الأمر عليك كثيرًا إذا كنت تدير عدِّة مواقع ولم تكن تريد أن تُتُعِبَ نفسك بمراقبة كل موقع على حدة، بما في ذلك تحديث الإضافات، والتعامل مع المشاكل الأمنية والمشاكل في الأداء، وإضافة المستخدمين …إلخ. هذه الميزة مفيدة للغاية إن كنت تريد أن تسمح لمستخدميك أن يتواصلوا مع بعضهم بعضًا عبر مجتمع (community) الذي تستطيع استضافته على شبكتك. ميزات تعدد المواقع هنالك عدّة ميزات لتعدد المواقع في ووردبريس مقارنةً مع إدارة عدد من المواقع لها أكثر من تثبيت ووردبريس: مساحة التخزين. هنالك نسخةٌ واحدةٌ فقط من ملفات ووردبريس ونسخةٌ واحدةٌ أيضًا من كل قالب أو إضافة مُخزَّنة على خادومك. التحديثات. ستحتاج إلى تحديث تثبيت ووردبريس والقوالب والإضافات مرةً واحدةً فقط بدلًا من فعل ذلك لكل موقعٍ على حدة (لكن عليك تجربة التحديث على موقع تجريبي أولًا!). المجتمع. يمكنك استخدام شبكة متعددة المواقع لإنشاء مجتمع من المواقع والمستخدمين. الخيارات المتاحة إليك تتضمن نشر المحتوى على أكثر من موقع في آنٍ واحد، والسماح للمستخدمين بمتابعة (follow) بعضهم بعضًا، والمزيد… سأشرح هذه النقطة بالتفصيل في هذه الدورة. إنشاء المواقع. تُمكِّنك ميزة تعدد المواقع من السماح للمستخدمين بإنشاء مواقع خاصة بهم في شبكتك. وهذه الميزة رائعة إذا كنت تدير شبكةً خاصةً بالمدرسة أو لنادٍ معيّن، أو إذا أردت أن تتلقى مقابلًا ماديًا لقاء إنشاء مواقع للآخرين. هنالك جزءٌ يتحدث عن إنشاء المواقع في هذه الدورة. الاختلافات الأساسية بين شبكة من المواقع والمواقع المفردة قبل أن تبدأ بالعمل على ميزة تعدد المواقع، يجب أن أنوِّه إلى بعض الاختلافات الرئيسية بينها وبين طريقة تثبيت ووردبريس الاعتيادية (أدعوها «المواقع المفردة» [standalone sites]). من الجيد أن تفهم هذه الاختلافات أولًا، لأنها ستساعدك في اتخاذ قرارك فيما إذا كانت شبكةٌ من المواقع المتعددة هي الأداة المثالية لك، وستساعدك أيضًا عندما تبدأ بإنشاء وإدارة شبكتك. لننظر إلى بعض هذه الاختلافات. ملاحظة عن الاصطلاحات: إذا كنت تستخدم مواقع ووردبريس المفردة من قبل، فستظن أنَّ «التثبيت» (installation) هو موقعك. لكنني سأستخدم هذا المصطلح في هذه الدورة بطريقةٍ مختلفة. الشبكة تمثل تثبيت ووردبريس، التي تتضمن عددًا من المواقع. والموقع هو أحد المواقع في الشبكة، وكانت تسمى تلك المواقع في الإصدارات القديمة من ميزة تعدد المواقع بالمدونات (blogs)، لكن ذلك تغيّر في إصدار 3.5 من ووردبريس. إدارة الشبكة عندما تُنشِئ شبكةً متعددة المواقع، فستصبح «مدير الشبكة» (super admin)، وهذا يعني أنَّ لديك القدرة على إدارة الشبكة بالإضافة إلى إدارة المواقع الموجودة فيها كُلًّا على حدة. عندما تُفعِّل الشبكة، فستشاهد بعض العناصر الإضافية في لوحة التحكم، وهي ظاهرةٌ في لقطة الشاشة الآتية: عناصر القائمة الإضافية هي: عنصر قائمة جديد اسمه «My Sites» (مواقعي) رابط «My Sites» (مواقعي) في شريط الإدارة العلوي الذي سيأخذك إلى صفحات إدارة الشبكة. صفحات إدارة الشبكة هي المكان الذي تُثبِّت وتُفعِّل فيه القوالب والإضافات وإنشاء وإدارة المواقع والمستخدمين: تتضمن قائمة إدارة الشبكة ستة أقسام: Dashboard (الرئيسية) – إدارة التحديثات والترقيات على الشبكة. Sites (المواقع) – إنشاء مواقع في الشبكة وإدارة المستخدمين والقوالب وضبط الخيارات لكلٍ منها. Users (أعضاء) – إضافة مستخدمين إلى شبكتك بنفس الطريقة التي كنتَ تُضيفهم فيها إلى المواقع المفردة. Themes (قوالب) – تثبيت القوالب، ومن ثم ستتمكن من تفعيلها لكامل الشبكة أو لمواقع معيّنة على الشبكة. Plugins (إضافات) – تثبيت الإضافات وتفعيلها على كامل الشبكة إن شئت. قد تتمكن من تفعيل بعض الإضافات لكامل الشبكة فقط، مثل إضافات النسخ الاحتياطي؛ بينما يمكن لبعضها الآخر أن يُفعَّل لكامل الشبكة إن شئت أن تعمل تلك الإضافة على جميع المواقع، أو يمكن لمدراء المواقع أن يختاروا ما الذي يريدون تفعيله على موقعهم. Settings (الإعدادات) – إدارة ضبط الشبكة. هذه هي بعض جوانب إدارة الشبكة التي قد تُحيّر بعض المبتدئين: الموقع الأساسي أو الرئيسي (Base site). عندما تُنشِئ شبكةً، فسيكون هنالك موقعٌ واحدٌ على الأقل لتبدأ معه ألا وهو «الموقع الرئيسي». هذا هو الموقع الذي بدأت فيه عبر تثبيت ووردبريس، وسيكون رابط URL الأساسي هو اسم النطاق (domain name). ستتمكن –كمدير للشبكة– أن تكون مديرًا لهذا الموقع أيضًا، ويمكنك إضافة مستخدمين آخرين بنفس طريقة إضافة المستخدمين للمواقع المفردة؛ لكن لا يمكنك إزالة هذا الموقع من الشبكة. تستطيع أيضًا أن تُفعِّل القوالب أو الإضافات كما في أي موقع آخر على الشبكة. تفعيل القوالب والإضافات. قد تتوقع أنك تستطيع تفعيل القوالب والإضافات بنفس الطريقة التي تعرفها. يمكنك تثبيت القوالب والإضافات عبر لوحة تحكم مدير الشبكة، لكن ستختلف الأمور عمّا اعتدت عليه بدءًا من هذه المرحلة. يمكنك أن تُفعِّل الإضافات على كامل مواقع الشبكة، أو ألّا تفعَّلها أبدًا، وفي هذه الحالة سيحتاج مدير الموقع إلى تفعيل الإضافات في صفحة الإضافات في لوحة تحكم الموقع. أما القوالب، فيمكنك أن تُمكِّن اختيارها عبر الشبكة، مما يُتيح لمدراء المواقع تفعيلها على موقعهم، أو يمكنك تمكين القوالب لمواقع معيّنة وذلك عبر صفحة «Sites» (المواقع) في لوحة التحكم، مما يعني أنَّ تلك المواقع هي المواقع التي تستطيع تفعيل القالب فقط ولن تتمكن بقية المواقع من رؤية القالب؛ قد تستفيد من هذه الميزة إن كنت تُدير شبكةً من مواقع العملاء ولدى كل موقع قالب خاصٌ به. إنشاء المستخدمين. يمكن لمدير الشبكة أو لمدراء المواقع إضافة المستخدمين؛ إذا أضفتُ مستخدمًا عبر لوحة تحكم مدير الشبكة، فسيُنشَأ حساب للمستخدم على الشبكة لكن دون أيّة امتيازات على المواقع الأخرى (بما في ذلك الموقع الرئيسي)؛ أما لو أضاف مدير موقعٍ ما مستخدمًا، فسيحصل المستخدم على وصول إلى ذاك الموقع فقط، لكن سيكون بإمكان بقية مدراء المواقع من إضافة المستخدم إلى مواقعهم أيضًا. التحديثات. عندما تُحدِّث قالبًا أو إضافةً أو نسخة ووردبريس نفسها، فعليك القيام بذلك من لوحة تحكم مدير الشبكة وستُطبَّق التغيرات على جميع المواقع في شبكتك. وهذا هو السبب وراء ضرورة اختبار أيّة تحديثات على موقعٍ تطويري قبل تطبيقها على شبكةٍ إنتاجية، فلو أدى تحديث إضافةٍ ما إلى حدوث خلل، فسيؤثر على جميع مواقع الشبكة التي تستعمل تلك الإضافة! يمكنك تثبيت إضافة تساعدك في التعرف على المواقع التي تستعمل قالبًا أو إضافةً معينة، وسنشرح ذلك بالتفصيل لاحقًا في هذه السلسلة. هنالك إمكانيات (capabilities) إضافية لمدير الشبكة مقارنةً بمدير الموقع: إمكانيات متعلقة بمدير الشبكة (أي superadmin): manage_network، و manage_sites، و manage_network_users، و manage_network_plugins، و manage_network_themes، و manage_network_options. إمكانيات كان يملكها مدير الموقع إن كان الموقع منفردًا ولكنها أصبحت متعلقة بمدير الشبكة في شبكة من المواقع: update_core، و update_plugins، و update_themes، و install_plugins، و install_themes، و delete_themes، و delete_plugins، و edit_plugins، و edit_themes، و edit_files، و edit_users، و create_users، و delete_users، و unfiltered_html. إمكانيات يملكها مدير الشبكة ومدير الموقع: activate_plugins، و delete_others_pages، و delete_others_posts، و delete_pages، و delete_posts، و delete_private_pages، و delete_private_posts، و delete_published_pages، و delete_published_posts، و edit_dashboard، و edit_others_pages، و edit_others_posts، و edit_pages، و edit_posts، و edit_private_pages، و edit_private_posts، و edit_published_pages، و edit_published_posts، و edit_theme_options، و export، و import، و list_users، و manage_categories، و manage_links، و manage_options، و moderate_comments، و promote_users، و publish_pages، و publish_posts، و read_private_pages، و read_private_posts، و read، و remove_users، و switch_themes، و upload_files. وهذا يعني أنَّ –كمدير شبكة– تستطيع أن تُعدل وتُدير المواقع الموجودة ضمن شبكتك. إدارة الموقع عندما يُنشَأ موقعٌ جديدٌ على شبكتك، فسيكون له مديرٌ خاصٌ به. فإذا أُنشِئ الموقع عبر مدير الشبكة، فسيكون هو مدير الموقع؛ أما لو أنشِئ الموقع عبر نموذج للتسجيل، فسيكون المستخدم الذي يُنشِئ الموقع هو مدير الموقع. من الممكن إضافة مدراء إضافيين لكل موقع كما لو كان موقع ووردبريس مفردًا. خصائص لوحة التحكم التي يستطيع مدراء المواقع الدخول إليها مختلفةٌ قليلًا عن المواقع المفردة. الاختلافات الجوهرية هي: تثبيت الإضافات والقوالب. لا يمكن لمدراء المواقع تثبيت إضافات أو قوالب. لكن إذا كان قالبٌ ما متاحًا على الشبكة أو متاحًا لموقعٍ معيّن، فيمكن لمدير الموقع أن يفعِّل القالب في صفحة القوالب بلوحة التحكم. وإذا أعطى مديرُ الشبكة مدراءَ المواقع امتيازاتٍ للتحكم بإدارة الإضافات، فيمكنهم تفعيل الإضافات، لكنهم لن يستطيعوا مشاهدة أيّة إضافات غير متاحة على الشبكة في صفحة الإضافات في لوحة التحكم، ولن يستطيع مدراء المواقع تحديث الإضافات أو القوالب. الضبط. هنالك بعض خيارات الضبط التي يستطيع مدير الشبكة الوصول إليها فقط؛ وهذا يتضمن الحجم الأقصى للملفات المرفوعة، وآلية تسجيل المستخدمين، وإعدادات المواقع الجديدة. لدى مدراء المواقع نفس الامتيازات وصفحات لوحة التحكم المتعلقة بإنشاء وتعديل المحتوى كما لو كان الموقع مفردًا، يمكنهم أيضًا إضافة مستخدمين جدد إلى مواقعهم، بما في ذلك المستخدمين الذي سبقَ وأن سجلوا على الشبكة، بالإضافة إلى المستخدمين الجدد. يمكن لمدراء المواقع أن يُفعِّلوا الإضافات غير المفعلة على كامل الشبكة إن سمح لهم مدير الشبكة بذلك. قد تستفيد من هذه الميزة إن كنت تسمح للأشخاص بإنشاء وإدارة مواقعهم بطريقة شبيهة بموقع edublogs أو WordPress.com. هيكلية الملفات تحتوي الشبكة متعددة المواقع على نفس ملفات ووردبريس كأي تثبيت آخر، لذا ستكون الملفات الموجودة في المجلد الجذر (أي المجلد الأساسي لووردبريس) ومجلد wp-admin ومجلد wp-includes متماثلة؛ وكذلك الأمر لملفات الإضافات والقوالب في مجلد wp-content. هذه هي طريقةٌ ذات كفاءةٍ عالية إذا كنت تُدير عدة مواقع: فبدلًا من تخزين كل قالب أو إضافة عدِّة مرات في عدة مواقع، يمكنك أن تخزن نسخة واحدة من القالب أو الإضافة على شبكة من المواقع ومن ثم تستعملها على أي عدد تريده من المواقع. الاختلاف الوحيد هو في كيفية هيكلة مجلد الملفات المرفوعة ضمن wp-content، حيث سيحتوي على مجلد uploads في المواقع المفردة الذي سيحتوي بدوره على مجلد لكل سنة، وفي ذاك المجلد عدِّة مجلدات للأشهر التي رُفعِتَ فيها الوسائط على الموقع. أما في شبكة من المواقع، فستخزن الملفات المرفوعة لكل موقع على حدة. ستُخزَّن الملفات المرفوعة للموقع الرئيسي بنفس طريقة تخزين الملفات المرفوعة للمواقع المفردة، وهذا مهم لأنك قد تفعل ميزة شبكة المواقع بعد أن أضفت محتوى ووسائط مرفوعة إلى الموقع الرئيسي، وبالتأكيد تريد أن تبقى تلك الملفات. لكن جميع المواقع الأخرى سيكون لها مجلد منفصل للوسائط المرفوعة، وذلك بإنشاء مجلد جديد باسم sites ضمن مجلد wp-content/uploads؛ وسيُنشَأ مجلد لكل موقع من مواقع الشبكة حيث يكون اسم المجلد هو مُعرِّف ID للموقع؛ إذ يملك كل موقع مُعرِّف ID رقمي خاص به يبدأ من الرقم 2. ومن ثم ستكون لتلك المجلدات نفس بنية مجلد uploads للمواقع المفردة (أي وجود مجلدات لكل سنة، ولكل شهر). هذا يعني أنك لو رفعت ملفًا اسمه media.png إلى موقع له المُعرِّف 10 ضمن شهر آذار عام 2016، فسيُخزَّن كالآتي: wp-content/uploads/sites/10/2016/03/media.png.. بنية قاعدة البيانات لدى قاعدة بيانات الشبكة متعددة المواقع بنية مختلقة عن المواقع المفردة. لدى موقع ووردبريس الاعتيادي أحد عشر جدولًا في قاعدة البيانات: wp_posts wp_postmeta wp_comments wp_commentmeta wp_links wp_term_relationships wp_term_taxonomy wp_terms wp_options wp_users wp_usermeta أما في الشبكة متعددة المواقع، فسُتفصَل جداول كل موقع. أي سيكون لديك نسخةٌ من الجداول السابقة (للموقع الرئيسي)، بالإضافة إلى نسخة لكل موقع؛ إذ سيُضاف تسعة جداول لكل موقع –أي كل الجداول ما عدا wp_users و wp_usermeta–. لا يُنسَخ جدولي المستخدمين وبياناتهم أكثر من مرة، لأن المستخدمين هم مستخدمون لكامل الشبكة وليسوا مستخدمين لموقعٍ واحدٍ فقط. أما بقية الجداول التسعة فتُنسَخ لكل موقع، مع إضافة مُعرِّف ID الموقع كسابقة لاسم الجدول، لذاك ستكون جداول موقع له المعرف 10 كالآتية: wp_10_posts wp_10_postmeta wp_10_comments wp_10_commentmeta wp_10_links wp_10_term_relationships wp_10_term_taxonomy wp_10_terms wp_10_options بالإضافة إلى ذلك، تُنشِئ ووردبريس بعض الجداول الإضافية لكي تُخزِّن فيها البيانات المتعلقة بالشبكة نفسها، تلك الجداول هي: wp_blogs wp_blog_versions wp_registration_log wp_signups wp_site wp_sitemeta wp_sitecategories (اختياري) تلك الجداول تُخزِّن البيانات المتعلقة بإعدادات الشبكة والمواقع المُنشَأة فيها. ترجمة –وبتصرّف– للمقال WordPress Multisite Masterclass: Getting Started لصاحبته Rachel McCollin.
  10. هذا هو الدرس الأول من درسين يشرحان لك كيف تطوِّر مواقع الويب ذات الاتجاه من اليمين إلى اليسار (RTL)، وهذا يعني جعل موقع الويب متوافقًا مع اللغات التي تُكتَب من اليمين إلى اليسار مثل العربية والفارسية والأردو. ماذا يعني اتجاه الكتابة من اليمين إلى اليسار؟ لتوضيح الأمور، هنالك فرقٌ بين اللغة (التي هي الشكل المحكي) والمخطوطة (script، أي طريقة الكتابة). أي تقنيًا، الاتجاه من اليمين إلى اليسار يكون للمخطوطات التي تُكتَب بها اللغات، وذلك مقارنةً مع الاتجاه من اليسار إلى اليمين في لغاتٍ مثل الإنكليزية أو الفرنسية. مصطلح «اليمين-إلى-اليسار» (Right-to-Left) أو RTL اختصارًا، يُشير إلى أكثر من مجرد المخطوطة المستعملة للكتابة؛ حيث يُشير إلى جميع الأمور المتعلقة بتطوير مواقع وتطبيقات الويب لكي تظهر بشكل سليم عند استخدام اللغة العربية أو غيرها من اللغات التي تُكتَب من اليمين إلى اليسار. قبل المتابعة، لنوضِّح بعض الالتباسات حول RTL. أولًا، RTL لا تعني ترجمة النص إلى العربية: فالأمر أكثر من مجرد ترجمة واجهة موقعك إلى العربية، وإنما جعل كل جزء من الواجهة الرسومية ملائمًا لاتجاه RTL؛ وأنصحك أن تفعل ذلك بشكلٍ صحيح، فعدم إكمالك لدعم اتجاه RTL سيفقدك زوارك ومصداقيتك. ثانيًا، اتجاه RTL لا يعني «قلب كل شيء»: لستُ متأكدًا إن أُصلِحتَ هذه العلة أم لا، لكن ضبط «المحلية» (locale) إلى العربية في واجهة Gnome سيؤدي إلى إظهار الوقت بالشكل «PM 33:12» بدلًا من «‏12:33‎ PM‏‏». هذا ليس صحيحًا، نحن لا نذكر الوقت بالمقلوب بالعربية. أي أنَّ هنالك استثناءات وأشياء يجب الانتباه إليها مثل الأرقام، وسنشرح ذلك لاحقًا. لماذا عليك أن تهتم بدعم موقعك لاتجاه RTL؟ ربما تطور مواقع عالمية بلغاتٍ مثل الإنكليزية أو الفرنسية، وأنت متخوفٌ من RTL وتحسبها صعبةً ومرعبةً وستُضيف كمًا كبيرًا من العمل على مشروعك. حسنًا، الأمر ليس كما تظن، فبعد أن تستوعب المبادئ الأساسية، فلن تحتاج إلا لبذل جهدٍ قليلٍ. من البدهي أن تهتم –كمطور ويب عربي– بدعم الموقع التي تطوره للغة العربية (أو لبقية اللغات التي تُكتَب من اليمين إلى اليسار)؛ فلا حاجة لتذكيرك أنَّ هنالك أكثر من 410 مليون شخص حول العالم يتحدث لغةً تُكتَب من اليمين إلى اليسار (وهذا عددٌ ضخمٌ جدًا. لاحظ أنَّ الرقم التقديري السابق مبنيٌ على قائمة اللغات في ويكيبيديا). أصبحت أغلبية الشركات تُضيف دعمًا للغات المكتوب من اليمين إلى اليسار في برمجياتها (مثل ويندوز، و iOS، وأندرويد) وكذلك الأمر للعديد من المواقع العالمية؛ لذا سيتوقع زوار موقعك مِمَن يتحدثون العربية (وغيرها من اللغات المكتوبة من اليمين إلى اليسار) أن يحقق موقعك هذه التوقعات بما يخص دعم RTL. فبدون هذا الدعم قد لا يتحول زوار موقعك إلى زوارٍ دائمين. دورة تطوير واجهات المستخدم ابدأ عملك الحر بتطوير واجهات المواقع والمتاجر الإلكترونية فور انتهائك من الدورة اشترك الآن نصائح عامة لدعم اتجاه RTL هذه بعض القواعد العامة التي أحببت أن أذكرك بها والتي عليك أن تبقيها ببالك عند تطوير تطبيقات ومواقع ويب تدعم RTL: ابدأ من اليمين: تُفتح الكتب من الطرف الأيمن في البلدان التي تتحدث بلغاتٍ مكتوبةٍ من اليمين إلى اليسار، ولهذا يجب أن تُقلَب أغلبية عناصر الواجهة المتعلقة بمقروئية النص. الأزرار العتادية لها نفس الترتيب في البلدان المتحدثة بالعربية: أماكن أزرار التحكم بالأجهزة الصوتية هو نفسه في البلدان التي تتحدث لغاتٍ تكُتَب من اليمين إلى اليسار. لذا لن تتغير أماكن أزرار «التشغيل» و«التمرير» و«الإيقاف المؤقت» و«الإيقاف». ولهذا فلا يُنصَح أن يُقلَب اتجاه أيٌ من تلك الأزرار. مثالٌ آخر هو أزرار الهواتف، فقبل ظهور الهواتف الذكية كانت هنالك الهواتف الأرضية التي ما تزال شائعةً جدًا في البلدان العربية. ولهذا السبب يكون من المستحسن ألّا نعكس مفاتيح الأرقام. لا تظن أنَّ مستخدمي RTL هم أشخاصٌ يستعملون أياديهم اليسرى. فأغلبية مَن يتحدث بالعربية يكتبها بيده اليمنى، كما هو الحال في بقية أنحاء العالم. لذا لا تقلب أيّ شيءٍ ليس متعلقًا بكيفية قراءة المستخدم لمحتوى الصفحة. مثالٌ على هذا هو شريط التمرير «أ-ي A-Z» (الموجود على يمين الصورة السابقة) في تطبيق «جهات الاتصال» في أغلبية الهواتف الذكية. هذا الشريط موضوعٌ على الجهة اليمنى لأنه من الأسهل استخدامه باليد اليمنى، لذا لا حاجة إلى قلب اتجاهه وجعله في الجزء الأيسر في نمط RTL. كيفية جعل المحتوى RTL أول خطوة نحو جعل اتجاه صفحة الويب من اليمين إلى اليسار هي إضافة dir="rtl"‎ إلى وسم <html>: <html dir="rtl"> الخاصية dir هي اختصارٌ لكلمة «direction»، وضبط قيمتها إلى rtl تجعل نقطة البداية الأفقية لعناصر صفحة الويب من اليمين بدلًا من اليسار. لكي تضع نمط CSS لعنصرٍ معيّن إذا كانت الصفحة RTL فقط: أنشِئ نسخةً من أيّة أنماط CSS اعتيادية التي ستُطبَّق على العنصر الهدف. أضف المُحدِّد html[dir="rtl"] قبل سلسلة المُحدِدات (selector chain). كلما وجدت خاصيةً أو قيمة تُمثِّل التموضع الأفقي للعنصر، فاستعمل القاعدة المُعاكسة لها. على سبيل المثال، إذا وجدتَ float: left فبدِّلها إلى float: right. كمثالٍ عمليٍ على كلامنا السابق، إذا كان أحد أنماط CSS كالآتي: .someClass { text-align: left; padding: 0 10px 0 0; text-decoration: underline; } فسنأخذ نسخةً منه ونحدِّثها كالآتي: html[dir="rtl"] .someClass { text-align: right; padding: 0 0 0 10px; } لاحظ أننا حذفنا القاعدة text-decoration: underline;‎، فلسنا بحاجة إلى إعادة تعريف هذه القيمة لنسخة RTL لأنها لا تؤثر على اتجاه أي عنصر. وبالتالي ستُطبَّق القاعدة الأصلية. هذا مثالٌ أكثر تفصيلًا. يمكنك أن تجربه مباشرةً على متصفحك. شيفرة HTML: <html dir="ltr"> <body> <input type="button" id="directionSwitch" value="Switch LTR/RTL"/> <hr/> <div class="boxOne"></div> <div class="boxTwo"></div> </body> </html> شيفرة CSS: .boxOne { width: 170px; height: 170px; background-color: #0072C6; margin: 0 0 0 20px; } .boxTwo { width: 120px; height: 120px; background-color: #C3191E; margin: 0 0 0 70px; } html[dir="rtl"] .boxOne { margin: 0 20px 0 0; } html[dir="rtl"] .boxTwo { margin: 0 70px 0 0; } شيفرة JavaScript: document.getElementById('directionSwitch').addEventListener('click', function() { var docDirection = document.documentElement.dir; var isRTL = (docDirection === 'rtl'); document.documentElement.dir = isRTL ? 'ltr' : 'rtl'; }); لن تكون الحالات العملية بهذه البساطة؛ فهنالك تطبيقاتٌ فيها عددٌ كبيرٌ جدًا من قواعد CSS ومن المحددات، ولديك عدِّة استراتيجيات يمكنك اتباعها. وهذه إحداها: انسخ القواعد واحذف ما ليس لازمًا: أولًا، انسخ محتوى صفحة الأنماط إلى ملفٍ آخر، وأضف html[dir="rtl"] إلى جميع القواعد، ثم احذف أيّة خاصيات لا تتعلق بالاتجاه الأفقي للعناصر. وسينتهي بك المطاف بملفٍ صغيرٍ هدفه هو التعامل مع اتجاه RTL فقط. اقلب الاتجاهات: عدِّل كل الخاصية التي تُشير إلى اليسار (left) في الملف الجديد إلى معاكسها: أي padding-left ستصبح padding-right، و float: right ستصبح float: left …إلخ. لاحظ أنَّه إذا كان عندك خاصية padding-left في الملف الأصلي ثم عدَّلتها إلى padding-right فستبقى الخاصية الأصلية padding-left مطبقةً. لذا عليك إضافة الخاصية padding-left: unset بالإضافة إلى خاصية padding-right، وإلا فسيُطبِّق المتصفح كلا القاعدتين: خاصية padding-left من ملف CSS الأصلي وخاصية padding-right من الملف المعدَّل لاتجاه RTL. والأمر مشابهٌ لبقية الخاصيات مثل margin-left|right و border-left|right … أعد لصق المحتويات في الملف الأصلي: بعد أن تنتهي من إكمال الخطوتين السابقتين، فالصق القواعد الجديدة في الملف الملف الأصلي بعد القواعد الأصلية. أفضِّل شخصيًا أن أضيف تعليقًا صغيرًا مثل /* **** RTL Content **** */ لكي أستطيع التفريق بسهولة بين الأنماط الأصلية والأنماط المُعدَّلة لاتجاه RTL. طريقة أفضل: فصل أنماط RTL و LTR عن بعضهما قد تواجه في بعض الأحيان حالاتٍ غريبة التي يحدث فيها تضاربٌ بين الأنماط. وهذا يتضمن وراثة قيم بعض الخاصيات من خاصياتٍ أخرى، مما يعني أنَّك في بعض الأحيان لا تعرف تمامًا ما هي القاعدة التي عليك تجاوزها. وربما أضفتَ margin-right إلى قواعد RTL لكنك لستَ متأكدًا ما هي القيمة التي عليك ضبطها إلى خاصية margin-left الأصلية. أرى أنَّه من المستحسن أن تتبع منهجية أخرى، والتي هي أفضل في الحالات العملية لكنها أطول بعض الشّيء. سنفصل الخاصيات التابعة لكل اتجاه (RTL و LTR) عن بعضها بعضًا تمامًا. هذا مثالٌ عنها: لنقل أنَّنا لدينا قاعدة تتضمن ‎.wrapper .boxContainer .fancyBox كالآتية: .wrapper .boxContainer .fancyBox { text-align: left; padding-left: 10px; text-decoration: underline; color: #4A8CF7; } بدلًا من إضافة خاصيات أخرى لاتجاه RTL فيها padding-left و padding-right، فيمكنك أن تكتب: .wrapper .boxContainer .fancyBox { text-decoration: underline; color: #4A8CF7; } html[dir="ltr"] .wrapper .boxContainer .fancyBox { text-align: left; padding-left: 10px; } html[dir="rtl"] .wrapper .boxContainer .fancyBox { text-align: right; padding-right: 10px; } هذا الحل يتضمن ثلاثة أجزاء: القاعدة (أو المُحدِّد) الأصلية وفيها خاصياتٌ ليست متعلقة بالاتجاه، لأنها مشتركة بين الاتجاهين RTL و LTR. محدد يُعالج الاتجاه من اليسار إلى اليمين –html[dir="ltr"]– لا يوجد فيه سوى خاصيات لها علاقة بالاتجاه والتي تتوافق قيمها مع التخطيط (layout) الذي لديك لاتجاه LTR. مُحدِّد يعالج الاتجاه من اليمين إلى اليسار –html[dir="rtl"]– بنفس خاصيات حالة LTR، لكن قيمها مضبوطة لكي تتوافق مع التخطيط الذي لديك لاتجاه RTL. لاحظ كيف أنَّ القاعدة الثانية تملك خاصية padding-left فقط، بينما القاعدة الثالثة تملك خاصية padding-right فقط. وهذا لأنَّ كل واحد منها خاصة بالاتجاه المُحدَّد في خاصية dir. هذه الطريقة جيدة وجميلة ويسهل التعامل معها، ولا تحتاج فيها إلى حذف قيم بعض الخاصيات (لاحظ أنَّ الكلمة المحجوزة unset غير مدعومة في جميع المتصفحات. لأكبر قدر من التوافقية، عليك أن تُعيد تعريف القيمة التي ترغب بها لأي خاصية تريد حذف قيمتها). كيفية ضبط اتجاه الصفحة باستخدام JavaScript؟ هذا سهلٌ جدًا، ويتطلب سطرًا وحيدًا فقط: document.documentElement.dir يمكنك طباعة هذه القيمة إلى console كما يلي: var direction = document.documentElement.dir; console.log(direction); أو تستطيع تجربة المثال الحي. استعمل السطر الآتي لضبط اتجاه الصفحة: document.documentElement.dir = "rtl" هذا مثالٌ متكاملٌ حول الحصول على قيمة اتجاه الصفحة وضبطها: شيفرة HTML: <html dir="ltr"> <body> <input type="button" value="Set document direction to RTL" id="setDirection"/> <div id="directionStatus"></div> </body> </html> شيفرة JavaScript: function getDirection() { var direction = document.documentElement.dir; document.getElementById('directionStatus').textContent = 'This document\'s direction is: ' + direction; } var dirButton = document.getElementById('setDirection'); dirButton.addEventListener('click', function(evt) { var isRTL = (document.documentElement.dir === 'rtl'); document.documentElement.dir = isRTL ? 'ltr' : 'rtl'; dirButton.value = isRTL ? 'Set document direction to RTL' : 'Set document direction to LTR'; getDirection(); }); حلول مؤتمتة تحدثنا بما فيه الكفاية عن الطرق اليدوية لقلب الاتجاه إلى RTL. لننظر الآن إلى بعض الحلول المؤتمتة. css-flip طوَّرت تويتر حلًا لأتمتة كامل العملية وجعل قلب الاتجاه إلى RTL أسهل للمشاريع الكبيرة. هذا المشروع مفتوح المصدر ويمكنك أن تجده على Github. إضافة NPM هذه تغطي جميع الحالات التي قد تواجهها عندما تعمل على قلب الاتجاه إلى RTL، بما في ذلك الحالات الغريبة. بعض ميزاتها: no-flip: لإخبار مُحرِّك القلب أنَّك لا تريد أن تُقلَب هذه الخاصية وذلك بإضافة /* @noflip*/ في بداية الخاصية. على سبيل المثال، إذا كتبتَ ‎/* @noflip*/ float: left فستبقى الخاصية float: left عند تشغيل css-flip. replace: عندما تكون لديك صور خلفية تختلف بين RTL و LTR، فيمكنك أن تُحدِّد صورةً بديلةً بتضمين ‎/*@replace: url(my/image/path) */‎ قبل الخاصية الأصلية. فلنقل أنَّ لديك الخاصية background-image: url(arrow-left.png) وإذا حدثت السطر إلى ‎/*@replace: url(arrow-rightish.png) */ background-image: url(arrow-left.png);‎ فستصبح الخاصية النهائية في اتجاه RTL كالآتية: background-image: url(arrow-rightish.png);‎. يمكنك استعمال css-flip عبر سطر الأوامر بتنفيذ css-flip path/to/file.css > path/to/file.rtl.css أو عبر تضمين css-flip في صفحتك. للمزيد من المعلومات راجع مستودع github. يجدر بالذكر أنَّ css-flip يُستعمَل في برمجيات تويتر. rtlcss أداةٌ أخرى لأتمتة العملية برمجها «محمد يونس» تدعى rtlcss التي تتوفر على github وتوفر ميزاتٍ رائعة. يمكنك باستخدام هذه الأداة أن: تعيد تسمية القواعد (مثلًا، إعادة تسمية ‎#boxLeft إلى ‎#boxRightSide). تتجاهل بعض القواعد من عملية القلب. تتجاهل خاصياتٍ معينة. تضيف قيمًا جديدة. تضيف قيم خاصيات جديدة بين قيم الخاصيات الأخرى. يمكنك استخدام هذه الأداة عبر سطر الأوامر؛ ويمكنك إنشاء ملف أنماط RTL بتنفيذ الأمر الآتي في نفس المجلد الذي يتوفر فيه ملف CSS الأصلي: rtlcss input.css output.rtl.css أو يمكنك أن تضمنه في صفحتك. للمزيد من المعلومات راجع صفحة المشروع على github. هذا المشروع مشهور جدًا ويستعمل من عدِّة مشاريع بما فيها ووردبريس. خاتمة ما يزال هنالك الكثير لنغطيه، لكن هذه المقالة أمدتك بالمعلومات اللازمة لبدء مشوارك مع RTL. سنشرح في المقالة القادمة مواضيع متقدمة عن RTL. إذا كانت لديك أيّة أسئلة حول RTL، فاطرحها في التعليقات. ترجمة -وبتصرّف- للمقال Building RTL-Aware Web Apps & Websites: Part 1 لصاحبه أحمد نفزاوي
  11. مضت فترةٌ لا بأس بها على ظهور إطارات العمل، وازدادت شهرتها في مجال تطوير واجهات المواقع. توفِّر تلك الإطارات مقتطفاتٍ من الشيفرات التي تستطيع نسخها ولصقها في موقعك لبناء تخطيط الصفحة وواجهة المستخدم. ربما قرأت مقالاتٍ كثيرة عن كيفية الاستفادة من تلك الإطارات في مشاريعك، لكنني هنا لأفعل العكس: أنوهك لبعض السلبيات التي قد تجلبها إطارات العمل إلى مواقعك أو تطبيقات الويب التي تبرمجها؛ وكيف تستطيع أن تتفاداها أو تقلل من تأثيرها. عندما أسألُ الآخرين لماذا يستعملون إطار عملٍ معيّن، فسيقع جوابهم تحت التصنيفات الآتية: السرعة: أغلبية الأشخاص يعتقدون أنهم سيطورون الموقع بشكلٍ أسرع. وربما يكون هذا صحيحًا في المراحل الأولى للمشروع؛ لكنه سيشكِّل حِملًا على عاتقك (سنتعرف لاحقًا لماذا). يعتقد بعض الأشخاص أنَّه من المستحسن أن يستعملوا إطار عمل، خصوصًا أولاءك الذين يبدؤون تطوير واجهات الويب لتوِّهم. ويدعم هذا الاعتقاد تضمين إطارات العمل في الورشات التدريبية أو في متطلبات الوظائف. التصميم: هنالك الكثير من المطورين الذين يرغبون بإطلاق مشاريعهم لكن لا يتوفر لديهم مصمِّم، لذا يلجؤون إلى إطارات العمل، التي توفِّر تصميمًا أساسيًا يستطيع المطورون الاعتماد عليه واستخدامه. وعلى الرغم من الفائدة العظيمة وراء ذلك، إلا أنَّ موقعك أو تطبيقك سينتهي به المطاف ليبدو كغيره من المواقع الحديثة الموجودة على الإنترنت، لكن قد ترى أنَّ ذلك لا يؤثر على مشروعك، فالأمر عائدٌ إليك في النهاية. السلبيات بغض النظر عن الأسباب التي تدفعك لاستخدام إطارات العمل، هنالك سلبيات لها. لكن قد تتغاضى عنها في بعض الحالات لكي تنشر مشروعك في أسرع وقت ممكن أو لتبني نسخةً أوليةً التي لن تَستعمِل شيفرتها في مشروعك النهائي. بيد أنَّ تلك السلبيات، إن وجدت في موقعٍ أو تطبيقٍ منشورٍ على الشبكة، قد تُسبِّب عرقلة عملية التطوير أو جعلها صعبة الإدارة. شيفرات HTML لا تحمل بُعدًا دلاليًا (Unsemantic HTML code) هذه المشكلة ليس مقتصرةً على إطارات العمل، لكنني أراها متفشيةً كثيرًا في أشهرها. على سبيل المثال، ربما تقرأ في توثيق إطار العمل الذي يتحدث عن تنسيق الأزرار، وتجد قطعةً من الشيفرات تخبرك كيف تستخدم صنفًا (class) من أصناف CSS للأزرار المُعطَّلة (disabled) بدلًا من إضافة خاصية disabled إلى عنصر <button> نفسه. وهنالك أمثلةٌ كثيرةٌ تستعمل عناصر <div> أو <span> في أماكن يُفضَّل أن يُستعمَل فيها عنصرٌ هيكليٌ مثل <article> أو <date>. ودعنا لا نتحدث عن وجود عنصر <div> داخل عنصر <div> داخل عنصر <div>… مُحدِّدات دقيقة جدًا مرةً أخرى، هذه المشكلة ليست خاصة بإطارات العمل، لكن يمكنك ملاحظتها في أشهرها: الميل إلى استخدام مُحدِّدات (selectors) دقيقة جدًا لإنشاء قواعد CSS. على سبيل المثال: .table-responsive > .table-bordered > thead > tr > th:first-child ماذا يحدث لو حاولت تجاوز بعض خاصيات <th>؟ ستحتاج إلى إنشاء مُحدَّد أكثر دقةً وتخصيصًا! لا يمكنك استعمال قاعدة CSS كالآتية: th.important-heading { color: #000; } وإنما عليك إنشاء قاعدة مثل هذه: .table-responsive > .table-bordered > thead > tr > th:first-child.important-heading { color: #000; } الذي يحدث فعلًا هو أنَّ الجميع يتفادون كتابة مثل هذه الشيفرات. لذا سنبدأ برؤية مثل هذه القاعدة: th.important-heading { color: #000; !important } والتي تزيد من تعقيد الأمر بشكل أكبر. قواعد لا تحتاج لها إذا ضمَّنتَ إطار العمل بأكمله بدلًا من استخدامك لأجزاءٍ تحتاج لها منه، فسينتهي بك المطاف بقواعد CSS لا تستعملها. ربما تتعامل مع هذه الحالة بمساعدة أدوات لمعالجة الشيفرات لإزالة القواعد غير المستخدمة، لكن عندما تبدأ بإضافة أو إزالة الأصناف (classes) ديناميكيًا باستخدام JavaScript، فلن تكون متأكدًا 100% أنَّك لن تحتاج إلى تلك الشيفرات. قواعد CSS غير المستخدمة تجعل ملفاتك كبيرةً، مما يُشكِّل مشكلةً للهواتف النقالة التي تتصل بالإنترنت عبر الشبكة الخلوية بدلًا من Wi-Fi. لكنك علاوةً على ذلك، ستجعل كمية الشيفرات في مشروعك كبيرةً مما يُصعِّب عملية الصيانة وتتبع الأخطاء. اتخذ قرارك جميع إطارات العمل تتبع وجهات نظر معيّنة، وهذه ليس مشكلةً إذا كانت لديك وجهة نظر متطابقة مع إطار العمل. لكن في بعض الأحيان لا تتطابق وجهة نظرك مع أسلوب إطار العمل. على سبيل المثال، هذه هي شيفرة HTML التي يقترحها أحد إطارات العمل لإنشاء لافتات نصيّة (text labels) ملونة: <span class=“label label-warning”>Out of stock</span> أرى أنَّ أسماء الأصناف مكررة، لأنني أفضِّل استخدام الأصناف التي أجدها ضروريةً فقط. إذا كنتُ صاحب الشيفرة السابقة، فسأستخدم الصنف label-warning فقط. ماذا لو كنتَ معجبًا بإحدى طرق الهيكلة في CSS التي لا يستعملها إطار عملك؟ بدائل إطارات العمل كتابة شيفرات HTML و CSS يدويًا! إذا لم تحب الشيفرات التي يُنتِجها إطار العمل، فلِمَ لا تكتب الشيفرات الخاصة بك. إذا كانت القواعد التي يوفرها إطار العمل تجعلك لا تعمل بكفاءة، فيجدر بك كتابة قواعدك الخاصة. إذا كنتَ تحتاج إلى شبكة (grid)، فيمكنك أن تستعمل Flexbox، التي تُسهِّل إنشاء تخطيط للصفحة مقارنةً مع استعمال عناصر <div> عائمة (float). إذا لم تكن تعرف كيفية التعامل مع Flexbox، فألق نظرة على هذا هذا الدرس. وإذا أردتَ أن تعرف كيف ستبدو الشيفرة، فانظر إلى المثال الآتي: شيفرة HTML <header class="main-header">Header</header> <div class="row"> <aside class="primary-sidebar">Sidebar (left)</aside> <main class="content">Main content goes here</main> <aside class="secondary-sidebar">Sidebar (right)</aside> </div> <footer class="main-footer">Footer</footer> شيفرة CSS * { padding: 0; margin: 0; } body { font-size: 1.2em; display: flex; flex-direction: column; height: 100vh; text-align: center; } .row { flex: 1 1 auto; background: green; display: flex; } .main-header { background: red; flex: 0 0 auto; } .main-footer { background: red; flex: 0 0 auto; } .primary-sidebar, .secondary-sidebar { background: turquoise; flex: 0 0 auto; width: 20%; } .content { flex: 1 1 auto; min-width: 400px; } سيكون لدينا Grid في المستقبل القريب، والتي ستُسهِّل إنشاء الشّبكات، ولن تشعر أنَّك بحاجة إلى استعمال شبكة أخرى قط. ملاحظة: لكي ترى الناتج على متصفحك، فجرِّب النسخة الليلية من Firefox أو آخر نسخة من متصفح Firefox Developer Edition. أما لكي تُشاهد هذه الأمثلة في نسخة Firefox أخرى، فعليك تفعيل الراية layout.css.grid.enabled بالذهاب إلى about:config. شيفرة HTML <header class="main-header">Header</header> <main class="content">Main content goes here</main> <aside class="primary-sidebar">Sidebar (left)</aside> <aside class="secondary-sidebar">Sidebar (right)</aside> <footer class="main-footer">Footer</footer> شيفرة CSS * { padding: 0; margin: 0; } body { font-size: 1.2em; text-align: center; height: 100vh; display: grid; grid-template-columns: 20% 1fr 20%; grid-template-rows: auto 1fr auto; grid-template-areas: "header header header" "sidebar-pri content sidebar-alt" "footer footer footer"; } .main-header { background: red; grid-area: header; } .main-footer { background: red; grid-area: footer; } .primary-sidebar, .secondary-sidebar { background: turquoise; } .primary-sidebar { grid-area: sidebar-pri; } .secondary-sidebar { grid-area: sidebar-alt; } .content { background: green; grid-area: content; } إذا كنتَ تحتاج إلى بعض عناصر الواجهة الرسومية، مثل القوائم المنسدلة، فلا حاجة إلى كتابتها من الصفر، وإنما تستطيع أخذ ذاك الجزء من إطار العمل (عوضًا عن كامل الإطار) أو أن تستعمل مكتبة خارجية ليس لها اعتماديات. إذ كنتَ تحتاج إلى تصميمٍ لواجهة موقعك أو تطبيقك، فيمكنك أن تحصل على ملفات Sass أو Less بدلًا من شيفرات CSS النهائية. وبهذا تستطيع إنشاء مُحدِّدات خاصة بك، مما يسمح لك باستخدام شيفرات HTML كما تحب. لكن تذكّر بأنَّ موقعك سيبدو كغيره من العديد من المواقع. إذا كنتَ تريد طريقةً لتوحيد عملية إنشاء عناصر الواجهة الرسومية في مشروعك، لكي يعرف الآخرون ما هي الشيفرة التي يجب أن يستخدموها وكيف يُضيفون عناصر جديدة؛ فالذي تبحث عنه هو «دليل لكتابة الأنماط» (style guide. باختصار: إطار عمل خاص بك). أرى أنَّ عليك استخدام هذه الطريقة في المشاريع الكبيرة. الخلاصة هنالك إيجابياتٌ كثيرة لاستخدام إطارات العمل، لكن هنالك أيضًا سلبياتٌ عليك توخي الحذر منها، ولا تنسَ أن تتعرف على الأدوات والواجهات البرمجية (APIs) المتاحة لك لإنشاء أنماط CSS خاصة بمشروعك أو تطبيقك. ترجمة -وبتصرّف- للمقال You might not need a CSS framework لصاحبته Belén Albeza.
  12. هنالك أكثر من 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. اقرأ أيضًا المقال التالي: توصيف الأشخاص باستخدام metadata في HTML5 المقال السابق: النماذج (Forms) في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  13. يشيع في بيئات الخواديم الوهمية (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.
  14. كلنا نعرف نماذج الويب، أليس كذلك؟ أنشِئ <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"‎ تُحدِّد القيمة الافتراضية. يُفترَض أن تكون هذه الخاصية مألوفةً لديك، فهي نفس الخاصية التي تستعملها دومًا لتحديد قيم حقول النموذج (ذكرتُ هذه النقطة هنا لكي أخبرك أنَّك لست بحاجة إلى تعلم HTML مرة أخرى، فإصدار 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. اقرأ أيضًا المقال التالي: مدخل إلى البيانات الوصفية (microdata) في HTML5 المقال السابق: تطبيقات الويب التي تعمل دون اتصال – الجزء الثاني النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  15. تحدثنا في الجزء الأول من هذا الدرس عن أساسيات التخزين المحلي، وسنستمر في مشوارنا مع تطبيقات الويب التي تعمل دون اتصال، وسننشِئ مثالًا عمليًا عليها. تسلسل الأحداث تحدثنا إلى الآن عن تطبيقات الويب التي تعمل دون اتصال، وعن ملف manifest للتخزين المؤقت، وعن مخزن التطبيق (appcache) بشكلٍ مبهم، وكأن الأمر يجري بطريقةٍ سحريةٍ. حيث تُنزَّل الملفات، وتقوم المتصفحات بمهامها على أتمِّ وجه، وكل شيء يعمل عملًا سليمًا! لكننا نتحدث هنا عن تطوير تطبيقات الويب، فلا يعمل أيّ شيءٍ من تلقاء نفسه. بدايةً، لنتكلم عن تسلسل الأحداث، تحديدًا أحداث DOM. عندما يزور متصفحك صفحةً تُشير إلى ملف manifest، فسيُطلِق سلسلةً من الأحداث في كائن window.applicationCache؛ أعلم أنَّ هذا قد يبدو معقدًا، لكن ثق بي، هذه أبسط نسخة تمكنت من كتابتها والتي لا تهمل أيّة معلومةٍ مهمةٍ. 1. بعد أن يلاحظ المتصفح خاصية manifest في عنصر <html>، فسيُطلِق الحدث checking مباشرةً (جميع الأحداث المذكورة هنا ستُفعَّل في الكائن window.applicationCache)، سيُفعَّل الحدث checking دومًا، حتى لو زرتَ هذه الصفحة من قبل أو كانت هنالك صفحاتٌ أخرى تشير إلى نفس ملف manifest. 2. إن لم يتعامل متصفحك مع ملف manifest المُحدَّد من قبل… سيُطلِق الحدث downloading، ثم سيبدأ بتنزيل الموارد المذكورة في ملف manifest للتخزين المؤقت. أثناء التنزيل، سيُطلِق متصفحك الحدث progress بين الحين والآخر، الذي يحتوي على معلوماتٍ عن عدد الملفات التي نُزِّلَت. بعد إكمال تنزيل جميع الموارد المذكورة في ملف manifest بنجاح، سيُطلِق المتصفح الحدث النهائي cached الذي يُشير إلى أنَّ تطبيق الويب قد خُزِّن مؤقتًا بشكلٍ كامل، وهو جاهز لكي يُستخدم دون اتصال. 3. في المقابل، إن زرتَ هذه الصفحة أو أي صفحة أخرى تُشير إلى إلى نفس ملف manifest من قبل، فسيعلم متصفحك عن ملف manifest؛ فربما خزَّن بعض الموارد في مخزن التطبيق (appcache)، وربما خزَّن كامل التطبيق. إذًا السؤال الآن هو: هل تغيّر ملف manifest منذ آخر مرة تحقق فيها المتصفح من ذلك؟ إذا كان الجواب: لا، لم يتغير ملف manifest؛ فسيُطلِق متصفحك الحدث noupdate، ولن يحتاج إلى اتخاذ خطواتٍ إضافية. إذا كان الجواب: نعم، تغيّر ملف manifest؛ فسيطلِق متصفحك الحدث downloading ويُعيد تنزيل كلُ موردٍ موجودٍ في ملف manifest. أثناء التنزيل، سيُطلِق متصفحك الحدث progress بين الحين والآخر، الذي يحتوي على معلومات عن عدد الملفات التي نُزِّلَت. بعد إعادة تنزيل كل الموارد الموجودة في ملف manifest بنجاح، سيُطلِق المتصفح الحدث النهائي updateready الذي يُشير إلى أنَّ النسخة الجديدة من تطبيق الويب قد خُزِّنت مؤقتًا بشكلٍ كامل، وهي جاهزة لكي تُستخدم دون اتصال. لكن انتبه إلى أنَّ النسخة الحديثة لن تُستعمَل فورًا، فلا تُبدَّل النسخة القديمة آنيًا، لكنك تستطيع استخدام النسخة الجديدة دون إجبار المستخدم على إعادة تحميل الصفحة باستدعاء الدالة window.applicationCache.swapCache()‎ يدويًا. إذا حدث أي شيء بشكلٍ خاطئ في أي نقطة في هذه المرحلة، فسيُطلِق متصفحك الحدث error وسيتوقف. هذه هي قائمة مختصرة بالأشياء التي قد تُسبِّب المشكلة: أعاد ملف manifest رسالة الخطأ HTTP 404 ‏(أي Page Not Found) أو 410 ‏(أي Permanently Gone). عُثِرَ على ملف manifest ولم يتغيّر، لكن فشل تنزيل صفحة HTML التي تُشير إلى الملف. تغيّر ملف manifest أثناء التحديث. عُثِرَ على ملف manifest وقد تغيّر، لكن المتصفح قد فشل بتنزيل أحد الموارد المذكورة فيه. تنقيح الأخطاء أريد أن أشير إلى نقطتين مهمتين هنا، أولهما هو شيءٌ قرأتَه لتوِّك لكنني متأكدٌ تمامًا أنَّك لم تعره اهتمامك، لذا دعني أكرره: إذا فشل تنزيل أحد الموارد الموجودة في ملف manifest تنزيلًا سليمًا، فستفشل عملية التخزين المؤقت لتطبيق الويب الذي يعمل دون اتصال بالكليّة، وسيُطلِق المتصفح الحدث error، لكن ليس هنالك أيّة إشارات إلى ماهية المشكلة. وهذا ما يجعل تنقيح (debugging) تطبيقات الويب التي تعمل دون اتصال معقدةً ومزعجةً أكثر من المعتاد. النقطة الثانية هي ليست –إذا ابتغينا الدقة التقنية– خطأً، لكنها ستبدو وكأنها علِّةٌ خطيرةٌ في المتصفح إلى أن تعلم ما الذي يجري. لهذه النقطة علاقةٌ بآليةِ تحققِ متصفحكَ فيما إذا تغيّر ملف manifest. وهي عمليةٌ تتألف من ثلاثِ خطواتٍ. هذه العملية مملةٌ لكنها مهمةٌ، لذا انتبه جيدًا لما سأتلوه عليك. سيسأل متصفحك –عبر البنى الهيكلية الاعتيادية في HTTP– إذا انتهت صلاحية التخزين المؤقت لملف manifest. وكما في بقية الملفات التي تُخدَّم عبر HTTP، سيُضمِّن خادوم الويب عندك بشكلٍ اعتيادي بعض المعلومات حول الملف في ترويسات HTTP. بعض تلك الترويسات (Expires و Cache-Control) تخبِر متصفحك كيف يُسمَح له بتخزين الملف مؤقتًا دون سؤال الخادوم إذا تغيِّر أم لا. لا علاقة لهذه النوع من التخزين بتطبيقات الويب التي تعمل دون اتصال؛ وهو يحدث تقريبًا لكل صفحة HTML أو صفحة أنماط CSS أو سكربتٍ ما أو صورةٍ أو أي موردٍ آخر على الويب. إذا انتهت صلاحية ملف manifest المؤقت (وفقًا لترويسات HTTP الخاصة به)، فسيسأل متصفحُك الخادومَ إذا كانت هنالك نسخةٌ جديدةٌ من الملف؛ فإن وجِدَت، فسيُنزِّلها المتصفح. وللقيام بهذا، سيُرسِل متصفحك طلبية HTTP التي تتضمَّن تاريخ آخر تعديل لملف manifest المُخزَّن مؤقتًا، وهو نفسه الذي أرسله الخادوم في جواب HTTP ‏(HTTP response) في آخر مرّة نزَّل فيها متصفحك ملف manifest. إذا قال خادوم الويب أنَّ ملف manifest لم يتغير منذ ذاك الوقت، فسيُعيد الخادوم حالة 304 (أي Not Modified). أكرِّر أنَّ هذا لا علاقة له بتطبيقات الويب التي تعمل دون اتصال، وهو يحدث لكل نوع من أنواع الموارد الموجودة في الويب. إذا ظنَّ خادوم الويب أنَّ ملف manifest قد تغيّر منذ ذاك التاريخ، فسيُعيد الحالة HTTP 200 (أي OK) متبوعةً بمحتويات الملف الجديد بالإضافة إلى ترويسات Cache-Control جديدة وتاريخ جديد لآخر تعديل، لذا ستعمل الخطوتان 1 و 2 بشكلٍ سليم في المرة القادمة (بروتوكول HTTP جميل جدًا؛ إذ تُخطِّط خواديم الويب للمستقبل دائمًا، فإن كان على خادوم الويب إرسال ملفٍ إليك، فسيفعل ما بوسعه لكي يتأكَّد أنَّه لن يحتاج إلى إرساله لك مرتين دون داعٍ). بعد تنزيل ملف manifest الجديد، سيقارن المتصفح المحتويات مع النسخة التي نزَّلها آخر مرة. إذا كانت محتويات ملف manifest مماثلةً لمحتويات آخر نسخة، فلن يُعيد متصفحك تنزيل أيٌّ من الموارد المذكورة في الملف. قد تعترض إحدى الخطوات السابقة طريقك أثناء تطويرك واختبارك لتطبيق الويب الذي يعمل دون اتصال، على سبيل المثال، لنقل أنَّك أنشأتَ نسخةٌ من ملف manifest، ثم بعد 10 دقائق أدركت أنَّك تحتاج إلى إضافة مورد آخر إلى الملف. لا توجد مشكلة، صحيح؟ أضفت سطرًا جديدًا وجربت التطبيق، لكنه لم يعمل! إليك ما حدث: عندما أعدت تحميل الصفحة، لاحظ المتصفح خاصية manifest، وأطلق الحدث checking، لكنه لم يفعل شيئًا… أصرَّ متصفحك بعنادٍ أنَّ ملف manifest لم يتغير. لماذا؟ لأنَّ خادوم الويب –افتراضيًا– مضبوطٌ للطلب من المتصفحات أن تُخزِّن الملفات الثابتة مؤقتًا لعدِّة ساعات (وذلك عبر ترويسات Cache-Control في بروتوكول HTTP). وهذا يعني أنَّ متصفحك سيبقى عالقًا في الخطوة رقم 1 من الخطوات الثلاث السابقة. من المؤكَّد أنَّ خادوم الويب يعرف أنَّ الملف قد تغيّر، لكن متصفحك لن يحاول سؤال خادوم الويب. لماذا؟ لأنَّه في آخر مرة نزَّل متصفحك فيها ملف manifest، طلب الخادوم منه أن يُخزِّن الملف مؤقتًا لعدِّة ساعات، لكن متصفحك حاول إعادة طلب ذاك الملف بعد 10 دقائق. دعني أوضِّح لك أنَّ هذه ليست علّة وإنما ميزة. إذ يعمل كل شيءٍ كما يجب. إن لم تكن هنالك طريقة لخواديم الويب (وللخواديم الوسيطة [proxies]) لتخزين الملفات مؤقتًا، فسينهار الويب بين ليلةٍ وضحاها. لكن هذا ليس عزاءً لك بعد أن قضيت عدِّة ساعات محاولًا معرفة لماذا لم يلاحظ متصفحك أنَّ ملف manifest قد تعدَّل (من الطريف إن كنت قد انتظرت فترةً كافيةً ثم أعدتَ تحميل الصفحة فسيعمل الملف بشكلٍ سحري! ذلك لأنَّ فترة صلاحية الملف قد انتهت، كما قد حُدِّدَ لها تمامًا). هذا ما عليك فعله فوريًا: إعادة ضبط خادوم الويب عندك لكي لا يُخزَّن ملف manifest مؤقتًا عبر بروتوكول HTTP. إذا كنتَ تستعمل خادوم ويب مبني على أباتشي، فكل ما عليك فعله هو إضافة السطرين الآتيين إلى ملف ‎.htaccess: ExpiresActive On ExpiresDefault "access" التعليمات السابقة ستُعطِّل التخزين المؤقت لكل ملف في ذاك المجلد وفي جميع المجلدات الفرعية، ومن المُرجَّح أنَّك لا تريد فعل هذا في خادومٍ إنتاجي؛ لذا عليك إما أن تضع التعليمات السابقة ضمن تعليمة <Files> لكي تؤثِّر على ملف manifest فقط، أو تُنشِئ مجلدًا فرعيًا لا يحتوي إلا على ملف manifest وملف ‎.htaccess فقط. وكالمعتاد، تختلف تفاصيل الضبط من خادومٍ إلى آخر، لذا راجع توثيق خادوم الويب الذي تستعمله لتفاصيلٍ حول كيفية التحكم بترويسات التخزين المؤقت لبروتوكول HTTP. بعد أن تُعطِّل التخزين المؤقت الخاص ببروتوكول HTTP على ملف manifest، ربما تواجهك مشكلة أخرى في أنَّك قد عدَّلت في أحد الموارد التي ستُخزَّن في مخزن التطبيق (appcache) لتُستعمَل دون اتصال، لكنها بقيت تملك نفس عنوان URL في خادومك. هنا ستعترض الخطوة رقم 2 من الخطوات الثلاث السابقة طريقك. إذا لم يتغير محتوى ملف manifest، فلن يلاحظ المتصفح أنَّ أحد الموارد المُخزَّنة مسبقًا قد تغيّر. ألقِ نظرةً إلى المثال الآتي: CACHE MANIFEST # rev 42 clock.js clock.css إذا عدِّلتَ ملف الأنماط clock.css وجربت التطبيق، فلن تلاحظ وجود التعديلات التي أجريتها، وذلك لأنَّ ملف manifest نفسه لم يُعدَّل. في كل مرة تُجري فيها تعديلًا لموردٍ ما في تطبيق الويب الذي يعمل دون اتصال، فعليك أن تُعدِّل ملف manifest نفسه. وهذا بسيطٌ جدًا، فلا يلزمك إلا تغييرُ حرفٍ وحيد. أسهل طريقة وجدتها لفعل هذا هو تضمين تعليق فيه رقم النسخة أو المراجعة (revision). غيّر رقم النسخة الموجود في التعليق، ثم سيلاحظ متصفحك أنَّ محتوى الملف قد تغيّر وسيعيد تنزيل كل الموارد المذكورة فيه. CACHE MANIFEST # rev 43 clock.js clock.css لننشِئ واحدًا! هل تتذكر لعبة الضامة التي برمجناها في درس canvas ثم حسّنّاها لاحقًا بحفظنا للتحركات عبر التخزين المحلي؟ ما رأيك أن نجعل اللعبة تعمل دون اتصال. علينا أن نُنشِئ ملف manifest يحتوي على قائمة بجميع الموارد التي تحتاج لها اللعبة. حسنًا، هنالك صفحة HTML رئيسية، وملف JavaScript وحيد الذي يحتوي على شيفرة اللعبة. لا توجد صور، لأننا رسمنا كل شيءٍ برمجيًا عبر الواجهة البرمجية لعنصر canvas. وجميع أنماط CSS موجودة في عنصر <style> في أعلى صفحة HTML. ها هو ذا ملف manifest الخاص باللعبة: CACHE MANIFEST halma.html ../halma-localstorage.js كلمة عن المسارات: أنشأتُ مجلدًا فرعيًا باسم offline/‎ في مجلد examples/‎، وملف manifest السابق موجودٌ هناك في المجلد الفرعي. ولأنَّ صفحة HTML ستحتاج إلى تعديلٍ بسيطٍ لكي تعمل دون اتصال (سآتيك به بعد دقيقة)، فأنشأتُ نسخةً منفصلةً من ملف HTML، الموجودةُ أيضًا في المجلد الفرعي offline/‎، ولعدم وجود أيّة تعديلات على شيفرة JavaScript منذ أن أضفنا دعمًا للتخزين المحلي، فسأستعمل ملفات ‎.js نفسها، الموجودة في المجلد الأب (examples/‎). ستبدو المسارات كالآتي: /examples/localstorage-halma.html /examples/halma-localstorage.js /examples/offline/halma.manifest /examples/offline/halma.html نريد الإشارة إلى ملفين في ملف manifest للتخزين المحلي (‎/examples/offline/halma.manifest)، أولهما هو النسخة التي تعمل دون اتصال لملف HTML (/examples/offline/halma.html) ولأن كلا الملفين في نفس المجلد، فمن الممكن ذكره في ملف manifest دون وجود سابقة للمسار. أما ثانيهما، فهو ملف JavaScript الموجود في المجلد الأب (‎/examples/halma-localstorage.js‎)، ويجب استخدام المسارات النسبية في ملف manifest:‏ ‎../halma-localstorage.js، وهذا مشابهٌ لاستعمالك لعنوان URL نسبي في خاصية <img src>. يمكنك أيضًا استعمال المسارات المطلقة (absolute paths، تلك التي تبدأ من المجلد الجذر للموقع) أو حتى عناوين URL المطلقة (التي تُشير إلى موارد موجودة على نطاقات أخرى). علينا إضافة خاصية manifest في ملف HTML لكي تُشير إلى ملف manifest للتخزين المؤقت: <!DOCTYPE html> <html lang="en" manifest="halma.manifest"> هذا كل ما عليك فعله! عندما تزور صفحة اللعبة التي تعمل دون اتصال بمتصفح حديث، فسينزِّل ملف manifest المُشار إليه في خاصية manifest ثم سيبدأ بتنزيل جميع الموارد الموجودة فيه ويضعها في مخزن التطبيق (appcache). ومن هنا ستتولى شيفرة الصفحة الأمر في كل مرة تزور فيها الصفحة. يمكنك اللعب دون اتصال وستُخزَّن بيانات اللعبة محليًا، لذا ستستطيع أن تُغلِق اللعبة ثم تعود إليها متى شئت. كلمة أخيرة أعلنت W3C أخيرًا أنَّها ستزيل هذه الميزة من معيار HTML5، إلا أنَّ هذه العملية ستستغرق وقتًا طويلًا. ونصحت W3C باستخدام Service Workers بدلًا منها. المشكلة أنَّ ميزة Service Workers غير مدعومة من الغالبية العظمى من المتصفحات، والمتصفحات التي تتواجد فيها ميزة Service Workers تدعمها دعمًا جزئيًا فقط. تركت W3C المطورين بين المطرقة والسندان، لكنني أرى أنَّ عليك الاستمرار في استخدام هذه الميزة، مع متابعة أخبار دعم Service Workers لتنتقل إليها في المستقبل. مصادر إضافية المعايير: Offline web applications في مواصفة HTML5 توثيقٌ من صانعي المتصفحات: Offline resources in Firefox HTML5 offline application cache، التي هي جزءٌ من Safari client-side storage and offline applications programming guide الدروس التعليمية: Gmail for mobile HTML5 series: using appcache to launch offline - part 1 Gmail for mobile HTML5 series: using appcache to launch offline - part 2 Gmail for mobile HTML5 series: using appcache to launch offline - part 3 Debugging HTML5 offline application cache an HTML5 offline image editor and uploader application ترجمة -وبتصرّف- لفصل Offline Web Applications من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: النماذج (Forms) في HTML5 المقال السابق: تطبيقات الويب التي تعمل دون اتصال – الجزء الأول النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  16. ما هي تطبيقات الويب التي تعمل دون اتصال؟ يبدو الأمر من الوهلة الأولى كأنَّ هنالك تضاربًا في المفاهيم. فهل هي صفحات الويب التي تنزِّلها ثم تفتحها بعد ذلك؟ لكن التنزيل يتطلب اتصالًا شبكيًا، فكيف ستستطيع تنزيل الصفحة عندما لا تكون متصلًا؟ لن تستطيع فعل ذلك بكل تأكيد، لكنك تستطيع تنزيل الصفحة عندما تكون متصلًا بالشبكة، وهذه هي آلية عمل تطبيقات HTML5 التي تعمل دون اتصال (offline web applications). بأبسط الكلمات: تطبيق الويب الذي يعمل دون اتصال هو قائمةٌ بروابط URL التي تُشير إلى ملفات HTML أو CSS أو JavaScript أو الصور أو أيّ موردٍ آخر. تُشير الصفحة الرئيسية إلى تلك القائمة التي تُسمى «ملف manifest» الذي هو ملفٌ نصيٌ موجودٌ في مكانٍ ما على خادوم الويب، وسيقرأ متصفح الويب الذي يدعم تشغيل تطبيقات الويب دون اتصال قائمةَ روابطِ URL الموجودةَ في ملف manifest، ثم يُنزِّل تلك الموارد (resources)، ثم يُخزِّنها تخزينًا مؤقتًا محليًا (local cache)، ثم سيُحدِّث النسخ المحلية منها تلقائيًا في حال تغيرت. وعندما يحين وقت محاولتك الوصول إلى تطبيق الويب دون اتصالٍ شبكي، فسيحاول متصفحك عرض النسخة المُخزَّنة محليًا تلقائيًا. ومن هذه النقطة، سيُلقى الحِمل على عاتِقِكَ تمامًا –كمطوِّر ويب–؛ فهنالك رايةٌ (flag) في DOM تخبرك إذا كان المتصفح متصلًا بالشبكة أم لا، وهنالك أحداث (events) تُفعَّل عندما تتغير حالة الوصول إلى الشبكة (أي لو كنتَ تعمل دون اتصال، ثم توفَّر لديك بعد دقيقةٍ اتصالٌ شبكي؛ أو بالعكس). لكن إن كان تطبيقُك يولِّد بياناتٍ أو يحفظها، فالأمر متروكٌ لك لتخزين البيانات محليًا عندما لا تكون متصلًا بالشبكة ثم تزامنها مع الخادوم البعيد بعد أن تستعيد اتصالك به. بعبارةٍ أخرى، تمكّنك HTML من جعل تطبيقك يعمل دون اتصال، لكن ما يفعله تطبيقك في هذه المرحلة عائدٌ إليك تمامًا. المتصفحات التي تدعم هذه الخاصية IE Firefox Safari Chrome Opera iPhone Android 10+ 3.5+ 4.0+ 5.0+ 10.6+ 2.1+ 2.0+ ملف Manifest يتمحور تطبيق الويب الذي يعمل دون اتصال حول ملف manifest للتخزين المؤقت. إذًا ما هو ملف manifest؟ هو قائمة بكل الموارد (resources) التي يحتاج لها تطبيق الويب لكي يستطيع المستخدم الوصول إليه وهو غير متصلٍ بالشبكة. وعليك أن تُشير إلى ملف manifest باستعمالك لخاصية manifest في عنصر <html> لتمهيد الطريق لعملية تنزيل وتخزين تلك الموارد تخزينًا مؤقتًا. <!DOCTYPE HTML> <html manifest="/cache.manifest"> <body> ... </body> </html> يمكن أن يتواجد ملف manifest للتخزين المؤقت في أي مكان في خادوم الويب عندك، لكن يجب أن يُخدَّم بنوع text/cache-manifest؛ إذا كنتَ تستعمل خادوم ويب يعتمد على أباتشي (Apache)، فمكن المرجَّح أنَّ كل ما عليك فعله هو إضافة تعليمة AddType في ملف ‎.htaccess في المجلد الجذر الذي يُخدَّم منه تطبيق الويب: AddType text/cache-manifest .manifest تأكَّد أنَّ اسم ملف manifest للتخزين المؤقت ينتهي باللاحقة ‎.manifest؛ إن كنتَ تستعمل خادوم ويب آخر أو ضبطًا مختلفًا لأباتشي، فراجع التوثيق الخاص بالخادوم لمزيدٍ من المعلومات حول التحكم في ترويسة Content-Type. حسنًا، على كل صفحة من صفحات HTML أن تُشير إلى ملف manifest للتخزين المؤقت، ويجب أن يُخدَّم ملف manifest بترويسة Content-Type مناسبة. لكن ماذا يوجد داخل ملف manifest؟ ها قد بدأت الإثارة. أول سطر من أي ملف manifest هو: CACHE MANIFEST وبعد هذا السطر، ستُقسَّم جميع ملفات manifest إلى ثلاثة أقسام: قسم «explicit»، وقسم «fallback» وقسم «online whitelist». لدى كل قسم ترويسةٌ مذكورةٌ في سطرٍ خاصٍ بها؛ وإن لم يحتوي ملف manifest على أيّة ترويسات، فهذا يعني ضمنيًا أنَّ كل الموارد المذكورة في الملف موجودةٌ في القسم «explicit». لا تحاول أن تطيل التفكير في الاصطلاحات السابقة، خشية أن ينفجر رأسك من الصداع. هذا ملف manifest سليمُ البنية، ويحتوي على ثلاثة موارد: ملف CSS، وملف JavaScript، وصورة JPEG. CACHE MANIFEST /clock.css /clock.js /clock-face.jpg لا يحتوي ملف manifest السابق على ترويسات للأقسام، لذا ستكون جميع الموارد المذكورة فيه موجودة في قسم «explicit» افتراضيًا. ستُنزَّل الموارد المذكورة في قسم «explicit» وتُخزَّن تخزينًا مؤقتًا محليًا، وستُستعمَل مكان نظيراتها الموجودة على الشبكة في حال كان المستخدمُ غيرَ متصلٍ. وبهذا –وعند تحميل ملف manifest للتخزين المؤقت– سينُزِّل متصفح الويب الملفات clock.css و clock.js و clock-face.jpg من المجلد الجذر لخادوم الويب، ثم إذا أزلتَ مقبس الشبكة وأعدتَ تحديث الصفحة، فستبقى كل تلك الموارد متوفرةً دون اتصال. قسم NETWORK هذا مثالٌ أكثر تعقيدًا. لنقل أنَّك تريد من تطبيق الساعة الذي أنشأته أن يتتبع الزوار باستخدام سكربت tracking.cgi الذي سيُحمَّل ديناميكيًا في خاصية <img src>، إلا أنَّ تخزين هذا الملف تخزينًا مؤقتًا سيؤثر سلبًا على غرض ذاك السكربت (الذي هو تتبع المستخدمين آنيًا)، لذا لا يجب أبدًا تخزين هذا المورد مؤقتًا ولا يجوز أن يُتاح دون اتصال. هذه هي طريقة فعل ذلك: CACHE MANIFEST NETWORK: /tracking.cgi CACHE: /clock.css /clock.js /clock-face.jpg ملف manifest السابق يحتوي على ترويسات للأقسام، فالسطر الذي يحتوي على NETWORK:‎ هو بداية قسم «online whitelist»، والموارد المذكورة في هذا القسم لن تُخزَّن محليًا ولن تتوفر دون اتصال (محاولة تحميلها عند عدم توفر اتصال ستؤدي إلى خطأ). أما السطر CACHE:‎ فهو بداية قسم «explicit»، وبقية ملف manifest مماثلة للمثال السابق، حيث سيُنزَّل وسيُخزَّن كل مورد من الموارد المذكورة محليًا وسيُتاح للاستعمال دون اتصال. قسم FALLBACK هنالك نوعٌ آخر من الأقسام في ملف manifest: قسمfallback، الذي تُعرِّف فيه البدائل عن الموارد الموجودة على الشبكة التي –لسببٍ من الأسباب– لا يمكن تخزينها مؤقتًا أو لم يتم ذلك بنجاح. توفِّر مواصفة HTML5 حلًا ذكيًا لطريقة استخدام قسم fallback: CACHE MANIFEST FALLBACK: / /offline.html NETWORK: * ماذا يفعل المثال السابق؟ أولًا، لنأخذ مثالًا موقعًا يحتوي ملايين الصفحات مثل ويكيبيديا؛ لا تستطيع تنزيل كامل الموقع، ومن المؤكد أنَّك لا ترغب بذلك. لكن لنقل أنَّك تريد أن تأخذ جزءًا منه وتجعله متوفرًا دون اتصال؛ لكن كيف ستقرر ما هي الصفحات التي تحتاج إلى تخزينها مؤقتًا؟ ماذا عن: كل صفحة زرتها في موقع ويكيبيديا –الذي افترضنا جدلًا أنَّه يدعم التشغيل دون اتصال– ستُنزَّل وستخزَّن مؤقتًا. وهذا يتضمن كل مُدخَلة من مدخلات الموسوعة التي زرتها، وكل صفحة نقاش (أي المكان الذي تتناقش فيه عن مُدخَلة معيّنة)، وكل صفحة تحرير (أي تلك الصفحة التي تُجري فيها تعديلاتٍ إلى مُدخلةٍ ما). هذا ما سيفعله ملف manifest السابق: لنفترض أنَّ كل صفحة HTML (صفحة المُدخلة أو النقاش أو التعديل أو تأريخ الصفحة) في ويكيبيديا تُشير إلى ملف manifest السابق؛ فعندما تزور أي صفحة تُشير إلى ملف manifest فسيقول متصفحك: «مهلًا، هذه الصفحة جزءٌ من تطبيق الويب يعمل دون اتصال، لكن هل أعرف شيئًا عن هذا التطبيق؟» إن لم يُنزِّل متصفحك ملف manifest المُحدَّد من قبل قط، فسيهِّيء «مخزن جديد للتطبيق» (appcache، اختصار للعبارة «application cache»)، وسيُنزِّل جميع الموارد المذكورة في ملف manifest، ثم سيُضيف الصفحة الحالية إلى مخزن التطبيق (appcache). إن كان يعرف متصفحك ملف manifest من قبل، فسيُضيف الصفحة الحالية إلى مخزن التطبيق (appcache) الموجود مسبقًا. وفي كلا الحالتين ستُضاف الصفحة التي زرتها إلى مخزن التطبيق. وهذا مهمٌ لأنَّه يعني أنَّك تستطيع بناء تطبيق ويب يُضيف الصفحات التي يزورها المستخدم تلقائيًا إلى مخزنه. فلستَ بحاجةٍ إلى وضع كل صفحة من صفحات HTML في ملف manifest للتخزين المؤقت. انظر الآن إلى قسم fallback في ملف manifest السابق، الذي يحتوي على سطرٍ وحيدٍ. القسم الأول من السطر (الذي يقع قبل الفراغ) ليس عنوان URL، وإنما نمط URL‏ (URL pattern)، حيث سيُطابِق المحرف / أي صفحة في موقعك وليس الصفحة الرئيسية فقط. فعندما تحاول زيارة صفحة ما عندما تكون غيرَ متصلٍ بالشبكة، فسيبحث متصفحك عنها في مخزن التطبيق (appcache)، فإن وجد المتصفحُ الصفحةَ في مخزن التطبيق (ﻷنك زُرتَها عندما كنتَ متصلًا بالشبكة، ومن ثم أُضيفَت الصفحة إلى مخزن التطبيق [appcache] في ذات الوقت)، فسيعرض المتصفح النسخة المُخزَّنة من الصفحة محليًا؛ إما إذا لم يجد متصفحك الصفحة في مخزن التطبيق، فبدلًا من عرض رسالة الخطأ، فسيعرض الصفحة ‎/offline.html التي تُمثِّل القسم الثاني في السطر المذكور في قسم fallback. في النهاية، لننظر إلى قسم الشبكة (network). يحتوي قسم الشبكة في ملف manifest السابق على سطرٍ فيه محرف وحيد (*)، هذا المحرف له معنىً خاص في قسم الشبكة. وهو يُدعى «online whitelist wildcard flag» وهي طريقة منمّقة لقول أنَّ كل شيءٍ غير موجودٍ في مخزن التطبيق (appcache) سيُنزَّل من عنوان الويب الأصلي لطالما هنالك اتصالٌ شبكيٌ بالإنترنت. هذا مهمٌ لتطبيقات الويب التي تعمل دون اتصال التي لا نريد تنزيلها كل صفحاتها ومواردها، وهذا يعني أنَّه أثناء تصفحك لموقع ويكيبيديا –الذي افترضنا جدلًا أنَّه يدعم العمل دون اتصال– مع اتصالٍ بالإنترنت، فسيحمِّل المتصفح الصور ومقاطع الفيديو والموارد الأخرى المضمَّنة بشكلٍ اعتيادي، حتى ولو كانت في نطاقٍ (domain) مختلف (هذا الأمر شائعٌ في المواقع الكبرى حتى لو لم تكن جزءًا من تطبيق ويب يعملُ دونَ اتصالٍ. صفحات HTML تولَّد وتُخدَّم محليًا في تلك الخواديم، لكن الصور والفيديو تُخدَّم عبر CDN في نطاقٍ آخر). لكن دون هذا المحرف البديل (wildcard)، فسيتصرّف تطبيق ويكيبيديا الذي افترضنا أنَّه يعمل دون اتصال تصرفاتٍ غريبة عندما تكون متصلًا بالشبكة. بشكلٍ خاص: لن يستطيع تنزيل أي صور أو مقاطع فيديو مخزَّنة على نطاقٍ خارجي. هل هذا المثال كاملٌ؟ لا، لأنَّ ويكيبيديا أكثر من مجرد صفحات HTML. فهي تستعمل موارد CSS و JavaScript وصور مشتركة في كل صفحة. فيجب أن تُذكَر تلك الموارد بشكلٍ صريحٍ في قسم CACHE:‎ من ملف manifest، لكي تُعرَض الصفحات عرضًا صحيحًا وتسلك سلوكًا سليمًا عندما تُشغَّل دون اتصال. لكن الغاية من قسم fallback تكمن عندما لا تنزِّل كامل صفحات تطبيق الويب الذي يحتوي على موارد لم تذكرها بصراحة في ملف manifest. ترجمة -وبتصرّف- لفصل Offline Web Applications من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: تطبيقات الويب التي تعمل دون اتصال – الجزء الثاني المقال السابق: التخزين المحلي (Local Storage) في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  17. كانت البرمجيات المكتبية تتفوق على تطبيقات الويب بإمكانية تخزين المعلومات محليًا تخزينًا دائمًا؛ حيث يوفِّر نظام التشغيل عادةً طبقةً وسيطةً لتخزين وقراءة بيانات خاصة بالتطبيق مثل الإعدادات وحالة التشغيل، وقد تُخزَّن هذه القيم في سجل النظام (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. اقرأ أيضًا المقال التالي: تطبيقات الويب التي تعمل دون اتصال – الجزء الأول المقال السابق: تحديد الموقع الجغرافي (GeoLocation) في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  18. سأقدِّم لك أداةً رائعةً للتصميم: أرأيت؟ ألم ترَ؟ إنها الفراغات البيضاء! حسنًا، ربما مزاحي لم يكن في محلّه، أعترف بذلك. أنا آسف. صحيحٌ أنَّ تعريفي للفراغات البيضاء لك لم يكن مثاليًا، لكن الفراغات البيضاء هي من أهم الأدوات في تصميم الويب، ولكن يغفل عنها الناس معظم الوقت. أعلمُ أنَّ من الممتع العمل على عناصر أخرى من تصميم الويب، مثل سمة الألوان، أو الخطوط، أو التخطيط العام للصفحة… خصوصًا عندما تُنشِئ مشروعًا لعميلٍ ما (ملاحظة شخصية: لا أظن أنَّ هنالك عميلًا في العالم يلقي بالًا لطريقة تحسين الفراغات البيضاء لموقعه الجديد!). ومع ذلك، قد تستفيد من الفراغات البيضاء خيرَ استفادة إن استطعت استخدامها استخدامًا صحيحًا. وسأتطرّف في هذا الدرس إلى آلية فعل ذلك. لماذا الفراغات البيضاء مهمة في تصاميم الويب بدايةً، الفراغات البيضاء ليست مفهومًا جديدًا في عالم التصميم، إذ استعمِلَت لقرون كالأداة رقم 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.
  19. تحديد الموقع الجغرافي هو آلية معرفة مكان وجودك في هذا العالم ومشاركة تلك المعلومات (اختياريًا) مع الأشخاص الذين تثق بهم؛ وهنالك أكثر من طريقة لمعرفة أين أنت: إما باستخدام عنوان 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 اقرأ أيضًا المقال التالي: التخزين المحلي (Local Storage) في HTML5 المقال السابق: التعامل مع التأريخ في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  20. أعتبرُ أنَّ شريط العنوان في المتصفح هو أشهرُ عنصرٍ من عناصر الواجهات الرسومية في العالم، إذ أصبحت تُعرَض روابط URL على اللوحات الإعلانية، وعلى جوانب الطرقات، وحتى في الكتابات على الجدران؛ مجتمعًا مع زر الرجوع إلى الخلف –أحد أهم الأزرار في المتصفح– ستحصل على مقدرةٍ على التنقل إلى الأمام وإلى الخلف في شبكة المعلومات الكبيرة التي نسميها الويب. الواجهة البرمجية للتعامل مع التأريخ في HTML5 هي طريقةٌ معياريةٌ لتعديل تأريخ (history) المتصفح باستخدام السكربتات. جزءٌ من هذه الواجهة البرمجية (التنقل في التأريخ) موجودٌ في الإصدارات السابقة من HTML؛ أما الأجزاء الجديدة في HTML5 تضمَّنت طرائق لإضافة مدخلات إلى تأريخ المتصفح، ولتغيير رابط URL الظاهر في شريط المتصفح (دون الحاجة إلى تحديث الصفحة)، وإضافة حدث سيُفعَّل عندما تُحذَف تلك المدخلات من المكدس (stack، وهذه هي آلية التعامل الداخلية مع التأريخ) بوساطة المستخدم عند ضغطه لزر الرجوع في المتصفح. وهذا يعني أنَّ رابط URL في شريط العنوان في المتصفح سيستمر بأداء عمله كمُعرِّف فريد للمورد الحالي (current resource)، حتى في التطبيقات التي تعتمد اعتمادًا كبيرًا على السكربتات التي لن تُجري تحديثًا كاملًا للصفحة. السبب وراء تعديل التأريخ لماذا تريد تعديل تأريخ المتصفح يدويًا؟ بكل بساطة، يمكن لرابطٍ بسيطٍ أن ينقلك إلى URL جديد؛ وهذه هي الطريقة التي عَمِلَ بها الويب لأكثر من 25 سنة، وسيستمر بذلك. هذه الواجهة البرمجية لن تحاول تقويض الويب، وإنما العكس. ففي السنوات الأخيرة، وجَدَ مطورو الويب طرائق جديدة ومثيرة لتخريب الويب دون أي مساعدة من المعايير الناشئة. صُمِّمَت واجهة التأريخ البرمجية في HTML5 للتأكّد من أنَّ روابط URL ستستمر بأداء وظيفتها وأن تبقى مفيدةً حتى في تطبيقات الويب التي تعتمد تمامًا على السكربتات. بالعودة إلى المفاهيم الأساسية، ما وظيفة رابط URL؟ إنَّه يُعرِّف موردًا فريدًا (unique resource). يمكنك إضافة رابط مباشر إليه، أو وضع علامة مرجعية إليه، ويمكن لمحركات البحث فهرسته، ويمكنك نسخه ولصقه في رسالةٍ عبر البريد الإلكتروني لشخصٍ ما، الذي يستطيع أن يضغط عليه ويرى نفس المورد الذي رأيته أنت. هذه الميزات الرائعة تُريك أهمية روابط URL. حسنًا، نحن نريد أن تكون للموارد المختلفة روابط URL فريدة. لكن المتصفحات تُعاني من قصورٍ أساسي: إذا غيّرتَ رابط URL، حتى باستخدام السكربتات، فستطلب من خادوم الويب البعيد تحديثًا لكامل الصفحة. وهذا يأخذ وقتًا ومواردَ، وسيبدو لك كم أنَّ ذلك مُبدِّدٌ للموارد إذا كانت الصفحة التي ستنتقل إليها شبيهةً جدًا بالصفحة الحالية. ستُنزَّل كل أجزاء الصفحة الجديدة، حتى الأجزاء المُماثِلة للصفحة الحالية. لا يوجد هنالك طريقةٌ لتغيير رابط URL وتنزيل نصف الصفحة فقط. أتت الواجهة البرمجية للتعامل مع التأريخ في HTML5 لحل هذه الإشكالية. فبدلًا من تحديث كامل الصفحة، يمكنك أن تستخدم سكربتًا لتنزيل «نصف صفحة». هذه الخدعة صعبة قليلًا وتحتاج إلى قليلٍ من العمل… لنقل أنَّ لديك صفحتان، الصفحة A و الصفحة B. الصفحتان متماثلتان بنسبة 90%؛ وهنالك 10% فقط من المحتوى يختلف بين الصفحتين. زار المستخدمُ الصفحةَ A، ثم حاول الانتقال إلى الصفحة B، لكن بدلًا من تحديث الصفحة تحديثًا كاملًا، حاولتَ اعتراض هذه العملية وإجراء الخطوات الآتية يدويًا: تحميل 10% من الصفحة B المختلفة عن A (ربما باستخدام XMLHttpRequest)، وهذا سيتطلب بعض التعديلات من جهة الخادوم لتطبيق الويب الخاص بك. إذ ستحتاج إلى كتابة شيفرة لكي تُعيد 10% فقط من الصفحة B المختلفة عن A، وربما تفعل ذلك عبر رابط URL مخفي أو عبر تمرير وسيط في الطلبية الذي لا يستطيع المستخدم النهائي رؤيته في الحالات العادية. تبديل المحتوى الذي تغيّر (عبر استخدام innterHTML أو دوال DOM الأخرى)، قد تحتاج أيضًا إلى إعادة ضبط أيّة دوال لمعالجة الأحداث المرتبطة بالعناصر الموجودة في المُحتوى المُبدَّل. تحديث شريط العنوان في المتصفح لكي يحتوي على رابط URL للصفحة B، وذلك عبر دالة خاصة من الواجهة البرمجية للتعامل مع التأريخ في HTML5 التي سأريك إياها بعد قليل. في نهاية هذه الخدعة (إذا طبّقتها تطبيقًا صحيحًا)، سينتهي الأمر بحصول المتصفح على شجرة DOM مماثلة لتلك التي سيحصل عليها لو انتقل إلى الصفحة B مباشرةً. وسيُصبِح العنوان في شريط المتصفح مساويًا لرابط URL للصفحة B، كما لو أنَّك انتقلت إلى الصفحة B مباشرةً. لكنك في الحقيقة لم تنتقل إلى الصفحة B ولم تُجرِ تحديثًا كاملًا للصفحة. وهذه هي الخدعة التي كنت أتحدث عنها. لأنَّ الصفحة النهاية مماثلةٌ تمامًا للصفحة B ولها نفس رابط URL للصفحة B، لكن المستخدم لم يلاحظ الفرق (ولم يكن ممنونًا لك على جهودك لتحسين تجربته مع التطبيق). طريقة تعديل التأريخ يوجد في واجهة التأريخ البرمجية عددٌ من الدوال في كائن window.history، بالإضافة إلى حدثٍ وحيد في الكائن window. يمكنك استخدامها لاكتشاف الدعم لواجهة التأريخ البرمجية، وهنالك دعمٌ لا بأس به من أغلبية المتصفحات الحديثة لهذه الواجهة البرمجية. IE Firefox Safari Chrome Opera iPhone Android 10+ 4.0+ 5.0+ 8.0+ 11.5+ 4.2.1+ *4.3+ * من المفترض أنَّ هنالك دعمٌ لواجهة التأريخ البرمجية في الإصدارات السابقة من متصفح أندرويد، لكن لوجود عدد كبير من العلل البرمجية في تطبيق هذا الدعم في إصدار 4.0.4، فسنعتبر أنَّ الدعم «الحقيقي» لواجهة التأريخ البرمجية قد بدأ منذ الإصدار 4.3. «Dive Into Dogs» هو مثالٌ بسيطٌ وعمليٌ لكيفية الاستفادة من الواجهة البرمجية للتأريخ في HTML5. حيث يُبرِز مشكلةً شائعةً: مقالةٌ طويلةٌ يرتبط بها معرضُ صورٍ. سيؤدي الضغط على رابط «Next» أو «Previous» في معرض الصور إلى تحديث الصورة آنيًا وتحديث رابط URL في شريط العنوان في المتصفح دون الحاجة إلى تحديث كامل الصفحة. أما في المتصفحات التي لا تدعم واجهة التأريخ البرمجية –أو في المتصفحات الداعمة لها، لكن المستخدم عطَّل السكربتات– سيعمل الرابطان «Next» و «Previous» كرابطين اعتياديين، ويأخذانك إلى الصفحة التالية بعد تحديث كامل الصفحة. وهذا يثير النقطة المهمة الآتية: لنتعمّق في مثال «Dive Into Dogs» ونرى كيف يعمل. هذه هي الشيفرة التي تُستعمل لعرض صورة وحيدة: <aside id="gallery"> <p class="photonav"> <a id="photonext" href="casey.html">Next ></a> <a id="photoprev" href="adagio.html">< Previous</a> </p> <figure id="photo"> <img id="photoimg" src="gallery/1972-fer-500.jpg" alt="Fer" width="500" height="375"> <figcaption>Fer, 1972</figcaption> </figure> </aside> لا يوجد شيءٌ غير اعتياديٍ فيما سبق. الصورة نفسها هي عنصر <img> موجودٌ ضمن عنصر <figure>، جميع الروابط هي عناصر <a>، وكل شيء محتوىً في عنصر <aside>، من المهم أن نلاحظ أنَّ هذه الروابط العادية تعمل عملًا صحيحًا. جميع الشيفرات التي تتحكم بالتأريخ موجودةٌ بعد سكربت لاكتشاف الدعم، فإن لم يكن متصفح المستخدم داعمًا للواجهة البرمجية للتأريخ، فلا حاجة إلى تنفيذ الشيفرات المتعلقة بها، وكذلك الأمر للمستخدمين الذي عطَّلوا تنفيذ السكربتات بالمجمل. الدالة الرئيسية تحصل على كل رابط من تلك الروابط وتمرِّره إلى الدالة addClicker()‎، التي عملها هو إعداد دالة خاصة للتعامل مع الحدثclick. function setupHistoryClicks() { addClicker(document.getElementById("photonext")); addClicker(document.getElementById("photoprev")); } هذه هي دالة addClicker()‎ التي تأخذ عنصر <a> وتُضيف إليه دالة للتعامل مع الحدث click، التي يحدث فيها أمرٌ مثيرٌ للاهتمام. function addClicker(link) { link.addEventListener("click", function(e) { swapPhoto(link.href); history.pushState(null, null, link.href); e.preventDefault(); }, false); } الدالة swapPhoto()‎ تقوم بأول خطوتين من الخطوات الثلاث التي تحدثنا عنها. أول قسم من الدالة swapPhoto()‎ يأخذ جزءًا من URL لرابط التنقل –أي casey.html و adegio.html وهكذا…– ويبني رابط URL جديد يُشير إلى صفحةٍ مخفيةٍ التي لا تحتوي إلا على الشيفرة اللازمة لعرض الصورة التالية. function swapPhoto(href) { var req = new XMLHttpRequest(); req.open("GET", "http://diveintohtml5.org/examples/history/gallery/" + href.split("/").pop(), false); req.send(null); هذا مثالٌ عن الشيفرات التي تُنتِجها تلك الصفحات (يمكنك التأكد من ذلك في متصفحك بزيادة الرابط السابق مباشرةً). <p class="photonav"> <a id="photonext" href="brandy.html">Next ></a> <a id="photoprev" href="fer.html">< Previous</a> </p> <figure id="photo"> <img id="photoimg" src="gallery/1984-casey-500.jpg" alt="Casey" width="500" height="375"> <figcaption>Casey, 1984</figcaption> </figure> هل تبدو الشيفرة السابقة مألوفةً لديك؟ هذا طبيعي، لأنها نفس الشيفرة التي استخدمناها في صفحتنا الرئيسية لعرض أول صورة. القسم الثاني من دالة swapPhoto()‎ يجري الخطوة الثانية من الخطوات الثلاث التي تحدثنا عنها: وضع الشيفرة الجديدة التي نُزِّلَت في الصفحة الحالية، تذكَّر أنَّ هنالك عنصر <aside> محيطٌ بالصورة والشرح التوضيحي لها، لذلك تكون عملية إضافة شيفرة الصورة الجديدة بسيطةً عبر ضبط خاصية innterHTML لعنصر <aside> إلى خاصية responseText من كائن XMLHttpRequest. if (req.status == 200) { document.getElementById("gallery").innerHTML = req.responseText; setupHistoryClicks(); return true; } return false; } لاحظ أيضًا استدعاء الدالة setupHistoryClicks()‎، وهذا ضروريٌ لإعادة ضبط دوال التعامل مع الحدث click في روابط التنقل الجديدة، لأن ضبط المحتوى باستخدام innerHTML سيمسح كل آثار الروابط القديمة مع دوال التعامل مع أحداثها. لنعد الآن إلى الدالة addClicker()‎، فبعد النجاح بتغيير الصورة، هنالك خطوةٌ إضافيةٌ علينا عملها من الخطوات الثلاث: ضبط رابط URL في شريط عنوان المتصفح دون تحديث الصفحة. history.pushState(null, null, link.href); تأخذ الدالة history.pushState()‎ ثلاثة وسائط: state: يمكن أن يكون أي بنية من بُنى بيانات JSON، وستُمرَّر إلى الدالة التي تتعامل مع الحدث popstate، الذي ستتعلم المزيد من المعلومات عنه بعد قليل. لا نحتاج إلى تتبع أي حالة في هذا المثال، لذا سأترك القيمة مساويةً إلى null. title: يمكن أن يكون أي سلسلة نصية. لكن هذا الوسيط غير مدعوم من أغلبية المتصفحات الرئيسية، فإذا أردت ضبط عنوان الصفحة، فعليك تخزينه في الوسيط state ثم ضبطه يدويًا في الدالة التي تتعامل مع الحدث popstate. url: يمكن أن يكون أي رابط URL، وهو الرابط الذي سيظهر في شريط العنوان في متصفحك. استدعاء الدالة history.pushState سيؤدي إلى تغيير رابط URL الظاهر في شريط العنوان في المتصفح على الفور، لكن أليست هذه نهاية القصة؟ ليس تمامًا، بقي علينا أن نتحدث عمّا سيحصل إذا ضغط المستخدم على الزر المهم «الرجوع». الحالة العادية عندما يزور المستخدم صفحة جديدة (بتحميلها كلها) هي إضافة المتصفح لرابط URL الجديد في «مكدس» التأريخ (history stack) ثم يُنزِّل ويعرض الصفحة الجديدة. وعندما يضغط المستخدم على زر الرجوع إلى الخلف، فسيزيل المتصفح الصفحة الحالية من مكدس التأريخ ويعرض الصفحة التي تسبقها، لكن ماذا سيحدث عندما عبثتَ بهذه الآلية لكي تتفادى تحديثًا لكامل الصفحة؟ حسنًا، لقد زيّفتَ «التقدم إلى الأمام» إلى رابط URL جديد، وحان الوقت الآن لتزييف «الرجوع إلى الخلف» إلى رابط URL السابق. والمفتاح نحو تزييف «الرجوع إلى الخلف» هو الحدث popstate. window.addEventListener("popstate", function(e) { swapPhoto(location.pathname); } بعد أن استخدمتَ الدالة history.pushState()‎ لإضافة رابط URL مزيّف في مكدس التأريخ الخاص بالمتصفح، سيُطلِق المتصفح الحدث popstate في الكائن window عندما يضغط المستخدم على زر الرجوع. وهذه هي فرصتك لإكمال عملك في إيهام المستخدم أنَّه انتقل فعلًا إلى تلك الصفحة. في هذا المثال، عملية إعادة الصفحة السابقة بسيطةٌ جدًا، فكل ما عليك فعله هو إعادة الصورة الأصلية، وذلك باستدعاء الدالة swapPhoto()‎ مع تمرير رابط URL الحالي لها. لأنَّه عند استدعاء الدالة التي تُعالِج الحدث popstate، يكون رابط URL الظاهر في المتصفح قد تغيّر إلى رابط URL السابق. وهذا يعني أيضًا أنَّ قيمة الخاصية العامة location قد حُدِّثَت لرابط URL السابق. لكي أساعدك في تخيل الوضع، دعني أتلو عليك العملية «السحرية» من بدايتها إلى نهايتها: يُحمِّل المستخدم الصفحة http://diveintohtml5.org/examples/history/fer.html ويشاهد القصة وصورةً للكلب Fer. يضغط المستخدم على الرابط المُعَنوَن «Next»، الذي هو عنصر <a> تكون قيمة خاصية herf فيه هي http://diveintohtml5.org/examples/history/casey.html. بدلًا من الانتقال إلى http://diveintohtml5.org/examples/history/casey.html مباشرةً وتحديث الصفحة تحديثًا كاملًا، فستعترض دالة خاصة للحدث click عملية الضغط على عنصر <a> وتُنفِّذ شيفرةً خاصةً بها. تستدعي تلك الدالة التي تُعالِج الحدث click الدالةَ swapPhoto()‎، التي تُنشِئ كائن XMLHttpRequest لكي ينُزِّل جزء HTML الموجود في http://diveintohtml5.org/examples/history/gallery/casey.html بشكل تزامني. الدالة swapPhoto()‎ تضبط الخاصية innerHTML للعنصر الذي يحوي الصورة (عنصر <aside>)، وبهذا ستُبدَّل صورة Casey بصورة Fer. في النهاية، ستستدعي الدالةُ التي تتعامل مع الحدث click الدالةَ history.pushState()‎ لتغيير رابط URL يدويًا في شريط عنوان المتصفح إلى http://diveintohtml5.org/examples/history/casey.html. يضغط المستخدم على زر الرجوع في المتصفح. يلاحظ المتصفح أنَّ رابط URL قد تغيّر يدويًا وأُضيف إلى مكدس التأريخ (عبر الدالة history.pushState()‎) وبدلًا من الانتقال إلى رابط URL السابق وإعادة تحديث الصفحة، فسيُحدِّث المتصفح الرابط الموجود في شريط العنوان إلى رابط URL للصفحة السابقة (http://diveintohtml5.org/examples/history/fer.html) ثم يُطلِق الحدث popstate. الدالة التي تُعالِج الحدث popstate ستستدعي الدالة swapPhoto()‎ مرةً أخرى، لكن هذه المرة مع تمرير الرابط القديم إليها الذي أصبح موجودًا الآن في شريط عنوان المتصفح. ثم باستخدام XMLHttpRequest مرةً أخرى، ستُنزِّل الدالة swapPhoto()‎ جزءًا من صفحة HTML الموجودة في http://diveintohtml5.org/examples/history/gallery/fer.html ثم ستضبط الخاصية innerHTML للعنصر الذي يحوي الصورة (عنصر <aside>)، وبهذا ستُبدَّل صورةFer بصورة Casey. اكتملت خدعتنا، جميع الأدلة الظاهرة (محتوى الصفحة، وعنوان URL في المتصفح) تُشير إلى أنَّ المستخدم قد انتقل إلى الأمام صفحةً وإلى الخلف صفحةً. لكن لم يحصل تحديثٌ كاملٌ للصفحة. مصادر إضافية Session history and navigation في معيار HTML5 Manipulating the browser history على Mozilla Developer Center استعراض بسيطة لواجهة التأريخ البرمجية Using HTML5 today التي تشرح كيف يستعمل موقع فيسبوك الواجهة البرمجية للتأريخ The Tree Slider التي تشرح استعمال موقع GitHub للواجهة البرمجية للتأريخ History.js هي مكتبةٌ للتعامل مع التأريخ في المتصفحات الحديثة والقديمة ترجمة -وبتصرّف- لفصل History من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: تحديد الموقع الجغرافي (GeoLocation) في HTML5 المقال السابق: إضافة مقاطع الفيديو عبر العنصر <video> في HTML5 النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  21. سنلقي في هذا الدرس نظرةً إلى آلية التعامل مع الأخطاء و الإشارات أثناء تنفيذ السكربتات. يُقاس الفرق بين البرمجة الجيدة والتعيسة عادةً بمدة استقرار البرنامج ومرونته، أي قابلية تعامل البرنامج مع الحالات التي لا تسير فيها الأمور على ما يرام. حالة الخروج تتذكر من دروسنا السابقة أنَّ كل برنامج مكتوب بطريقة جيدة سيُعيد حالة خروج عندما ينتهي تنفيذه؛ فإذا انتهى تنفيذ البرنامج بنجاح، فستكون حالة الخروج مساويةً للصفر، وإن كانت حالة الخروج مساوية لقيمة مختلفة عن الصفر، فهذا يدلّ أنَّ البرنامج قد واجهة مشكلةً ما منعته من إتمام مهمته. من المهم جدًا التحقق من حالة خروج البرامج التي تستدعيها في سكربتاتك، ومن المهم أيضًا أن تُعيد سكربتاتك حالة خروج مفيدة عندما تنتهي. لقد مرّ عليّ مدير أنظمة يونكس قد كتب سكربتًا لنظام إنتاجي يحتوي على السطرين الآتيين: # 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.
  22. تعرفنا في الدروس السابقة على صيغ ترميز الفيديو والصوت و كيفية ترميز مقاطع الفيديو باستخدام عدة صيغ. حسنًا، يُفترَض أنَّ هذه السلسلة عن 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. اقرأ أيضًا المقال التالي: التعامل مع التأريخ في HTML5 المقال السابق: ترميز مقاطع الفيديو بعدة صيغ النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  23. هنالك أدواتٌ عديدةٌ لترميز الفيديو، وهنالك خياراتٌ كثيرةٌ للترميز تؤثر على جودة الفيديو؛ فإن لم تكن تريد فهم كل شيءٍ متعلقٍ بترميز الفيديو فعليك بهذا المقال. ترميز الفيديو باستخدام Miro Video Converter Miro Video Converter هو برنامج مفتوح المصدر مُرخَّص برخصة GPL لترميز الفيديو بعدِّة صيغ، يمكنك تنزيله لنظامَي Mac OS X أو ويندوز. وهو يدعم التحويل إلى جميع الصيغ التي ذكرناها سابقًا في الدرس السابق، لكنه لا يوفر خيارات سوى اختيار ملف الفيديو والصيغة التي سيحوَّل إليها. يمكنه نظريًا قبول أي صيغة من صيغ الفيديو كملف مصدري، بما في ذلك فيديو DV الذي تُنتِجه كاميرات الفيديو الرقمية من فئة المستهلكين. يُنتِج البرنامج مقاطع فيديو ذات جودة مقبولة؛ ولأن هذا البرنامج لا يوفر لك خياراتٍ كثيرة، فليس عندك حل سوى تغيير البرنامج إن لم تعجبك مقاطع الفيديو الناتجة من البرنامج. عليك أولًا تشغيل برنامج Miro Video Converter: الشكل 1: واجهة برنامج Miro Video Converter الرئيسية انقر فوق Choose file واختر ملف الفيديو المصدري الذي تريد ترميزه. الشكل 2: اختيار مقطع فيديو اضغط فوق القائمة المنسدلة Pick a Device or Video Format التي فيها قائمة بـمختلف الأجهزة والصيغ. لكننا مهتمين بثلاثةٍ من تلك العناصر: (WebM (vp8 هو فيديو WebM (أي فيديو VP8 وصوت Vorbis في حاوية WebM). Theora هو فيديو Theora وصوت Vorbis في حاوية Ogg. iPhone هو فيديو H.264 ذو النمط Baseline وصوت AAC ذو النمط low-complexity في حاوية MP4. اختر WebM أولًا. الشكل 3: اختيار صيغة WebM اضغط فوق زر Convert وسيبدأ برنامج Miro Video Converter بترميز الفيديو مباشرةً. سيُسمى الملف الناتج باسم SOURCEFILE.webm وسيُحفَظ في نفس مجلد ملف الفيديو المصدري. الشكل 4: ستُحدِّق في هذه الشاشة لفترةٍ طويلة سترجع إلى الشاشة الرئيسية بعد أن ينتهي الترميز، اختر Theora في هذه المرة من قائمة الأجهزة والصيغ. الشكل 5: حان الوقت لترميز Theora اضغط على زر Convert مرةً أخرى لترميز الفيديو بصيغة Theora؛ وسيُسمَّى الملف الناتج باسم SOURCEFILE.theora.ogv وسيُحفَظ في نفس مجلد المقطع الأصلي. الشكل 6: اذهب واشرب كوبًا من القهوة في النهاية، عليك ترميز المقطع بمرماز H.264 المتوافق مع iPhone وذلك باختيار iPhone من قائمة الأجهزة والصيغ. الشكل 7: اختر iPhone وليس iPhone 4 سيعطيك برنامج Miro Video Converter خيارًا عند ترميز فيديو متوافق مع هواتف iPhone هو إرسال المقطع المُرمَّز إلى مكتبتك في iTunes. القرار عائدٌ إليك هنا، لكنه ليس ضروريًا لنشره على الويب. الشكل 8: لا ترسل الملف إلى iTunes اضغط على زر Convert السحري وانتظر، سيُسمَّى الناتج باسم SOURCENAME.iphone.mp4 وسيُحفَظ بنفس مجلد الملف المصدر. الشكل 9: تصفح موقع فيسبوك قليلًا أو افعل شيئًا مفيدًا يجب أن يكون لديك ثلاثة ملفات فيديو بجانب ملفك الأصلي؛ إن أعجبتك جودة الفيديو فانتقل إلى الدرس القادم لترى كيف تجمِّعها مع بعضها في عنصر <video> وحيد الذي يعمل في جميع المتصفحات. أما إذا كنت تريد تعلم المزيد حول الأدوات الأخرى للترميز، فأكمل القراءة. ترميز الفيديو باستخدام Firefogg ملاحظة: سأستخدم المصطلح "فيديو Ogg" في هذا القسم اختصارًا للعبارة "فيديو Theora مع صوت Vorbis في حاوية Ogg"، وهذه تجميعة من المرمازات + حاوية التي تعمل دون إضافات في متصفحَي Firefox و Google Chrome. Firefogg هي إضافة مفتوحة المصدر مرخَّصة برخصة GPL لمتصفح Firefox لترميز فيديو Ogg. يجب أن يكون لديك إصدار Firefox 3.5 أو ما بعده لتثبيت هذه الإضافة، وذلك بزيادة موقعها الرسمي firefogg.org. الشكل 10: صفحة Firefogg الرئيسية اضغط على زر "Install Firefogg"، وسيسألك Firefox إذا كنت تريد السماح لهذه الموقع بتثبيت إضافة (extension). اضغط على "Allow" للمتابعة. الشكل 11: السماح بتثبيت Firefogg سيُظهِر لك Firefox نافذة تثبيت الإضافات الاعتيادية. اضغط على "Install Now" للمتابعة. الشكل 12: تثبيت Firefogg اضغط على "Restart Firefox" لإكمال التثبيت. الشكل 13: أعد تشغيل Firefox سيؤكد لك موقع firefogg.org أنَّك ثبتت Firefogg تثبيتًا سليمًا بعد إعادة تشغيل Firefox. الشكل 14: نجاح عملية تثبيت Firefogg اضغط على "Make Ogg Video" لبدء عملية الترميز. الشكل 15: البدء في ترميز مقاطع الفيديو اضغط على "Select file" لتحديد ملف الفيديو المصدري. الشكل 16: اختيار ملف الفيديو الذي تريد تحويله هنالك ستة أقسام في Firefogg: Presets (إعدادات الضبط المسبقة): الخيار الافتراضي هو "web video"، وهو جيدٌ لأغراضنا. Encoding range (مجال الترميز): قد يستغرق ترميز مقطع فيديو وقتًا طويلًا. وقد ترغب في البداية أن تُرمِّز جزءًا من الفيديو (لنقل، أول 30 ثانية) حتى تجد تركيبة الإعدادات التي تعجبك. Basic quality and resolution control (التحكم الأساسي بالجودة والدقة): يحتوي هذا القسم على أغلبية الخيارات المهمة. Metadata (البيانات الوصفية): لن أشرحها هنا، لكنك تستطيع إضافة البيانات الوصفية إلى مقاطع الفيديو التي تُرمِّزها مثل العنوان (title) والمؤلف (author). ربما أضفتَ من قبل البيانات الوصفية إلى مجموعتك الموسيقية المُفضَّلة عبر iTunes أو مدير موسيقي آخر. هذا القسم يُجسِّد هذه الفكرة. Advanced video encoding controls (خيارات ترميز الفيديو المتقدمة): لا تعبث بهذه الخيارات إلا إن كنت تعلم ما الذي تفعله (يوفِّر Firefogg مساعدةً تفاعليةً لأغلبية هذه الخيارات؛ اضغط على رمز "i" بجوار كل خيار لكي تتعلم المزيد عنه). Advanced audio encoding controls (خيارات ترميز الصوت المتقدمة): أكرر مرةً أخرى، لا تعبث بهذه الخيارات إلا إذا كنت تعلم ما الذي تفعل. الشكل 17: مختلف أقسم Firefogg الخيارات التي سأشرحها موجودةٌ في قسم "Basic quality and resolution control". وهي تحتوي على كل الخيارات المهمة: Video Quality (جودة الفيديو): تُقاس الجودة هنا بمقياس من 0 (الجودة الأدنى) إلى 10 (أعلى جودة). كلما ازدادت جودة الفيديو كلما كبرت المساحة التخزينية للملف الناتج، لذلك عليك أن تجرب قليلًا لكي تُحدِّد ما هي نسبة المساحة التخزينية/الجودة التي تلائم احتياجاتك. Audio Quality (جودة الصوت): تُقاس بمقياس من -1 (أقل جودة) إلى 10 (أعلى جودة). وكلما ازدادت جودة الفيديو كلما كبرت المساحة التخزينية للملف الناتج مثل خيار جودة الفيديو السابق. Video Codec (مرماز الفيديو): يجب أن تكون قيمة هذا الخيار هي "theora" دائمًا. Audio Codec (مرماز الصوت): يجب أن تكون قيمة هذا الخيار هي "vorbis" دائمًا. Video Width and Video Height (عرض الفيديو وارتفاعه): تكون قيمها الافتراضية هي العرض والارتفاع الحقيقي للمقطع المصدري الذي تُرمِّزه. إذا أردت إعادة تحجيم الفيديو أثناء ترميزه، فتستطيع تعديل قيمة العرض (أو الارتفاع) في هذا الخيار؛ ثم سيُعدِّل Firefogg البُعد الآخر للحفاظ على نسبة البُعدَين الأصلية (لكي لا تتشوه صورة مقطع الفيديو). الشكل 18: التحكم الأساسي بالجودة والدقة سأجرب في هذا المثال إعادة تحجيم مقطع الفيديو إلى نصف عرضه الأصلي، لاحظ كيف يُعدِّل Firefogg الارتفاع تلقائيًا لكي يتناسب مع العرض. الشكل 19: تعديل عرض وارتفاع مقطع الفيديو بعد أن تأخذ وقتك بتعديل الخيارات السابق، فاضغط على "Save Ogg" لبدء عملية الترميز؛ وسيسألك Firefogg عن اسم الملف الناتج. الشكل 20: حفظ الفيديو الناتج سيُظهِر لك Firefogg شريطًا لكي تعرف مقدار تقدم عملية الترميز؛ كل ما عليك فعله هو الانتظار (ثم الانتظار، ثم الانتظار). الشكل 21: شريط يُظهِر تقدم عملية الترميز ترميز عدة مقاطع إلى Ogg دفعة واحدة باستخدام ffmpeg2theora ملاحظة: كما في القسم السابق، سأستخدم المصطلح "فيديو Ogg" في هذا القسم اختصارًا للعبارة "فيديو Theora مع صوت Vorbis في حاوية Ogg"، وهذه تجميعة من المرمازات + حاوية التي تعمل دون إضافات في متصفحَي Firefox و Google Chrome. إذا كنتَ تتطلع نحو ترميز الكثير من ملفات فيديو Ogg وتريد أتمتة العملية، فعليك حكمًا أن تلقي نظرةً إلى ffmpeg2theora. ffmpeg2theora هو برنامج مفتوح المصدر مرخَّص برخصة GPL لترميز الفيديو بصيغة Ogg. هنالك ملفات تنفيذية متوفرة لأنظمة Mac OS X وويندوز وتوزيعات لينكس الحديثة؛ ويقبل ffmpeg2theora ترميز مقاطع الفيديو بأي صيغة تقريبًا، بما في ذلك فيديو DV الذي تُنتِجه كاميرات الفيديو الرقمية من فئة المستهلكين. عليك أن تستدعي ffmpeg2theora من سطر الأوامر لتستعمله (في نظام Mac OS X افتح "Applications > Utilities > Terminal"، أما في ويندوز، فافتح "Start Menu > Programs > Accessories > Command Prompt" أو بالضغط على مفتاح ويندوز+R ثم كتابة "cmd"). يقبل ffmpeg2theora عددًا كبيرًا من خيارات سطر الأوامر (جرب كتابة ffmpeg2theora --help لتعلم المزيد عنها)، لكنني سأركِّز على ثلاثةٍ منها: ‎--video-quality Q: حيث "Q" يمثل رقم من 0 إلى10. ‎--audio-quality Q: حيث "Q" يمثل رقم من -2 إلى 10. ‎--max_size=WxH: حيث "W" و "H" هما العرض والارتفاع الأقصى للمقطع الناتج. (الرمز "x" بينهما هو الحرف "x" العادي وليس إشارة الضرب "×"). سيُعيد ffmpeg2theora تحجيم مقطع الفيديو بحيث يكون بُعداه متناسبين لكي يتسع ضمن الأبعاد المُحدَّدة، لذا قد تكون أبعاد المقطع النهائي أصغر من WxH. على سبيل المثال، ترميز فيديو بأبعاد ‎720×480 مع الخيار ‎--max_size 320x240 سيُنتج مقطعًا أبعاده 320‎×213. إذًا، هذه هي طريقة ترميز مقطع فيديو بنفس الإعدادات التي استخدمناها في القسم السابق: you@localhost$ ffmpeg2theora --videoquality 5 --audioquality 1 --max_size 320x240 pr6.dv سيُحفَظ مقطع الفيديو الناتج في نفس مجلد مقطع الفيديو الأصلي بعد إضافة اللاحقة ‎.ogv له. يمكنك اختيار مسار أو اسم ملف مختلف بتمرير الخيار ‎--output=/path/to/encoded/video إلى ffmpeg2theora. ترميز فيديو H.264 باستخدام HandBrake ملاحظة: سأستخدم المصطلح "فيديو H.264" في هذا القسم اختصارًا للعبارة "فيديو H.264 بنمط (profile) ‏Baseline مع صوت AAC بنمط low-complexity في حاوية MPEG-4"، وهذه تجميعة من المرمازات+حاوية التي تعمل دون إضافات في Safari، و Adobe Flash، وفي هواتف iPhone، والهواتف العاملة بنظام أندرويد. إذا غضضنا النظر عن المشاكل في الترخيص فإن أسهل طريقة لترميز فيديو H.264 هي استخدام HandBrake. ‏HandBrake هو برمجية مفتوحة المصدر مرخصة برخصة GPL لترميز فيديو H.264 (كان تستطيع ترميز صيغ فيديو أخرى فيما سبق، لكن المطورين أسقطوا الدعم عن الصيغ الأخرى لتركيز جهودهم على صيغة H.264). تتوفر ملفات تنفيذية لويندوز و Mac OS X وتوزيعات لينكس الحديثة. يأتي برنامج HandBrake بنسختين: رسومية، وسطرية (أي تعمل من سطر الأوامر). سأشرح لك الواجهة الرسومية أولًا، ثم سأريك كيف يمكن تحويل الإعدادات إلى نسخة سطر الأوامر. أول شيءٍ ستفعله بعد أن تبدأ برنامج HandBrake هو اختيار ملف الفيديو المصدري. اضغط على زر "Source" ثم "Video File" لكي تختار ملفًا. يمكن أن يقبل HandBrake نظريًا أي صيغة من صيغ الفيديو، بما في ذلك فيديو DV الذي تُنتِجه كاميرات الفيديو الرقمية من فئة المستهلكين. الشكل 22: اختر ملف الفيديو الذي تريد ترميزه سيشتكي HandBrake من أنَّك لم تضبط مجلدًا افتراضيًا لتحفظ فيه نواتج ترميز ملف الفيديو. يمكنك أن تتجاهل ذاك التحذير، أو أن تفتح نافذة الخيارات ("options") الواقعة تحت قائمة "Tools" ثم تضبط المجلد الافتراضي. الشكل 23: تجاهل رسالة التحذير الظاهرة هنالك قائمة بأنماط الضبط المسبق (presets) على الجانب الأيمن من البرنامج، اختر منها نمط "iPhone & iPod Touch" الذي سيضبط أغلبية الخيارات التي ستحتاج لها. الشكل 24: اختر نمط iPhone أحد الخيارات المهمة المُعطَّلة افتراضية هو الخيار "Web optimized"، تفعيل هذا الخيار سيؤدي إلى إعادة ترتيب بعض البيانات الوصفية داخل الفيديو المُرمَّز لكي تستطيع مشاهدة بداية الفيديو في أثناء تنزيل الباقي في الخلفية. أنصحك –وبشدة– أن تُفعِّل هذا الخيار دومًا، حيث لا يؤثر على جودة أو حجم الملف الناتج، لذا لن يكون هنالك أي سبب لعدم تفعيله. الشكل 25: فعِّل الخيار "Web optimized" دومًا يمكنك أن تضبط العرض والارتفاع الأقصى لمقطع الفيديو المُرمَّز في لسان "Picture"؛ ويجب عليك دومًا تفعيل الخيار "Keep Aspect Ratio" (أي المحافظة على تناسب الأبعاد) لضمان أنَّ HandBrake لن يشوه مقطع الفيديو عند إعادة تحجيمه. الشكل 26: اضبط العرض والارتفاع يمكنك أن تضبط أربعة خيارات مهمة في لسان "Video": Video Codec: تأكد أنَّ قيمة هذا الخيار هي (H.264 (x264. ‎2-Pass Encoding: إذا فعَّلت هذا الخيار، فسيُشغِّل HandBrake مُرمِّزَ الفيديو مرتين؛ سيحلِّل الفيديو في أول مرة فقط باحثًا عن أشياء مثل تركيبات الألوان، والحركة، ومكان انتهاء المشاهد. وفي المرة الثانية سيُرمِّز الفيديو فعليًا مستخدمًا المعلومات التي جمعها في أول مرة. وكما توقعت، سيستغرق ذلك ضعف المدة الزمنية، لكنه يؤدي إلى دقة أفضل دون زيادة في مساحة الملف التخزينية. أُفعِّل هذا الخيار دومًا عند ترميز H.264. وعليك فعل ذلك ما لم تكن ترمِّز مقاطع الفيديو 24 ساعة في اليوم. Turbo First Pass: بعد أن تُفعِّل "‎2-Pass Encoding" فيمكنك توفير بعض الوقت بتفعيل الخيار "Turbo First Pass"؛ الذي يقلل حجم العمل المُنجَز في أول مرور على الفيديو (أي مرحلة تحليل المقطع)، في حين أنَّه يقلل الجودة بشكل بسيط جدًا. أُفعِّل هذا الخيار عادةً، لكن إن كانت جودة الفيديو مهمةً جدًا لك، فعليك إبقاؤه معطلًا. Quality: هنالك عدِّة طرق لتحديد "جودة" مقاطع الفيديو: يمكنك تحديد المساحة التخزينية التي تتوقعها للملف الناتج، وسيحاول HandBrake جاهدًا أن يضمن أنَّ الملف الناتج ليس أكبر من المساحة المُحدَّدة. أو يمكنك أن تُحدِّد معدل "البث" (bitrate) الوسطي، الذي هو عدد البتات اللازمة لتخزين ثانية واحدة من الفيديو (ولقد سمي معدل البث "الوسطي" لأن بعض الثواني تحتاج إلى بتات أكثر من غيرها). أو يمكنك أن تُحدِّد جودةً ثابتةً على مقياسٍ من 0 إلى 100%. كلما ازدادت النسبة ستزداد الجودة لكن الملفات ستستهلك مساحة تخزينية أكبر. لا يمكن أن أعطيك جوابًا واحدًا عن ضبط الجودة الذي عليك استخدامه. س: هل يمكنني المرور مرتين (two-pass) في فيديو Ogg أيضًا؟ ج: نعم، لكن بسبب الاختلافات الجوهرية في طريقة عمل المُرمِّز، فمن المرجح أنَّك لن تحتاج إلى فعل ذلك. المرور مرتين عند الترميز بمرماز H.264 يضمن لك (في الغالبية العظمى من الحالات) زيادة في جودة مقطع الفيديو. أما المرور مرتين في فيديو Ogg مفيدٌ فقط إذا كنت تحاول أن يكون المقطع الناتج ذو مساحةٍ تخزينيةٍ محدَّدة (ربما سيثير هذا الأمر اهتمامك، لكن ذلك ليس غرض الأمثلة المذكورة هنا، ولا أرى أنَّ ذلك مفيدٌ جدًا لمقاطع الفيديو التي ستُنشَر على الويب). لأفضل جودة لفيديو Ogg، استخدم خيارات جودة الفيديو، ولا تعبأ بتقنية المرور مرتين. لقد اخترت في هذا المثال معدل بث وسطي هو 600 كيلوبت/ثانية، الذي يُعتبَر مرتفعًا بالنسبة إلى فيديو بأبعاد 320x240، واخترت أيضًا ترميز مع المرور مرتين، ويكون أول مرور "سريعًا" (turbo). الشكل 27: خيارات جودة الفيديو لا يفترض عليك تعديل أي شيء في لسان "Audio"؛ أما لو كان لمقطع الفيديو المصدري أكثر من مسار صوتي، فربما تريد اختيار أيُّ المسارات تريدها في الفيديو الناتج. إذا كان محتوى الفيديو في مجمله عن شخصٍ يتحدث (على النقيض من الموسيقى أو الأصوات العامة)، فيمكنك تخفيض معدل البث للصوت إلى 96 كيلوبت/ثانية أو ما شابهه. فيما عدا الحالتين السابقتين، تكون القيم الافتراضية المأخوذة من نمط "iPhone" جيدةً عمومًا. الشكل 28: خيارات جودة الصوت ثم اضغط على زر "Browse" واختر مجلدًا واسم ملف لتحفظ فيه مقطع الفيديو الناتج. الشكل 29: اختر اسم الملف الناتج في النهاية اضغط على "Start" لبدء ترميز الفيديو. الشكل 30: بدء عملية ترميز الفيديو سيعرض HandBrake بعض الإحصائيات عن تقدم عملية ترميز مقطع الفيديو. الشكل 31: "إنَّ غدًا لناظره قريب" ترميز عدة مقاطع إلى H.264 دفعة واحدة باستخدام HandBrake ملاحظة: كما في القسم السابق، سأستخدم المصطلح "فيديو H.264" في هذا القسم اختصارًا للعبارة "فيديو H.264 بنمط (profile) ‏Baseline مع صوت AAC بنمط low-complexity في حاوية MPEG-4"، وهذه تجميعة من المرمازات+حاوية التي تعمل دون إضافات في Safari، و Adobe Flash، وفي هواتف iPhone، والهواتف العاملة بنظام أندرويد. يأتي HandBrake بنسخة سطرية (أي تعمل من سطر الأوامر) كما هو الحال في ffmpeg2theora، نسخة سطر الأوامر من HandBrake توفر عددًا هائلًا من الخيارات (اكتب HandBrakeCLI --help لتقرأ المزيد عنها)، لكنني سأركِّز على بعضها: ‎--preset "X"‎: حيث "X" هو اسم نمط (preset) من أنماط ضبط HandBrake. النمط الذي تريد استخدامه لفيديو H.264 المُخصَّص للويب هو "iPhone & iPod Touch"، ومن المهم أن تضع الاسم بأكمله ضمن علامتَي اقتباس. ‎--width W: حيث "W" هو عرض الفيديو الذي تريد ترميزه، وسيُعدِّل HandBrake الارتفاع تلقائيًا ليُحافِظ على تناسب أبعاد المقطع الأصلي. ‎--vb Q: حيث "Q" هو معدَّل البث الوسطي (مُقاسًا بواحدة الكيلوبت/ثانية). ‎--two-pass: تفعيل المرور مرتين (‎2-pass) أثناء الترميز. ‎--turbo: تسريع المرور الأول عند تفعيل ميزة المرور مرتين أثناء الترميز. ‎--input F: حيث "F" هو مسار ملف الفيديو المصدري. ‎--output E: حيث "E" هو مسار ملف الفيديو الناتج. هذا مثالٌ عن كيفية استدعاء HandBrake من سطر الأوامر، مع استخدام خيارات تُطابِق الإعدادات التي اخترناها في الواجهة الرسومية سابقًا. you@localhost$ HandBrakeCLI --preset "iPhone & iPod Touch" --width 320 --vb 600 --two-pass --turbo --input pr6.dv --output pr6.mp4 شرح الأمر السابق من الأعلى إلى الأسفل: تشغيل HandBrake مع نمط "iPhone & iPod Touch"، وإعادة تحجيم المقطع إلى 320x240، وضبط معدل البث الوسطي إلى 600 كيلوبت/ثانية، وتفعيل خيار المرور مرتين مع تسريع أول مرور، وقراءة الملف المصدري pr6.dv وترميزه وإخراج الملف النهائي إلى pr6.mp4. ترميز فيديو WebM باستخدام FFMPEG دُعِمَت صيغة WebM دعمًا كاملًا في ffmpeg 6.0 وما بعده. نفِّذ الأمر ffmpeg في سطر الأوامر دون وسائط وتحقق أنَّه مبنيٌ مع دعم مرماز VP8: you@localhost$ ffmpeg FFmpeg version SVN-r23197, Copyright (c) 2000-2010 the FFmpeg developers built on May 19 2010 22:32:20 with gcc 4.4.3 configuration: --enable-gpl --enable-version3 --enable-nonfree --enable-postproc --enable-pthreads --enable-libfaac --enable-libfaad --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libtheora --enable-libx264 --enable-libxvid --enable-x11grab --enable-libvorbis --enable-libvpx إن لم تشاهد الكلمتين السحريتين ‎--enable-libvorbis و ‎--enable-libvpx فلا يدعم إصدار ffmpeg المثبت لديك WebM (إذا بَنَيتَ ffmpeg من المصدر بنفسك، فتحقق إذا كانت لديك نسختين منه. لا بأس في ذلك، فلن يتعارضا مع بعضهما، لكن عليك استخدام المسار الكامل لنسخة ffmpeg التي تدعم مرماز VP8). سأستخدم ميزة المرور مرتين، سيمسح (scan) المرور الأول ملف الدخل (‎-i pr6.dv) وسيكتب بعض الإحصائيات إلى السجل (الذي سيُسمَّى تلقائيًا باسم pr6.dv-0.log). وسأُحدِّد مرماز الفيديو باستخدام الخيار ‎-vcodec: 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 614400 -s 320x240 -aspect 4:3 -an -y NUL غالبية خيارات الأمر ffmpeg السابق ليس لها علاقة بمرماز VP8 أو صيغة WebM. تدعم مكتبة libvpx عددًا من الخيارات الخاصة بمرماز VP8 التي يمكنك تمريرها إلى ffmpeg، لكنني لم أفعل ذلك في الأمر السابق. وعند المرور مرةً أخرى على الملف، فسيقرأ ffmpeg الإحصائيات التي كتبها أثناء المرور الأول وسيبدأ بترميز الفيديو والصوت، ثم سيكتب ملف ‎.webm. 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 614400 -s 320x240 -aspect 4:3 -acodec libvorbis -y pr6.webm هنالك خمسة خيارات مهمة للأمر السابق: ‎-vcodec libvpx: يُحدِّد أننا نريد ترميز المقطع بمرماز VP8. تستخدم صيغة WebM مرماز VP8 للفيديو دومًا. ‎-b 614400: تحديد معدل البث (bitrate). وعلى النقيض من بقية الصيغ، تتوقع مكتبة libvpx أن يُعطى معدل البث بالبتات، وليس بالكيلوبت. فإذا أردت ترميز مقطع فيديو بمعدل بث 600 كيلوبت/ثانية، فعليك ضرب 6000 بالعدد 1024 لتحصل على 614400. ‎-s 320x240: تحديد أبعاد الفيديو الناتج، العرض ثم الارتفاع. ‎-aspect 4:3: تحديد نسبة أبعاد الفيديو. مقاطع الفيديو ذات الجودة العالية تكون نسبة أبعادها 4:3 عادةً، أما أغلبية مقاطع الفيديو عالية الجودة (HD) فتكون نسبة أبعادها 16:9 أو 16:10. أثناء تجاربي واختباراتي، وجدتُ أنَّ عليّ تحديد هذه القيمة بوضوح في سطر الأوامر، بدلًا من الاعتماد على ffmpeg لكي يكتشفها بنفسه. ‎-acodec libvorbis: تحديد أننا نريد ترميز الصوت بمرماز Vorbis. تستخدم صيغة WebM مرماز Vorbis للصوت دومًا. ترجمة -وبتصرّف- لفصل "Video" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim. اقرأ أيضًا المقال التالي: إضافة مقاطع الفيديو عبر العنصر <video> في HTML5 المقال السابق: صيغ ترميز الفيديو والصوت وحاوياتها وكيفية عملها في الويب النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
  24. بعد أن تعلمنا التعامل مع المعاملات الموضعية (Positional parameters) في الدرس السابق، حان الوقت الآن لشرح آخر بُنية من بُنى التحكم: for. وكما في البنيتين while و until، تُستعمَل for لإنشاء حلقات تكرار. الشكل العام لحلقة for: for variable in words; do commands done الخلاصة هي أنَّ for تُسنِد كلمةً من قائمة الكلمات إلى متغيّرٍ معيّن، ثم تُنفِّذ الأوامر الموجودة داخل الحلقة، ثم تكرر ذلك إلى أن تُستعمَل جميع الكلمات الموجودة في القائمة. هذا مثالٌ عنها: #!/bin/bash for i in word1 word2 word3; do echo $i done أُسنِدَت -في بادئ الأمر- القيمة word1 إلى المتغير i، ثم نُفِّذ الأمر echo $i، ثم أُسنِدَت القيمة word2 إلى المتغير i، ثم نُفِّذ الأمر echo $i، وهكذا، إلى أن تُسنَد جميع الكلمات إلى المتغير i. الشيء المثير للاهتمام في for هو تنوع الطرائق التي تستطيع فيها بناء قائمة الكلمات، حيث يمكن استخدام جميع أنواع التوسعات (expansions). سنولِّد قائمة الكلمات في المثال الآتي باستخدام تعويض الأوامر (command substitution): #!/bin/bash count=0 for i in $(cat ~/.bash_profile); do count=$((count + 1)) echo "Word $count ($i) contains $(echo -n $i | wc -c) characters" done قمنا في المثال السابق بإحصاء عدد الكلمات في ملف ‎.bash_profile، ثم أظهرنا عدد الحروف في كل كلمة. حسنًا، ما علاقة ذلك بالمعاملات الموضعية؟ حسنًا، يمكن استخدام المعاملات الموضعية كقائمة بالكلمات التي ستمرّ عليها الحلقة for: #!/bin/bash for i in "$@"; do echo $i done المتغير "@" هو متغيرٌ خاصٌ بالصَدَفة ويحتوي على قائمة بوسائط سطر الأوامر. تُستعمَل هذه التقنية عادةً لمعالجة قائمة ملفات عبر سطر الأوامر. هذا مثالٌ آخر: #!/bin/bash for filename in "$@"; do result= if [ -f "$filename" ]; then result="$filename is a regular file" else if [ -d "$filename" ]; then result="$filename is a directory" fi fi if [ -w "$filename" ]; then result="$result and it is writable" else result="$result and it is not writable" fi echo "$result" done جرِّب السكربت السابق، ومرِّر إليه قائمةً بعدِّة ملفات، أو استعمل محرفًا بديلًا مثل * وانظر إلى مخرجاته. هذا سكربتٌ آخر يُقارِن الملفات الموجودة في مجلدين، ويظهِر قائمة بالملفات الموجودة في المجلد الأول وغير الموجودة في المجلد الثاني: #!/bin/bash # cmp_dir - program to compare two directories # Check for required arguments if [ $# -ne 2 ]; then echo "usage: $0 directory_1 directory_2" 1>&2 exit 1 fi # Make sure both arguments are directories if [ ! -d $1 ]; then echo "$1 is not a directory!" 1>&2 exit 1 fi if [ ! -d $2 ]; then echo "$2 is not a directory!" 1>&2 exit 1 fi # Process each file in directory_1, comparing it to directory_2 missing=0 for filename in $1/*; do fn=$(basename "$filename") if [ -f "$filename" ]; then if [ ! -f "$2/$fn" ]; then echo "$fn is missing from $2" missing=$((missing + 1)) fi fi done echo "$missing files missing" لنوظِّف ما سبق في مثالٌ عملي. لنحاول تحسين الدالة home_space في السكربت الذي نبنيه لكي تُخرِج المزيد من المعلومات. كانت النسخة القديمة من الدالة تبدو كما يلي: home_space() { # Only the superuser can get this information if [ "$(id -u)" = "0" ]; then echo "<h2>Home directory space by user</h2>" echo "<pre>" echo "Bytes Directory" du -s /home/* | sort -nr echo "</pre>" fi } # end of home_space هذه هي النسخة الحديثة منها: home_space() { echo "<h2>Home directory space by user</h2>" echo "<pre>" format="%8s%10s%10s %-s\n" printf "$format" "Dirs" "Files" "Blocks" "Directory" printf "$format" "----" "-----" "------" "---------" if [ $(id -u) = "0" ]; then dir_list="/home/*" else dir_list=$HOME fi for home_dir in $dir_list; do total_dirs=$(find $home_dir -type d | wc -l) total_files=$(find $home_dir -type f | wc -l) total_blocks=$(du -s $home_dir) printf "$format" $total_dirs $total_files $total_blocks done echo "</pre>" } # end of home_space تتضمن هذه النسخة المُحسَّنة أمرًا جديدًا هو printf، الذي يُستخدم لتنسيق المُخرجات بناءً على محتويات "عبارة التنسيق" (format string). تنحدر أصول الأمر printf من لغة البرمجة C وهو موجودٌ أيضًا في لغاتٍ برمجيةٍ أخرى مثل C++‎ و Perl و awk و java و PHP وبالطبع bash. هنالك أمرٌ آخر جديد هو الأمر find، الذي يُستخدم للبحث عن ملفات أو مجلدات تُطابِق معيارًا أو مقياسًا محدَّدًا (criteria). استخدمنا الأمر find في الدالة home_space لعرض قائمة بالمجلدات والملفات العادية الموجودة في مجلد المنزل، ثم أحصينا عدد الملفات والمجلدات باستخدام الأمر wc (تذكَّر أنَّه اختصار للعبارة "Word Count"). النقطة المثيرة للاهتمام في دالة home_space المُعدَّلة هي كيفية تعاملنا مع مشكلة عدم السماح بالوصول إلى مجلدات المنزل من المستخدمين العاديين، يمكنك ملاحظة أنَّنا اختبرنا إن كان المستخدم هو الجذر بوساطة id ثم -اعتمادًا على ناتج الاختبار- أسندنا سلاسل نصية مختلفة إلى المتغير dir_list، الذي سيُمثِّل قائمة الكلمات لحلقة for التي تليه. وبهذه الطريقة، إذا شغَّل مستخدمُ عاديٌ السكربت، فستُعرض معلومات عن مجلد المنزل الخاص به فقط. موضوعٌ آخر يمكننا توظيف حلقة for فيه هو الدالة system_info التي لم نكملها بعد. يمكننا كتابتها كالآتي: system_info() { # Find any release files in /etc if ls /etc/*release 1>/dev/null 2>&1; then echo "<h2>System release info</h2>" echo "<pre>" for i in /etc/*release; do # Since we can't be sure of the # length of the file, only # display the first line. head -n 1 $i done uname -orp echo "</pre>" fi } # end of system_info حدَّدنا في بادئ الأمر إن كانت هنالك ملفات release لكي نعالجها. تحتوي ملفات release على اسم التوزيعة وإصدارها. وهي موجودة في مجلد ‎/etc. ولكي نكتشف وجودها، سنستعمل الأمر ls لكننا لسنا مهتمين بمخرجات الأمر، وإنما بحالة الخروج، التي ستكون مساوية للصفر (true) إن وُجِدَت أيّة ملفات. الخطوة الآتية هي طباعة شيفرة HTML لهذا القسم من الصفحة؛ ولمّا كنا نعلم أنَّ هنالك عدِّة ملفات release لكي نعالجها، فسنستخدم حلقة for للمرور على كلٍ واحدٍ منها. ثم سنستعمل الأمر head في داخل الحلقة للحصول على السطر الأول من كل ملف. في النهاية، استعملنا الأمر uname مع الخيارات o و r و p للحصول على بعض المعلومات الإضافية حول النظام. ترجمة -وبتصرّف- للمقال Flow Control - Part 3 لصاحبه William Shotts.
  25. لدينا السكربت التالي: #!/bin/bash # sysinfo_page - A script to produce a system information HTML file ##### Constants TITLE="System Information for $HOSTNAME" RIGHT_NOW=$(date +"%x %r %Z") TIME_STAMP="Updated on $RIGHT_NOW by $USER" ##### Functions system_info() { echo "<h2>System release info</h2>" echo "<p>Function not yet implemented</p>" } # end of system_info show_uptime() { echo "<h2>System uptime</h2>" echo "<pre>" uptime echo "</pre>" } # end of show_uptime drive_space() { echo "<h2>Filesystem space</h2>" echo "<pre>" df echo "</pre>" } # end of drive_space home_space() { # Only the superuser can get this information if [ "$(id -u)" = "0" ]; then echo "<h2>Home directory space by user</h2>" echo "<pre>" echo "Bytes Directory" du -s /home/* | sort -nr echo "</pre>" fi } # end of home_space ##### Main cat <<- _EOF_ <html> <head> <title>$TITLE</title> </head> <body> <h1>$TITLE</h1> <p>$TIME_STAMP</p> $(system_info) $(show_uptime) $(drive_space) $(home_space) </body> </html> _EOF_ تعمل أغلبية الميزات التي فيه عملًا سليمًا، لكن هنالك بعض الميزات التي أرغب بإضافتها: أريد تحديد اسم ملف الخرج في سطر الأوامر، بالإضافة إلى ضبط اسم ملف افتراضي إن لم يُحدِّد المستخدم اسم الملف. أريد توفير نمط تفاعلي يسأل المستخدم عن اسم الملف ويُحذِّر المستخدم إن كان الملف موجودًا ويسأله إذا كان يريد إعادة الكتابة فوقه. من البديهي توفر خيار للمساعدة يعرض رسالة توضِّح كيفية الاستخدام. تتطلب جميع الميزات السابقة استخدام الخيارات والوسائط في سطر الأوامر، وتوفِّر لنا الصَدَفة المعاملات الموضعية (positional parameters) للوصول إليها. المعاملات الموضعية هي سلسلة من المتغيرات (من ‎$0‎ إلى ‎$9) التي تحتوي على قيم الوسائط في سطر الأوامر. لنتخيل تنفيذ الأمر الآتي: $ some_program word1 word2 word3 إذا كان some_program سكربت صَدَفة، فسيستطيع قراءة كل عنصر من عناصر السطر السابق لأنَّ المعاملات الموضعية تحتوي على ما يلي: سيحتوي المتغير ‎$0 على "some_program" سيحتوي المتغير ‎$1 على "word1" سيحتوي المتغير ‎$2 على "word2" سيحتوي المتغير ‎$3 على "word3 هذا هو السكربت الذي تستطيع تجربته لتشاهد ما سبق عمليًا: #!/bin/bash echo "Positional Parameters" echo '$0 = ' $0 echo '$1 = ' $1 echo '$2 = ' $2 echo '$3 = ' $3 اكتشاف وجود وسائط في سطر الأوامر عليك عادةً التحقق من وجود وسائط لكي يتصرَّف برنامجك وفقها؛ وهنالك طريقتان لفعل ذلك. أولهما هي التحقق من احتواء المتغير ‎$1 لأي قيمة كما يلي: #!/bin/bash if [ "$1" != "" ]; then echo "Positional parameter 1 contains something" else echo "Positional parameter 1 is empty" fi تحتوي الصَدَفة على متغير اسمه ‎$#‎ الذي يحتوي على عدد الوسائط في سطر الأوامر، وهذه هي الطريقة الثانية. #!/bin/bash if [ $# -gt 0 ]; then echo "Your command line contains $# arguments" else echo "Your command line contains no arguments" fi خيارات سطر الأوامر العديد من البرامج، وخصوصًا تلك التي أتت من مشروع GNU، تدعم خيارات طويلة ومختصرة لسطر الأوامر. فمثلًا، ستستعمل الخيار المختصر ‎-h لعرض رسالة المساعدة لأغلبية البرامج أو الخيار الطويل ‎--help. تُسبَق أسماء الخيارات الطويلة عادةً بشرطتَين (--). سنستعمل هذا العرف في سكربتاتنا. interactive= filename=~/sysinfo_page.html while [ "$1" != "" ]; do case $1 in -f | --file ) shift filename=$1 ;; -i | --interactive ) interactive=1 ;; -h | --help ) usage exit ;; * ) usage exit 1 esac shift done الشيفرة السابقة معقدة بعض الشيء، تحملني قليلًا ريثما أشرحها لك. السطران الأولان سهلان، لم نضبط قيمةً للمتغير interactive، مما يُشير إلى أنَّ المستخدم لم يطلب الوضع التفاعلي، ثم ضبطنا المتغير filename إلى قيمة افتراضية، حيث سيُستخدَم هذا الاسم إن لم يُحدَّد اسمٌ آخر في سطر الأوامر. أصبح لدينا الآن قيمٌ افتراضية في حال لم يضع المستخدم أيّة خيارات في سطر الأوامر. أنشأنا بعد ذلك حلقة while التي تمر على جميع عناصر سطر الأوامر وتتحقق من قيمها عبر كتلة case، التي تكتشف وضع كل خيار من الخيارات الممكنة وتتعامل معه كما يجب. الجزء المهم في السكربت السابق هو آلية عمل حلقة التكرار. تعتمد الحلقة السابقة على الأمر shift. الأمر shift هو أمرٌ مضمَّن في الصَدَفة ويتعامل مع المعاملات الموضعية، حيث يؤدي إلى "إزاحة" أرقام جميع المعاملات وذلك بإنقاص 1 منها وذلك في كل مرة يُستدعى فيها. سيصبح ‎$2 -على سبيل المثال- ‎$1، و ‎$3 سيصبح ‎$2، و ‎ $4سيصبح ‎$3، وهكذا. جرِّب السكربت الآتي: #!/bin/bash echo "You start with $# positional parameters" # Loop until all parameters are used up while [ "$1" != "" ]; do echo "Parameter 1 equals $1" echo "You now have $# positional parameters" # Shift all the parameters down by one shift done الحصول على وسيط أحد الخيارات يتطلب الخيار ‎-f ذكر اسم الملف الذي ستُحفَظ فيه المخرجات بعده. يمكننا استخدام shift مرةً أخرى للحصول على اسم العنصر التالي من وسائط سطر الأوامر وإسناد قيمته إلى المتغير filename. سنتحقق لاحقًا من قيمة filename للتأكد أنَّها تُمثِّل اسم ملف صحيح. دمج مفسر خيارات سطر الأوامر مع السكربت علينا الآن نقل بعض الأقسام من مكانها، وإضافة دالة لعرض طريقة استخدام الخيارات التي أضفناها أخيرًا إلى السكربت، وسنضيف اختبارًا للتأكد من صحة تفسير خيارات سطر الأوامر. سيبدو السكربت بعد التعديل كالآتي: #!/bin/bash # sysinfo_page - A script to produce a system information HTML file ##### Constants TITLE="System Information for $HOSTNAME" RIGHT_NOW=$(date +"%x %r %Z") TIME_STAMP="Updated on $RIGHT_NOW by $USER" ##### Functions system_info() { echo "<h2>System release info</h2>" echo "<p>Function not yet implemented</p>" } # end of system_info show_uptime() { echo "<h2>System uptime</h2>" echo "<pre>" uptime echo "</pre>" } # end of show_uptime drive_space() { echo "<h2>Filesystem space</h2>" echo "<pre>" df echo "</pre>" } # end of drive_space home_space() { # Only the superuser can get this information if [ "$(id -u)" = "0" ]; then echo "<h2>Home directory space by user</h2>" echo "<pre>" echo "Bytes Directory" du -s /home/* | sort -nr echo "</pre>" fi } # end of home_space write_page() { cat <<- _EOF_ <html> <head> <title>$TITLE</title> </head> <body> <h1>$TITLE</h1> <p>$TIME_STAMP</p> $(system_info) $(show_uptime) $(drive_space) $(home_space) </body> </html> _EOF_ } usage() { echo "usage: sysinfo_page [[[-f file ] [-i]] | [-h]]" } ##### Main interactive= filename=~/sysinfo_page.html while [ "$1" != "" ]; do case $1 in -f | --file ) shift filename=$1 ;; -i | --interactive ) interactive=1 ;; -h | --help ) usage exit ;; * ) usage exit 1 esac shift done # Test code to verify command line processing if [ "$interactive" = "1" ]; then echo "interactive is on" else echo "interactive is off" fi echo "output file = $filename" # Write page (comment out until testing is complete) # write_page > $filename إضافة النمط التفاعلي يمكن إضافة النمط التفاعلي بهذه الشيفرة: if [ "$interactive" = "1" ]; then response= echo -n "Enter name of output file [$filename] > " read response if [ -n "$response" ]; then filename=$response fi if [ -f $filename ]; then echo -n "Output file exists. Overwrite? (y/n) > " read response if [ "$response" != "y" ]; then echo "Exiting program." exit 1 fi fi fi تحققنا أولًا من أنَّ النمط التفاعلي مُفعَّل، وإلا فلا حاجة إلى فعل شيء. ثم سألنا المستخدم عن اسم الملف؛ لاحظ طريقة صياغة السؤال: echo -n "Enter name of output file [$filename] > " سنعرض القيمة الحالية للمتغير filename، لأنَّه لو ضغط المستخدم على زر Enter دون كتابة أيّ شيء، فستُستخدم القيمة الافتراضية للمتغير filename؛ ويتم ذلك عبر السطرين اللذان يليانه، حيث يتحققا من قيمة response؛ فإن لم تكن قيمة response فارغةً، فستُسنَد قيمة response إلى المتغير filename. وإلا فستُترَك قيمة filename على حالها، محتفظةً بقيمتها الافتراضية. بعد أن يُدخِل المستخدم اسم ملف الخرج، فسنتحقق من أنَّه موجود، فإن كان موجودًا، فسنسأل المستخدم إن كان يريد استبداله، وإن لم يكن جواب المستخدمy، فسينتهي تنفيذ البرنامج دون كتابة الملف. ترجمة -وبتصرّف- للمقال Positional Parameters لصاحبه William Shotts.
×
×
  • أضف...