Cloud Firestore

يشكّل هذا الدرس التطبيقي جزءًا من الدورة التدريبية "تطوير تطبيقات الويب التقدّمية" ومطوّري البرامج التي طوّرها فريق تدريب Google Developers. ستحصل على أقصى قيمة من هذه الدورة التدريبية إذا كنت تعمل من خلال الدروس التطبيقية حول الترميز بالتسلسل.

للحصول على التفاصيل الكاملة حول الدورة التدريبية، راجع نظرة عامة على تطوير تطبيقات الويب التقدّمية.

ما ستتعرَّف عليه

  • كيفية إعداد مشروع Cloud Firestore على Firebase
  • عمليات القراءة والكتابة الأساسية باستخدام Firestore
  • (اختياري) كيفية استخدام Cloud Firestore بلا اتصال بالإنترنت

ما يجب معرفته

  • HTML الأساسي وCSS وJavaScript
  • تعهدات ES2015
  • كيفية تشغيل الأوامر من سطر الأوامر
  • (لقسم اختياري) الدراية بعاملي الخدمة وصندوق العمل

المتطلبات

  • جهاز كمبيوتر يتضمن إمكانية الوصول إلى الوحدة الطرفية/الصدفة
  • الاتصال بالإنترنت
  • محرِّر نصوص
  • العقدة وnpm

يمكنك تنزيل مستودع pwa-training-labs أو استنساخه من github وتثبيت إصدار LTS من Node.js، إذا لزم الأمر.

افتح مجلد firestore-lab/project/ في محرِّر النصوص المفضَّل لديك. المجلد project/ هو المكان الذي سيتم إنشاء الدرس التطبيقي فيه.

إنشاء مشروع جديد على Cloud Firestore:

  1. افتح وحدة تحكُّم Firebase وأنشئ مشروعًا جديدًا.
  2. في قسم قاعدة البيانات، انقر على تجربة الإصدار التجريبي من Firestore.
  3. تحديد الخيار "&بدء" في الوضع التجريبي
  4. انقر على Enable (تفعيل).

ملاحظة: لا يمكنك استخدام كل من Cloud Firestore وCloud Datastore في المشروع نفسه، ما قد يؤثر على التطبيقات التي تستخدم App Engine. إذا كان مشروعك يستخدم Cloud Datastore، حاوِل استخدام Cloud Firestore مع مشروع مختلف.

بعد ذلك، أضِف النصوص البرمجية التالية إلى index.html قبل علامة الإغلاق </body> مباشرةً:

index.html

<script src="/__/firebase/4.9.0/firebase-app.js"></script>
<script src="/__/firebase/4.9.0/firebase-auth.js"></script>
<script src="/__/firebase/4.9.0/firebase-firestore.js"></script>
<script src="/__/firebase/init.js"></script>

يستورد هذا الرمز المكتبات المطلوبة عند استضافة التطبيق باستخدام استضافة Firebase.

يجب الحصول على إصدار مُفعّل من Cloud Firestore من واجهة سطر الأوامر (CLI) في Firebase.

افتَح نافذة سطر الأوامر وغيِّر الأدلة إلى المجلد project/.

بعد ذلك، شغِّل الأمر التالي:

npm install -g firebase-tools

وبعد الانتهاء من تثبيت الأدوات، سجِّل الدخول للسماح لواجهة سطر الأوامر (CLI) في Firebase بالتفاعل مع مشروع Firebase.

firebase login

بعد ذلك، شغِّل الأمر التالي في الدليل project/ لإنشاء ملفات الإعداد وإعداد تطبيق Firebase:

firebase init

بعد تنفيذ الأمر أعلاه، ستظهر رسالة مطالبة في الوحدة الطرفية. اتّبِع التعليمات الموضحة أدناه:

  1. اختر Firestore (عن طريق تحريك المؤشر إلى خيار Firestore والضغط على مفتاح المسافة) والضغط على return
  2. اختَر مشروع Firebase الذي أنشأناه للتو واضغط على return.
  3. اضغط على return لاستخدام الملف التلقائي لقواعد Firestore
  4. اضغط على return لاستخدام الملف التلقائي لفهارس Firestore.

والآن، إذا شغّلنا تطبيق الويب، سيعرف تلقائيًا مشروع Firebase (وFirestore) الذي يجب استخدامه، ولكن علينا اتخاذ بضع خطوات أخرى حتى نتمكّن من عرض تطبيقنا.

افتح ملف firebase.json وأضِف عملية ضبط hosting. يجب أن يظهر الملف بالكامل كما يلي:

firebase.json

{
  "hosting": {
    "public": "./",
    "ignore": [
      "firebase.json",
      "database-rules.json",
      "storage.rules",
      "functions"
    ],
    "headers": [
      {
        "source": "**/*.@(js|html)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=0"
          }
        ]
      }
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  }
}

الشرح

تتيح لك إعدادات hosting تحديد كيفية استضافة Firebase لتطبيقك. ويجب تحديد هذا الخيار من أجل استخدام خادم تطوير Firebase، وهو ما سنفعله في خطوة لاحقة. يمكنك الرجوع إلى مرجع إعداد النشر لمزيد من المعلومات.

على الرغم من أن المصادقة ليست محل تركيز هذا الدرس التطبيقي، إلا أنه من المهم تقديم شكل من أشكال المصادقة في التطبيق. وسنستخدم تسجيل الدخول مع إخفاء الهوية، ما يعني أن المستخدم سيتم تسجيل دخوله بدون إشعار.

يمكنك تفعيل المصادقة المجهولة في تطبيقك باستخدام وحدة تحكُّم Firebase. نفِّذ الأمر التالي ليتم توجيهك تلقائيًا إلى صفحة إعداد مزوِّدو خدمة تسجيل الدخول.

firebase open auth

بدلاً من ذلك، في وحدة تحكم Firebase لمشروعك، انتقل إلى تطوير &gt؛ المصادقة &gt؛ طريقة تسجيل الدخول.

عندما تكون في هذه الصفحة، انقر على مجهول، ثم انقر على تفعيل وانقر على حفظ:

نحن مستعدون لبدء العمل على تطبيقنا لنشغّلها محليًا باستخدام الأمر firebase:

firebase serve

والآن افتح متصفحك واعرض localhost:5000. ستشاهد نسختك من سباق الفضاء التي تم ربطها بمشروع Firebase.

تم ربط التطبيق بمشروعنا تلقائيًا وتسجيل الدخول تلقائيًا كمستخدم مجهول.

في هذا القسم، سنكتب بعض البيانات إلى Firestore حتى نتمكن من تعبئة واجهة مستخدم التطبيق. يمكن إجراء ذلك يدويًا عبر وحدة تحكُّم Firebase، ولكننا سنفعل ذلك في التطبيق نفسه لإظهار كتابة Firestore الأساسية.

كائن النموذج الأساسي في تطبيقنا هو سفينة فضاء. تمثّل مستندات Firestore المستندات التي يتم تنظيمها في مجموعات ومجموعات فرعية. في هذا التطبيق، سنخزّن كل سفينة كمستند في مجموعة عالية المستوى تُسمى &ship;ships". ويمكنك الاطّلاع على مزيد من المعلومات حول نموذج بيانات Firestore في المستندات.

لا تتضمن قاعدة بيانات تطبيقات Cloud Firestore أي بيانات في الوقت الحالي. في وحدة تحكم Firebase لمشروعك، انتقل إلى تطوير &gt؛ قاعدة بيانات ولاحظ أن قاعدة البيانات فارغة.

لنبدأ تنفيذ الوظيفة لإضافة البيانات إلى تطبيقنا.

افتح scripts/SpaceRace.Data.js وابحث عن الدالة SpaceRace.prototype.addShip. استبدل الدالة بكاملها بالرمز التالي:

Space مضمّنة في Data.js

SpaceRace.prototype.addShip = function(data) {
  const collection = firebase.firestore().collection('ships');
  return collection.add(data);
};

يضيف الرمز أعلاه مستندًا جديدًا إلى مجموعة Firestore في ships. تحصل الدالة أولاً على مرجع لمجموعة ships ثم add's إلى data. المستند data يأتي من كائن JavaScript عادي.

قواعد الأمان

نحن على وشك الانتهاء - قبل أن نتمكن من كتابة مستندات على Cloud Firestore، علينا ضبط قواعد أمان Firestore. تصف هذه القواعد أجزاء قاعدة البيانات التي يجب أن تكون قابلة للقراءة وقابلة للقراءة ومن قِبل المستخدمين. في الوقت الحالي، سنسمح لجميع المستخدمين الذين تمت مصادقتهم بالقراءة والكتابة في قاعدة البيانات بأكملها. وهذا الأمر متسامح إلى حد ما مع تطبيق الإنتاج، ولكن أثناء عملية التطوير نريد شيئًا مريحًا بما يكفي حتى لا نواجه مشاكل في المصادقة أثناء التجربة.

افتح الملف باسم firestore.rules واستبدِل الملف بالكامل بالرمز التالي:

Firestore.rules

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      // Only authenticated users can read or write data
      allow read, write: if request.auth != null;
    }
  }
}

وبعد ذلك، نفِّذ ما يلي في سطر الأوامر:

firebase deploy --only firestore:rules

سيؤدي هذا الإجراء إلى نشر ملف firestore.rules إلى مشروع Firebase.

وبدلاً من ذلك، يمكنك فتح وحدة تحكُّم Firebase والانتقال إلى تطوير > قاعدة البيانات > القواعد واستبدال القواعد التلقائية بالقواعد الجديدة.

ملاحظة: إذا كنت تريد التعرّف على المزيد من المعلومات عن قواعد الأمان، يمكنك الاطّلاع على وثائق قواعد الأمان.

حدّث الصفحة وانقر على الزر &إضافة بيانات زائفة. يستدعي هذا الزر الدالة addMockShips المحددة في SpaceRace.Mock.js، والتي تستخدم الدالة addShip التي حددناها سابقًا. ينشئ addMockShips مجموعة من مستندات الشحن، على الرغم من أنك لن ترى هذه الميزة بعد في التطبيق. لا نزال بحاجة إلى تنفيذ استرداد البيانات.

وبعد ذلك، انتقِل إلى علامة التبويب قاعدة البيانات في وحدة تحكُّم Firebase. من المفترض أن تظهر الآن إدخالات جديدة في مجموعة ships (قد تحتاج إلى إعادة تحميل الصفحة):

تهانينا، لقد كتبت بيانات إلى Cloud Firestore من تطبيق ويب. في القسم التالي، ستتعرّف على كيفية استرداد البيانات من Firestore وعرضها في التطبيق.

ستتعرّف في هذا القسم على كيفية استرداد البيانات من Firestore وعرضها في التطبيق. وتتمثل الخطوتان الرئيسيتان في إنشاء طلب بحث وإضافة أداة معالجة للنبذة. سيتم إشعار هذا المستمع بكل البيانات الحالية التي تطابق طلب البحث وتلقّي آخر المعلومات في الوقت الفعلي.

لننشئ طلب البحث الذي سيخدم قائمة السفن. استبدل طريقة SpaceRace.prototype.getAllShips() بالرمز التالي:

Space مضمّنة في Data.js

SpaceRace.prototype.getAllShips = function(render) {
  const query = firebase.firestore()
    .collection('ships')
    .limit(50);
  this.getDocumentsInQuery(query, render);
};

في هذا المقتطف، ننشئ طلب بحث لاسترداد ما يصل إلى 50 سفينة من مجموعة المستوى الأعلى باسم "ships". وبعد إعلاننا عن طلب البحث هذا، نمرره إلى طريقة getDocumentsInQuery() التي تكون مسؤولة عن تحميل البيانات وعرضها. ستستخدم هذه الطريقة أداة معالجة لقطات. استبدل طريقة SpaceRace.prototype.getDocumentsInQuery() بالرمز التالي:

Space مضمّنة في Data.js

SpaceRace.prototype.getDocumentsInQuery = function(query, render) {
  query.onSnapshot(snapshot => {
    if (!snapshot.size) return render();
    snapshot.docChanges.forEach(change => {
      if (change.type === 'added') {
        render(change.doc);
      }
      else if (change.type === 'removed') {
        document.getElementById(change.doc.id).remove();
      }
    });
  });
};

يشغّل query.onSnapshot في الرمز أعلاه وسيطة رد الاتصال في كل مرة يحدث فيها تغيير في نتيجة طلب البحث. في المرة الأولى، يتم تشغيل معاودة الاتصال باستخدام مجموعة النتائج الكاملة لطلب البحث، وهي مجموعة ships الكاملة من Firestore. تحتوي تغييرات المستند الفردي على موقع type، ويشير إلى كيفية تغييرها. إذا كان المستند added، يتم تمرير المستند إلى الدالة render. إذا كان المستند هو removed، ستتم إزالة بطاقة الشحن المناسبة من نموذج العناصر في المستند (DOM).

الآن وبعد أن طبّقنا كلاً من طرق استرداد البيانات، أعِد تحميل التطبيق وتحقَّق من أن السفن التي أضفناها إلى Cloud Firestore (ونرى في وحدة تحكم Firestore) تظهر الآن في التطبيق. إذا أكملت هذا القسم بنجاح، يصبح تطبيقك الآن يقرأ البيانات ويكتبها باستخدام Cloud Firestore.

حاوِل إضافة سفنك المخصّصة من خلال ملء نموذج التطبيق والنقر على &إضافة&;إضافة. مع تغيّر قائمة السفن، سيستمر هذا المستمع في التعديل تلقائيًا. والآن جرِّب الانتقال إلى وحدة التحكم في Firebase وإضافة سفينة يدويًا، وستظهر هذه الميزة على موقعك فورًا.

ملاحظة: يمكن أيضًا جلب المستندات من Firestore مرة واحدة، بدلاً من الاستماع إلى التعديلات في الوقت الفعلي باستخدام طريقة Query.get().

لنبدأ كتابة وظيفة لحذف سفن معينة من المجموعة.

استبدل طريقة SpaceRace.prototype.deleteShip() في SpaceRace.Data.js بالرمز التالي:

Space مضمّنة في Data.js

SpaceRace.prototype.deleteShip = function(id) {
  const collection = firebase.firestore().collection('ships');
  return collection.doc(id).delete()
    .catch(function(error) {
      console.error('Error removing document: ', error);
    });
};

بعد تنفيذ ذلك، ستتمكّن من حذف سفن معيّنة من خلال النقر على رمز "&&X" الخاص أعلى سفينة السفينة. احفظ الملف وأعِد تحميل التطبيق وحاول حذف سفينة.

يدعم Cloud Firestore أيضًا الاحتفاظ بالبيانات بلا اتصال بالإنترنت، الذي يخزّن نسخة محلية من بيانات تطبيقك في ذاكرة التخزين المؤقت والتي يمكن تعديلها عندما يكون المستخدمون بلا اتصال بالإنترنت، وتتم مزامنتها بعد استعادة الاتصال. إلى جانب عاملي الخدمة، يمكننا تحديث تطبيقنا للعمل بلا اتصال بالإنترنت.

أنشئ ملفًا باسم sw.js في جذر دليل project/ باستخدام الرمز التالي:

importScripts('https://storage.googleapis.com/workbox-cdn/releases/3.4.1/workbox-sw.js');

if (workbox) {

  // Pre-cache HTML, CSS, and image assets
  workbox.precaching.precacheAndRoute([
    {
      "url": "index.html",
      "revision": "a7a5b45e7a48ecf2cb10fd8bddf70342"
    },
    {
      "url": "style/main.css",
      "revision": "7ca18ea2f5608b3c3f67339a57a4fc8e"
    },
    {
      "url": "images/delete.svg",
      "revision": "840ae217e9fe8c73c6d76286aefef63f"
    },
    {
      "url": "images/rocket-form.svg",
      "revision": "6bcd12b01e14547c1f9e0069c3da5f0d"
    },
    {
      "url": "images/rocket-icon.png",
      "revision": "f61c19851368484e8cb7efebf4d26a77"
    },
    {
      "url": "images/rocket.svg",
      "revision": "19df337059a0d6420869bcd20bdc6fab"
    },
    {
      "url": "images/ship_0.jpg",
      "revision": "58bb2ed6c80b6ca362c18515f07f2aee"
    },
    {
      "url": "images/ship_1.jpg",
      "revision": "94895878d03c00fae4f19583efb53ad2"
    },
    {
      "url": "images/ship_2.jpg",
      "revision": "992f720b3d4d3d21c83a7e71057effc9"
    },
    {
      "url": "images/ship_3.jpg",
      "revision": "06c2a683898186f728c564c9e518d16c"
    },
    {
      "url": "images/ship_4.jpg",
      "revision": "04673dcead6d46a65fdfb7c78984afd8"
    },
    {
      "url": "images/ship_5.jpg",
      "revision": "d08b0352c8971af5f881dcde0542ed97"
    },
    {
      "url": "images/ship_6.jpg",
      "revision": "0ccd8c0c257264e0496eed72d4beb936"
    },
    {
      "url": "images/ship_7.jpg",
      "revision": "af52e423fd57b2205c95bd308d50663e"
    },
    {
      "url": "images/ship_8.jpg",
      "revision": "00a5102cdfac3dbc041fb5b286e6b0e7"
    },
    {
      "url": "images/ship_9.jpg",
      "revision": "4b57c477216cb8c106b0c09ee9376249"
    }
  ]);

  // Force update of newest service worker
  workbox.skipWaiting();
  workbox.clientsClaim();

  // Google Fonts
  workbox.routing.registerRoute(
    new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
    workbox.strategies.staleWhileRevalidate()
  );

  // Material Design & navigation library
  workbox.routing.registerRoute(
    new RegExp('https://unpkg.com/(.*)'),
    workbox.strategies.staleWhileRevalidate()
  );

  // App scripts
  workbox.routing.registerRoute(
    new RegExp('/scripts/(.*)'),
    workbox.strategies.staleWhileRevalidate()
  );

  // Firebase libraries
  workbox.routing.registerRoute(
    new RegExp('http://localhost:5000/__/firebase'),
    workbox.strategies.staleWhileRevalidate()
  );

} else {
  console.log(`Workbox didn't load 😬`);
}

وبعد ذلك، يمكنك إضافة نص برمجي قبل علامة الإغلاق body في index.html لتسجيل مشغّل الخدمات الذي أنشأناه للتو:

index.html

  <script>
    if ('serviceWorker' in navigator) {
      window.addEventListener('load', () => {
        navigator.serviceWorker.register('/sw.js')
          .then(reg => {
            console.log('Service worker registered! 😎', reg);
          })
          .catch(err => {
            console.log('😥 Registration failed: ', err);
          });
      });
    }
  </script>

أخيرًا، استبدِل الدالة SpaceRace في SpaceRace.js بالرمز التالي، الذي يستدعي الطريقة enablePersistence:

النصوص البرمجية/Spaceracing.js

function SpaceRace() {
  firebase.auth().signInAnonymously().then(() => {
    firebase.firestore().enablePersistence()
      .then(() => {
        this.initTemplates();
        this.initRouter();
      });
  }).catch(err => {
    console.log(err);
  });
}

حدّث التطبيق في المتصفح مرتين (مرة لتثبيت مشغّل الخدمة ومرة واحدة لتخزين مواد العرض في الموقع مؤقتًا). ثم ألغِ تفعيل خادم firebase المحلي مع Ctrl + c، وأوقِف Wi-Fi على جهاز الكمبيوتر. أعد تحميل تطبيق الويب ولاحظ أنه يتم تحميله بلا إنترنت. حاوِل إضافة سفن جديدة وحذف السفن الحالية. يمكنك استعادة اتصال Wi-Fi وإعادة تشغيل الخادم باستخدام firebase serve. ثم أعِد تحميل الصفحة ولاحظ أنّ جميع التغييرات التي تم إجراؤها بدون الاتصال بالإنترنت قد استمرت.

ملاحظة: قد لا تعمل Firestore المُفعّلة بلا إنترنت كما هو متوقع في حال فتح علامات تبويب متعددة. احرص على اختبار التطبيق في علامة تبويب واحدة. اطّلِع على المستندات لمزيد من المعلومات.

الشرح

يستخدم مشغّل الخدمات الذي تم توفيره Workbox لتخزين مواد عرض الموقع الإلكتروني مؤقتًا على الجهاز، مثل HTML وCSS وJavaScript. ويسمح ذلك بتحميل تطبيقنا (بسرعة كبيرة جدًا) حتى في حال تعذر جلب هذه الموارد من الشبكة.

بالإضافة إلى استخدام واجهة برمجة التطبيقات لعامل الخدمة للموارد غير المتصلة بالإنترنت، أضفنا أيضًا طريقة enablePersistence إلى الدالة التي تعمل على إعداد التطبيق، SpaceRace. يؤدي ذلك إلى ضبط مثيل Cloud Firestore لتخزين نسخة مؤقتة من بيانات Firestore محليًا. هناك نوعان من المزايا الرئيسية لاستخدام البيانات المحلية:

  1. يتم تحميل بيانات تطبيقنا بسرعة كبيرة لأنها ليست مضطرة لانتظار الشبكة.
  2. يمكن للتطبيق الوصول إلى البيانات وتعديلها حتى بلا اتصال بالإنترنت، ومزامنة التغييرات بعد رجوع الاتصال.

يمكنك الاطّلاع على المستندات للحصول على المزيد من المعلومات عن تثبيت الملفات في المتجر، بما في ذلك الفروقات المهمة حول الأمان.

في هذا الدرس التطبيقي حول الترميز، تعلّمت كيفية إجراء عمليات القراءة والكتابة الأساسية باستخدام Firestore، بالإضافة إلى كيفية تأمين الوصول إلى البيانات باستخدام قواعد الأمان. وتعلّمت أيضًا كيفية استخدام Cloud Firestore وعاملي تقديم الخدمات لتفعيل الوظائف بلا اتصال بالإنترنت.

لمعرفة المزيد من المعلومات عن Firestore، يمكنك الانتقال إلى المصادر التالية:

للاطّلاع على جميع الدروس التطبيقية حول الترميز في الدورة التدريبية لتطبيق الويب التقدّمي (PWA)، راجِع الدرس التطبيقي حول الترميز الترحيبي للدورة التدريبية/