تحدثنا في الجزء الأول من هذا الدرس عن أساسيات التخزين المحلي، وسنستمر في مشوارنا مع تطبيقات الويب التي تعمل دون اتصال، وسننشِئ مثالًا عمليًا عليها.
تسلسل الأحداث
تحدثنا إلى الآن عن تطبيقات الويب التي تعمل دون اتصال، وعن ملف 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
توثيقٌ من صانعي المتصفحات:
HTML5 offline application cache، التي هي جزءٌ من Safari client-side storage and offline applications programming guide
الدروس التعليمية:
Gmail for mobile HTML5 series: using appcache to launch offline - part 1
Gmail for mobile HTML5 series: using appcache to launch offline - part 2
Gmail for mobile HTML5 series: using appcache to launch offline - part 3
Debugging HTML5 offline application cache
an HTML5 offline image editor and uploader application
ترجمة -وبتصرّف- لفصل Offline Web Applications من كتاب Dive Into HTML5 لمؤلفه Mark Pilgrim.
اقرأ أيضًا
- المقال التالي: النماذج (Forms) في HTML5
- المقال السابق: تطبيقات الويب التي تعمل دون اتصال – الجزء الأول
- النسخة العربية الكاملة من كتاب نحو فهم أعمق لتقنيات HTML5
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.