المحتوى عن 'مدخل إلى html5'.



مزيد من الخيارات

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

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

نوع المُحتوى


التصنيفات

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

التصنيفات

  • PHP
    • Laravel
    • ووردبريس
    • Magento
  • جافاسكريبت
    • Node.js
    • jQuery
    • AngularJS
  • HTML5
  • CSS
    • Sass
    • إطار عمل Bootstrap
  • سي شارب #C
    • منصة Xamarin
  • بايثون
    • Flask
    • Django
  • لغة روبي
    • إطار العمل Ruby on Rails
  • لغة Go
  • لغة جافا
  • برمجة أندرويد
  • لغة R
  • سير العمل
    • Git
  • صناعة الألعاب
    • Unity3D
  • مقالات عامّة

التصنيفات

  • تجربة المستخدم
  • الرسوميات
    • إنكسكيب
    • أدوبي إليستريتور
    • كوريل درو
  • التصميم الجرافيكي
    • أدوبي فوتوشوب
    • أدوبي إن ديزاين
    • جيمب
  • التصميم ثلاثي الأبعاد
    • 3Ds Max
  • مقالات عامّة

التصنيفات

  • خواديم
    • الويب HTTP
    • قواعد البيانات
    • البريد الإلكتروني
    • DNS
    • Samba
  • الحوسبة السّحابية
    • Docker
  • إدارة الإعدادات والنّشر
    • Chef
    • Puppet
    • Ansible
  • لينكس
  • FreeBSD
  • حماية
    • الجدران النارية
    • VPN
    • SSH

التصنيفات

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

التصنيفات

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

التصنيفات

  • الإنتاجية وسير العمل
    • مايكروسوفت أوفيس
    • ليبر أوفيس
    • جوجل درايف
    • شيربوينت
    • Evernote
    • Trello
  • تطبيقات الويب
    • ووردبريس
  • أندرويد
  • iOS
  • macOS
  • ويندوز

التصنيفات

  • شهادات سيسكو
    • CCNA
  • شهادات مايكروسوفت
  • شهادات Amazon Web Services
  • شهادات ريدهات
    • RHCSA
  • شهادات CompTIA
  • مقالات عامة

أسئلة وأجوبة

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

التصنيفات

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

تمّ العثور على 18 نتائج

  1. ما هي تطبيقات الويب التي تعمل دون اتصال؟ يبدو الأمر من الوهلة الأولى كأنَّ هنالك تضاربًا في المفاهيم. فهل هي صفحات الويب التي تنزِّلها ثم تفتحها بعد ذلك؟ لكن التنزيل يتطلب اتصالًا شبكيًا، فكيف ستستطيع تنزيل الصفحة عندما لا تكون متصلًا؟ لن تستطيع فعل ذلك بكل تأكيد، لكنك تستطيع تنزيل الصفحة عندما تكون متصلًا بالشبكة، وهذه هي آلية عمل تطبيقات 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
  2. تحدثنا في الجزء الأول من هذا الدرس عن أساسيات التخزين المحلي، وسنستمر في مشوارنا مع تطبيقات الويب التي تعمل دون اتصال، وسننشِئ مثالًا عمليًا عليها. تسلسل الأحداث تحدثنا إلى الآن عن تطبيقات الويب التي تعمل دون اتصال، وعن ملف 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
  3. مدخل إلى html5

    أعتبرُ أنَّ شريط العنوان في المتصفح هو أشهرُ عنصرٍ من عناصر الواجهات الرسومية في العالم، إذ أصبحت تُعرَض روابط 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
  4. البيانات الوصفية ليست محدودةً لنوع اصطلاحاتٍ وحيد. صفحة «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
  5. تعلمنا في الدرسين السابقين كيف نوصِّف الأشخاص والمنظمات، وسنتناول في هذا الدرس الأحداث والمراجعات توصيف الأحداث تحدث بعض المناسبات في أوقاتٍ معيّنة، ألن يكون من الجميل أن تستطيع إخبار محركات البحث متى ستقع تلك المناسبات تحديدًا؟ هنالك طريقةٌ لفعل هذا في 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
  6. إن كنت قد قرأت المقال السّابق الذي يشرح ماهية 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.
  7. هنالك أكثر من 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.
  8. مدخل إلى html5

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

    كانت البرمجيات المكتبية تتفوق على تطبيقات الويب بإمكانية تخزين المعلومات محليًا تخزينًا دائمًا؛ حيث يوفِّر نظام التشغيل عادةً طبقةً وسيطةً لتخزين وقراءة بيانات خاصة بالتطبيق مثل الإعدادات وحالة التشغيل، وقد تُخزَّن هذه القيم في سجل النظام (registry) أو ملفات ini أو ملفات XML أو في مكانٍ آخر وفقًا للتقاليد المُتبَعة في نظام التشغيل؛ أما لو احتاج التطبيق المكتبي إلى تخزينٍ محليٍ أكثر تعقيدًا من مجرد تخزين البيانات على شكل "المفتاح/القيمة"، فيمكنك أن تُضمِّن قاعدة البيانات الخاصة بتطبيقك، أو أن تبتكر صيغة ملفات للتخزين، أو غيره ذلك من الحلول. لكن على مرِّ التاريخ، لم تملك تطبيقات الويب هذا الامتياز، وعلى الرغم من ابتكار الكعكات (Cookies) في بدايات الويب لكن كان الغرض منها هو التخزين المحلي لكميةٍ قليلةٍ من البيانات، إلا أنَّ هنالك ثلاثة أسباب تمنعنا من استخدامها لهذا الغرض: ستُضمَّن الكعكات في كل طلبية HTTP، مما يؤدي إلى حدوث بطء في تطبيق الويب بسبب نقل نفس البيانات مرارًا وتكرارًا دون داعٍ ستُضمَّن الكعكات في كل طلبية HTTP، وهذا يعني إرسال البيانات دون تشفير عبر الإنترنت (إلا إذا كان يُخدَّم تطبيق الويب عندك عبر طبقة SSL) المساحة التخزينية للكعكات محدودة إلى حوالي 4 كيلوبايت من البيانات، وهي كافية لإبطاء تطبيقك (انظر أعلاه)، لكنها ليست كافية لتخزين شيءٍ مفيدٍ ما نحتاج له حقًا هو: مساحة تخزينية كبيرة موجودة على جهاز العميل يمكن أن تبقى حتى بعد تحديث الصفحة لن تُنقَل طوال الوقت إلى الخادوم جميع المحاولات -قبل HTML5- لتحقيق ما سبق كانت غير مرضية لمختلف الأسباب. لمحة تاريخية عن محاولات تخزين البيانات محليا قبل HTML5 لم يكن هنالك سوى متصفح Internet Explorer في بدايات الويب، أو على الأقل هذا ما حاولت مايكروسوفت إيهام العالم به، ولتحقيق هذه الغاية، وكجزءٍ من الحرب الكبرى الأولى للمتصفحات، ابتكرت مايكروسوفت ميزاتٍ كثيرة ووضعتها في متصفحها -Internet Explorer- الذي أنهى تلك الحرب. واحدة من تلك الميزات تُسمى "DHTML Behaviors" وكان أحد خصائصها يُدعى userData. تسمح ميزة userData لصفحات الويب أن تُخزِّن 64 كيلوبايت كحد أقصى لكل نطاق (domain)، وذلك عبر هيكلية تعتمد على XML (أما النطاقات الموثوقة، مثل مواقع إنترانت [intranet]، فتستطيع تخزين 10 أضعاف الكمية؛ وكانت 640 كيلوبايت في ذاك الوقت أكثر من كافية). لم يوفِّر IE أي مربع حوار لأخذ إذن المستخدم، ولم تكن هنالك إمكانية لزيادة كمية البيانات التي يمكن تخزينها محليًا. في عام 2002، أضافت شركة Adobe ميزةً في Flash 6 التي اكتسبت الاسم "Flash cookies"، لكن هذه الميزة كانت معروفةً ضمن بيئة Flash بالاسم Local Shared Objects؛ باختصار، تسمح هذه الميزة لكائنات Flash أن تُخزِّن 100 كيلوبايت من البيانات كحد أقصى لكل نطاق. طوَّر Brad Neuberg نموذجًا أوليًا لجسرٍ يربط تقنية Flash بلغة JavaScript أسماه AMASS (اختصار للعبارة AJAX Massive Storage System)، لكنه كان محدودًا بسبب بعض المشكلات في تصميم صيغة Flash. لكن في 2006، ومع مجيء ExternalInterface في Flash 8، أصبح من الممكن بسهولة وسرعة الوصول إلى الكائنات المشتركة المخزنة محليًا (Local Shred Objects أو اختصارًا LSOs) من JavaScript؛ ولهذا السبب أعاد Brad كتابة AMASS ودمجها مع Dojo Toolkit تحت الاسم dojox.storage. وبهذا مَنَحَ Flash كل نطاق 100 كيلوبايت من التخزين المحلي "مجانًا"، وستُطلَب موافقة المستخدم عند كل زيادة في تخزين البيانات (1 ميغابايت، 10 ميغابايت، وهكذا). في عام 2007، أصدرت Google إضافة Gears، التي هي إضافة مفتوحة المصدر للمتصفحات غرضها هو توفير إمكانيات إضافية إليها (تحدثنا سابقًا عن Gears في سياق /توفير واجهة برمجية لتحديد الموقع الجغرافي لمتصفح IE/). توفِّر Gears واجهة برمجية (API) للوصول إلى قاعدة بيانات SQL مدمجة فيها مبنيةٌ على محرك قواعد البيانات SQLite. يمكن لإضافة Gears تخزين كمية غير محدودة من البيانات لكل نطاق في جداول قاعدة بيانات SQL بعد أخذ إذن المستخدم. في تلك الأثناء، أكمل Brad Neuberg وآخرون مشوارهم في تطوير dojox.storage لتوفير واجهة موحَّدة لمختلف الإضافات، وبحلول 2009 أصبح بمقدور dojox.storage أن تكتشف دعم (وتوفر واجهة موحدة) لبرمجية Adobe Flash و Gears و Adobe AIR والنموذج الأولي من التخزين المحلي في HTML5 الذي كان مُطبَّقًا في الإصدارات القديمة من Firefox فقط. عندما تنظر إلى تلك الحلول، فستكتشف أنَّ جميعها كان خاصًا بمتصفح معيّن أو كان يتبع لإضافة خارجية. وعلى الرغم من الجهود البطولية لتوحيد تلك الاختلافات (dojox.storage) إلا أنَّ تلك الحلول تملك واجهات برمجية مختلفة جذريًا عن بعضها، ولكلٍ منها حدود قصوى لمقدار المساحة التخزينية المتوفرة، ولكلٍ منها تجربة مستخدم مختلفة. هذه هي المشكلة التي أتت HTML5 لحلها: توفير واجهة برمجية معيارية، ومطبَّقة في جميع المتصفحات، دون الحاجة إلى استخدام إضافات خارجية. مدخل إلى التخزين المحلي في HTML5 ما أشير إليه على أنَّه "التخزين المحلي في HTML5"‏ (HTML5 Storage) هو مواصفة باسم "Web Storage" التي كانت جزءًا من معيار HTML5، لكنها انقسمت وأصبحت معيارًا مستقلًا لأسباب ليست مهمة. بعض الشركات المسؤولة عن المتصفحات تطلِق عليها الاسم "التخزين المحلي" (Local Storage) أو "تخزين DOM" ‏(DOM Storage). ازداد تعقيد موضوع التسميات خصوصًا بعد ظهور عدد من المعايير الجديدة التي سأناقشها في نهاية هذا الدرس. إذًا، ما هو التخزين المحلي في HTML؟ بشكل مبسّط: هو طريقة تتمكن صفحات الويب من خلالها أن تُخزِّن البيانات على شكل "المفتاح/القيمة" محليًا داخل متصفح الويب في حاسوب العميل. ومثل الكعكات، ستبقى البيانات موجودةً حتى بعد إغلاقك للسان الصفحة في المتصفح، أو إغلاق المتصفح. لكن على عكس الكعكات، لن تُرسَل البيانات تلقائيًا إلى خادوم الويب البعيد؛ وعلى النقيض من كل المحاولات السابقة لتوفير ميزة التخزين المحلي، هذه الميزة موجودة داخليًا في متصفحات الويب، لذلك ستكون متاحة للاستخدام حتى لو لم تتوفر إضافاتٌ خارجيةٌ للمتصفح. ما هي المتصفحات التي تدعمها؟ حسنًا، التخزين المحلي في HTML5 مدعومٌ من أغلبية المتصفحات، وحتى القديمة منها. IE Firefox Safari Chrome Opera iPhone Android 8.0+ 3.5+ 4.0+ 4.0+ 10.5+ 2.0+ 2.0+ تستطيع الوصول إلى التخزين المحلي في HTML5 في شيفرات JavaScript عبر الكائن localStorage الموجود في الكائن العام window؛ لكن قبل أن تستخدمها، عليك أن تكتشف دعم المتصفح لها. function supports_html5_storage() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } } لكن بدلًا من كتابة الدالة السابقة يدويًا، يمكنك استخدام Modernizr لاكتشاف دعم التخزين المحلي في HTML5. if (Modernizr.localstorage) { // window.localStorage متوفرة! } else { // لا يوجد دعم للتخزين المحلي :( // ربما تجرب dojox.storage أو مكتبة أخرى } استخدام التخزين المحلي في HTML5 يعتمد التخزين المحلي في أساسه على تخزين البيانات على شكل "مفتاح/قيمة". أي أنَّك تُخزِّن البيانات في مفتاح له اسم مُميِّز، ثم تستطيع الحصول على تلك البيانات مرةً أخرى باستخدام نفس المفتاح. ذاك المفتاح هو سلسلة نصية، ويمكن أن تكون البيانات المُخزَّنة من أي نوع تدعمه لغة JavaScript بما في ذلك السلاسل النصية والقيم المنطقية (true و false) أو الأعداد الصحيحة أو الأعداد العشرية؛ لكن في الواقع، ستُخزَّن البيانات كسلسلة نصية، وهذا يعني أنَّه لو لم تكن القيمة المُخزَّنة نصيةً فستحتاج إلى استعمال دوال مثل parseInt()‎ أو parseFloat()‎ لكي تحوِّل البيانات التي حصلت عليها إلى نوع البيانات الذي تريده. interface Storage { getter any getItem(in DOMString key); setter creator void setItem(in DOMString key, in any data); }; سيؤدي استدعاء الدالة setItem()‎ مع تمرير مفتاح موجود مسبقًا إلى إعادة الكتابة فوق القيمة السابقة دون إشعار. وسيؤدي استدعاء الدالة getItem()‎ مع تمرير مفتاح غير موجود إلى إعادة null بدلًا من رمي استثناء (throw an exception). وكما هو الحال مع بقية الكائنات في JavaScript، يمكنك أن تُعامِل الكائن localStorage على أنَّه مصفوفة ترابطية (associative array). فبدلًا من استخدام الدالتين getItem()‎ و setItem()‎، تستطيع بكل بساطة أن تستعمل الأقواس المربعة (التي تستعملها للوصول إلى عناصر المصفوفات). يمكن على سبيل المثال أن نُعيد كتابة هذه الشيفرة: var foo = localStorage.getItem("bar"); localStorage.setItem("bar", foo); var foo = localStorage["bar"]; localStorage["bar"] = foo; هنالك دوالٌ أخرى لحذف قيمة مرتبطة بمفتاح معيّن، ولحذف كل ما هو مُخزَّنٌ محليًا (وهذا يعني حذف كل المفاتيح والقيم معًا). interface Storage { deleter void removeItem(in DOMString key); void clear(); }; لن يؤدي استدعاء الدالة removeItem()‎ مع تمرير مفتاح غير موجود إلى فعل أي شيء. وأخيرًا، هنالك خاصية للحصول على العدد الكلي للقيم المُخزَّنة محليًا، ودالة للحصول على اسم كل مفتاح عبر تمرير فهرسه المكاني*. interface Storage { readonly attribute unsigned long length; getter DOMString key(in unsigned long index); }; لو استدعيتَ الدالة key()‎ مع فهرس لا يقع بين 0 - 1 فستُعيد الدالة null. تتبّع التغييرات في مساحة التخزين المحلي إذا أردت أن تتبَّع التغييرات في مساحة التخزين (storage area) برمجيًا، فعليك أن تستعمل الحَدَث storage، الذي يُفعَّل (fired) في الكائن العام window في كل مرة تُستدعى فيها الدالة setItem()‎ أو removeItem()‎ أو clear()‎ وتجري تلك الدالة تغييرًا ما. فعلى سبيل المثال، لو أعدتَ ضبط قيمة موجودة مسبقًا وكانت القيمة الجديدة مساوية للقيمة القديمة، أو استدعيت الدالة clear()‎‎ لكن لم تكن هنالك أيّة قيم في مساحة التخزين، فلن يُفعَّل الحدث storage، لعدم تغيّر شيء في مساحة التخزين. الحدث storage مدعوم في كل متصفح يدعم الكائن localStorage، وهذا يتضمن Internet Explorer 8، لكن IE 8 لا يدعم الدالة المعيارية من W3C لمراقبة الأحداث addEventListener (لكنها أُضيفت في نهاية المطاف في IE 9)؛ ولهذا، إذا أردت مراقبة تفعيل الحدث storage فعليك أن تكتشف ما هي آلية الأحداث التي يدعمها المتصفح أولًا (إذا فعلتَ هذا من قبل مع الأحداث الأخرى، فيمكنك تخطي هذه الفقرة والانتقال إلى آخر القسم. مراقبة الحدث storage مماثلة تمامًا لعملية مراقبة الأحداث الأخرى التي سبقَ وأن راقبتَها؛ وإذا كنتَ تُفضِّل استخدام jQuery أو مكتبة JavaScript أخرى لتسجيل دوال مراقبة الأحداث، فتستطيع فعل ذلك مع الحدث storage أيضًا). if (window.addEventListener) { window.addEventListener("storage", handle_storage, false); } else { window.attachEvent("onstorage", handle_storage); }; ستُستدعى الدالة handle_storage مع تمرير كائن من نوع StorageEvent، عدا في متصفح Internet Explorer حيث يُخزَّن الكائن في window.event. function handle_storage(e) { if (!e) { e = window.event; } } سيكون المتغير e -عند هذه النقطة- كائنًا من نوع StorageEvent، الذي لديه الخاصيات المبيّنة في الجدول الآتي: الخاصية النوع الشرح key سلسلة نصية مفتاح القيمة التي أُضيفَت أو حُذِفَت أو عدِّلَت oldValue أي نوع القيمة (التي كُتِبَ فوقها)، أو null إذا أُضيف عنصرٌ جديد newValue أي نوع القيمة الجديدة، أو null إن حُذِفَ عنصرٌ ما url* سلسلة نصية الصفحة التي تحتوي على الدالة التي أجرت هذا التغيير ملاحظة: كان اسم الخاصية url الأصلي هو uri، وذلك لأنَّ بعض المتصفحات امتلكت هذه الخاصية قبل تغيير مواصفة التخزين المحلي. لأكبر قدر من التوافقية، عليك أن تتحقق من وجود الخاصية url، فإن لم تكن موجودًا فتحقق من قيمة الخاصية uri. لا يمكن إلغاء الأحداث في الحدث storage. فلا توجد طريقة من داخل الدالة handle_storage تستطيع إيقاف تغيير ما من الحدوث. بكل بساطة، هذه طريقة لكي يخبرك المتصفح: "هذا ما حصل لتوِّه، لا يمكنك فعل أي شيء تجاهه؛ كل ما أستطيع فعله هو إخبارك ما الذي حدث". المحدوديات في المتصفحات الحالية في حديثي عن اللمحة التاريخية عن محاولات تخزين البيانات محليًا باستخدام إضافات خارجية، حرصتُ على ذكر محدوديات كل تقنية من تلك التقنيات، مثل محدودية المساحة التخزينية. لكنني لم أذكر شيئًا عن محدوديات التخزين المحلي في HTML5 المعياري. سأعطيك الأجوبة أولًا ثم سأشرحها. الأجوبة هي -بترتيبها حسب الأهمية-: "5 ميغابايت"، و"QUOTA_EXCEEDED_ERR" و "لا". "5 ميغابايت" هي المساحة التي يُسمَح لكل موقع بالحصول عليها افتراضيًا، وهذه القيمة متساوية -على غير العادة- بين المتصفحات، على الرغم من أنَّها مذكورة في مواصفة التخزين المحلي في HTML5 على أنَّها "اقتراح". ابقِ في ذهنك أنَّك تُخزِّن سلاسل نصية، ولا تخزِّن البيانات بصيغتها الأصلية، فلو كنت تُخزِّن الكثير من الأعداد الصحيحة (integers) أو العشرية (floats)، فسيكون الفرق في طريقة تمثيل البيانات مؤثرًا، إذ يُخزَّن كل رقم من عدد عشري كمحرف (character)، وليس بالتمثيل التقليدي لعدد عشري. "QUOTA_EXCEEDED_ERR" هو الاستثناء (exception) الذي سيُرمى (thrown) عندما تتجاوز حد 5 ميغابايت. أما "لا" فهو الجواب على السؤال البدهي الذي سيخطر ببالك: "هل يمكنني طلب المزيد من المساحة التخزينية من المستخدم؟" إلى حد الساعة، لا تدعم أيّة متصفحات أي آلية يتمكن خلالها مطورو الويب من طلب المزيد من المساحة التخزينية. لكن بعض المتصفحات (مثل Opera أو Firefox) تسمح للمستخدم أن يتحكم بالحدّ الأقصى للتخزين المحلي، لكن هذا منوطٌ بالمستخدم تمامًا، ولا يمكنك -كمطوِّر ويب- الاعتماد على ذلك لبناء تطبيقك. مثال عملي عن استخدام التخزين المحلي لنأخذ مثالًا عمليًا عن التخزين المحلي في HTML. هل تتذكر لعبة الضامة التي بنيناها في الدرس الذي يتحدث عن canvas؟ هنالك مشكلة صغيرة مع هذه اللعبة: ستخسر تقدّمك في اللعبة عندما تُغلِق نافذة المتصفح. لكن باستخدام التخزين في HTML5، سنستطيع حفظ التقدّم محليًا داخل المتصفح. هذا مثالٌ حيّ للعبة بعد التعديل. حرِّك بعض القطع، ثم أغلق لسان الصفحة (أو المتصفح)، ثم أعد فتح الصفحة. فإذا كان يدعم متصفحك التخزين المحلي، فيجب أن تتذكر الصفحة السابقة خطواتك التي أجريتها في اللعبة، بما في ذلك عدد الخطوات التي تحركت بها، ومكان كل قطعة على رقعة اللعب، وحتى آخر قطعة قمتَ بتحديدها. ما هي الآلية التي اتبعناها لفعل ذلك؟ سنستدعي الدالة الآتية في كل مرّة يطرأ فيها تغيير داخل اللعبة: function saveGameState() { if (!supportsLocalStorage()) { return false; } localStorage["halma.game.in.progress"] = gGameInProgress; for (var i = 0; i < kNumPieces; i++) { localStorage["halma.piece." + i + ".row"] = gPieces[i].row; localStorage["halma.piece." + i + ".column"] = gPieces[i].column; } localStorage["halma.selectedpiece"] = gSelectedPieceIndex; localStorage["halma.selectedpiecehasmoved"] = gSelectedPieceHasMoved; localStorage["halma.movecount"] = gMoveCount; return true; } كما لاحظت، تستعمل الدالة السابقة الكائن localStorage لتخزين أنَّ المستخدم قد بدأ اللعب (المفتاح gGameInProgress، الذي هو قيمة منطقية [Boolean]). ثم ستدور حلقة for على جميع القطع (المتغير gPieces، الذي هو مصفوفة في لغة JavaScript) ثم يحفظ رقم السطر والعمود لكل قطعة؛ ثم تحفظ الدالة بعض المعلومات الإضافية عن اللعبة، بما في ذلك القطعة التي تم تحديدها (القيمة gSelectedPieceIndex، التي هي رقمٌ صحيح [integer])، وفيما إذا كانت القطعة في منتصف سلسلة من القفزات (القيمة gSelectedPieceHasMoved، التي هي قيمة منطقية)، والعدد الكلي للحركات التي قام بها اللاعب (القيمة gMoveCount، التي هي عدد صحيح). وعند تحميل الصفحة، وبدلًا من الاستدعاء التلقائي للدالة newGame()‎ التي ستُعيد ضبط جميع المتغيرات إلى قيم مُحدَّدة مسبقًا، فسنستدعي الدالة resumeGame()‎. التي تتحقق -باستخدام التخزين المحلي في HTML5- فيما إذا كانت هنالك نسخة محفوظة من اللعبة مُخزَّنةٌ محليًا؛ فإن وُجِدَت، فستُستعاد تلك القيم باستخدام الكائن localStorage. function resumeGame() { if (!supportsLocalStorage()) { return false; } gGameInProgress = (localStorage["halma.game.in.progress"] == "true"); if (!gGameInProgress) { return false; } gPieces = new Array(kNumPieces); for (var i = 0; i < kNumPieces; i++) { var row = parseInt(localStorage["halma.piece." + i + ".row"]); var column = parseInt(localStorage["halma.piece." + i + ".column"]); gPieces[i] = new Cell(row, column); } gNumPieces = kNumPieces; gSelectedPieceIndex = parseInt(localStorage["halma.selectedpiece"]); gSelectedPieceHasMoved = localStorage["halma.selectedpiecehasmoved"] == "true"; gMoveCount = parseInt(localStorage["halma.movecount"]); drawBoard(); return true; } أهم فكرة في هذه الدالة هي تطبيق التحذير الذي ذكرته لك سابقًا في هذا الدرس، وسأكرره هنا: "ستُخزَّن البيانات كسلسلة نصية، وهذا يعني أنَّه لو لم تكن القيمة المُخزَّنة نصيةً فستحتاج إلى تحويل البيانات التي حصلت عليها إلى نوع البيانات الذي تريده". فعلى سبيل المثال، القيمة التي تُحدِّد فيما إذا كانت هنالك لعبة قيد اللعب (gGameInProgress) هي قيمة منطقية، وفي الدالة saveGameState()‎ خزَّنا القيمة دون أن نلقي بالًا لنوعها: localStorage["halma.game.in.progress"] = gGameInProgress; لكن في دالة resumeGame()‎ علينا أن نُعامِل القيمة التي أخذناها من التخزين المحلي كسلسلةٍ نصيةٍ كالآتي: gGameInProgress = (localStorage["halma.game.in.progress"] == "true"); وبشكلٍ مشابه، يُخزَّن عدد الخطوات في gMoveCount كعددٍ صحيحٍ؛ فلقد خزَّناها ببساطة في الدالة saveGameState()‎: localStorage["halma.movecount"] = gMoveCount; لكن في دالة resumeGame()‎ علينا أن نحوِّل القيمة إلى عدد صحيح باستخدام الدالة parseInt()‎ الموجودة في JavaScript: gMoveCount = parseInt(localStorage["halma.movecount"]); مستقبل التخزين المحلي في تطبيقات الويب على الرغم من أنَّ الماضي كان مليئًا بالطرق الالتفافية، لكن الوضع الراهن للتخزين المحلي في HTML5 مشرقٌ، فهنالك واجهة برمجية (API) جديدة قد وُضِعَ لها معيارٌ وطبِّق هذا المعيار في جميع المتصفحات الرئيسية على مختلف المنصات والأجهزة. فهذا أمرٌ لا تراه كل يوم كمطوِّر ويب، أليس كذلك؟ لكن ألا تتطلع إلى أكثر من "5 ميغابايت من الثنائيات على شكل "مفتاح/قيمة"؟ حسنًا، هنالك عدد من الرؤى التنافسية لمستقبل التخزين المحلي. إحدى تلك الرؤى هي اختصارٌ تعرفه بالتأكيد: SQL. أطلقَت Google في عام 2007 إضافة Gears المفتوحة المصدر التي تعمل على مختلف المتصفحات والتي احتوت على قاعدة بيانات مُضمَّنة فيها مبنية على SQLite. أثَّر هذا النموذج الأولي لاحقًا على إنشاء مواصفة Web SQL Database، والتي كانت تعرف رسميًا باسم "WebDB" التي توفر طبقةً للوصول إلى قاعدة بيانات SQL، سامحةً لك بالقيام بأشياء شبيهة بما يلي عبر JavaScript (لاحظ أنَّ الشيفرة الآتية حقيقية وتعمل على أربعة متصفحات): openDatabase('documents', '1.0', 'Local document storage', 5*1024*1024, function (db) { db.changeVersion('', '1.0', function (t) { t.executeSql('CREATE TABLE docids (id, name)'); }, error); }); كما لاحظت، ما يهم في الشيفرة السابقة هو السلسلة النصية التي مررتها إلى الدالة executeSql، ويمكن أن تحتوي تلك السلسلة النصية على أيّة تعليمات SQL مدعومة، بما في ذلك SELECT و UPDATE و INSERT و DELETE. الأمر هنا شبيهٌ ببرمجة قواعد البيانات بلغةٍ مثل PHP، إلا أنَّك تقوم بذلك عبر JavaScript! طُبِّقت مواصفات Web SQL Database من أربعة متصفحات ومنصات. IE Firefox Safari Chrome Opera iPhone Android . . 4.0+ 4.0+ 10.5+ 3.0+ 2.0+ وبكل تأكيد، لو سبق وأن استخدمت أكثر من مُحرِّك لقواعد البيانات في حياتك، فأنت تعلم أنَّ "SQL" هي مصطلح تسويقي أكثر من كونها معيارًا متكاملًا (قد يقول البعض أنَّ HTML5 كذلك، لكن لا تأبه بقولهم). حسنًا، هنالك معيار للغة SQL (يسمى SQL-92) لكن لا يوجد خادوم قواعد بيانات في العالم يتوافق تمامًا مع ذاك المعيار. فهنالك نسخة SQL لقواعد بيانات Oracle، ونسخة أخرى لقواعد MSSQL، ونسخة أخرى لقواعد بيانات MySQL، وأخرى لقواعد بيانات PostgreSQL، ولا ننسَ نسخة SQL لقواعد بيانات SQLite. وحتى كل منتج من تلك المنتجات يُضيف ميزات SQL جديدة على مرّ الزمن، وبهذا يكون قولنا "نسخة SQL لقواعد بيانات SQLite" ليس كافيًا لتحديد ما نتحدث عنه بدقّة. فعليك أن تقول "نسخة SQL التي تأتي مع قواعد بيانات SQLite ذات الإصدار X.Y.Z". كل ما سبق أدى إلى الإعلان الآتي، التي يقبع الآن في أعلى صفحة مواصفة Web SQL Database: وعلى ضوء هذا، سأعرِّفك على رؤية تنافسية أخرى لتخزينٍ محليٍ متقدم وثابت لتطبيقات الويب: Indexed Database API المعروفة رسميًا باسم "WebSimpleDB" التي اشتهرت باسم "IndexedDB". تحتوي IndexedDB على ما يُسمى "مخزن الكائنات" (object store)، الذي يتشارك مع قاعدة بيانات SQL في الكثير من المفاهيم؛ فهنالك "قواعد بيانات" (databases) فيها "سجلات" (records)، ويملك كل سجل عددًا من "الحقول" (fields)، وكل حقل له نوع بيانات معيّن، الذي يُعرَّف عند إنشاء قاعدة البيانات. تستطيع أيضًا تحديد مجموعة فرعية من السجلات، ثم تعرضها عبر "مؤشر" (cursor)، ويتم التعامل مع التغييرات على مخزن الكائنات عبر "التحويلات" (transactions). إذا سبق وأن برمجتَ قليلًا بأي نوع من أنواع قواعد بيانات SQL ، فمن المرجح أن تبدو المصطلحات السابقة مألوفةً لديك. الفرق الرئيسي هو أنَّ "مخزن الكائنات" ليس لديه "لغة استعلام بنيوية"، لا تستطيع كتابة عبارات مثل "SELECT * from USERS where ACTIVE = 'Y'‎" لكنك تستطيع استخدام الدوال التي يوفرها مخزن الكائنات لفتح "مؤشر" (cursor) في قاعدة البيانات "USERS"، ثم تمر عبر السجلات، وتستبعد سجلات المستخدمين غير النشيطين، ثم تستخدم دوالًا للوصول إلى قيم كل حقل في السجلات المتبقية. دعم IndexedDB موجودٌ في Firefox منذ الإصدار 4.0 (صرَّحَت Mozilla أنَّها لن تدعم Web SQL Database في متصفحها)، وChrome منذ الإصدار 11، وحتى Internet Explorer أصبح يدعم IndexedDB منذ الإصدار 10. ترجمة -وبتصرّف- لفصل "Local Storage" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  10. تحديد الموقع الجغرافي هو آلية معرفة مكان وجودك في هذا العالم ومشاركة تلك المعلومات (اختياريًا) مع الأشخاص الذين تثق بهم؛ وهنالك أكثر من طريقة لمعرفة أين أنت: إما باستخدام عنوان 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
  11. تعرفنا في الدروس السابقة على صيغ ترميز الفيديو والصوت و كيفية ترميز مقاطع الفيديو باستخدام عدة صيغ. حسنًا، يُفترَض أنَّ هذه السلسلة عن 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
  12. مدخل إلى html5

    هنالك أدواتٌ عديدةٌ لترميز الفيديو، وهنالك خياراتٌ كثيرةٌ للترميز تؤثر على جودة الفيديو؛ فإن لم تكن تريد فهم كل شيءٍ متعلقٍ بترميز الفيديو فعليك بهذا المقال. ترميز الفيديو باستخدام 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.
  13. أي شخصٍ زار موقع YouTube في الأعوام الماضية يعلم تمام العلم أنَّ بالإمكان تضمين مقاطع الفيديو في صفحات الويب؛ لكن لم تكن هنالك طريقةٌ معياريةٌ لفعل ذلك قبل وجود HTML5. نظريًا كل مقاطع الفيديو التي سبق وأن شاهدتها على "الويب" تمَّ تشغيلها عبر إضافة خارجية (ربما QuickTime أو RealPlayer أو Flash ‏-YouTube يستخدم تقنية Flash-). تتكامل تلك الإضافات مع متصفحك بشكلٍ ممتاز إلى درجة أنَّك لن تلاحظ استخدامها إلى أن تحاول مشاهدة مقطع فيديو على منصة (أو جهاز) لا تدعم تلك الإضافة. تُعرِّف HTML5 طريقةً معياريةً لتضمين مقاطع الفيديو في صفحة الويب وذلك باستخدام العنصر <video>، ما يزال دعم العنصر <video> قيد التطوير، وهذه طريقةٌ مهذبةٌ للتصريح أنَّه لا يعمل بعد، أو على الأقل لا يعمل في كل مكان؛ لكن لا تقنط ولا تقلق، هنالك بدائل وخيارات أخرى كثيرة نستطيع اللجوء إليها. يبيّن الجدول الآتي إصدارات مختلف المتصفحات التي تدعم العنصر <video>: IE Firefox Safari Chrome Opera iPhone Android 9.0+ 3.5+ 3.0+ 3.0+ 10.5+ 1.0+ 2.0+ لكن الدعم لعنصر <video> نفسه ليس كافيًا بل هو جزءٌ صغيرٌ من الحكاية. لكن قبل أن نتحدث عن دعم الفيديو في HTML5، علينا أن نفهم بعض المعلومات حول مقاطع الفيديو نفسها (إن كنت تعرف تلك المعلومات، فيمكنك تخطي الفقرات الآتية والبدء من فقرة "ما الصيغ التي تعمل في الويب"). حاويات الفيديو قد تتخيل ملفات الفيديو على أنها ملفات "AVI" أو "MP4". لكن "AVI" و "MP4" في الواقع ما هي إلا صيغ حاوية للفيديو. فهي تُشبِه ملفات ZIP التي يمكنها احتواء أي نوع من الملفات ضمنها، فصيغ حاويات الفيديو (video container formats) تُعرِّف طريقة تخزين الأشياء ضمنها فقط، ولا تُحدِّد ما هي أنواع البيانات المُخزَّنة (الأمر أكثر تعقيدًا من هذا، لعدم توافق جميع أنواع مسارات (أو دفق) الفيديو (video streams) مع جميع صيغ الحاويات، لكن اغضض الطرف عنها الآن). يحتوي ملف الفيديو عادةً على عدِّة "مسارات" (tracks)، التي هي مسار الفيديو (دون صوت) بالإضافة إلى مسار صوتي أو أكثر (دون فيديو). تكون المسارات مترابطةً عادةً، فيحتوي مسار الصوت على مؤشرات أو علامات ضمنه للمساعدة في مزامنة الصوت مع الفيديو. يمكن لكل مسار أن يملك بيانات وصفية (metadata)، مثل نسبة العرض إلى الارتفاع في مسار الفيديو، أو لغة مسار الصوت. يمكن للحاويات أيضًا امتلاك بيانات وصفية، مثل عنوان (title) الفيديو نفسه، وغلاف (cover) للفيديو، وأرقام الحلقات (للمسلسلات)، وهلمَّ جرًا. هنالك العديد والعديد من صيغ حاويات الفيديو، هذه أشهرها: MPEG 4، التي تأتي عادةً مع اللاحقة ‎.mp4 أو ‎.m4v، حاوية MPEG 4 مبنية على حاوية QuickTime القديمة (من Apple) ‎.mov، ما تزال تستخدم "الأفلام القصيرة" في موقع Apple حاوية QuickTime القديمة، لكن الأفلام التي تأخذها من iTunes مبنية على حاوية MPEG 4. Flash Video، تأتي عادةً مع اللاحقة ‎.flv، صيغة Flash Video (بدهيًا) تُستعمَل من Adobe Flash؛ وكانت هذه هي صيغة الحاويات الوحيدة التي كان يدعمها Flash قبل الإصدار 9.0.60.184 (أي مُشغِّل Flash الإصدار 9 التحديث 3). الإصدارات الحديثة من Flash تدعم حاوية MPEG 4 أيضًا. Ogg، تأتي عادةً مع اللاحقة ‎.ogv؛ صيغة Ogg معيارها مفتوح المصدر، وغير مرتبطة بأيّة براءات اختراع. متصفحات Firefox 3.5 و Chrome 4 و Opera 10.5 تدعم -داخليًا، دون إضافات خاصة- الحاوية Ogg، ومسار فيديو Ogg (المُسمى "Theora")، ومسار صوت Ogg (المُسمى "Vorbis"). صيغة Ogg مدعومة تلقائيًا في جميع توزيعات لينُكس المشهورة، ويمكنك استخدامها على Mac وويندوز بتثبيت "QuickTime components" أو "DirectShow filters" على التوالي وبالترتيب. من الممكن أيضًا تشغيل هذه الصيغة ببرنامج VLC على جميع المنصات. WebM، هي صيغة حاويات جديدة، وهي -تقنيًا- شبيهة بصيغة أخرى تُسمى Matroska. أُعلِنَ عن WebM في أيار/مايو من عام 2010، وهي مصمَّمة لكي تُستخدم حصريًا مع مرماز الفيديو VP8 ومرماز الصوت Vorbis (سنأتي على ذكرهما بعد قليل)، وهذه الصيغة مدعومةٌ داخليًا -دون إضافات خاصة- في آخر إصدار من Chromium و Google Chrome و Mozilla Firefox و Opera، وأعلنت Adobe أنَّ Flash 10.1 سيدعم صيغة WebM. Audio Video Interleave، التي تأتي عادةً مع اللاحقة ‎.avi تم ابتكار صيغة AVI من مايكروسوفت منذ وقتٍ طويل، حين كانت إمكانية تشغيل الحواسيب لمقاطع الفيديو أمرًا عظيمًا. لا تدعم هذه الصيغة –رسميًا– ميزات صيغ الحاويات الأحدث منها مثل البيانات الوصفية المدمجة، ولا تدعم أيضًا -رسميًا- أغلبية ترميزات الفيديو والصوت المُستعمَلة حاليًا. وقد حاولت الشركات مع مرور الوقت توسعة هذه الصيغة بطرق غير متوافقة مع بعضها بعضًا لدعم بعض الميزات، ولكنها مع ذلك بقيت صيغة الحاوية الافتراضية لبعض المُرمِّزات (encoders) مثل MEncoder. مرمزات الفيديو عندما تتحدث عن "مشاهدة مقطع فيديو" فغالبًا ستقصد مشاهدة تجميعة من مسار للفيديو ومسار للصوت؛ لكن ليس عندك ملفان منفصلان، فكل ما لديك هو ملف "فيديو" واحد، وربما يكون ملف AVI أو MP4؛ تلك هي صيغ الحاويات، مثل ملف ZIP الذي يحتوي على عدِّة أنواع من الملفات داخله. تُعرِّف صيغة الحاوية آلية تخزين مسارات الفيديو والصوت في ملفٍ وحيد. سيقوم مُشغِّل الفيديو بثلاثة أشياء على الأقل عندما "تشاهد مقطع فيديو": محاولة تفسير صيغة الحاوية لمعرفة ما هي مسارات الفيديو والصوت المتوفرة، وطريقة تخزينها ضمن الملف كي يعرف أين سيعثر على البيانات التي يجب عليه فك ترميزها لاحقًا فك ترميز مسار الفيديو وعرض سلسلة من الصور على الشاشة فك ترميز مسار الصوت وإرسال الأصوات إلى مكبرات الصوت في جهازك مرماز الفيديو (video codec) هو الخوارزمية التي يُرمَّز (encode) فيها مقطع الفيديو، أي أنَّه يُحدِّد طريقة القيام بالخطوة رقم 2 مما سبق (الكلمة "codec" آتية من دمج الكلمتين "coder" و "decoder"). يفك مُشغِّل الفيديو ترميز (decode) مسار الفيديو وفقًا لمرماز الفيديو المستخدم، ثم سيعرض سلسلة من الصور أو "الإطارات" على الشاشة، أغلبية مرمزات الفيديو الحديثة تستخدم حيلًا عديدة لتقليل حجم المعلومات اللازمة لعرض الإطار تلو الإطار. على سبيل المثال، بدلًا من تخزين كل إطار على حدة (كأنه لقطة شاشة)، فسيتم تخزين الاختلافات بين الإطارين. لا يحدث في أغلبية مقاطع الفيديو تغيرات كبيرة بين الإطارات المتتالية، مما يسمح بضغطها بدرجة أكبر، مما يؤدي إلى تقليل المساحة التخزينية للملف. هنالك مرمازات فيديو تؤدي إلى ضياع في البيانات (lossy) وأخرى لا تؤدي إلى ضياع البيانات (lossless). المساحة التخزينية لملفات الفيديو التي ليس فيها ضياعٌ في البيانات كبيرةٌ جدًا ولن تكون ذات فائدةٍ في الويب، لهذا سنركِّز على المرمازات التي تؤدي إلى ضياعٍ في البيانات. مرماز يؤدي إلى ضياعٍ في البيانات يعني أنَّ بعض المعلومات ستضيع دون إمكانية استعادتها أثناء عملية الترميز (encoding)، مثل عملية نسخ كاسيت صوتي إلى آخر. ستفقد في هذه العملية بعض المعلومات المتعلقة بالفيديو المصدري، وستُقلِّل الجودة في كل مرة تُعيد ترميز المقطع فيها. فبدلًا من صوت "التشويش" في شريط الكاسيت (إن كنت تستعمله في صغرك)، سيبدو مقطع الفيديو الذي تُعيد ترميزه مراتٍ عدِّة غير واضحٍ، خصوصًا المشاهد التي فيها الكثير من الحركة (في الواقع، يمكن أن يحدث ذلك حتى لو قمتَ بالترميز من المصدر مباشرةً، فربما اخترت مرماز فيديو سيئ أو مررت مجموعة وسائط خاطئة). أما على الكفة الأخرى، تضغط مرمزات الفيديو التي تؤدي إلى ضياعٍ في البيانات مقاطع الفيديو ضغطًا كبيرًا ويصعب في الوقت نفسه ملاحظة فقدان البيانات بالعين المجردة. هنالك الكثير من مرمازات الفيديو، لكن أهم ثلاثة منها هي H.264، و Theora، و VP8. H.264 H.264 معروفٌ أيضًا باسم "MPEG-4 part 10" أو "MPEG-4 AVC" أو "MPEG-4 Advanced Video Coding". طوِّرَ مرماز H.264 من MPEG group وتم جعله معيارًا قياسيًا في 2003، ويهدف إلى توفير مرماز واحد للأجهزة ذات التراسل الشبكي المحدود وقدرة المعالجة المحدودة (أي الهواتف المحمولة)، وللأجهزة التي يتوفر لها تراسلٌ شبكيٌ كبير وقدرة معالجة ممتازة (مثل الحواسيب الحديثة)، وأي شيء يقع بينهما. ولكي يتم تحقيق ذلك، قُسِّم معيار H.264 إلى "أنماط" (profiles) التي يُحدِّد كلٌ منها مجموعة من الميزات الاختيارية التي توازن بين تعقيد ترميز الملف ومساحته التخزينية. الأنماط العليا (Higher profiles) تستخدم ميزات اختيارية أكثر، وتوفر جودةً أكبر بمساحة تخزينية أقل، لكنها تأخذ وقتًا أطول لتُرمَّز، وتستهلك طاقة معالجة أكبر لفك الترميز في الوقت الحقيقي. لإعطائك فكرةً عامةً عن تنوع الأنماط: يدعم iPhone ‏نمط "Baseline"، ويدعم AppleTV نمطي ‏"Baseline" و "Main"، بينما يدعم Adobe Flash على الحواسيب أنماط "Baseline" و "Main" و "High". ويستعمل YouTube الآن مرماز H.264 لترميز مقاطع الفيديو عالية الدقة (HD) التي يمكن تشغليها عبر Adobe Flash؛ ويدعم YouTube أيضًا إرسال مقاطع الفيديو المرمزة بمرماز H.264 إلى الهواتف المحمولة، بما في ذلك هواتف iPhone والهواتف العاملة بنظام أندرويد. إضافةً إلى ما سبق، مرماز H.264 هو المرماز الذي تستعمله مواصفة Blu-Ray، إذ تستخدم أقراص Bul-Ray ‏نمط "High" في العموم. أغلبية الأجهزة التي تُشغِّل فيديو H.264 عدا الحواسيب (بما في ذلك هواتف iPhone وقارئات Blu-Ray) تستعمل شريحة منفصلة لفك ترميز الفيديو، لأن معالجاتها المركزية ضعيفة نسبيًا لفك ترميز الفيديو في الوقت الحقيقة. وأصبحت في هذه الأيام بطاقات العرض الرخيصة للحواسيب المكتبية تدعم فك ترميز فيديو H.264 عتاديًّا. وهنالك أيضًا تنافس بين مرمزات H.264، بما في ذلك المكتبة مفتوحة المصدر x264. معيار H.264 محمي ببراءات اختراع؛ ويتم الترخيص عبر MPEG LA group. يمكن تضمين فيديو H.264 في أغلبية الحاويات الحديثة، بما في ذلك MP4 (تستعملها Apple بشكلٍ أساسي في متجر iTunes) و MKV (التي تُستعمَل بشكلٍ أساسي من هواة الفيديو الذين ليس لهم هدف تجاري). Theora تم تطوير Theora من مرماز VP3 ثم طوِّرَ من مؤسسة Xiph.org.‏ Theora مرماز مجاني الاستخدام (royalty-free) وليس محميًا بأيّة براءات اختراع عدا براءات اختراع مرماز VP3 الأصلي، الذي رُخِّص على أنَّه مجانيُ الاستخدام أيضًا. على الرغم من أنَّ المعيار قد "جُمِّدَ" (frozen) منذ عام 2004، إلا أنَّ Theora project (الذي يتضمن مكتبات ترميز وفك ترميز مفتوحة المصدر) قد أصدر النسخة 1.0 في تشرين الثاني/نوفمبر عام 2008، والإصدار 1.1 في أيلول/سبتمبر عام 2009. يمكن تضمين الفيديو المرمَّز بمرماز Theora في أيّة صيغة من صيغ الحاويات، لكن من الشائع أن نراه في حاوية Ogg. تدعم جميع توزيعات لينكس Theora، ويتضمن متصفح Mozilla Firefox 3.5 دعمًا داخليًا للفيديو المرمز بمرماز Theora؛ وأقصد بكلمة "داخليًا" أنَّه متوفرٌ على جميع الأنظمة دون إضافات خاصة. يمكنك أيضًا تشغيل الفيديو المرمز بمرماز Theora على ويندوز أو على Mac OS X بعد تثبيت برمجية فك الترميز المفتوحة المصدر من Xiph.org. VP8 VP8 هو مرماز فيديو آخر من On2، نفس الشركة التي طوَّرت VP3 (الذي أصبح لاحقًا Theora). بكلامٍ تقني، يُنتِج هذا المرماز فيديو بنفس جودة النمط "High" في H.264، في حين يحاول تقليل تعقيد عملية فك الترميز كما في نمط "Baseline" في H.264. في عام 2010، اشترت Google شركة On2 ونشرت مواصفات مرماز الفيديو وأصدرت برمجية ترميز وفك ترميز مفتوحة المصدر. وكان جزءًا من هذه العملية هو "فتح" Google لجميع براءات الاختراع التي سجلتها شركة On2 لمرماز VP8، وذلك بجعلها مجانية الاستخدام أي royalty-free (وهذا أفضل ما يمكن فعله مع براءات الاختراع، فلا يمكنك حقًا "التخلي" عنها أو حذفها بعد تسجيلها. لكن لجعلها متوافقة مع البرمجيات مفتوحة المصدر فيمكن ترخيص للاستخدام مجانًا، وبهذا يستطيع أي شخصٍ استخدام التقنيات المُغطاة من براءة الاختراع دون دفع أي شيء أو دون محاولة الحصول على ترخيص خاص). وبهذا أصبح VP8 مرماز عصري مجاني الاستخدام ليس محميًا بأيّة براءات اختراع عدا تلك التي سجلتها شركة On2 (وتملكها Google حاليًا) والتي يمكن استعمالها مجانًا. مرمزات الصوت ستحتاج إلى تضمين مسار صوتي في مقاطع الفيديو إلا إذا كنتَ ستنشر الأفلام قبل عام 1927 فقط. ومثل مرمزات الفيديو، مرمازات الصوت هي خوارزميات التي يُرمَّز (encode) بها مسار الصوت. وأيضًا مثل مرمازات الفيديو، هنالك مرمازات صوت ليس فيها ضياعٌ في البيانات (lossless) وأخرى تضيع فيها بعض البيانات (lossy). وكما هو حال الفيديو الذي لا يوجد فيه ضياعٌ في البيانات، ستكون المساحة التخزينية لمسارات الصوت التي ليس فيها ضياعٌ في البيانات كبيرةً جدًا لكي نستفيد منها على الويب؛ لذلك سنركِّز على مرمازات الصوت التي تسبب فقدانًا لبعض البيانات. وسأضيق المجال قليلًا، لوجود تصنيفاتٍ مختلفة لمرمازات الصوت؛ إذ أنَّ الصوت يستعمل في أماكن لا يُستعمَل فيها الفيديو (في الهواتف مثلًا)، وهنالك تصنيفٌ كاملٌ من مرمازات الصوت المُتخصصة بترميز الكلام، فلا تفكر في ترميز الموسيقى بهذه المرمازات، لأن النتيجة النهائية ستبدو وكأن طفلًا ذا أربعة أعوام يغني على الهاتف. لكنك تستطيع استخدامها في خدمة PBX من Asterisk لأن تراسل البيانات ثمين هناك، ولأن هذه المرمازات تضغط الصوت البشري ليأخذ مساحةً تخزينيةً لا تشكِّل إلا جزءًا بسيطًا من المساحة التخزينية التي كانت ستأخذها المرمازات ذات الغرض العام. لكن نتيجةً لعدم دعم تلك المرمازات في المتصفحات داخليًا أو عبر الإضافات الخارجية، فلم يسطع نجم تلك المرمازات في الويب، ولهذا سنركِّز على مرمزات الصوت العامة التي تسبب ضياعًا في بعض البيانات. وكما ذكرنا سابقًا، سيقوم حاسوبك بثلاثة أشياء على الأقل في وقتٍ واحد عندما "تشاهد مقطع فيديو": محاولة تفسير صيغة الحاوية فك ترميز مسار الفيديو فك ترميز مسار الصوت وإرسال الأصوات إلى مكبرات الصوت في جهازك يُحدِّد مرماز الصوت طريقة القيام بالخطوة رقم 3 مما سبق: فك ترميز مسار الصوت وتحويله إلى موجات رقمية يمكن لمكبرات الصوت عندك تحويلها إلى صوت. وكما في مرمازات الفيديو، هنالك حيلٌ عدِّة تُستعمَل لتقليل حجم البيانات المُخزَّنة في مسار الصوت. ولمّا كنّا نتحدث عن مرمازات الصوت التي تُسبِّب ضياعًا في بعض البيانات، فسنفقد بعض المعلومات أثناء عملية "التسجيل ← الترميز ← فك الترميز ← الاستماع". تحذف مختلف مرمزات الصوت أشياءً مختلفة، لكنها كلها تخدم نفس الغرض: ألّا تشعر بضياعِ أيِّ شيءٍ عندما تستمع إلى المسار. هنالك مفهومٌ في مسارات الصوت ليس موجودًا في مسارات الفيديو ألا وهو "القنوات" (channels). نحن نرسل الصوت إلى مكبرات الصوت، صحيح؟ حسنًا، ما عدد مكبرات الصوت عندك؟ إذا كنت تجلس أمام حاسوبٍ محمول فربما يكون عندك مكبران: أحدهما على اليمين والآخر على اليسار. لكن حاسوبي المكتبي يملك ثلاثة مكبرات: واحد على اليمين وآخر على اليسار وثالث على الأرض. أنظمة "الصوت المحيطي" (surround sound) السمعية تملك ستة مكبرات أو أكثر متوزعة توزيعًا مدروسًا في أنحاء الغرفة. مهمة كل مكبر أن يُخرِج قناة (channel) معيّنة من التسجيل الأصلي. الفكرة النظرية وراء ذاك النظام هي أنَّك ستجلس في منتصف المسافة الفاصلة بين تلك المكبرات محاطًا بست قنوات منفصلة من الصوت، وسيدرك مخك الصوت وستشعر كما لو أنك في منتصف الأحداث التي تسمع صوتها. لكن هل تعمل فعلًا كما شرحنا أعلاه؟ تقول الشركات ذوات رؤوس الأموال الكبيرة أنها تعمل. تستطيع أغلبية مرمازات الصوت ذات الغرض العام التعامل مع قناتين من الصوت. يُقسَم الصوت أثناء التسجيل إلى قناتين يمنى ويسرى؛ وستُخزَّن كلا القناتين في نفس المسار الصوتي أثناء الترميز؛ وستُرسَل محتويات كل قناة إلى مكبر الصوت الموافق لها أثناء فك الترميز. يمكن لبعض مرمزات الصوت التعامل مع أكثر من قناتين، إذ يعرفون أي قناة يجب أن تُرسَل لأي مكبر ثم سيتولى مُشغِّل الصوتيات عندك الأمر. هنالك الكثير من مرمازات الصوت، هل ذكرتُ لك سابقًا أنَّ هنالك الكثير من مرمازات الفيديو؟ انسَ ذلك! هنالك الكثير والكثير من مرمازات الصوت، لكن يهمنا في الويب ثلاثةٌ منها: MP3 و ACC و Vorbis. MPEG-1 Audio Layer 3 MPEG-1 Audio Layer 3 معروفة بالعامية على أنها "MP3"؛ لا أعرف من أي كوكبٍ أتيت إن لم تسمع من قبل عن MP3. تبيع شركة Walmart مشغِّلات موسيقى محمولة وتسميها "MP3 players". وهي منتشرة انتشارًا كبيرًا. يمكن أن تحتوي ملفات MP3 على قناتين صوتيتين على الأكثر، ويمكن ترميزها بمختلف معدلات البث (أي bitrates): 64 كيلوبت/ثانية، أو 128 كيلوبت/ثانية، أو192 كيلوبت/ثانية، أو غيرها التي تتراوح قيمها من 32 إلى 320. قيم معدلات البث (bitrates) الأعلى تعني أنَّ مساحة الملفات أكبر وجودة الصوت أدق، لكن العلاقة بين جودة الصوت ومعدل البث ليست خطيّة (linear، أي أنَّ جودة الأصوات المرمزة بمعدل بث 64 كيلوبت/ثانية أفضل بالضعفين من 64 كيلوبت/ثانية، لكن 256 كيلوبت/ثانية ليس أكثر جودة بمقدار الضعفين من 128 كيلوبت/ثانية). تسمح صيغة MP3 بالترميز ذي معدل البث المتغير (variable bitrate encoding، الذي يعني أنَّ أجزاءً من المسار مضغوطةٌ أكثر من غيرها). على سبيل المثال، يمكن ترميز السكت بين النوتات الموسيقية بمعدل بث منخفض، ثم سيرتفع معدل البث عندما يُعزَف لحنٌ معقَّدٌ على عدِّة آلات موسيقية. يمكن أيضًا ترميز MP3 بمعدل بث ثابت، ولا غرابة أن يُسمى ذلك بالترميز ذي معدل البث الثابت (constant bitrate encoding). لا يُعرِّف معيار MP3 كيفية الترميز باستخدام MP3 تمامًا (على الرغم من أنَّه يُعرِّف تمامًا كيفية فك الترميز)؛ تستخدم مختلف المُرمِّزات آلياتٍ مختلفة تُنتِج نتائج متنوعة كثيرًا، لكن يمكن قراءتها جميعًا من نفس المشغِّلات. مشروع LAME المفتوح المصدر هو أفضل مرمِّز حر، ويمكن القول أنَّه أفضل المرمِّزات لجميع معدلات البث إلا المنخفضة منها. صيغة MP3 (التي وُضِعَ معيارها عام 1991) هي صيغة محمية ببراءة اختراع، وهذا هو السبب وراء عدم إمكانية تشغيل نظام لينكس لملفات MP3 مباشرةً. وبشكل عام، تدعم جميع مشغلات الموسيقى المحمولة ملفات MP3، ويمكن تضمين مسارات MP3 ضمن أيّ حاوية فيديو. ويمكن أن يُشغِّل Adobe Flash ملفات MP3 لوحدها أو مسارات MP3 الموجودة ضمن حاوية MP4. Advanced Audio Coding Advanced Audio Coding المعروفة أيضًا باسم "AAC" التي وضِعَ معيارها عام 1997، والتي أنتشر صيتها عندما اختارتها Apple صيغةً افتراضيةً لمتجر iTunes. كانت جميع ملفات AAC التي تم "شراؤها" من متجر iTunes مشفرةً بحقوق رقمية مملوكة لشركة Apple ‏(أي DRM) التي كانت تدعى FairPlay؛ أصبحت بعض الأغنيات متاحةً في متجر iTunes كملفات AAC غير محمية، التي تدعوها Apple ‏"iTunes Plus" لأن ذلك أفضل من تسمية جميع الأشياء الأخرى باسم "iTunes Minus". صيغة AAC محمية ببراءات اختراع؛ وأسعار الترخيص متوفرة على الإنترنت. صُمِّمَت AAC لتوفير صوت بجودة أعلى من MP3 بنفس معدل البث (bitrate)، ويمكن ترميز الصوت بأي معدل بث (صيغة MP3 محدودة بعدد ثابت من معدلات البث، حدّها الأقصى هو 320 كيلوبت/ثانية). يمكن لصيغة AAC ترميز 48 قناة صوتية كحد أقصى، على الرغم من عدم وجود حالة لتطبيق ذلك عمليًا. تختلف صيغة AAC عن MP3 في أنها تُعرِّف أنماطًا (profiles) بطريقة مشابهة لمرمز H.264 ولنفس الأسباب. فهنالك "نمط" بسيط مُصمَّم لكي يُشغَّل في الوقت الحقيقي على الأجهزة ذات قدرة المعالجة المحدودة، بينما توجد "أنماط" توفر صوتًا أكثر جودةً بنفس معدل البث لكن ذلك على حساب البطء في الترميز وفك الترميز. جميع منتجات Apple الحالية بما فيها iPod و AppleTV و QuickTime تدعم بعض "profiles" صيغة AAC في ملفات الصوت المنفصلة وفي مسارات الصوت المدمجة في حاوية MP4. يدعم Adobe Flash جميع "أنماط" صيغة AAC في MP4، وكذلك المشغلات الحرة مثل MPlayer و VLC. أما للترميز، فمكتبة FAAC هي مكتبةٌ مفتوحة المصدر؛ وهنالك خيارٌ في وقت البناء (compile-time) في برمجية mencoder و ffmpeg لدعم ترميز AAC. Vorbis Vorbis تُسمى عادةً "Ogg Vorbis" (على الرغم من أنَّ هذا خاطئ تقنيًا، لأن "Ogg" هي صيغة حاويات ويمكن لمسارات صوت Vorbis أن تُضمَّن في حاويات أخرى). صيغة Vorbis ليست محمية بأيّة براءات اخترع ولهذا ستجدها مدعومةً في توزيعات لينكس مباشرةً وفي جميع الأجهزة المحمولة التي تُشغِّل نظام Rockbox. يدعم Mozilla Firefox 3.5 مسارات صوت Vorbis في حاوية Ogg، أو فيديو Ogg مع مسار صوتي بصيغة Vorbis، ويمكن أيضًا للهواتف العاملة بنظام أندرويد أن تُشغِّل ملفات Vorbis الصوتية المستقلة. عادة ما تُضمَّن مسارات Vorbis الصوتية في حاوية Ogg أو WebM، لكن يمكن أيضًا تضمينها في حاوية MP4 أو MKV (أو بعد تطبيق بعض الحيل: في حاوية AVI). تدعم صيغة Vorbis الصوتية أي عدد من القنوات الصوتية. هنالك عددٌ من برمجيات ترميز وفك ترميز صوت Vorbis المفتوحة المصدر، بما في ذلك OggConvert (للترميز)، و ffmpeg (فك ترميز)، و aoTuV (للترميز)، و libvorbis (لفك الترميز). وهنالك إضافات لبرمجية QuickTime لنظام Mac OS X و DirectShow لنظام ويندوز. ما الصيغ التي تعمل في الويب إن لم يؤلمك رأسك إلى الآن فأنت تبلي بلاءً حسنًا. يمكنك أن تستنتج بسهولة أنَّ الفيديو (والصوت) هو موضوعٌ معقدٌ، وما أوردناه سابقًا هو النسخة المختصرة من القصة! أنا متأكد أنك تتساءل عن ارتباط HTML5 بما سبق. حسنًا، يوجد في HTML5 عنصرٌ اسمه <video> لتضمين مقاطع الفيديو في صفحة ويب، ولا توجد هنالك أيّة قيود على المرماز المستخدم لترميز الفيديو أو الصوت أو حتى صيغة الحاوية التي يمكنك استخدامها لمقاطع الفيديو. يمكن لعنصر <video> وحيد أن يُشير إلى عدِّة ملفات فيديو، وسيختار المتصفح أول ملف فيديو يستطيع تشغيله. كل ما عليك فعله هو معرفة ما هي الحاويات والمرمازات التي تدعمها المتصفحات. هذا هو حال دعم صيغ الفيديو في HTML5 في الوقت الراهن: متصفح Mozilla Firefox ‏(3.5 أو ما بعده) يدعم فيديو Theora وصوت Vorbis في حاوية Ogg. ويدعم Firefox 4 صيغة WebM. متصفح Opera ‏(10.5 وما بعده) يدعم فيديو Theora وصوت Vorbis في حاوية Ogg. يدعم Opera 10.60 صيغة WebM. متصفح Google Chrome ‏(3.0 وما بعده) يدعم فيديو Theora وصوت Vorbis في حاوية Ogg. يدعم Google Chrome 6.0 صيغة WebM. متصفح Safari على نظامي Mac وويندوز (3.0 وما بعده) سيدعم أي شيء يدعمه QuickTime. نظريًا، هذا يعني أنَّك قد تطلب من مستخدميك تثبيت إضافات خارجية إلى مُشغِّل QuickTime؛ لكن عمليًا لن يفعل ذلك إلا القليل. لذلك عليك أن تستعمل الصيغ التي يدعمها QuickTime مباشرةً. وهذه قائمة طويلة إلا أنها لا تحتوي WebM، أو Theora، أو Vorbis، أو حاوية Ogg. لكن QuickTime فيه دعمٌ مدمج لفيديو H.264‏ (نمط main) وصوت AAC في حاوية MP4. الهواتف المحمولة متل iPhone والهواتف التي تعمل بنظام أندرويد تدعم فيديو H.264 ‏(نمط baseline) وصوت AAC ‏(‎نمط ‎"low complexity"‎) في حاوية MP4. يدعم Adobe Flash (الإصدار 9.0.60.184 وما بعده) فيديو H.264 وصوت AAC في حاوية MP4. يدعم Internet Explorer جميع "أنماط" فيديو H.264 وصوت AAC أو MP3 في حاوية MP4. ويمكنه أيضًا تشغيل فيديو WebM إن ثبتتَ مرمازًا خارجي، الذي لا يكون مثبتًا افتراضيًا في أي إصدار من ويندوز. لا يدعم IE9 المرمازات الخارجية الأخرى (على عكس Safari، الذي سيُشغِّل ما يستطيع QuickTime تشغيله). لا يدعم متصفح Internet Explorer 8 عنصر video في HTML5 إطلاقًا، لكن تقريبًا جميع مستخدمي Internet Explorer يملكون إضافة Adobe Flash. وسأريك لاحقًا في هذا الفصل كيف يمكنك استخدام عنصر video في HTML5 لكن مع خيارٍ احتياطي هو استخدام Flash. ربما من الأسهل تلخيص ما سبق في جدول. الترميز/الحاوية IE Firefox Safari Chrome Opera iPhone Android Theora+Vorbis+Ogg . 3.5+ * 3.0+ 10.5+ . 2.3+ H.264+AAC+MP4 9.0+ 21+ 3.2+ 3.0+**** 25+ 3.2+ 2.0+ WebM 9.0+** 4.0+ * 6.0+ 10.6+ . 2.3*** * متصفح Safari سيُشغِّل أي شيء يستطيع QuickTime تشغيله، لكن QuickTime لا يأتي مع دعمٍ مسبق إلا لصيغ H.264/AAC/MP4. ** متصفح Internet Explorer 9 سيدعم WebM "فقط إذا ثبَّت المستخدم مرماز VP8"، الذي يعني ضمنيًا أنَّ مايكروسوفت لن تضيف المرماز بنفسها. *** على الرغم من أنَّ متصفح أندرويد 2.3 يدعم WebM، لكن لا توجد إمكانية لفك الترميز عتاديًا، وهذا يقلل من عمر البطارية. **** قد أعلِنَ أنَّ Google Chrome سيسقط الدعم عن H.264 قريبًا، وقد تم تبرير ذاك القرار؛ لكنه لم يُطبَّق إلى الآن. ولكن هذه هي الطامة الكبرى: لا توجد تجميعة واحدة من الحاويات والمرمازات التي تعمل في جميع متصفحات HTML5، ومن غير المحتمل أن يتغير هذا في المستقبل القريب. لكي تجعل مقاطع الفيديو قابلة للعرض في مختلف الأجهزة والمنصات، فعيلك أن تُرمِّز مقطع الفيديو بأكثر من صيغة. هذا هو مسار عملك إذا كنت تريد توفير مقطع الفيديو بأكبر قدر من التوافقية بين المتصفحات: أنشِئ نسخةً تستعمل WebM ‏(VP8 + Vorbis). أنشِئ نسخةً أخرى تستعمل فيديو H.264 بنمط baseline مع صوت AAC‏ بنمط low complexity‎ في حاوية MP4. أنشِئ نسخةً أخرى تستعمل فيديو Theora وصوت Vorbis في حاوية Ogg. اربط ملفات الفيديو الثلاثة السابقة إلى عنصر <video> وحيد، واترك مجالًا لاستعمال مشغِّل يعتمد على تقنية Flash إن لم يدعم المتصفح العنصر video. مشاكل الترخيص مع فيديو H.264 قبل أن نكمل، علي أن أُشير أنَّ هنالك تكلفة لترميز مقاطع الفيديو مرتين. حسنًا، لا أقصد أنَّ عليك فعلًا ترميزها مرتين، وهذا يعني استهلاك أكبر للوقت ولطاقة المعالجة فيما لو كنت سترمِّزها مرةً واحدة. لكن هنالك تكلفة حقيقة مرتبطة بفيديو H.264: تكلفة الترخيص. هل تتذكر عندما شرحتُ H.264 أول مرة، وذكرت بسرعة أنَّ ذاك الترميز خاضع لبراءات اختراع ويمكن أخذ ترخيص بالاستخدام من MPEG LA؟ قد تبيّن أنَّ ما سبق مهم، ولكن لتفهم لماذا، سأحيلك إلى مقتطف من مقالة "The H.264 Licensing Labyrinth": تُقسِّم MPEG LA رخصة H.264 إلى رخصتين فرعيتين: واحدة لمصنعي برمجيات الترميز وفك الترميز، وأخرى لموزعي المحتوى. الرخصة الفرعية المتعلقة بتوزيع المحتوى تُقسَّم بدورها إلى أربعة تصنيفات أساسية، اثنان منها (الاشتراك subscription و "title-by-title" أي لكل مقطع على حدة) مرتبطان فيما إذا كان المستخدم النهائي سيدفع مباشرةً لقاء خدمة بث الفيديو، واثنان منها (البث "المجاني" للتلفاز وللإنترنت) مرتبطان بالربح القادم من مصادر أخرى بخلاف المشاهد. تكلفة الرخصة للبث "المجاني" للتلفاز مبنيةٌ على خيارَي دفع. أول خيار هو دفع مبلغ 2500$ لكل مُرمِّز AVC، الذي يعني تسليم مُرمِّز AVC واحد فقط "مُستخدَم من قبل أو نيابةً عن المُرخَّص له لبث فيديو AVC للمستخدم النهائي"، الذي سيفك الترميز ويشاهد الفيديو. إذا كنت تتساءل فيما إذا كانت التكلفة مضاعفة هنا، فالجواب هو نعم: دُفِعَت أجرة الرخصة من مُصنِّع المرمِّز، وسيدفع الطرف الذي سيبث الفيديو أحد خيارَي الدفع. الخيار الثاني للترخيص هو دفع اشتراك سنوي للبث. وأجرة البث السنوي مُقسَّمة إلى شرائح تختلف حسب عدد المشاهدين: ‎2500‏‎$‎ في كل سنة لكل سوق من أسواق البث (broadcast markets) لعدد الأسر التي تشاهد البث بين 100,000–499,999 5000‏$‏ في كل سنة لكل سوق من أسواق البث (broadcast markets) لعدد الأسر التي تشاهد البث بين 500,000–999,999 10,000‏$‏ في كل سنة لكل سوق من أسواق البث (broadcast markets) لعدد الأسر التي تشاهد البث الأكبر من 1,000,000 بعد استعراض جميع المشاكل التي تحف البث "المجاني" للتلفاز، لماذا يجب أن يهتم بها شخصٌ لا يبث الفيديو؟ كما ذكرتُ سابقًا، تكلفة الاشتراك تنطبق على أيّ عملية توصيل للمحتوى، لكن تعريف البث "المجاني" للتلفاز يعني أكثر من البث عبر الهوائيات (over-the-air). أضافت MPEG LA أجور اشتراك لبث الفيديو عبر الإنترنت التي لن يدفع المستخدم النهائي أيّة تكاليف لمشاهدة المقطع، أي بكلامٍ آخر: أي بث عام لفيديو سواءً كان عبر الهوائيات أو خدمة "الكيبل" أو الأقمار الاصطناعية أو عبر الإنترنت… سيخضع إلى أجور الاشتراك. لكن MPEG-LA أعلنوا أنَّهم لن يأخذوا أجورًا على البث عبر الإنترنت، لكن هذا لا يعني أنَّ مرماز H.264 مجاني الاستخدام لجميع المستخدمين؛ وبشكل خاص برمجيات الترميز (كالتي تُعالِج الفيديو المرفوع على موقع YouTube) وبرمجيات فك الترميز (مثل البرمجية الموجودة في متصفح Microsoft Internet Explorer 9) التي ما زالت تخضع لأجور الترخيص. ترجمة -وبتصرّف- لفصل "Video" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  14. مدخل إلى html5

    تُعرِّف HTML العنصر <canvas> على أنه "لوحة نقطية ذات أبعاد معينة يمكن استخدامها لعرض المخططات ورسومات الألعاب وغيرها من الصور المرئية برمجيًا". ويُمثِّل مستطيلًا في صفحتك حيث تستخدم JavaScript لرسم أي شيء تريده فيه. الدعم الأساسي للعنصر canvas IE Firefox Safari Chrome Opera iPhone Android 7.0+* 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+ * يتطلب IE ‏7 و 8 مكتبة خارجية هي explorercanvas، أما IE 9 فهو يدعم العنصر <canvas> داخليًا. كيف يبدو عنصر canvas؟ ليس له شكل، حقًا! ليس في عنصر <canvas> أي محتوى وليس له إطارٌ حتى. يُضاف عنصر canvas كالآتي: <canvas width="300" height="225"></canvas> لنضف إطارًا منقطًا لكي نستطيع أن نرى ما الذي نتعامل معه: الشكل 1: عنصر canvas مع إطار يمكن أن يكون لديك أكثر من عنصر <canvas> في نفس الصفحة، وسيظهر كل عنصر على حدة في شجرة DOM، ويحافظ كل عنصر canvas على خاصياته؛ فإن أعطيت كل عنصر canvas خاصية id، فيمكنك الوصول إليه كما تفعل مع أي عنصر آخر. لنوسِّع الشيفرة السابقة لكي تتضمن خاصية id: <canvas id="a" width="300" height="225"></canvas> أصبح بإمكاننا بسهولة العثور على عنصر <canvas> السابق في شجرة DOM. var a_canvas = document.getElementById("a"); الأشكال البسيطة IE Firefox Safari Chrome Opera iPhone Android 7.0+* 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+ * يتطلب IE ‏7 و 8 مكتبة خارجية هي explorercanvas، أما IE 9 فهو يدعم رسم الأشكال البسيطة في العنصر <canvas> داخليًا. يبدأ كل عنصر canvas فارغًاـ ثم علينا الرسم فيه. لنبدأ ببعض الأشكال. يمكن استخدام الحدث (action) ‏onclick لاستدعاء الدالة الآتية عندما يضغط المستخدم على زرٍ ما: function draw_b() { var b_canvas = document.getElementById("b"); var b_context = b_canvas.getContext("2d"); b_context.fillRect(50, 25, 150, 100); } لا يوجد شيءٌ مميزٌ في أول سطر من الدالة، إذ أنَّ مهمته هي العثور على عنصر <canvas> الموجود في شجرة DOM. function draw_b() { var b_canvas = document.getElementById("b"); var b_context = b_canvas.getContext("2d"); b_context.fillRect(50, 25, 150, 100); } لدى كل عنصر canvas ما نسميه "إطار الرسم" (drawing context)، الذي يحدث فيه كل الأمور المسلية. فبعد أن تعثر على عنصر <canvas> في شجرة DOM (باستخدام document.getElementById()‎ أو أيّة طريقة أخرى) ستستطيع أن تستدعي الدالة getContext()‎، يجب عليك تمرير السلسلة النصية "2d" دومًا إلى الدالة getContext()‎. س: هل يمكن رسم رسوميات 3D في canvas؟ ج: نعم، عبر تقنية WebGL التي يمكنها رسم الأشكال ثلاثية الأبعاد في المتصفحات دون إضافات. تدعم أغلبية المتصفحات الحديثة هذه التقنية (Firefox الإصدار الرابع وما بعده، و Chrome الإصدار التاسع وما بعده، و Opera 12 وما بعده، و Safari 5.1 وما بعده، و IE 11)؛ يتم العمل على تطوير هذه التقنية في مجموعة عمل WebGL. إذًا، أصبح لديك عنصر <canvas> ولديك إطار الرسم (drawing context) الخاص به. إطار الرسم هو المكان الذي ستُعرَّف فيه جميع دوال وخاصيات الرسم. هنالك مجموعةٌ كاملةٌ من الخاصيات والدوال المُكرَّسة لرسم المستطيلات: يمكن أن تكون الخاصية fillStyle لونًا من ألوان CSS، أو نقشًا (pattern)، أو تدرجًا لونيًا (gradient) (سنذكر مزيدًا من المعلومات عن التدرجات اللونية بعد قليل). القيمة الافتراضية لهذه الخاصية هي اللون الأسود، لكنك تستطيع أن تضبطها لما تشاء. سيتذكر كل إطار رسم (drawing context) خاصياته لطالما بقيت الصفحة مفتوحةً إلا إن فعلت شيئًا لإعادة ضبطه. الدالة (fillRect(x, y, width, height ترسم مستطيلًا مملوءًا باللون أو النقش الموجود في fillStyle. الخاصية strokeStyle شبيهة بخاصية fillStyle، فيمكن أن تكون لون CSS أو نقشًا أو تدرجًا لونيًا. الدالة (strokeRect(x, y, width, height ترسم مستطيلًا دون ملئه، إذ ترسم حوافه وإطاره الخارجي فحسب، وتَستعمل الخاصية strokeStyle لذلك. الدالة (clearRect(x, y, width, height تسمح كل البكسلات الموجودة في المستطيل المُحدَّد. س: هل يمكنني أن أعيد ضبط لوحة الرسم في عنصر canvas؟ ج: نعم، فسيؤدي تحديد عرض أو ارتفاع عنصر <canvas> إلى إعادة ضبط (reset) كل الخاصيات في إطار الرسم إلى قيمها الافتراضية. لاحظ أنَّه ليس من الضروري تغيير العرض، إذ يمكنك أن تضبطه إلى قيمته الحالية كما يلي: var b_canvas = document.getElementById("b"); b_canvas.width = b_canvas.width; بالعودة إلى الشيفرة في المثال السابق: var b_canvas = document.getElementById("b"); var b_context = b_canvas.getContext("2d"); b_context.fillRect(50, 25, 150, 100); سترسم الدالة fillRect()‎ عند استدعائها مستطيلًا وتملؤه بنمط الملء الحالي الذي هو اللون الأسود (إلا إذا غيرتَه). يُعرَّف المستطيل عبر زاويته العليا اليسرى (50، 25)، وعرضه (150)، وارتفاعه (100). لكي نفهم ذلك فهمًا جيدًا، فلنلقِ نظرةً إلى نظام الإحداثيات في canvas. الإحداثيات في عنصر canvas يمكننا تخيل لوحة الرسم في عنصر canvas على أنها شبكة ثنائية البعد، ويكون مبدأ الإحداثيات فيها (‎0, 0) في الزاوية العليا اليسرى من لوحة الرسم. تزداد القيم على المحور X عند الانتقال نحو الحافة اليمنى من لوحة الرسم، وتزداد القيم على المحور Y بالانتقال نحو الحافة السفلية من لوحة الرسم. الشكل 2: توضيح لنظام الإحداثيات في عنصر canvas رُسِمَ الشكل السابق عبر عنصر <canvas> الذي يحتوي على: مجموعة من الخطوط الأفقية مجموعة من الخطوط الشاقولية خطين أفقيين سوداوين خطين صغيرين سوداوين مائلين يشكلان سهمًا خطين شاقوليين سوداوين خطين صغيرين سوداوين مائلين يشكلان السهم الآخر الحرف "x" الحرف "y" النص "(‎(0, 0" قرب الزاوية العليا اليسرى النص "(‎500, 375)" قرب الزاوية السفلى اليمنى نقطة في الزاوية العليا اليسرى، وأخرى في الزاوية السفلى اليمنى علينا أولًا تعريف العنصر <canvas> نفسه، حيث يُحدِّد العنصر <canvas> خاصية العرض width والارتفاع height، والمُعرِّف id كي نستطيع العثور عليه بسهولة لاحقًا. <canvas id="c" width="500" height="375"></canvas> ثم علينا كتابة سكربت لكي نجد عنصر <canvas> في شجرة DOM ونحصل على إطار الرسم الخاص به. var c_canvas = document.getElementById("c"); var context = c_canvas.getContext("2d"); نستطيع الآن البدء في رسم الخطوط. المسارات IE Firefox Safari Chrome Opera iPhone Android 7.0+* 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+ * يتطلب IE ‏7 و 8 مكتبة خارجية هي explorercanvas، أما IE 9 فهو يدعم المسارات في العنصر <canvas> داخليًا. تخيل أنك تريد أن ترسم رسمةً بالحبر، من المؤكد أنَّك لن تبدأ الرسم بأقلام الحبر مباشرةً، لأنك قد تُخطئ؛ فعوضًا عن ذلك سترسم المستقيمات والمنحنيات بقلم الرصاص، ثم إن أعجبتك فيمكنك أن "تُحبِّرَها". هنالك ما نسميه "المسارات" (paths) في عناصر canvas، وتعريف المسار يُشبِه تمامًا الرسم بقلم الرصاص؛ يمكنك رسم ما تشاء لكنه لن يكون جزءًا من اللوحة النهائية إلا إن أمسكت أقلام التحبير ومررتها فوق المسار الذي رسمتَه. استعمل الدالتين الآتيتين لرسم المستقيمات "بقلم الرصاص": (moveTo(x, y تحريك قلم الرصاص إلى نقطة البداية المُحدَّدة. (lineTo(x, y رسم خط إلى نقطة النهاية المُحدَّدة. سيزداد حجم المسار كلما استدعيت الدالتين moveTo()‎ و lineTo()‎. ترسم الدالتان السابقتان "بقلم الرصاص" (يمكنك أن تسمي هذه العملية ما تشاء)، أي أنَّك لن ترَ شيئًا على لوحة الرسم إلى أن تستدعي إحدى دوال "التحبير". لنبدأ برسم الشبكة: //رسم المستقيمات الشاقولية for (var x = 0.5; x < 500; x += 10) { context.moveTo(x, 0); context.lineTo(x, 375); } //رسم المستقيمات الأفقية for (var y = 0.5; y < 375; y += 10) { context.moveTo(0, y); context.lineTo(500, y); } تلك الدوال "رصاصية"، أي لن يظهر شيءٌ على لوحة الرسم بعد؛ إذ سنحتاج إلى دالة "تحبير" لإظهار تلك الخطوط. context.strokeStyle = "#eee"; context.stroke(); الدالة storke()‎ هي إحدى دوال "التحبير"، وهي تُحبِّر المسار المعقد الذي عرَّفتَه بدوال moveTo()‎ و lineTo()‎ السابقة. خاصية strokeStyle تتحكم بلون تلك الخطوط. هذه هي النتيجة: الشكل 3: شبكة مرسومة داخل عنصر canvas س: لماذا بدأت x و y من 0.5 وليس من 0؟ ج: تخيل أنَّ كل بكسل هو مربع كبير، وأنَّ الإحداثيات ذات الرقم الكامل (0، 1، 2…) هي حواف تلك المربعات؛ فإذا أردت أن ترسم خطًا عرضُه واحدة الأطوال (one-unit-wide) بين الإحداثيات ذات الرقم الكامل، فسوف يمتد إلى أن يصل إلى طرفَي المربع (انظر الشكل الآتي للإيضاح)، وسيكون الخط الناتج مرسومًا بعرض بكسلين. أما لرسم خط عرضه بكسل واحد، فعليك أن تُزيح الإحداثيات بمقدار 0.5 عموديًا على منحى (أو اتجاه) الخط. على سبيل المثال، إذا كنت تحاول رسم خط من (‎1, 0) إلى (‎1, 3)، فسيرسم المتصفح خطًا يغطي 0.5 بكسل على جانبَيّ x=1، ولكن الشاشة غير قادرة على عرض نصف بكسل، لذلك سيمتد الخط لكي يُغطي بكسلين: أما لو أردت أن ترسم خطًا من (1.5, 0) إلى (1.5, 3)، فسيرسم المتصفح خطأ يغطي 0.5 بكسل على طرفَي x=1.5، الذي يؤدي إلى رسم خط بعرض 1 بكسل: لنرسم الآن السهم الأفقي. جميع الخطوط والمنحنيات الموجودة على نفس المسار ستُرسَم بنفس اللون (أو التدرج اللوني، وسنأتي لذكر ذلك لاحقًا). لكننا نريد أن نرسم السهم بلون مختلف (الأسود)، لذلك نحتاج إلى البدء بمسار (path) جديد. //مسار جديد context.beginPath(); context.moveTo(0, 40); context.lineTo(240, 40); context.moveTo(260, 40); context.lineTo(500, 40); context.moveTo(495, 35); context.lineTo(500, 40); context.lineTo(495, 45); وبنفس الطريقة نرسم السهم الشاقولي؛ ولمّا كان السهم الشاقولي بنفس لون السهم الأفقي، فلا حاجة إلى إنشاء مسار جديد، إذ سيُشكِّل السهمان مسارًا واحدًا. //لا حاجة إلى إنشاء مسار جديد context.moveTo(60, 0); context.lineTo(60, 153); context.moveTo(60, 173); context.lineTo(60, 375); context.moveTo(65, 370); context.lineTo(60, 375); context.lineTo(55, 370); لقد قلتُ أنَّ هذين السهمين يجب أن يُرسما باللون الأسود، لكن لون strokeStyle ما يزال "الفضي الفاتح" (لا تتم عملية إعادة تعيين قيم fillStyle و strokeStyle عندما تبدأ مسارًا جديدًا). لا بأس، لأننا رسمنا إلى الآن "بالرصاص" ولم "نُحبِّر" بعد، علينا الآن أن نضبط قيمة strokeStyle إلى اللون الأسود؛ وإلا سيُرسم هاذين السهمين بالفضي الفاتح وسيصعب علينا رؤيتهما. سيُغيّر السطران الآتيان اللون إلى الأسود ويرسمان الخطوط في لوحة الرسم: context.strokeStyle = "#000"; context.stroke(); هذه هي النتيجة: الشكل 4: المحوران مرسومان دون تسمية على لوحة الرسم النص IE Firefox Safari Chrome Opera iPhone Android 7.0+* 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+ * يتطلب IE ‏7 و 8 مكتبة خارجية هي explorercanvas، أما IE 9 فهو يدعم طباعة النصوص في العنصر <canvas> داخليًا. ** يتطلب Firefox 3.0 مكتبة للتوافقية. بالإضافة إلى القدرة على رسم خطوط في لوحة الرسم، يمكنك أيضًا "رسم" الجمل النصية. وعلى عكس النصوص في الصفحة المحيطة بلوحة الرسم، لا يوجد هنالك أيّة خاصيات CSS تتعلق بالتخطيط (layout) مثل floats و margins و padding و word wrapping. تستطيع ضبط بعض خاصيات الخط، ثم اختيار نقطة على لوحة الرسم و"رسم" نصك هناك. تتوفر خاصيات الخطوط الآتية في لوحة الرسم: الخاصية font التي يمكن أن تُضبَط إلى أي شيءٍ تستطيع ضبطه في خاصية font في CSS. وهذا يتضمن نمط الخط، ونوع الخط، وسماكة الخط، وحجم الخط، وارتفاع السطر، واسم "عائلة" الخط. الخاصية textAlign تتحكم بمحاذاة النص، وهي شبيهة (لكن ليس مماثلة تمامًا) بخاصية text-align في CSS. القيم المحتملة هي start و end و left و right و center. الخاصية textBaseline تتحكم في مكان "رسم" النص نسبةً إلى نقطة البداية. القيم المحتملة هي top أو hanging أو middle أو alphabetic أو ideographic أو bottom. الخاصية textBaseline معقدة ودقيقة، لأن طريقة كتابة النصوص معقدة (لا أقصد هنا الإنكليزية، لكنك تستطيع رسم أي محرف يونيكود في عنصر canvas، وكتابة نصوص يونيكود معقدة)، تشرح مواصفات HTML5 مختلف خطوط الأساس (baselines): أعلى مربع em هو تقريبًا على مستوى أعلى المحارف في الخط (font)، أما "hanging baseline" فهو مكان ارتكاز بعض المحارف مثل आ، أما خطmiddle فهو يقع في منتصف المسافة بين أعلى مربع em وأسفل مربع em. أما خط alphabetic فهو مكان ارتكاز الأحرف مثل Á و ÿ و f و Ω، وخط ideographic هو مكان ارتكاز المحارف مثل 私 و 達، وأسفل مربع em هو تقريبًا أسفل المحارف الموجودة في خطٍ (font) ما. قد يكون أعلى وأسفل الصندوق المحيط (bounding box) بعيدًا عن خطوط الأساس السابقة نتيجةً لامتداد بعض المحارف خارج مربع em. الشكل 5: خطوط الأساس تستطيع استخدام top أو middle أو bottom لخاصية textBaseline للأبجديات البسيطة مثل الإنكليزية دون أن تكترث للبقية. لنرسم بعض النصوص! النص المرسوم داخل عنصر canvas يرث حجم الخط ونمطه من عنصر <canvas> نفسه، لكنك تستطيع إعادة تعريف تلك القيم بضبط خاصية font في إطار الرسم. //تغيير نمط الخط context.font = "bold 12px sans-serif"; context.fillText("x", 248, 43); context.fillText("y", 58, 165); ترسم الخاصية fillText()‎ النص. context.font = "bold 12px sans-serif"; //رسم النص context.fillText("x", 248, 43); context.fillText("y", 58, 165); س: هل أستطيع استخدام مقاسات الخطوط النسبية لرسم النصوص في عنصر canvas؟ ج: نعم. مثل أي عنصر HTML آخر في صفحتك، يحسب العنصر <canvas> مقاس الخط بناءً على قواعد CSS في صفحتك. فإذا ضبطت خاصية context.font إلى مقاس خط نسبي مثل 1.5em أو ‎150%، فسيضرب متصفحك هذا الرقم بحجم الخط المحسوب لعنصر <canvas> نفسه. أريد أن يكون أعلى النص الموجود في الزاوية العليا اليسرى على بعد y=5 من الحافة العلوية، لكنني كسول ولا أريد قياس ارتفاع النص وحساب بعد خط الأساس (baseline)، وإنما سأضبط textBaseline إلى top وسأمرر إحداثيات الزاوية العليا اليسرى من مربع النص. context.textBaseline = "top"; context.fillText("( 0 , 0 )", 8, 5); أما للنص في الزاوية السفلى اليمنى، فسأرسم الزاوية السفلى اليمنى للنص في الإحداثيات (492,370)، أي بضعة بكسلات من الزاوية السفلى اليمنى من لوحة الرسم؛ لكنني لا أريد قياس عرض أو ارتفاع النص، لهذا أضبط الخاصية textAlign إلى right والخاصية textBaseline إلى botton، ثم استدعي الدالة fillText()‎ مُمرِّرًا إليها إحداثيات الزاوية السفلى اليمنى من مربع النص. context.textAlign = "right"; context.textBaseline = "bottom"; context.fillText("( 500 , 375 )", 492, 370); وهذه هي النتيجة: الشكل 6: تسمية المحاور لقد نسينا النقاط الموجودة على الزوايا! سنتعلم كيف نرسم الدوائر لاحقًا، أما الآن، فسأغش قليلًا وأرسمها كمستطيلات. context.fillRect(0, 0, 3, 3); context.fillRect(497, 372, 3, 3); هذا كل ما في الأمر! هذه هي النتيجة النهائية: الشكل 7: الرسم النهائي للإحداثيات عبر عنصر canvas التدرجات اللونية IE Firefox Safari Chrome Opera iPhone Android التدرجات اللونية الخطية 7.0+* 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+ التدرجات اللونية الشعاعية 9.0+ 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+ * يتطلب IE ‏7 و 8 مكتبة خارجية هي explorercanvas، أما IE 9 فهو يدعم التدرجات اللونية في العنصر <canvas> داخليًا. لقد تعلمنا سابقًا في هذا الفصل كيف نرسم مستطيلًا مملوءًا بلونٍ ما، ثم تعلمنا كيف نرسم مستطيلًا ذا إطارٍ ملوَّن بلونٍ معين. لكن الأمر ليس محدودًا للألوان فقط، إذ يمكنك فعل ما تشاء بالتدرجات اللونية، لننظر إلى أحد الأمثلة. الشكل 8: تدرج لوني من اليسار إلى اليمين سيبدو وسم canvas كغيره من الوسوم: <canvas id="d" width="300" height="225"></canvas> علينا أولًا أن نعثر على عنصر <canvas> ثم نحصل على إطار الرسم: var d_canvas = document.getElementById("d"); var context = d_canvas.getContext("2d"); يمكننا أن نبدأ بتعريف التدرج اللوني بعد حصولنا على إطار الرسم. التدرج اللوني هو انتقالٌ ناعمٌ بين لونين أو أكثر. يمكن تعريف نوعين من التدرجات اللونية في إطار الرسم: الدالة (createLinearGradient(x0, y0, x1, y1 ترسم تدرجًا لونيًا عبر قطعة مستقيمة من النقطة (x0, y0) إلى (x1, y1). الدالة (createRadialGradient(x0, y0, r0, x1, y1, r1 ترسم تدرجًا لونيًا عبر مخروط (cone) بين دائرتين. وتُمثِّل أول ثلاثة معاملات (parameters) دائرة البداية ذات المركز (x0, y0) ونصف القطر r0. أما آخر ثلاثة معاملات فهي تمثل دائرة النهاية ذات المركز (x1, y1) ونصف القطر r1. لنُنشِئ تدرجًا لونيًا خطيًّا (linear)؛ يمكن أن تكون التدرجات اللونية بأي قياس، لكننا سنجعل هذا التدرج بعرض 300 بكسل، مثل لوحة الرسم. var my_gradient = context.createLinearGradient(0, 0, 300, 0); لما كانت قيم y (الوسيط الثاني والرابع) تساوي 0، فسيكون اتجاه التدرج اللوني من اليسار إلى اليمين. يمكنك تعريف ألوان التدرج بعد أن تحصل على كائن التدرج اللوني. يمكن أن يكون للتدرج محطتَي توقف لوني أو أكثر. التوقف اللوني (color stop) يمكن أن يكون في أي مكان في التدرج. ولإضافة توقف لوني، عليك تحديد مكانه ضمن التدرج اللوني، يمكن أن تكون تلك القيم بين 0 و 1. لنُعرِّف تدرجًا لونيًا من الأسود إلى الأبيض. my_gradient.addColorStop(0, "black"); my_gradient.addColorStop(1, "white"); لا يُرسَم أيّ شيءٍ في لوحة الرسم عند تعريف التدرج اللوني، إذ سيُخزَّن ذاك الكائن في مكانٍ ما في الذاكرة. أما لرسم التدرج اللوني، فعليك أن تضبط خاصية fillStyle إلى ذاك التدرج ثم ترسم شكلًا ما مثل مستطيل أو مستقيم. context.fillStyle = my_gradient; context.fillRect(0, 0, 300, 225); وهذه هي النتيجة: الشكل 9: تدرج لوني من اليسار إلى اليمين لنفترض أنَّك تريد تدرجًا لونيًا من الأعلى إلى الأسفل؛ فسيكون عليك إنشاء كائن للتدرج اللوني تكون فيه قيمة x ثابتة (الوسيطين الأول والثالث)، وتجعل قيم y (الوسطين الثاني والرابع) تتراوح بين 0 إلى ارتفاع لوحة الرسم. var my_gradient = context.createLinearGradient(0, 0, 0, 225); my_gradient.addColorStop(0, "black"); my_gradient.addColorStop(1, "white"); context.fillStyle = my_gradient; context.fillRect(0, 0, 300, 225); وهذه هي النتيجة: الشكل 10: تدرج لوني من الأعلى إلى الأسفل وتستطيع أيضًا أن تجعل التدرجات اللونية قطرية. //قيم x و y متغيرة var my_gradient = context.createLinearGradient(0, 0, 300, 225); my_gradient.addColorStop(0, "black"); my_gradient.addColorStop(1, "white"); context.fillStyle = my_gradient; context.fillRect(0, 0, 300, 225); وهذه هي النتيجة: الشكل 11: تدرج لوني قطري الصور IE Firefox Safari Chrome Opera iPhone Android 7.0+* 3.0+ 3.0+ 3.0+ 10.0+ 1.0+ 1.0+ * يتطلب IE ‏7 و 8 مكتبة خارجية هي explorercanvas، أما IE 9 فهو يدعم تضمين الصور في العنصر <canvas> داخليًا. يُظهِر الشكل الآتي قطةً معروضةٌ عبر عنصر <img>: الشكل 12: قطة معروضة عبر عنصر img أما هذه فهي نفس القطة لكن مرسومة في عنصر canvas: الشكل 13: قطة معروضة ضمن عنصر canvas هنالك دالة اسمها drawImage()‎ في إطار الرسم تُستعمل لتضمين صورة في عنصر canvas. تأخذ هذه الدالة ثلاثة، أو خمسة، أو تسعة وسائط: (drawImage(image, dx, dy تأخذ صورةً وترسمها في لوحة الرسم، والإحداثيات المعطية (dx, dy) هي إحداثيات الزاوية العليا اليسرى من الصورة. فلو مررنا الإحداثيات (‎(0, 0 فستُرسَم الصورة في الزاوية العليا اليسرى من لوحة الرسم. (drawImage(image, dx, dy, dw, dh تأخذ صورةً وتغير عرضها إلى dw وارتفاعها إلى dh، ثم ترسمها على لوحة الرسم في الإحداثيات (dx, dy). (drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh تأخذ صورةً وتقتطعها لتصبح مساويةً إلى المستطيل (sx, sy, sw, sh) ثم تغير أبعادها إلى (dw, dh) ثم ترسمها على لوحة الرسم في الإحداثيات (dx, dy). توضِّح مواصفات HTML5 معاملات (parameters) الدالة drawImage()‎: المستطيل المصدر هو المستطيل (ضمن حدود الصورة المصدرية) الذي تكون رؤوس أضلاعه هي (sx, sy)، و (sx+sw, sy)، و (sx+sw, sy+sh)، و (sx, sy+sh). أما المستطيل الوجهة، فهو المستطيل (ضمن حدود لوحة الرسم) الذي تكون رؤوس أضلاعه هي النقاط (dx, dy)، و (dx+dw, dy)، و (dx+dw, dy+dh)، و (dx, dy+dh). الشكل 14: تمثيل رسومي لمعاملات الدالة drawImage لرسم صورة داخل لوحة الرسم، فستحتاج إلى صورة! يمكن أن تكون الصورة عنصر <img> موجود مسبقًا، أو بإمكانك إنشاء كائن Image()‎ باستخدام JavaScript. وفي كلا الحالتين عليك أن تتأكد أنَّ الصورة محمَّلة تحميلًا كاملًا قبل أن ترسمها في لوحة الرسم. إذا كنت تستخدم عنصر <img> موجود مسبقًا، فيمكنك رسم الصورة في لوحة الرسم أثناء الحدث window.onload. <img id="cat" src="images/cat.png" alt="sleeping cat" width="177" height="113"> <canvas id="e" width="177" height="113"></canvas> <script> window.onload = function() { var canvas = document.getElementById("e"); var context = canvas.getContext("2d"); var cat = document.getElementById("cat"); context.drawImage(cat, 0, 0); }; </script> أما لو كنتَ تُنشِئ كائن الصورة عبر JavaScript، فيمكنك رسم الصورة داخل لوحة الرسم أثناء الحدث Image.onload. <canvas id="e" width="177" height="113"></canvas> <script> var canvas = document.getElementById("e"); var context = canvas.getContext("2d"); var cat = new Image(); cat.src = "images/cat.png"; cat.onload = function() { context.drawImage(cat, 0, 0); }; </script> المعاملان الاختياريان الثالث والرابع في دالة drawImage()‎ يتحكمان في تغيير أبعاد الصورة. هذه هي نفس الصورة بعد تقليل أبعادها إلى النصف مرسومةً بشكل تكراري في إحداثياتٍ مختلفة ضمن نفس لوحة الرسم. الشكل 15: الكثير من القطط! هذا هو السكربت الذي يؤدي إلى رسم الشكل السابق: cat.onload = function() { for (var x = 0, y = 0; x < 500 && y < 375; x += 50, y += 37) { //تغيير أبعاد الصورة context.drawImage(cat, x, y, 88, 56); } }; ربما تتساءل سؤالًا منطقيًا: لماذا تريد رسم صورة في لوحة الرسم أصلًا؟ بماذا ستستفيد من التعقيد الناتج عن رسم صورة داخل لوحة الرسم عوضًا عن استخدام عنصر <img> وبعض خاصيات CSS؟ حتى التأثير السابق (تكرار صورة القطة) يمكن أن يتم باستخدام 10 عناصر <img> متداخلة. الجواب المبسط هو: لنفس سبب حاجتك إلى رسم النصوص في لوحة الرسم. لاحظ كيف تُضمَّن مخطط إحداثيات canvas نصًا ومستقيماتٍ وأشكالًا. فوضع النص ضمن لوحة الرسم هو جزءٌ من عملٍ أكبر. والمخططات الأكثر تعقيدًا ستستفيد كثيرًا من الدالة drawImage()‎ لتضمين الأيقونات والرسوميات وغيرها. ماذا عن متصفح IE؟ لم تكن الإصدارات الأقدم من الإصدار التاسع من متصفح Internet Explorer تدعم الواجهة البرمجية (API) لعنصر canvas (يدعم IE9 واجهة canvas البرمجية بالكامل). لكن الإصدارات القديمة من Internet Explorer تدعم تقنية مملوكة من مايكروسوفت اسمها VML، التي تفعل العديد من الأشياء التي يفعلها العنصر <canvas>، وبهذا وُلِدَت المكتبة excanvas.js. Explorercanvas ‏(excanvas.js) هي مكتبة JavaScript مفتوحة المصدر مُرخَّصة برخصة Apache التي تجعل من الممكن استعمال واجهة canvas البرمجية في متصفح Internet Explorer. عليك تضمين وسم <script> الآتي في أعلى صفحتك لكي تستخدمها. <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Dive Into HTML5</title> <!--[if lt IE 9]> <script src="excanvas.js"></script> <![endif]--> </head> <body> ... </body> </html> الأسطر ‎<!--[if lt IE 9]>‎ و ‎<![endif]-->‎ تسمى التعليقات الشرطية. حيث يعاملها Internet Explorer كأنها عبارة if الشرطية: "إن كان الإصدار الحالي من متصفح Internet Explorer أقل من الإصدار 9، فنفِّذ الشيفرة الآتية."، وستعامل بقية المتصفحات تلك الشيفرة على أنها تعليقات HTML. فالخلاصة هي أنَّ متصفح Internet Explorer (الإصدار الثامن والسابع) سيُنزِّل السكربت ثم سيُنفِّذه، لكن ستتجاهله بقية المتصفحات تمامًا، وهذا يجعل صفحتك تُحمَّل بشكلٍ أسرع في المتصفحات التي تدعم واجهة canvas البرمجية داخليًا (أي أنها لن تُنزِّل السكربت، ولن تنفذه، ولن تفعل أيّ شيءٍ معه). ليس عليك فعل أي شيءٍ إضافي بعد تضمين excanvas.js في ترويسة صفحتك. ضمِّن عناصر <canvas> في شيفرتك أو أنشِأها ديناميكيًا باستخدام JavaScript، واتبع التعليمات الواردة في هذا الفصل للحصول على لوحة رسم عنصر <canvas>، ثم تستطيع البدء برسم الأشكال والنصوص والنقوش. ليس تمامًا… هنالك بعض المحدوديات: يمكن أن تكون التدرجات اللونية خطيةً فقط. التدرجات اللونية الشعاعية غير مدعومة. يجب تكرار النقوش (patterns) في كلا الاتجاهين. ميزة Clipping regions غير مدعومة. تغيير الأبعاد غير المنتظم لا يستطيع تغيير أبعاد حدود الأشكال (strokes) بشكلٍ صحيح. إنها بطيئة؛ ولا أظن أنَّ ذلك صادمٌ لأي شخص، فمن المعلوم أنَّ مُفسِّر JavaScript في متصفح Internet Explorer أبطأ من غيره من المتصفحات؛ فما بالك برسم أشكال معقدة عبر مكتبة JavaScript ستحول الدوال والأوامر إلى تقنيةٍ مختلفة تمامًا؟ لن تلاحظ بطئًا في الأداء في الأمثلة البسيطة مثل رسم بعض الخطوط وإجراء بعض العمليات على صورة، لكنك سترى ذلك بوضوح بعد أن تحاول إنشاء تأثيرات حركية باستخدام تقنية canvas. هنالك تحذيرٌ آخر حول استخدام excanvas.js، وهذه مشكلة واجهتني أثناء إنشاء أمثلة هذا الفصل. يُهيّئ ExplorerCanvas واجهته البرمجية لعنصر canvas في مكان تضمينك لسكربت excanvas.js في صفحة HTML. لكن ذلك لا يعني أنَّ Internet Explorer جاهزٌ لاستخدامها مباشرةً، وفي بعض الحالات ستواجه حالة خاصة تكون فيها واجهة canvas جاهزة "تقريبًا" (لكن ليس تمامًا) للاستعمال. الأعراض الظاهرة لهذه المشكلة هي أنَّ Internet Explorer سيشتكي من أنَّ "object doesn’t support this property or method" في كل مرة تحاول فيها فعل أي شيء مع عنصر <canvas> مثل الحصول على لوحة الرسم الخاصة به. أسهل حل هذه المشكلة هي تأجيل كل العمليات التي ستجريها على canvas إلى أن يحدث الحدث onload، ولكن هذا قد يستغرق بعضًا من الوقت، خصيصًا إذا كانت في صفحتك الكثير من الصور أو مقاطع الفيديو، التي ستؤخر الحدث onload، لكن ذلك سيعطي المكتبة ExplorerCanvas وقتًا لفعل اللازم. مثال متكامل "الضامة" هي لعبةٌ قديمةٌ جدًا، وهنالك عدِّة نسخ منها؛ أنشأتُ في هذا المثال نسخة السوليتر (أي التي يلعبها شخصٌ واحد فقط) من الضامة ذات تسعِ قطعٍ في رقعةٍ مقاسها 9×9. تتواجد القطع في بداية اللعبة في مربع 3×3 في الركن الأسفل الأيسر من الرقعة. هدف اللعبة هو نقل جميع القطع كي تُشكِّل مربع 3×3 في الركن الأعلى الأيمن من الرقعة بأقل عدد من الحركات. هنالك نوعان من الحركات المسموحة في الضامة: خذ قطعةً وحركها إلى أي مربع فارغ مجاور. المربع "الفارغ" يعني أنَّه لا يحتوي على قطعةٍ فيه حاليًا. أما "مجاور" فهي تعني المربع الذي يقع مباشرةً في شمال أو جنوب أو شرق أو غرب أو الشمال الغربي أو الشمال الشرقي أو الجنوب الغربي أو الجنوب الشرقي من موقع القطعة الحالي (لا يمكن الالتفاف في الرقعة من طرفٍ آخر لآخر، فلو كانت قطعتك في العمود الأيسر من الرقعة، فلا يمكنك أن تحركها نحو الغرب أو الشمال الغربي أو الجنوب الغربي. وإذا كانت قطعتك في السطر الأدنى من الرقعة، فلا تستطيع تحريكها نحو الجنوب أو الجنوب الشرقي أو الجنوب الغربي). يمكنك أخذ قطعة وجعلها "تقفر" فوق قطعة مجاورة، ويمكنك تكرار ذلك. هذا يعني أنَّه إذا قفزت فوق قطعة مجاورة ثم قفزت فوق قطعة أخرى مجاورة لموقعك الجديد، فكل هذا يُحتَسب على أنَّه حركة واحدة. وفي الحقيقة، يتم احتساب أي عدد من القفزات على أنها خطوة واحدة (لأن الهدف من اللعبة هو تقليل العدد الكلي من الحركات؛ ويتطلب اللعب بشكلٍ جيد في الضامة بناءً واستخدامًا لسلاسل طويلة من القطع لكي تستطيع القطع الأخرى القفز قوفها في سلاسل طويل). هذه لقطة شاشة للعبة، يمكنك زيارة الصفحة الآتية لتجربة اللعبة تجربةً حيةً أو لإلقاء نظرة إلى مصدر الصفحة. الشكل 16: لقطة شاشة للعبة الضامة كيف تعمل؟ أنا ممتن لسؤالك. لن أريك كل الشيفرات هنا (يمكنك رؤيتها في halma.js)، وسأتخطى معظم الشيفرات التي تستعمل في تحديد آلية اللعب، لكنني سأركِّز على بعض الأجزاء التي مهتمها هي الرسم في عنصر canvas والاستجابة إلى نقرات الفأرة. سنُهيّئ اللعبة أثناء تحميل الصفحة بضبط أبعاد عنصر <canvas> ثم تخزين مرجع (reference) متعلق بلوحة الرسم. gCanvasElement.width = kPixelWidth; gCanvasElement.height = kPixelHeight; gDrawingContext = gCanvasElement.getContext("2d"); ثم سنفعل شيئًا لم نفعله من قبل قط: سنضيف متتبع أحداث إلى العنصر <canvas> لكي يتتبع حدث النقر على العنصر. gCanvasElement.addEventListener("click", halmaOnClick, false); ستُستدَعى الدالة halmaOnClick()‎ عندما ينقر المستخدم في أي مكان ضمن العنصر canvas، والوسيط الذي يُمرَّر إليها هو كائن MouseEvent الذي يحتوي على معلومات حول مكان نقر المستخدم. function halmaOnClick(e) { var cell = getCursorPosition(e); for (var i = 0; i < gNumPieces; i++) { if ((gPieces.row == cell.row) && (gPieces.column == cell.column)) { clickOnPiece(i); return; } } clickOnEmptyCell(cell); } الخطوة الآتية هي أخذ الكائن MouseEvent وحساب في أي مربع في رقعة الضامة قد تم النقر. تحتل رقعة الضامة كامل عنصر canvas، ولهذا تكون كل نقرة هي نقرة في مكانٍ ما على الرقعة، وكل ما علينا هو معرفة المكان. هذا أمرٌ معقدٌ بعض الشيء، لأن الأحداث المولدة من الفأرة تختلف بشكلٍ أو بآخر بين المتصفحات. function getCursorPosition(e) { var x; var y; if (e.pageX != undefined && e.pageY != undefined) { x = e.pageX; y = e.pageY; } else { x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; } } أصبح لدينا في هذه المرحلة إحداثيات x و y نسبةً إلى المستند (أي صفحة HTML كلها)، أي أنها ليس مفيدةً بعد؛ حيث نريد أن تكون الإحداثيات منسوبةً إلى لوحة الرسم. x -= gCanvasElement.offsetLeft; y -= gCanvasElement.offsetTop; أصبح لدينا إحداثيات x و y منسوبةً إلى لوحة الرسم، هذا يعني أنَّه لو كانت قيمة x مساويةً للصفر و y مساوية للصفر، فهذا يعني أنَّ المستخدم قد نقر على البكسل في أعلى وأيسر لوحة الرسم. من هنا سنستطيع معرفة أي مربع قد نقر المستخدم عليه، ثم نتصرف بناءً على ذلك. var cell = new Cell(Math.floor(y/kPieceHeight), Math.floor(x/kPieceWidth)); return cell; أوه! أمرُ الأحداث المرتبطة بالفأرة معقدٌ. لكنك يمكنك استخدام نفس التسلسل المنطقي (أو بالأحرى، يمكنك استخدام نفس الشيفرة) في جميع البرمجيات التي تعتمد على عنصر canvas. تذكر: نقرة بالفأرة ← إحداثيات منسوبة إلى المستند كله ← إحداثيات منسوبة إلى لوحة الرسم ← شيفرة خاصة بالتطبيق. حسنًا، لنلقِ نظرةً على آلية الرسم الرئيسية. لمّا كانت الرسوميات بسيطةً جدًا، فقررت مسح محتويات لوحة الرسم وإعادة رسم الرقعة في كل مرة يحصل فيها تغييرٌ في اللعبة ولكن هذا ليس ضروريًا. سيحتفظ إطار الرسم في عنصر canvas بمحتواه الذي رسمته عليه حتى لو تخطاه المستخدم بتمريره في الصفحة، أو إذا غيّر إلى لسانٍ (tab) آخر ثم عاد إليه لاحقًا. إذا كنتَ تُطوِّر تطبيقًا مبنيًا على عنصر canvas فيه رسوميات معقدة (مثل ألعاب الورق) فيمكنك تحسين الأداء بتتبع المناطق التي يجب مسحها من لوحة الرسم وإعادة الرسم داخل تلك المناطق فقط. لكن هذا خارجٌ عن نطاق هذا الكتاب. gDrawingContext.clearRect(0, 0, kPixelWidth, kPixelHeight); يجب أن تكون الشيفرات الآتية مألوفةً لديك، لأنها شبيهة بتلك التي استعملناها لرسم مخطط إحداثيات canvas سابقًا. gDrawingContext.beginPath(); // الخطوط الشاقولية for (var x = 0; x <= kPixelWidth; x += kPieceWidth) { gDrawingContext.moveTo(0.5 + x, 0); gDrawingContext.lineTo(0.5 + x, kPixelHeight); } // الخطوط الأفقية for (var y = 0; y <= kPixelHeight; y += kPieceHeight) { gDrawingContext.moveTo(0, 0.5 + y); gDrawingContext.lineTo(kPixelWidth, 0.5 + y); } // لنرسم الرسمة gDrawingContext.strokeStyle = "#ccc"; gDrawingContext.stroke(); المتعة الحقيقة تبدأ عندما نهم برسم القطع. القطعة هي دائرة، لكننا لم نرسم الدوائر من قبل، وعلاوةً على ذلك، لو اختار المستخدم قطعةً لكي يحركها، فسنحتاج إلى رسم تلك القطعة كدائرة مملوءة. يُمثِّل المعامل p هنا قطعة، التي لها خاصيات row (سطر) و column (عمود) التي تُحدِّد المكان الحالي للقطعة. سنستخدم آلية لتحويل (column, row) إلى إحداثيات منسوبة إلى لوحة الرسم (x, y)، ثم سنرسم دائرة، ثم -إن كانت القطعة مُحدَّدةً من المستخدم- سنملأ الدائرة بلونٍ معين. function drawPiece(p, selected) { var column = p.column; var row = p.row; var x = (column * kPieceWidth) + (kPieceWidth/2); var y = (row * kPieceHeight) + (kPieceHeight/2); var radius = (kPieceWidth/2) – (kPieceWidth/10); أصبح لدينا الآن إحداثيات (x, y) منسوبة إلى لوحة الرسم لمركز الدائرة التي نريد رسمها. للأسف لا توجد دالة باسم circle()‎ في واجهة canvas البرمجية، لكن هنالك الدالة arc()‎؛ لكن الدائرة ما هي إلا قوسٌ تتصل بدايته مع نهايته. هل تتذكر أساسيات الهندسة؟ تأخذ الدالة arc()‎ نقطة المركز (x, y)، ونصف قطر، وزاوية البداية والنهاية (بالردايان)، ورايةً (flag) لتحدد اتجاه الدوران (false لاتجاه دوران عقارب الساعة، و true لعكس جهة دوران عقارب الساعة). يمكنك استخدام وحدة Math المبنية في لغة JavaScript لحساب الزوايا بالردايان. gDrawingContext.beginPath(); gDrawingContext.arc(x, y, radius, 0, Math.PI * 2, false); gDrawingContext.closePath(); انتظر برهة! لم يُرسَم شيء. الدالة arc()‎ تشبه عمل moveTo()‎ و lineTo()‎. حيث ترسم "بقلم الرصاص". ولرسم الدائرة فعليًا، علينا ضبط خاصية strokeStyle ثم استدعاء الدالة stroke()‎ "لتحبيرها". gDrawingContext.strokeStyle = "#000"; gDrawingContext.stroke(); ماذا لو كانت القطعة مُحدَّدةً؟ يمكننا إعادة استخدام نفس المسار الذي أنشأناه لرسم حدود القطعة لملئها بلونٍ معيّن. if (selected) { gDrawingContext.fillStyle = "#000"; gDrawingContext.fill(); } هذا كل ما في الأمر. تهتم بقية السكربت بالبنية المنطقية للعبة، مثل التفريق بين الحركات الصحيحة وغير الصحيحة، وإحصاء عدد الحركات، ومعرفة إذا انتهت اللعبة. مرحى! لقد أنشأنا لعبةً عبر عنصر canvas برسم 9 دوائر وبعض المستقيمات ودالة وحيدة مرتبطة بالحدث onclick. لمزيد من المعلومات حول عنصر canvas يمكنك الإطلاع على سلسلة التعامل مع العنصر canvas باستخدام جافاسكربت. ترجمة -وبتصرّف- لفصل "Canvas" من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  15. ربما تتساءل: "كيف أستطيع البدء باستخدام HTML5 إن لم تكن تدعمها المتصفحات القديمة؟" لكن السؤال نفسه مُضلِّل، فليست HTML5 شيئًا واحدًا كبيرًا، وإنما هي مجموعة من الميزات المتفرقة، فلا يمكنك الكشف عن "دعم HTML5" لأن هذا غير منطقي، وإنما يمكنك الكشف عن دعم الميزات المتفرقة مثل canvas أو تشغيل الفيديو أو تحديد الموقع الجغرافي. تقنيات الاكتشاف عندما يُحمِّل متصفحك صفحة ويب، فإنه يُنشِئ ما نسميه "Document Object Model" (اختصارًا DOM)، الذي هو مجموعة من الكائنات التي تُمثِّل عناصر HTML الموجودة في الصفحة، فكل عنصر -كل وسم <p> أو <div> أو <span>- يُمثَّل في DOM بكائن مختلف (هنالك كائنات عامة مثل window و document، التي لا ترتبط بعناصر محددة). تتشارك جميع كائنات DOM بمجموعةٍ من الخاصيات المشتركة، لكن لبعض الكائنات خاصيات أكثر من بعضها الآخر. ويكون لدى بعض الكائنات خاصيات فريدة في المتصفحات التي تدعم ميزات HTML5؛ لذلك يكون من الكافي عادةً إلقاء نظرة على DOM لتعرف ما هي الميزات المدعومة. هنالك أربع تقنيات أساسية لاكتشاف دعم المتصفح لميزة مُحدَّدة، هذه هي بالترتيب من الأبسط إلى الأكثر تعقيدًا: التحقق من وجود خاصية معينة في كائن عام (مثل window أو navigator)، مثلما هو عليه الحال مع دعم تحديد الموقع الجغرافي. إنشاء عنصر، ثم التحقق من وجود خاصية معيّنة في ذاك العنصر، مثلما هو عليه الحال مع تحديد دعم canvas. إنشاء عنصر، ثم التحقق من وجود دالة (method) معينة في ذاك العنصر، ثم استدعاء تلك الدالة والتحقق من القيمة التي تُعيدها. مثلما هو عليه الحال مع معرفة صيغ الفيديو المدعومة. إنشاء عنصر، وضبط خاصية فيه إلى قيمةٍ معيّنة، ثم التحقق من أنَّ تلك الخاصية قد احتفظت بقيمتها. مثل ما هو عليه الحال مع معرفة أنواع حقول <input> المدعومة. Modernizr: مكتبة اكتشاف دعم ميزات HTML5 Modernizr هي مكتبة JavaScript مفتوحة المصدر مُرخَّصة برخصة MIT مهمتها اكتشاف الدعم للعديد من ميزات HTML5 و CSS3، وأنصحك باستعمال آخر إصدار منها دومًا. عليك -لاستعمالها- تضمينها عبر عنصر <script> في ترويسة (header) صفحتك كما يلي: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>Dive Into HTML5</title> <script src="modernizr.min.js"></script> </head> <body> ... </body> </html> سيعمل Modernizr تلقائيًا، فلا توجد هنالك دالة modernizr_init()‎ لكي تستدعيها. فعندما يُشغَّل السكربت، فإنه يُنشِئ كائنًا عامًا اسمه Modernizr يحتوي على مجموعة من الخاصيات المنطقية (أي True أو False) لكل خاصية يمكنه الكشف عن دعمها. فعلى سبيل المثال، إن كان متصفحك يدعم استخدام canvas، فإن قيمة الخاصية Modernizr.canvas ستكون True، وإن لم يكن متصفحك يدعمها، فستكون قيمة الخاصية Modernizr.canvas مساويةً إلى False. if (Modernizr.canvas) { // لنرسم بعض الأشكال } else { // لا يوجد دعم لخاصية canvas } الكشف عن دعم Canvas تُعرِّف HTML العنصر <canvas> على أنه "لوحة نقطية ذات أبعاد معينة يمكن استخدامها لعرض المخططات ورسومات الألعاب وغيرها من الصور المرئية برمجيًا". ويُمثِّل مستطيلًا في صفحتك حيث تستخدم JavaScript لرسم أي شيء تريده فيه، وتُعرِّف HTML5 مجموعةً من الدوال (تدعى "canvas API") لرسم الأشكال (shapes) وتعريف المسارات (paths) وإنشاء التدرجات اللونية وتطبيقات التحويلات (transformations) على العناصر. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للعنصر canvas، فإن دعم متصفحك واجهة canvas البرمجية (API) فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <canvas> سيملك الدالة getContext()‎، وإن لم يكن يدعم متصفحك واجهة canvas البرمجية، فسيملك الكائن المُنشَأ لعنصر <canvas> الخاصيات العامة لكل العناصر، وليس من بينها أي شيءٍ متعلقٍ بتقنية canvas. function supports_canvas() { return !!document.createElement('canvas').getContext; } تبدأ هذه الدالة عملها بإنشاء عنصر <canvas> لا حول له ولا قوة، لكن ذاك العنصر لن يرتبط مطلقًا بصفحتك، ولن يراه أحد، وإنما سيبقى عائمًا بالذاكرة دون أن يفعل شيئًا. return !!document.createElement('canvas').getContext; وبعد إنشاء عنصر <canvas>، ستختبر وجود الدالة getContext()‎، فستكون هذه الدالة موجودةً إذا كان المتصفح يدعم واجهة canvas البرمجية. return !!document.createElement('canvas').getContext; وفي النهاية، استعملنا علامة النفي المزدوجة (!!) كي تكون النتيجة قيمةً منطقية (أي true أو false). return !!document.createElement('canvas').getContext; تكتشف هذه الدالة الدعم لأغلبية مكونات واجهة canvas البرمجية، بما في ذلك الأشكال، والمسارات، والتدرجات اللونية والأنماط. لكنها لا تكتشف وجود المكتبة الخارجية explorercanvas التي تسمح باستخدام واجهة canvas البرمجية في متصفح Internet Explorer. وبدلًا من كتابة الدالة السابقة بنفسك، تستطيع استعمال Modernizr للكشف عن دعم واجهة canvas البرمجية. if (Modernizr.canvas) { // لنرسم بعض الأشكال } else { // لا يوجد دعم لخاصية canvas :( } هنالك اختبارٌ منفصلٌ للتحقق من دعم واجهة canvas text البرمجية، وذلك في الفقرة الآتية. الكشف عن دعم النصوص في عنصر Canvas حتى لو كان يدعم متصفحك واجهة canvas البرمجية، فقد لا يدعم واجهة canvas text البرمجية، فنَمَتْ واجهة canvas البرمجية على مر الزمن، وأُضيفت دوال النصوص في فترةٍ لاحقة، لهذا هنالك بعض المتصفحات التي تدعم canvas لكن قبل أن يكتمل العمل على دوال النصوص. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للعنصر canvas، فإن دعم متصفحك واجهة canvas البرمجية (API) فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <canvas> سيملك الدالة getContext()‎، وإن لم يكن يدعم متصفحك واجهة canvas البرمجية، فسيملك الكائن المُنشَأ لعنصر <canvas> الخاصيات العامة لكل العناصر، وليس من بينها أي شيءٍ متعلقٍ بتقنية canvas. function supports_canvas_text() { if (!supports_canvas()) { return false; } var dummy_canvas = document.createElement('canvas'); var context = dummy_canvas.getContext('2d'); return typeof context.fillText == 'function'; } تبدأ الدالة السابقة بالتحقق من دعم canvas باستخدام دالة supports_canvas()‎ التي رأيتها في القسم السابق، فإن لم يكن يدعم متصفحك واجهة canvas البرمجية، فهو بالتأكيد لن يدعم إضافة النصوص إلى عناصر canvas. if (!supports_canvas()) { return false; } ثم ستُنشِئ عنصر <canvas> جديد ثم تحاول الوصول إلى "لوحة الرسم" (drawing context)، ومن المؤكد أن ما سبق سيعمل دون مشاكل لأن الدالة supports_canvas()‎ قد تحققت من وجود الدالة getContext()‎ في جميع عناصر canvas. var dummy_canvas = document.createElement('canvas'); var context = dummy_canvas.getContext('2d'); وفي النهاية، ستتحقق إن كان لدى لوحة الرسم الدالة fillText()‎، فإن كانت تملكها، فهنالك دعمٌ للنصوص في canvas. return typeof context.fillText == 'function'; وبدلًا من كتابة الدالة السابقة بنفسك، تستطيع استعمال Modernizr للكشف عن دعم واجهة canvas text البرمجية. if (Modernizr.canvastext) { // لنضع بعض النصوص } else { // لا يوجد دعم لخاصية canvas text } الكشف عن دعم الفيديو أضافت HTML5 عنصرًا جديدًا هو <video> لتضمين مقاطع الفيديو في صفحات الويب، كان تضمين الفيديو في السابق من المستحيلات دون استخدام إضافات خارجية مثل Apple QuickTime®‎ أو Adobe Flash®‎. صُمِّمَ عنصر <video> ليُستعمَل دون الحاجة إلى أيّة سكربتات للكشف عن الدعم، فيمكنك تحديد عدِّة مسارات لمقاطع الفيديو، وستختار المتصفحات التي تدعم HTML5 video أحدها بناءً على الصيغ التي تدعمها. المتصفحات التي لا تدعم HTML5 video ستتجاهل وسم <video> تمامًا. لكنك تستطيع استخدام ذلك لصالحك بإخبارها أن تُشغِّل المقطع باستخدام إضافة خارجية. برمَج Kroc Camen حلًا اسمه Video for Everybody!‎ الذي يستخدم دعم الفيديو في HTML5 عند توفره، لكنه سيعود إلى استخدام QuickTime أو Flash في المتصفحات القديمة. لا يستعمل هذا الحل JavaScript مطلقًا، ويعمل نظريًا على أي متصفح، بما في ذلك متصفحات الهواتف المحمولة. إذا أردت القيام بأكثر من مجرد وضع الفيديو في صفحتك وتشغيله، فستحتاج إلى استخدام JavaScript، نستخدم التقنية الثانية للتحقق من دعم الفيديو. فإذا كان متصفحك يدعم HTML5 video، فإن كائن DOM الذي سيُنشئه المتصفح لتمثيل عنصر <video> سيملك الخاصية canPlayType()‎. وإن لم يكن يدعم متصفحك HTML5 video، فإن كائن DOM المُنشَأ لعنصر <video> سيملك الخاصيات العامة لأي عنصر. يمكنك التحقق من دعم الفيديو عبر هذه الدالة: function supports_video() { return !!document.createElement('video').canPlayType; } وبدلًا من كتابة الدالة السابقة بنفسك، تستطيع استعمال Modernizr للكشف عن دعم HTML5 video. if (Modernizr.video) { // لنشغل مقاطع الفيديو } else { // لا يوجد دعم للفيديو :( // ربما علينا استخدام QuickTime أو Flash بدلًا منه } سأشرح -في الدرس الخاص بالفيديو- حلًا آخر يستعمل تقنيات الكشف السابقة لتحويل عناصر <video> إلى مشغلات فيديو مبنية على تقنية Flash، وذلك لتشغيل الفيديو على المتصفحات التي لا تدعم HTML5 video. هنالك اختبارٌ منفصلٌ للتحقق من صيغ الفيديو التي يمكن للمتصفح تشغيلها، مشروحٌ في الفقرة الآتية. صيغ الفيديو مَثَلُ صيغ الفيديو كمَثَلِ اللغات المكتوبة، فقد تحتوي صحيفة إنكليزية على نفس المعلومات التي تحتويها صحيفة عربية، لكن إن كنت تقرأ اللغة العربية فقط، فستكون إحدى تلك الصحيفتين مفيدةً لك. ولتشغيل مقطع فيديو، يجب أن يفهم المتصفح "اللغة" التي كُتِبَ فيها هذا المقطع. تسمى "اللغة" التي تكتب فيها مقاطع الفيديو "بالمرماز" (codec) الذي هو الخوارزمية المستخدمة لترميز (encode) مقطع الفيديو إلى سلسلة من البتات، وهنالك عدِّة مرمزات، لكن أيها تستعمل؟ في الواقع، من المؤسف أن المتصفحات لم تتوافق على مرماز معيّن، لكنهم قللوا الخيارات إلى خيارين فقط. أحدهما يكلف مالًا (بسبب براءة الاختراع)، لكنه يعمل في متصفح Safari وفي iPhone (وهو يعمل أيضًا في مشغلات Flash إن كنت ستستعمل حلًا مثل Video for Everybody!‎). أما المزمار الآخر فهو مجاني ويعمل على المتصفحات مفتوحة المصدر مثل Chromium و Firefox. نستخدم التقنية الثالثة لمعرفة صيغ الفيديو المدعومة. وإذا كان متصفحك يدعم HTML5 video، فإن كائن DOM الذي سيُنشئه المتصفح لتمثيل عنصر <video> سيملك الخاصية canPlayType()‎. ستخبرك الطريقة الآتية إن كان متصفحك يدعم صيغة فيديو معينة. تتحقق هذه الدالة من دعم الصيغة المحمية بحقوق براءة الاختراع والمدعومة من أجهزة Mac و iPhone. function supports_h264_baseline_video() { if (!supports_video()) { return false; } var v = document.createElement("video"); return v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); } تبدأ هذه الدالة بالتحقق من دعم HTML video في المتصفح، وذلك باستخدام الدالة supports_video()‎ التي رأيتها في القسم السابق. فإن لم يكن يدعم متصفحك HTML5 video، فهو بالتأكيد لن يدعم أيّة صيغة من صيغ الفيديو. if (!supports_video()) { return false; } ثم ستُنشِئ الدالة عنصر <video> (لكن دون أن تضيفه إلى الصفحة، أي أنه لن يكون مرئيًا) وتستدعي الدالة canPlayType()‎، من المؤكد وجود هذه الدالة لأن الدالة supports_video()‎ تحققت منها في الخطوة السابقة. var v = document.createElement("video"); في الواقع، "صيغة الفيديو" هي مجموعة من عدِّة أشياء، فبكلامٍ تقني، أنت تسأل المتصفح إن كان يستطيع تشغيل فيديو H.264 بنمط Baseline مع صوت AAC LC في حاوية MPEG-4. return v.canPlayType('video/mp4; codecs="avc1.42E01E, mp4a.40.2"'); لن تُعيد الدالة canPlayType()‎ القيمة true أو false، وإنما ستعيد سلسلة نصية آخذةً بعين الاعتبار الطبيعة المُعقَّدة لصيغ الفيديو: "probably" إن كان المتصفح واثقًا أنه يستطيع تشغيل هذه الصيغة "maybe" إن ظن المتصفح أنه يستطيع تشغيل هذه الصيغة "" (سلسلة نصية فارغة) إن كان المتصفح متأكدًا أنه لن يستطيع تشغيل هذه الصيغة أما الدالة الآتية فهي تتحقق من دعم صيغة الفيديو المفتوحة المدعومة من متصفح Firefox وغيره من المتصفحات مفتوحة المصدر. العملية مماثلة تمامًا لما سبق، لكن الاختلاف الوحيد هو في السلسلة النصية التي تمررها إلى الدالة canPlayType()‎. تقنيًا، أنت تسأل المتصفح إن كان قادرًا على تشغيل فيديو Theora مع صوت Vorbis في حاوية Ogg. function supports_ogg_theora_video() { if (!supports_video()) { return false; } var v = document.createElement("video"); return v.canPlayType('video/ogg; codecs="theora, vorbis"'); } في النهاية، WebM هو مرماز فيديو جديد ومفتوح المصدر (وغير محمي ببراءة اختراع) تم دعمه في الإصدارات الجديدة من المتصفحات الحديثة، بما في ذلك Chrome، و Firefox، و Opera. ويمكنك استخدام نفس التقنية السابقة لاكتشاف دعم صيغة WebM. function supports_webm_video() { if (!supports_video()) { return false; } var v = document.createElement("video"); return v.canPlayType('video/webm; codecs="vp8, vorbis"'); } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.5 أو ما بعده) لاكتشاف الدعم لمختلف صيغ الفيديو في HTML5. if (Modernizr.video) { // لنشغل مقاطع الفيديو! لكن ما صيغتها؟ if (Modernizr.video.webm) { // لنجرب WebM } else if (Modernizr.video.ogg) { // لنجرب Ogg Theora + Vorbis في حاوية Ogg } else if (Modernizr.video.h264){ // لنجرب H.264 video + AAC audio في حاوية MP4 } } التخزين المحلي Local Storage يوفر التخزين المحلي لمواقع الويب طريقةً لتخزين المعلومات على حاسوبك ثم استعادتها لاحقًا، مفهومها مشابه لمفهوم "الكعكات" (cookies)، لكنها مصممة لكمية معلومات أكبر، فالكعكات محدودة الحجم، ويرسلها المتصفح إلى خادوم الويب في كل مرة يطلب فيها صفحة جديدة (مما يأخذ وقتًا أطول ويستهلك بيانات التراسل). أما تخزين HTML5 فهو يبقى في حاسوبك، وتستطيع مواقع الويب الوصول إليه عبر JavaScript بعد أن يتم تحميل الصفحة. س: هل التخزين المحلي هو جزءٌ من HTML5؟ ولماذا وضعوه في معيار منفصل؟ ج: الجواب المختصر هو "نعم"، التخزين المحلي هو جزءٌ من HTML5. أما الجواب الأطول فهو أن التخزين المحلي كان جزءًا من معيار HTML5 الرئيسي، لكنه أصبح معيارًا منفصلًا بسبب شكوى بعض الأشخاص في مجموعة عمل HTML5 أن HTML5 أصبحت كبيرة جدًا. حتى لو كان ذلك يشبه تقسيم شطيرة إلى قطع صغيرة لتقليل كمية الحريرات :-| حسنًا، أهلًا بك في العالم العجيب للمعايير القياسية. سنستخدم تقنية الكشف الأولى للتحقق من دعم المتصفح للتخزين المحلي، فإن دَعَم متصفحك التخزين المحلي، فستكون هنالك خاصية localStorage في كائن window العام. وإن لم يكن يدعم متصفحك التخزين المحلي، فلن تكون الخاصية localStorage معرَّفةً. وبسبب علِّة في الإصدارات القديمة من Firefox، سيسبب هذا الخيار بحدوث استثناء (exception) إن كانت الكعكات (cookies) مُعطَّلةً، لذلك وضعنا الاختبار في عبارة try..catch. function supports_local_storage() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch(e){ return false; } } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم للتخزين المحلي. if (Modernizr.localstorage) { // متوفرة! window.localStorage } else { // لا يوجد دعم للتخزين المحلي // ربما تجرب Gears أو مكتبة أخرى } لاحظ أنَّ JavaScript حساسة لحالة الأحرف، إذ تُدعى خاصية Modernizr ‏"localstorage" (جميعها بأحرفٍ صغيرة)، أما خاصية DOM فهي window.localStorage (حرف S كبير). س: ما مدى أمان قاعدة بيانات التخزين المحلي في HTML5؟ هل يستطيع أحدٌ قراءتها؟ ج: أي شخص لديه وصولٌ فيزيائيٌ لحاسوبك قد يستطيع عرض (أو حتى تعديل) البيانات الموجودة في قاعدة بيانات التخزين المحلي في HTML5. ويستطيع أي موقع ويب قراءة وتخزين القيم الخاصة به، لكن لا يستطيع الوصول إلى القيم التي خزنتها المواقع الأخرى، وهذا يسمى same-origin restriction. Web Workers توفر ميزة Web Workers طريقةً قياسيةً لتشغيل JavaScript في الخلفية، إذ تستطيع تشغيل عدِّة "خيوط" (threads) التي تعمل في نفس الوقت تقريبًا (تذكر طريقة تشغيل الحاسوب لعدِّة تطبيقات معًا في آنٍ واحد)، تستطيع تلك الخيوط التي تعمل في الخلفية أن تجري عمليات حسابية معقدة، أو أن تجري طلبيات شبكيّة، أو أن تصل إلى التخزين المحلي في أثناء استجابة صفحة الويب إلى تفاعل المستخدم معها. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة Web Worker البرمجية، وذلك إن وُجِدَت الخاصية Worker في الكائن العام window، وإن لم يكن يدعم متصفحك واجهة Web Worker البرمجية، فستكون خاصية Worker غير معرفة. function supports_web_workers() { return !!window.Worker; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم لواجهة Web Workers البرمجية. if (Modernizr.webworkers) { // window.Worker متوفرة! } else { // لا يوجد دعم لواجهة Web Workers :( // ربما تجرب Gears أو مكتبة أخرى } لاحظ أنَّ JavaScript حساسة لحالة الأحرف، إذ تُدعى خاصية Modernizr ‏"webworkers" (جميعها بأحرفٍ صغيرة)، أما خاصية DOM فهي window.Worker (حرف W كبير في Worker). تطبيقات الويب دون اتصال Offline Web Applications يمكن ببساطة قراءة صفحات الويب الثابتة دون اتصال: اتصل إلى الإنترنت، حمِّل صفحة الويب، اقطع اتصالك بالإنترنت، ثم سافر إلى بلدٍ آخر، واقرأ الصفحة في وقت فراغك (يمكنك تخطي خطوة السفر إلى بلدٍ آخر لتوفير الوقت). لكن ماذا عن تطبيقات الويب مثل Gmail أو Google Docs؟ الفضل يعود إلى HTML5، التي تُمكِّن الجميع (وليس فقط Google) من بناء تطبيقات ويب تعمل دون اتصال. تبدأ تطبيقات الويب التي تعمل دون اتصال كتطبيقات تعمل بوجود اتصال بالإنترنت، ففي أول مرة تزور فيها تطبيق ويب يدعم العمل دون اتصل، فسيخبر الخادومُ متصفحَك ما هي الملفات التي يحتاج لها كي يعمل دون اتصال. قد تكون هذه الملفات من أي نوع: صفحات HTML، أو JavaScript، أو الصور أو حتى مقاطع الفيديو. وبعد أن ينزِّل متصفحك كل الملفات الضرورية، ستستطيع إعادة زيادة موقع الويب حتى لو لم تكن متصلًا بالإنترنت، وسيلاحظ متصفحك أنَّك غير متصل وسيستعمل الملفات التي نزلها من قبل. وعندما تتصل مجددًا بالإنترنت، فيمكن رفع أيّة تعديلات أجريتها على خادم الويب البعيد. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم تشغيل تطبيقات الويب دون اتصال، وذلك إن وُجِدَت الخاصية applicationCache في الكائن العام window، وإن لم يكن يدعم متصفحك تشغيل تطبيقات الويب دون اتصال، فستكون خاصية applicationCache غير معرفة؛ يمكنك التحقق من دعم تشغيل تطبيقات الويب دون اتصال مع هذه الدالة: function supports_offline() { return !!window.applicationCache; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم لتشغيل تطبيقات الويب دون اتصال. if (Modernizr.applicationcache) { // window.applicationCache متوفرة! } else { // لا يوجد دعم للتطبيقات دون اتصال :( // ربما تجرب Gears أو مكتبة أخرى } لاحظ أنَّ JavaScript حساسة لحالة الأحرف، إذ تُدعى خاصية Modernizr ‏"applicationcache" (جميعها بأحرفٍ صغيرة)، أما خاصية DOM فهي window.applicationCache (حرف c كبير في Cache). تحديد الموقع الجغرافي Geolocation يفيد تحديد الموقع الجغرافي في معرفة أين أنت في هذا الكوكب و (اختياريًا) مشاركة تلك المعلومات مع الأشخاص الذين تثق بهم، هنالك أكثر من طريقة لمعرفة أين أنت: عبر عنوان IP، أو عبر اتصال شبكتك اللاسلكية، أو أيّ برج تغطية خلوية تتصل منه، أو عبر عتاد GPS الذي يحسب إحداثيات موقعك الحالي عبر المعلومات التي ترسلها الأقمار الاصطناعية في السماء. س: هل تحديد الموقع الجغرافي جزءٌ من HTML5؟ ولماذا تتحدث عنه إذًا؟ ج: لقد أضيف دعم تحديد الموقع الجغرافي من المتصفحات مع ميزات HTML5 الجديدة. لكن إذا ابتغينا الدقة، يُوفَّر معيار تحديد الموقع الجغرافي من مجموعة عمل Geolocation، التي هي مجموعة عمل منفصلة عن مجموعة عمل HTML5، لكننا سنتحدث عن تحديد الموقع الجغرافي في هذه السلسلة على أيّة حال، لأنه جزءٌ من التطوير الذي يحدث في الويب في الوقت الراهن. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة تحديد الموقع الجغرافي البرمجية، وذلك إن وُجِدَت الخاصية geolocation في الكائن العام navigator، وإن لم يكن يدعم متصفحك تحديد الموقع الجغرافي، فستكون خاصية geolocation غير معرفة؛ يمكنك التحقق من دعم تحديد الموقع الجغرافي مع هذه الدالة: function supports_geolocation() { return !!navigator.geolocation; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr لاكتشاف الدعم لتحديد الموقع الجغرافي. if (Modernizr.geolocation) { // لنكتشف أين أنت الآن! } else { // لا يوجد دعم لتحديد الموقع الجغرافي :( // ربما تجرب Gears أو مكتبة أخرى } إن لم يدعم متصفحك واجهة تحديد الموقع الجغرافي البرمجية داخليًا، فلا تيأس. فهنالك Gears التي هي إضافة مفتوحة المصدر للمتصفحات من Google التي تعمل على ويندوز و Mac OS X ولينُكس والهواتف العاملة بنظامَي ويندوز وأندرويد. حيث توفر ميزاتٍ للمتصفحات القديمة التي لا تدعم الأشياء الجديدة التي تحدثنا عنها في هذا الدرس. إحدى الميزات التي توفرها Gears هي تحديد الموقع الجغرافي، لكنها ليست مطابقة لواجهة navigator.geolocation البرمجية، لكنها تخدم نفس الغرض. هنالك واجهات برمجية لتحديد المواقع لأنظمة الهواتف القديمة مثل BlackBerry، و Nokia، و Palm، و OMTP BONDI. سيشرح الدرس الخاص بتحديد الموقع الجغرافي بالتفصيل كيفية استخدام مختلف الواجهات البرمجية السابقة. أنواع الإدخال في النماذج Input Types أنت تعرف الكثير عن نماذج الويب، صحيح؟ أنشِئ عنصر <form> ثم أضف بعض عناصر <input type="text"‎> إليه وربما عنصر <input type="password"‎>، ثم أنهِ النموذج بزر <input type="submit"‎>. حسنًا، ذلك جزءٌ يسيرٌ من النماذج، إذ أضافت HTML5 حوالي ثلاثة عشر نوعًا من أنواع المدخلات التي يمكنك استعمالها في نماذجك. <"input type="search"> لصناديق البحث <"input type="number"> لإدخال الأرقام <"input type="range"> للمزلاج (slider) لتحديد مجال <"input type="color"> لاختيار الألوان <"input type="tel"> لأرقام الهواتف <"input type="url"> لعناوين الويب <"input type="email"> لعناوين البريد الإلكتروني <"input type="date"> التقويم لاختيار التاريخ <"input type="month"> للأشهر <"input type="week"> للأسابيع <"input type="time"> لبصمات الوقت <"input type="datetime"> لبصمات الوقت والتاريخ بدقة <"input type="datetime-locale"> للوقت والتاريخ المحليين سنستخدم التقنية الرابعة لاكتشاف أنواع الحقول المدعومة في النماذج. في البداية سننشِئ عنصر <input> في الذاكرة، وسيكون نوع الحقل الافتراضي لجميع عناصر <input> هو "text"، وسيتضح لك لماذا هذا مهمٌ جدًا. var i = document.createElement("input"); ثم سنضبط خاصية type في عنصر <input> إلى نوع حقل الإدخال الذي تريد معرفة إن كان مدعومًا أم لا. i.setAttribute("type", "color"); إن كان يدعم متصفحك نوع حقل الإدخال المعين، فستحتفظ خاصية type بالقيمة التي ضبطتها، أما إن لم يكن يدعم متصفحك نوع الحقل المعيّن، فسيتجاهل القيمة التي ضبطتها وستبقى قيمة الخاصية type مساويةً إلى "text". return i.type !== "text" بدلًا من كتابة 13 دالة منفصلة يدويًا، تستطيع استخدام Modernizr لاكتشاف الدعم لجميع أنواع حقول الإدخال المُعرَّفة في HTML5. يُعيد Modernizr استخدام عنصر <input> وحيد لكي يكتشف ما هي أنواع حقول الإدخال المدعومة. ثم يبني جدولًا من نوع hash باسم Modernizr.inputtypes يحتوي على 13 مفتاح (خاصيات type في HTML5) و 13 قيمة منطقية (أي true إن كان الحقل مدعومًا، أو false إن لم يكن كذلك). if (!Modernizr.inputtypes.date) { // لا يوجد دعم لحقل <input type="date"‎> :( // ربما تستعمل مكتبة Dojo أو jQueryUI } النص البديل Placeholder بالإضافة إلى أنواع حقول الإدخال الجديدة، تضمنت HTML5 بعض الإضافات الصغيرة للنماذج. أحدها هو إمكانية وضع نص بديل (placeholder) في حقل الإدخال. يُعرَض النص البديل في حقل الإدخال لطالما كان الحقل فارغًا، وبمجرد أن تكتب شيئًا في الحقل، فسيختفي ذاك النص البديل. هنالك لقطات للشاشة في درس نماذج الويب يمكنك النظر إليها إن واجهت صعوبةً في تخيله. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للنص البديل في حقول الإدخال، فإن دعم متصفحك النص البديل فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <input> سيملك الخاصية placeholder (حتى لو لم تضع خاصية placeholder في شيفرة HTML)، وإن لم يكن يدعم متصفحك النص البديل، فلن يملك الكائن المُنشَأ لعنصر <input> الخاصية placeholder. function supports_input_placeholder() { var i = document.createElement('input'); return 'placeholder' in i; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم للنص البديل في حقول الإدخال. if (Modernizr.input.placeholder) { // يمكنك استعمال النص البديل في حقول الإدخال! } else { // لا يوجد دعم للنص البديل :( // استعمل سكربت لفعل ذلك } التركيز التلقائي على النماذج autofocus تستعمل مواقع الويب JavaScript للتركيز (focus) على حقل من حقول الإدخال في نماذج الويب. على سبيل المثال، الصفحة الرئيسية لمحرك البحث Google ستُركِّز تلقائيًا على حقل البحث كي تستطيع كتابة ما الذي تريد البحث عنه مباشرةً دون الحاجة إلى النقر على حقل الإدخال، ربما هذا ملائم للكثيرين، لكنه مزعج للمستخدمين المتقدمين أو لأولي الاحتياجات الخاصة، فإن ضغطت على زر المسافة (space) متوقعًا أن تُمرِّر إلى الأسفل، فلن يتم ذلك، لوجود المؤشر في حقل إدخال (وستُكتب مسافة فارغة في حقل الإدخال بدلًا من التمرير)، وإن ركزت على حقل إدخال مختلف في أثناء تحميل الصفحة، فسيحرك سكربت التركيز التلقائي التركيز إلى الحقل المُحدَّد بعد إكمال تحميل الصفحة، مما يؤدي إلى كتابتك في المكان الخاطئ. ولأن التركيز التلقائي كان يتم عبر JavaScript، فمن الصعب التعامل مع جميع الحالات الغريبة، وليس هنالك ملجأ من التركيز التلقائي للحقول لِمَن لا يريد ذلك! ولحل هذه المشكلة، قدَّمَت HTML5 خاصية autofocus في جميع عناصر نماذج الويب. وهذه الخاصية تفعل تمامًا كما هو واضح من اسمها: تنقل التركيز إلى حقل إدخال معين، ولأنها من شيفرة HTML بدلًا من كونها سكربت، فإن سلوكها سيكون متناغمًا ومتماثلًا في كل مواقع الويب، ويمكن لصانعي المتصفحات (أو مطوري الإضافات) توفير طريقة للمستخدمين لكي يُعطِّلوا إمكانية التركيز التلقائي. سنستخدم تقنية الكشف عن الدعم الثانية للتحقق من دعم المتصفح للتركيز التلقائي في حقول الإدخال، فإن دعم متصفحك التركيز التلقائي فهذا يعني أن الكائن في DOM الذي يُمثِّل عنصر <input> سيملك الخاصية autofocus (حتى لو لم تضع خاصية autofocus في شيفرة HTML)، وإن لم يكن يدعم متصفحك التركيز التلقائي، فلن يملك الكائن المُنشَأ لعنصر <input> الخاصية autofocus. يمكنك اكتشاف دعم التركيز التلقائي عبر هذه الدالة: function supports_input_autofocus() { var i = document.createElement('input'); return 'autofocus' in i; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.1 أو ما بعده) لاكتشاف الدعم للتركيز التلقائي في حقول الإدخال. if (Modernizr.input.autofocus) { // التركيز التلقائي يعمل! } else { // التركيز التلقائي غير مدعوم :( // استعمل سكربت لفعل ذلك } البيانات الوصفية Microdata البيانات الوصفية (Microdata) هي الطريقة القياسية لتوفير هيكليّة معنوية لصفحات الويب. على سبيل المثال، يمكنك استعمال البيانات الوصفية لتصرِّح أن صورةً ما مرخَّصة بإحدى رخص المشاع الإبداعي. وكما سترى لاحقًا في هذه السلسلة، يمكنك استعمال البيانات الوصفية لتوصيف صفحة "معلومات عني"، فيمكن للمتصفحات –أو لإضافات المتصفحات– أو لمحركات البحث تحويل تلك البيانات الوصفية إلى vCard، التي هي صيغة معيارية لمشاركة معلومات الاتصال؛ يمكنك أيضًا تعريف أنواع خاصة بك من البيانات الوصفية. معيار البيانات الوصفية في HTML5 يتضمن شيفرات HTML (تستعملها محركات البحث بشكلٍ أساسي) ومجموعة من دوال DOM (تستعملها المتصفحات بشكلٍ أساسي). لا حرج في تضمين البيانات الوصفية في صفحات الويب، فهي مجرد خاصيات ذات معنى خاص، وستتجاهلها محركات البحث التي لا تستطيع تفسير البيانات الوصفية. لكن إن كنت تريد الوصول إلى أو تعديل البيانات الوصفية عبر DOM، فعليك أن تتحقق أن متصفحك يدعم واجهة البيانات الوصفية البرمجية (API). نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة البيانات الوصفية البرمجية، وذلك إن وُجِدَت الدالة getItems()‎ في الكائن العام document، وإن لم يكن يدعم متصفحك البيانات الوصفية، فستكون الدالة getItems()‎ غير معرفة. function supports_microdata_api() { return !!document.getItems; } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr لاكتشاف الدعم للبيانات الوصفية. if (Modernizr.microdata) { // هنالك دعمٌ للبيانات الوصفية! } else { // البيانات الوصفية غير مدعومة :( } التأريخ History API واجهة التأريخ البرمجية في HTML5 هي طريقة معيارية لتعديل تأريخ (history) المتصفح عبر السكربتات، جزءٌ من هذه الواجهة -التنقل في التأريخ- كان متوفرًا في الإصدارات السابقة من HTML. أما الجزء الجديد في HTML5 هو طريقة إضافة مدخلات جديدة إلى تأريخ المتصفح، والاستجابة عندما تُحذَف تلك المدخلات عبر ضغط المستخدم لزر الرجوع، وهذا يعني أن URL سيبقى يعمل عمله كمُعرِّف فريد للمورد الحالي، حتى في التطبيقات التي تعتمد اعتمادًا كبيرًا على السكربتات التي لا تجري عملية تحديث لكامل الصفحة. نستعمل طريقة الاكتشاف الأولى لمعرفة إن كان المتصفح يدعم واجهة التأريخ البرمجية، وذلك إن وُجِدَت الدالة pushState()‎ في الكائن العام history، وإن لم يكن يدعم متصفحك واجهة التأريخ البرمجية، فستكون الدالة pushState()‎ ‎ غير معرفة. function supports_history_api() { return !!(window.history && history.pushState); } بدلًا من كتابة هذه الدالة بنفسك، يمكنك استعمال Modernizr (الإصدار 1.6 أو ما بعده) لاكتشاف الدعم لواجهة التأريخ البرمجية. if (Modernizr.history) { // يمكنك تعديل تأريخ المتصفح! } else { // لا يوجد دعم لتعديل التأريخ :( // استعمل مكتبة مثل History.js } مراجع إضافية مكتبات JavaScript: Modernizr، مكتبة اكتشاف الدعم لميزات HTML5 geo.js، مكتبة لإضافة الدعم لواجهة تحديد المواقع Video for Everybody!‎ ترجمة -وبتصرّف- للفصل Detecting HTML5 Features من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  16. مدخل إلى html5

    وقعتُ أخيرًا بالصدفة على اقتباس من مطور في مشروع Mozilla عن التوتر الكامن في إنشاء المعايير القياسية: ابقِ هذه المقولة في عقلك، ولنشرح كيف أصبحت HTML5 على ما هي عليه. أنواع MIME هذه السّلسلة تتحدث حول HTML5، وليس عن إحدى إصدارات HTML السابقة أو أي إصدارٍ من XHTML، لكن لفهم تاريخ HTML5 والدوافع خلف إنشائها تمامًا، فعليك أن تستوعب بعض التفاصيل التقنية أولًا، تحديدًا أنواع MIME. في كل مرة يطلب فيها متصفح الويب صفحةً، فسيُرسِل الخادوم "ترويسات" (headers) قبل أن يرسل شيفرة الصفحة نفسها، وهذه الترويسات غير ظاهرة عمومًا على الرغم من توفر أدوات تطويرية تمكنك من رؤيتها إن كنت مهتمًا بها. لكن الترويسات مهمة لأنها تُخبر المتصفح كيف يُفسِّر الشيفرات الموجودة في الصفحة، أحد أهم تلك الترويسات هو Content-Type وهو يبدو كما يلي: Content-Type: text/html "text/html" يدعى "Content Type" أو نوع المحتوى أو نوع MIME، هذه الترويسة هي الشيء الوحيد الذي يُحدِّد طبيعة المورد المُحدَّد وكيف يجب عرضه. فالصورة لها أنواع MIME خاصة بها (image/jpeg لصور JPEG، و image/png لصور PNG، وهلمَّ جرًا)؛ ولملفات JavaScript نوع MIME خاص بها، وكذلك الأمر لصفحات الأنماط CSS، فلكل شيء في الويب له نوع MIME خاص به. وبالطبع الواقع معقَّد أكثر من هذا، فالجيل الأول من خواديم الويب (وهنا أتكلم عن خواديم الويب في 1993) لم تكن تُرسِل ترويسة Content-Type لأنها لم تكن موجودةً في ذاك الوقت (إذ لم تُستعمَل إلا في 1994)، وكانت بعض المتصفحات الشهيرة تتجاهل ترويسة Content-Type في بعض الظروف لأسبابٍ لها علاقة بالتوافقية (وهذا يسمى "content sniffing")، لكن كقاعدة عامة، كل شيء يمكنك الحصول عليه على الويب (كصفحات HTML، والصور، والسكربتات، والفيديو، وملفات PDF، وكل شيء له رابط URL) يجب أن يُخدَّم بإضافة نوع MIME المناسب في ترويسة Content-Type. خزِّن المعلومات السابقة في عقلك، وسنعود إليها لاحقًا. شرح مسهب لكيفية إنشاء المعايير لماذا لدينا عنصر <img>؟ لن تسمع مثل هذا السؤال كل يوم. بكل تأكيد أنشأه أحدهم، إذ لم تظهر تلك الأشياء من العدم، فكل عنصر وكل خاصية (attribute) وكل ميزة من ميزات HTML التي استخدمتها من قبل قد أنشأها أحدهم وقرر كيف يجب أن تعمل وكتب كل ذلك، هؤلاء الأشخاص الذين كتبوا المعايير ليسوا خارقين وإنما مجرد بَشَر ولكنهم أذكياء. أحد الأشياء الرائعة عن المعايير هي أنها طُوِّرَت على الملأ، أي أنَّك تستطيع العودة في الزمن والإجابة عن ذاك النوع من الأسئلة؛ إذ أنَّ النقاشات كانت تُجرى في القوائم البريدية، التي أُرشِفَت وأصبحت متاحةً للعموم كي يبحثوا فيها؛ لذلك قررت أن "أنقِّب" في رسائل البريد الإلكتروني كي أحاول الإجابة عن السؤال السابق: "لماذا لدينا عنصر <img>؟"، كان علي البدء في حقبةِ قبل إنشاء منظمة باسم "رابطة الشبكة العالمية" (World Wide Web Consortium اختصارًا W3C)، ذهبت إلى الأيام الأولى للويب، حيث كنتَ تستطيع عدّ صفحات الويب على أصابع اليدين. في 25 شباط/فبراير من عام 1993، كتب Marc Andreessen: كانت صيغتا Xbm و Xpm صيغتَا رسوميات شهيرتان في أنظمة يونكس. "Mosaic" كان من أوائل متصفحات الويب ("X Mosaic" كان الإصدار الذي يعمل على أنظمة يونكس)، عندما كتب Marc Andreessen رسالته في بدايات 1993، لم يكن قد أسس الشركة التي جعلته مشهورًا (شركة Mosaic Communications Corporation)، ولم يكن بدأ العمل على المنتج الرائد في تلك الشركة: "Mosaic Netscape" (ربما تعرفها بأسمائها الحديثة "Netscape Corporation" و "Netscape Navigator"). عبارته "ربما أنواع MIME" كانت تُشير إلى ميزة Content negotiation في HTTP، حيث يُخبر العميل (أي متصفح الويب) الخادوم (أي خادوم الويب) ما هي أنواع الوسائط التي يدعمها (مثل image/jpeg) ومن ثم سيُرسِل الخادوم الوسائط إلى العميل بالصيغة التي يُفضلِّها. عُرِّفَت الصيغة الأصلية من HTTP في 1991 (وهي النسخة الوحيدة التي كانت موجودةً في شباط/فبراير 1993) ولم تكن توفِّر طريقةً للعملاء لأن يخبروا الخواديم ما هي صيغ الصور التي يدعموها، وهذه هي المعضلة التي واجهها Marc. بعد عدِّة ساعات، ردِّ Tony Johnson: Midas هو متصفح آخر من فترة بدايات الويب، وكان منافسًا لمتصفح X Mosaic، وكان متعدد المنصات، إذ كان يعمل على أنظمة يونكس و VMS. أما "SLAC" فهي تُشير إلى Stanford Linear Accelerator Center التي أصبح اسمها الآن "SLAC National Accelerator Laboratory" التي استضافت أول خادوم ويب في الولايات المتحدة (وفي الواقع، أول خادوم ويب خارج أوروبا). عندما كتب Tony هذه الرسالة، كانت SLAC من المحاربين القدامى في الشبكة العالمية، التي استضافت خمس صفحات على خادوم الويب الخاص بها لمدة 441 يومًا. أكمل Tony قائلًا: قصد ببروتوكول "HTTP2" بروتوكول HTTP الأساسي المُعرِّف في 1992، الذي لم يُطبَّق تطبيقًا كاملًا في بدايات 1993، وعُرِفَت المسودة باسم HTTP2 ثم أصبحت معيارًا قياسيًا باسم "HTTP 1.0" (بالرّغم من أن الأمر قد تم ذلك بعد ثلاثة أعوام)، تضمَّن بروتوكول HTTP 1.0 ترويسات طلبات لتحديد صيغة المحتوى (request headers for content negotiation)، أي أنواع MIME. أكمل Tony: لم يُطبَّق اقتراحه أبدًا، إلا أنَّ فكرة توفير نص إن لم تتوفر الصورة هي تقنية مهمة لتسهيل الوصول التي كانت ناقصة من اقتراح Marc الأولي لوسم <IMG>. استعملت هذه الميزة في خاصية <img alt>، التي أساء متصفح Netscape معاملتها باعتبارها تلميحًا (tooltip). بعد عدِّة ساعات من إرسال Tony لرسالته، ردَّ Tim Berners-Lee عليها: لم يُطبَّق هذا الاقتراح مطلقًا، لكن خاصية rel ما زالت موجودةً. أضاف Jim Davis: لم يُطبَّق هذا الاقتراح قط، لكن أضاف متصفح Netscape لاحقًا دعمًا لتضمين عناصر الوسائط المتعددة باستعمال عنصر <embed>. سأل Jay C. Weber: ردَّ Marc Andreessen: أجاب Jay C. Weber له قائلًا: أدركنا بعد فوات الأوان أنَّ مخاوف Jay كان لها أساسٌ من الصحة لكن ذلك استغرق فترةً طويلةً، فقد أضافت HTML5 أخيرًا عنصرَيّ <video> و <audio>. ردًا على رسالة Jay الأصلية، قال Dave Raggett: لاحقًا في 1993، اقترح Dave Raggett معيار HTML+‎ الذي كان تطويرًا لمعيار HTML. لكن لم يُطبَّق اقتراحه مطلقًا، ثم حلَّت HTML 2.0 محله التي أعطت طابعًا رسميًا للميزات شائعة الاستعمال: "هذا المعيار يضفي توضيحًا وطابعًا رسميًا لمجموعة من ميزات HTML التي كانت شائعة الاستعمال قبل حزيران/يونيو عام 1994". كتب Dave لاحقًا معيار HTML 3.0 المبني على مسودة HTML+‎ التي كتبها سابقًا، وذلك خارج إطار W3C للمعايير (المسمى Arena)؛ لكن لم تُطبَّق HTML 3.0 أبدًا، وحلَّت محلها HTML 3.2، التي رسَّمَت الميزات كالإصدار السابق HTML 2.0: "أضافت HTML 3.2 الميزات شائعة الاستخدام مثل الجداول، و applets والتفاف النص حول الصور، بالإضافة إلى توافقيتها مع معيار HTML 2.0". شارك Dave لاحقًا بوضع معيار HTML 4.0، وطوَّر HTML Tidy، وشارك في المساعدة بتطوير XHTML، و XForms، و MathML، وغيرها من معايير W3C الحديثة. بالعودة إلى 1993، رد Marc على Dave: Intermedia هو مشروعٌ من جامعة Brown، طوِّر من عام 1985 إلى 1991 وكان يعمل على نظام A/UX، الذي هو نظام شبيه بِيونكس (Unix-like) لحواسيب ماكنتوش في ذاك الوقت. راجت فكرة "لغة لتوصيفِ الرسومات ذاتُ غرضٍ عام"، وذلك بدعم المتصفحات الحديثة لصيغة SVG (لغة توصيفية يمكن دمج السكربتات معها)، و <canvas> (واجهة برمجية [API] إجرائية [procedural] مباشرة)، على الرغم من أن الأخيرة بدأت كإضافة مملوكة (proprietary extension) قبل أن يتم ترسيمُها من WHATGW. ردBill Janssen: "Andrew" هو إشارة إلى Andrew User Interface System (إلا أنه كان يسمى في ذاك الوقت Andrew Project). في ذاك الحين، وجد Thomas Fine فكرةً مختلفةً: "Display Postscript" هي تقنية لعرض Postscript على الشاشة طوِّرَتها كل من Adobe و NeXT. لم يُطبَّق هذا الاقتراح أبدًا، لكن الفكرة أنَّ أفضل طريقة لحل مشاكل HTML هي استبدال شيءٍ ما آخر بها ما زالت تظهر من وقتٍ لآخر. قال Tim Berners-Lee في الثاني من آذار/مارس عام 1993: HyTime كان نظام مستندات قديم مبني على SGML، وقد أثَّرَ كثيرًا في النقاشات الأولية للغة HTML، ولاحقًا لغة XML. لم يُطبَّق اقتراح Tim لوسم <INCLUDE> مطلقًا، لكنك تجده صداه واضحًا في عناصر <object> و <embed> و <iframe>. في النهاية، قال Marc Andreessen في الثاني عشر من آذار/مارس عام 1993: ترجمة -وبتصرّف- لفصل A Quite Biased History of HTML5 من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  17. أنا منبهر كثيرًا بجميع نواحي المحادثة ذات 23 عامًا، التي أدت إلى إنشاء عنصر HTML الذي استعمل في كل صفحة ويب تقريبًا. خذ بعين الاعتبار أنَّ: HTTP ما زال موجودًا، ونجح بالتطور من الإصدار 0.9 إلى 1.0 ثم إلى 1.1، وما يزال يتطور. HTML ما زالت موجودةً، صيغة البيانات البدائية تلك التي لم تكن تدعم تضمين الصور! تطورت إلى الإصدار 2.0 ثم إلى 3.2 ثم إلى 4.0، أي أنَّ خط تطويرها لم ينقطع، لكنه بالتأكيد خطٌ ملتوٍ ومتعرج، وفيه العديد من النهايات المغلقة. لكن ها نحن ذا في عام 2016، وما زالت صفحات الويب من عام 1990 تُعرَض بشكلٍ جيد في المتصفحات الحديثة. ولقد فتحتُ إحداها في متصفح هاتفي الحديث العامل بنظام أندرويد، ولم أحصل على رسالة تقول "الرجاء الانتظار إلى أن يتم استيراد الصيغة القديمة". نتجت HTML من النقاشات بين صانعي المتصفحات والمطورين وواضعي المعايير والأشخاص الذين يحبون التحدث عنها. أغلبية الإصدارات الناجحة من HTML كانت عبارة عن إضفاءِ صفةٍ رسميةٍ لما هو موجودٌ مُحاوِلَةً توجيهه نحو الطريق الصحيح. أي شخص يخبرك أنه يجب أن تكون HTML «صرفة» (أي على الأرجح بتجاهل صانعي المتصفحات أو المطورين أو كليهما) هو شخصٌ لديه معلوماتٌ خاطئة. لم تكن HTML صرفةً أبدًا، وأيةّ محاولات لجعلها كذلك قد باءت بالفشل. لم يبق أي متصفح منذ عام 1993 موجودًا كما هو. فتم التخلي عن متصفح Netscape Navigator في 1998، ثم أعيدت كتابته من الصفر لإنشاء Mozilla Suite، ثم تم الاشتقاق لإنشاء Firefox. وكانت لمتصفح Internet Explorer بداياتٌ متواضعة في «Microsoft Plus!‎» لويندوز 95، حيث حُزِّمَ مع بعض سمات سطح المكتب ولعبة pinball (لكن بالطبع يمكن تتبع تاريخ المتصفح إلى أبعد من تلك النقطة). بعض أنظمة التشغيل من عام 1993 ما زالت موجودةٌ، لكن ليس لها صلة بالويب الحديث الذي نعرفه. فأغلبية مستخدمي الويب الآن بدؤوا باستعماله على حاسوب مكتبي يعمل بنظام ويندوز 2000 أو أحدث منه، أو على جهاز Mac يعمل بنظام OS X، أو على حاسوب مكتبي يعمل بتوزيعة من توزيعات لينُكس، أو على هاتف محمول مثل هواتف أندرويد أو iPhone. لكن كان إصدار ويندوز 3.1 في عام 1993، وكان لينُكس يوزَّع عبر Usernet. بقي بعض الأشخاص موجودين وما يزالون منهمكين في العمل على ما ندعون «معايير الويب»، حيث بقي هؤلاء الأشخاص يعملون لأكثر من 20 عامًا، وعمل بعضهم على اللغات التي تسبق HTML التي يمكن تتبع تاريخها إلى ثمانينيات القرن الماضي. بالحديث عن اللغات التي سبقت HTML، أصبح من السهل نسيان الصيغ والأنظمة التي كانت موجودةٌ عند إنشاء HTML بعد الانتشار الواسع والكبير للغة HTML والويب. ماذا عن Andrew؟ أو Intermedia؟ أو HyTime؟ لم تكن HyTime مشروعًا بحثيًا أكاديميًا لا شأن لها، وإنما كانت من معايير ISO، ولقد كانت تُستعمل لأغراضٍ عسكرية. لم يُجِب كل ما سبق عن تساؤلنا الأصلي: لماذا لدينا عنصر <img>؟ لماذا ليس عنصر <icon>؟ أو عنصر <include>؟ لماذا ليس رابطًا مع خاصية include، أو مجموعةٌ من قيم الخاصية rel؟ لماذا عنصر <img>؟ الجواب بسيطٌ للغاية، لأن Marc Andreessen قام بتنفيذ (بناء) وتوفير العنصر <img> في مُتصفّحه والذي يقوم ببناء العنصر وتوفيره في متصفّحه سيربح. لكن هذا لا يعني أنَّ مَن يبني العُنصر وينشره سيربح دائمًا، فلا تنسَ أنَّ Adnrew و Intermedia و HyTime قامت بذلك أيضًا، فبناء العنصر وتوفيره هو شرطٌ لازمٌ لكنه غير كافٍ للنجاح؛ وأنا هنا لا أقصد أنَّ بناء العُنصر قبل المعيار هو الحل الأمثل، ووسم <img> لم يكن يستعمل صيغة صور شائعة، ولم يُعرِّف كيف يلتف النص حول الصورة، ولم يكن يدعم النص البديل أو محتوى احتياطي للمتصفحات القديمة، وبعد 23 عامًا، ما زلنا نعاني مع مشكلة Content Sniffing، وما تزال هذه المشكلة مصدرًا للثغرات الأمنية. ويمكنك تتبع كل هذا إلى 23 عامًا مضت، مرورًا بحرب المتصفحات، إلى 23 شباط/فبراير عام 1993، عندما قال Marc Andreessen بشكلٍ عابر «ربما في يوم ما سنعتمد الـ MIME» ومع ذلك نشر العنصر الذي قام ببنائه وتضمينه في مُتصفّحه. التسلسل الزمني لتطوير HTML من 1997 إلى 2004 في كانون الأول/ديسمبر من عام 1997، نشرت رابطة الشبكة العالمية (W3C) نسخة HTML 4.0 ثم أغلقت مجموعة عمل HTML‏ (HTML Working Group) في الحال. وبعد أقل من شهرين، نشرت مجموعة عمل أخرى من W3C معيار XML 1.0، وبعد ذلك بثلاثة أشهر، أنشَأ القائمون على W3C ورشة عمل (workshop) اسمها "بلورة مستقبل HTML"‏ (Shaping the Future of HTML) للإجابة عن هذا السؤال: "هل تخلت W3C عن HTML؟" وكان جوابهم: منحت W3C مجموعة عمل HTML هذا التكليف لإنشاء "مجموعة من وسوم XML"، كانت أول خطوة –في كانون الأول/ديسمبر 1998– هي كتابة مسودة لمعيار مؤقت (انتقالي) الذي كانت مهمته إعادة قولبة HTML في XML دون إضافة أيّة عناصر أو خاصيات جديدة، عُرِفَ هذا المعيار لاحقًا باسم "XHTML 1.0" وعرَّف نوع MIME جديد لمستندات XHTML ‏"application/xhtml+xml"، لكن لتسهيل انتقال صفحات HTML 4 الموجودة من قبل، فقد تضمن المعيار "الملحق C" الذي "يلخص إرشادات التصميم للمطورين الذين يريدون تشغيل مستندات XHTML في متصفحات HTML الموجودة من قبل"، إذ قال الملحق C أنك تستطيع كتابة ما يسمى "صفحات XHTML" لكن تستطيع تخديمها عبر نوع MIME القديم text/html. الهدف الجديد هو النماذج (forms)، ففي آب/أغسطس 1999، نشرت مجموعة عمل HTML نفسها أول مسودةٍ لنماذج XHTML الموسعة ولقد وضعوا التوقعات التي يرمون إلى تنفيذها في أول فقرة: وبعد عدِّة أشهر أُعيدت تسمية "XHTML Extended Forms" إلى "XForms" وانتقلت إلى مجموعة العمل الخاصة بها، وعَمِلَت هذه المجموعة على التوازي مع مجموعة عمل HTML ونَشرت في النهاية الإصدار الأول من XForms 1.0 في تشرين الأول/أكتوبر 2003. وفي تلك الأثناء -وبعد اكتمال التحويل إلى XML- حطَّت مجموعة عمل HTML أنظارها إلى إنشاء "الجيل الجديد من HTML"، ففي أيار/مايو 2001، نشروا الإصدار الأول من XHTML 1.1، الذي أضاف بعض الميزات الصغيرة على XHTML 1.0، لكنه أزال الملحق C، فيجب -بدءًا من الإصدار 1.1- تخديم مستندات XHTML بنوع MIME الخاص بها application/xhtml+xml. كل شيء تعرفه عن XHTML خاطئ لماذا أنواع MIME مهمة جدًا؟ لماذا أذكرها بين الحين والآخر؟ لسبب واحد: draconian error handling (أي عدم التساهل في معالجة الأخطاء). فكانت المتصفحات "متسامحة" مع HTML، فلو أنشأت صفحة HTML ولكن نسيت وسم الإغلاق ‎</head>‎، فستُظهِرها المتصفحات على أيّة حال (لأن هنالك وسومًا معيّنة تشير إلى نهاية قسم <head> وبداية <body>)، فيجب عليك أن تضع هيكلية معيّنة عند تداخل الوسوم (أي إغلاق الوسوم المفتوحة بنمط "last-in-first-out" أي أن آخر وسم مستعمل سيُغلق أولًا) لكن إن كتبتَ <b><i></b></i>، فستتعامل المتصفحات معها (بطريقةٍ ما) وستكمل عرض الصفحة دون إظهار رسالة خطأ. وكما قد تتوقع، أدت عدم كتابة شيفرات HTML بطريقة سليمة تمامًا إلى جعل المطورين يكتبون شيفرات غير سليمة. ففي بعض التقديرات، أكثر من 99% من صفحات HTML على الويب فيها خطأ واحد على الأقل، لكن لما كانت هذه الأخطاء لا تسبب عرض رسالة خطأ مرئية من المتصفحات، ولهذا لن يصلحها أحد. رأت W3C هذه المشكلة الأصولية في الويب، ثم همّوا لتصحيحها. لغة XML، المنشورة في 1997، تخلصت من العملاء المتساهلين وقالت بأن جميع البرامج التي تستعمل XML يجب أن تعامل الأخطاء المتعلقة "بطريقة الكتابة السليمة" على أنها أخطاء (Fetal Errors). اشتهر مفهوم توقف المعالجة عند أول خطأ بالاسم "draconian error handling" وذلك نسبةً إلى القائد الإغريقي Draco الذي شرَّع عقوبة الموت لأيّة تجاوزات (ولو صغيرة نسبيًا) لقوانينه. فعندما أعادت W3C صياغة HTML بمفردات XML، اعتبرت أنَّ جميع المستندات التي تُخدَّم بنوع MIME الجديد application/xhtml+xml ستخضع إلى سياسة draconian error handling، فلو كان هنالك خطأٌ ما في أسلوب صياغة صفحة XHTML -نسيان وسم الإغلاق ‎</head>‎ أو تداخل غير صحيح للوسوم على سبيل المثال- فلن يكون لمتصفح الويب خيارٌ إلا إيقاف معالجة الصفحة وإظهار رسالة خطأ إلى المستخدم النهائي. لم تكن هذه الفكرة شائعةً على نطاقٍ واسع، لأن النسبة المُقدَّرة للأخطاء في صفحات الويب هي 99%، هذا يعني أن رسائل الخطأ ستُعرَض على المستخدم طوال الوقت، ولقلة الميزات التي توفرها XHTML 1.0 و 1.1، فقرر مطورو الويب تجاهل application/xhtml+xml، لكن هذا لا يعني أنهم تجاهلوا XHTML بالكلية، لأن الملحق C من معيار XHTML 1.0 أعطى مطوري الويب ثغرةً يمكنهم استعمالها: "اكتب شيئًا يشبه بنية XHTML، لكن خدِّمه بنوع MIME القديم text/html"، وهذا ما فعله الآلاف من مطوري الويب، إذ «حدَّثوا» صفحاتهم إلى بنية XHTML لكن بقيت تلك الصفحات مُخدَّمة بنوع MIME القديم text/html. ولليوم، ما تزال تعلن ملايين الصفحات أنها XHTML، فيبدؤون بنوع المستند doctype الخاص بلغة XHTML في أول سطر، ويستعملون أحرفًا صغيرةً لأسماء الوسوم، ويستعملون علامات الاقتباس حول قيم الخاصيات، ويضيفون خطًا مائلًا بعد العناصر الفارغة مثل <br /‎> و <hr /‎> لكن قلّة قليلة من تلك الصفحات تُخدَّم بنوع MIME الجديد application/xhtml+xml، الذي سيُفعِّل عدم التساهل في الأخطاء الخاص بلغة XML. فأيّة صفحة تُخدَّم بنوع MIME القديم text/html -بغض النظر عن doctype أو بنية الوسوم- ستُفسَّر باستعمال مُفسِّر HTML المُتساهل، وسيتم تجاهل الأخطاء دون إظهار أيّة رسالة، ودون تحذير المستخدم (أو أي شخص آخر) حتى لو كانت الصفحة فيها أخطاء تقنية. فتحت XHTML 1.0 هذه النافذة، ولكن XHTML 1.1 أغلقتها، والنسخة التي لن تُصدَر XHTML 2.0 استمرت في منهج عدم التساهل في الأخطاء، وهذا هو السبب وراء ادعاء ملايين الصفحات على أنها XHTML 1.0 وأنَّ حفنة قليلة منهم هي XHTML 1.1 (أو XHTML 2.0)، فهل أنت تستعمل XHTML حقًا؟ تحقق من نوع MIME (في الواقع، إن لم تكن تعرف ما هو نوع MIME الذي تستعمله، فأنا أضمن لك أنك ما زلتَ تستعمل text/html)، فما لم تُخدِّم صفحاتك بنوع MIME الجديد application/xhtml+xml، فإن «XHTML» هي XML بالاسم فقط. رؤية تنافسية في حزيران/يونيو 2004، أقامت W3C ورشة عمل حول تطبيقات الويب والمستندات المجمّعة، حضر هذا الاجتماع ممثلون عن مصنعي ثلاثة متصفحات، وشركات تطوير الويب، وأعضاء آخرون من W3C، ومجموعة من الجهات المهتمة بما في ذلك منظمة Mozilla وشركة Opera Software، وأعطوا رؤيتهم لمستقبل الويب: تطوير معيار HTML 4 لتضمين ميزات جديدة تساعد مطوري تطبيقات الويب الحديثة. تمثِّل المبادئ السبعة الآتية ما نظن أنه أهم المتطلبات اللازمة لهذا العمل: التوافقية مع الإصدارات القديمة، ومسار واضح للهجرة يجب أن تكون تقنيات تطبيقات الويب مبنية على تقنيات مألوفة للمطورين، بما في ذلك HTML و CSS و DOM و JavaScript. يجب أن تُطبَّق الميزات الأساسية لتطبيقات الويب باستعمال السكربتات وصفحات الأنماط في IE6، لذا يكون هنالك مسارٌ واضحٌ للهجرة نصب عيني المطورين. أي حل لا يمكن أن يستعمل مع المتصفحات ذات الشعبية الكبيرة حاليًا دون حاجة إلى إضافات لن يكون ناجحًا. نظام معالجة أخطاء مُحدَّد بدقة يجب تفصيل كيفية معالجة الأخطاء إلى درجة لا تحتاج المتصفحات فيها إلى اختراع نظام معالجة أخطاء خاص بهم، أو أن يبنوا واحدًا مشتقًا من المتصفحات الأخرى. لا يجب عرض الأخطاء على المستخدمين يجب أن تُحدِّد المعايير السلوك اللازم اتباعه عند حدوث أي نوع من الأخطاء، ويجب أن يكون نظام معالجة الأخطاء متساهلًا (كما في CSS) بدلًا من أن يكون واضحًا للمستخدم وكارثيًا (كما في XML). الاستعمال العملي يجب أن تُبرَّر إضافة كل ميزة إلى معايير تطبيقات الويب بحالات الاستعمال العملي لها، لكن العكس ليس صحيحًا بالضرورة: ليست كل حالة استخدام تتطلب إنشاء ميزة جديدة. يُفضَّل أن تكون حالات الاستخدام ذات أساس في مواقع حقيقة استخدم فيها المطورون حلًا ليس مثاليًا للالتفاف على القصور في التقنية. سيبقى استخدام السكربتات قائمًا لكن يجب الابتعاد عنه إذا توفرت وسوم مناسبة. ويجب أن لا تتدخل السكربتات في طريقة العرض، وأن تكون مستقلة عن الجهاز المشغل لها. يجب تجنب التفريق بين الأجهزة يجب أن يتمكن المطورون من استخدام نفس الميزات الموجودة في نسخة سطح المكتب ونسخة الهواتف المحمولة لنفس المتصفح. عملية مفتوحة استفاد الويب من كونه مطوَّرًا في بيئة مفتوحة. وستصبح تطبيقات الويب أساسية في المستقبل، ويجب أن يكون تطويرها في بيئةٍ مفتوحةٍ أيضًا، ويجب إتاحة القوائم البريدية والأرشيفات ومسودات المعايير للجميع. سُئِلَ المشاركون في ورشة العمل في استطلاعٍ للرأي: «هل يجب على W3C تطوير إضافات لوظائف جاهزة في HTML و CSS، وإضافات لأساس DOM لكي تتحقق متطلبات تطوير تطبيقات الويب متوسطة التعقيد. أم عليها تطوير واجهة برمجية (API) كاملة على مستوى النظام (OS-level)؟ (هذا الاقتراح من Ian Hickson، من Opera Software)» كان التصويت 11 إلى 8 ضد هذا الاقتراح، وكتبت W3C في ملخص الورشة: وبعد هذا القرار، كان لدى الأشخاص الذين اقترحوا تطوير HTML ونماذج HTML خياران فقط: الاستسلام، أو إكمال عملهم خارج إطار W3C، وقرروا المضي قدمًا في الخيار الثاني، وسجلوا النطاق whatwg.org، وفي حزيران/يونيو 2004، وُلِدَت مجموعة عمل WHAT. مجموعة عمل WHAT ما هي مجموعة عمل WHAT؟ سأقتبس من كلامهم: الجملة الأساسية في الفقرة السابقة هي "دون التسبب بمشاكل تتعلق بالتوافقية"، ليست XHTML (دون الثغرة التي وفرها الملحق C) متوافقةً مع HTML، وتتطلب نوع MIME خاص بها، وXForms ليست متوافقة مع نماذج HTML لعدم القدرة على استعمالها إلا مع الصفحات المُخدَّمة بنوع MIME الخاص بصفحات XHTML، هذا يعني أن XForms تتطلب عدم التساهل في التعامل مع الأخطاء. فبدلًا من أن نرمي ما يقارب عقدًا من الزمن من العمل على HTML ونجعل 99% من صفحات الويب الموجودة حاليًا غير قابلة للاستخدام، قررت مجموعة عمل WHAT أن تنتهج منهجًا آخر: توثيق خوارزميات التعامل مع الأخطاء «المتسامحة» التي تستعملها المتصفحات. كانت وما زالت المتصفحات متساهلةً مع أخطاء HTML، لكن لم يُتعِب أحدٌ نفسه ويوثق آلية التساهل بالضبط. لدى متصفح NCSA Mosaic خوارزميات خاصة به للتعامل مع الأخطاء، وحاول Netscape أن يقلده، ثم حاول Internet Explorer تقليد Netscape، ثم حاول Opera و Firefox تقليد Internet Explorer، ثم حاول Safari تقليد Firefox، وهلّم جرًا حتى يومنا هذا. إذ ضيع المطورون آلاف الساعات محاولين جعل منتجاتهم متوافقة مع منتجات منافسيهم. استغرقت مجموعة عمل WHAT خمسة أعوامٍ من العمل للنجاح في توثيق كيفية تفسير HTML (ما عدا بعض الحالات الخاصة جدًا والغامضة) بطريقة متوافقة مع جميع المحتوى الموجود على الويب. فلا يوجد في الخوارزمية النهائية أيّة حالة يتوقف فيها مفسر HTML عن إكمال تفسير صفحة HTML ويعرض رسالة خطأ للمستخدم النهائي. وفي الفترة التي كانت تجرى فيها الهندسة العكسية، كانت تعمل مجموعة WHAT بصمت على أشياءٍ أخرى أيضًا، منها معيارٌ سُمي في بادئ الأمر "Web Forms 2.0" الذي أضاف بعض عناصر التحكم إلى نماذج HTML (ستتعلم المزيد عن نماذج HTML في درسٍ لاحق). ومسودة معيار أخرى باسم "Web Applications 1.0" التي حَوَت ميزات أخرى رئيسية مثل canvas والدعم المُضمَّن لتشغيل الصوت والفيديو دون إضافات. العودة إلى W3C لمدة تقارب السنتين ونصف، تجاهلت W3C ومجموعة عمل WHAT بعضهما البعض، على الرغم من أن مجموعة عمل WHAT كانت تركز على نماذج الويب وميزات HTML الجديدة، ومجموعة عمل W3C مشغولة بإصدار 2.0 من XHTML، لكن بحلول تشرين الأول/أكتوبر، كان جليًا أن مجموعة عمل WHAT قد أحدثت زخمًا كبيرًا، بينما كانت XHTML 2.0 تقبع على شكل مسودة لم يتم تطبيقها من أيّ متصفح رئيسي. في تشرين الأول/أكتوبر، أعلن Tim Berners-Lee -مؤسس W3C- أن W3C ستعمل مع مجموعة عمل WHAT لتطوير HTML. واحد من أول الأشياء التي قررت مجموعة عمل W3C الجديدة فعلها هي إعادة تسمية "Web Applications 1.0" إلى "HTML5"، ومن هنا بدأت رحلة هذا السلسلة. حاشية في تشرين الأول/أكتوبر 2009، أغلقت W3C مجموعة عمل XHTML 2 وأصدرت هذه الإفادة لشرح قرارها: مراجع إضافية The History of the Web، مسودة قديمة كتبها Ian Hickson HTML/History كتبها Michael Smith، و Henri Sivonen، وآخرون A Brief History of HTML لكاتبها Scott Reynen ترجمة -وبتصرّف- لفصل A Quite Biased History of HTML5 من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
  18. مدخل إلى html5

    هذه السلسلة عبارة عن ترجمة لكتاب Dive Into HTML5 لمؤلفه Mark Pilgrim والتي سنتعلم من خلالها أساسيات HTML5 وكيفية الإنتقال إليها من نسخ HTML أقدم مع مراعاة دعم المتصفحات المختلفة. قبل البدء باستخدام HTML5 سنتطرق في هذا الدرس الأول إلى خمسة أشياء عليك معرفتها حول HTML5. 1. HTML5 ليست شيئا واحدا كبيرا ربما تتساءل: "كيف يمكنني البدء باستعمال HTML5 إن لم تكن تدعمها المتصفحات القديمة؟" لكن السؤال نفسه سيُضلِّلُكَ، HTML5 ليست شيئًا واحدًا كبيرًا، وإنما مجموعة من الميزات المنفصلة عن بعضها، أي أنَّك لن تحاول اكتشاف "دعم HTML5" في المتصفح، لأن ذلك غير منطقي؛ وإنما يمكنك اكتشاف الدعم للمزايا المختلفة مثل التخزين المحلي، أو عرض الفيديو، أو الحصول على الموقع الجغرافي. ربما تظن أنَّ HTML هي مجموعة من الوسوم وتلك الأقواس التي تشبه الزاوية… إن هذا جزءٌ مهمٌ منها، لكنه لا يمثلها كلها. إذ تُعرِّف مواصفات HTML5 كيف تتفاعل تلك الوسوم مع لغة JavaScript وذلك عبر ما يُعرَف بالمصطلح "DOM" (اختصار للعبارة Document Object Model). فلا تُعرِّف HTML وسمًا باسم <video> فقط، وإنما هنالك واجهة برمجية للتعامل مع كائنات الفيديو عبر DOM. يمكنك استعمال تلك الواجهة البرمجية (أي API) لكي تكتشف الدعم لمختلف صيغ الفيديو، ولكي تبدأ المقطع أو توقفه مؤقتًا، أو أن تكتم صوته، أو أن تعرف ما هو المقدار الذي نُزِّل (downloaded) من الفيديو، وكل شيءٍ آخر يلزمك لبناء تجربة مستخدم رائعة عند استعمال وسم <video> لعرض المقاطع. 2. ليس عليك التخلي عن كل شيء شئت أم أبيت، لا تستطيع أن تنكر أنَّ HTML 4 هي أنجح لغة توصيف (markup) على الإطلاق. بُنيَت HTML5 على هذا النجاح، وليس عليك أن تتخلى عن الشيفرات التي كتبتها، وليس عليك إعادة تعلم أشياء تعرفها من قبل، فإن كان تطبيقك يعمل البارحة باستخدام HTML 4، فسيبقى يعمل اليوم في عصر HTML5. لكن إن أتيت لتحسين تطبيق الويب الخاص بك، فقد أتيت إلى المكان الصحيح. هذا مثالٌ واقعي: تدعم HTML5 كل عناصر النماذج (forms) في HTML 4، لكنها تتضمن عناصر جديدة أخرى. كنا ننتظر إضافة بعض تلك العناصر بفارغ الصبر، مثل المزلاج (slider) ومنتقي التاريخ (date picker)؛ بعضها الآخر ذو ميزاتٍ خفية. فحقل email مثَلَهُ كمَثَلِ حقل الإدخال النصي العادي، إلا أنَّ متصفحات الهواتف الذكية ستخصص لوحة المفاتيح الظاهرة على الشاشة لتسهيل كتابة عناوين البريد الإلكتروني. بعض المتصفحات القديمة لا تدعم حقل email وستعامله على أنَّه حقل نصي عادي، وسيبقى النموذج يعمل دون تعديلات في الشيفرة أو استخدام أساليب ملتوية عبر JavaScript. هذا يعني أنك تستطيع تحسين النماذج في صفحاتك اليوم، حتى لو كان زوارك يستعملون IE 6. 3. من السهل البدء باستعمالها يمكن أن يكون "التحديث" إلى HTML5 بسيطًا لدرجة أنَّ كل ما عليك فعله هو تعديل doctype، الذي يجب أن يكون أول سطر من كل صفحة HTML. تُعرِّف الإصدارات السابقة من HTML الكثير من أنواع doctype، وكان من الصعب اختيار النوع المناسب؛ لكن هنالك نوع doctype وحيد في HTML5: <!DOCTYPE html> لن يضر التحديث إلى نمط doctype في HTML5 شيفراتك المكتوبة، لأنَّ جميع الوسوم (tags) المُعرَّفة في HTML 4 ما تزال مدعومةً في HTML5، لكنها ستسمح لك باستعمال –والتحقق من صحة صياغة– العناصر التنظيمية الجديدة مثل <article> و <section> و <header> و <footer>، سنتحدّث عن هذه العناصر الجديدة في مقال قادم. 4. إنها تعمل بالفعل سواءً كنت تريد الرسم عبر canvas، أو تشغيل مقطع فيديو، أو تصميم نماذج أفضل، أو بناء تطبيقات ويب تعمل دون اتصال؛ فستجد أنَّ HTML5 مدعومةً دعمًا جيدًا، حيث يوجد دعمٌ لخاصية canvas في Firefox و Safari و Chrome و Opera ومتصفحات الهواتف الذكية وتشغيل الفيديو وتحديد المواقع والتخزين المحلي والمزيد. تدعم غوغل (في متصفحها) البيانات الوصفية الخاصة (microdata)، وحتى مايكروسوفت –المشهورة بتأخرها عن اللحاق بركب دعم المعايير القياسية– تدعم أغلبية ميزات HTML5 في متصفح "Internet Explorer 9". يتضمن كل درس من هذه السلسلة جداول لتوافقية المتصفحات الشهيرة للميزة المشروحة، ولكن الأهم من ذلك أنَّ كل درس يتضمن نقاشًا عن خياراتك إن كنت تحتاج إلى دعم المتصفحات القديمة. تم توفير ميزات في HTML5 مثل تحديد الموقع الجغرافي وتشغيل الفيديو في السابق عبر إضافات للمتصفح مثل Gears أو Flash. الميزات الأخرى، مثل canvas، تستطيع محاكاتها بشكلٍ تام باستعمال JavaScript. ستتعلم من خلال هذه السّلسلة (التي تقرأ الآن درسها الأول) كيف تستهدف المتصفحات ذات الدعم المدمج لتلك الميزات، دون أن تترك خلفك المتصفحات القديمة. 5. HTML5 ستبقى وستتطور اخترع "Tim Berners-Lee" الشبكة العنكبوتية في بدايات التسعينات من القرن الماضي، ثم أنشَأ جمعية W3C لكي تكون المرجع في معايير الويب، وهذا ما فعلته تلك الجمعية لأكثر من 20 عامًا. هذا ما قالته W3C عن مستقبل معايير الويب في تموز/يوليو عام 2009: ستبقى HTML5 في المستقبل، لنبدأ بتعلمها. ترجمة -وبتصرّف- لفصل Introduction من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.