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

استخدام Vue.js للاتصال بالإنترنت


حسام برهان

سنتعلّم في هذا الدرس:

  • إنشاء قاعدة بيانات على Google Firebase
  • تنصيب مكتبة الاتصال بالانترنت vue-resource
  • بناء تطبيق باستخدام Vue.js للقراءة والإضافة من وإلى قاعدة البيانات
  • إضافة ميزة تعديل البيانات للتطبيق السابق

سنتعلم في هذا الدرس كيفية الاتصال بخواديم بعيدة باستخدام مكتبة مخصّصة لـ Vue.js لهذا الغرض. وبما أنّنا نهتم بتبسيط المعلومة من خلال التركيز على مفهوم جديد محدّد، فلن ندخل في مجال بناء تطبيق خلفية كامل (Backend Application) مخصّص لكي يتعامل معه تطبيق Vue.js، ولكن سنعتمد على إنشاء تطبيق بسيط مجاني على Google Firebase، وهو عبارة عن خدمة قاعدة بيانات سيتواصل معها تطبيقنا لتوضيح الفكرة المطلوبة.

إنشاء قاعدة بيانات على Google Firebase

لن نتوسّع في هذا الدرس في الشرح حول Google Firebase ومزاياها، إذ يمكنك الاطلاع حول المزيد عنها من خلال الوثائق الخاصة بها والتي سأزودك بالبعض منها بعد قليل. يكفيك الآن أن تعلم أنّ Google Firebase هي منصّة مملوكة من قبل Google وظيفتها مساعدة المطوّرين على بناء وتحسين وتنمية التطبيقات التي يعملون عليها، فهي عبارة عن خدمة خلفية (Backend Service) يمكنك من خلالها إنشاء قواعد بيانات وخدمات استيثاق (Authentication Services) وغيرها من المزايا المفيدة لتطبيقاتك المختلفة، سواء كانت تطبيقات لأجهزة الموبايل أو تطبيقات ويب أو غيرها.

بالنسبة إلينا، سنستخدم Firebase في هذا الدرس لبناء قاعدة بيانات بسيطة كخدمة تمثّل دعمًا لتطبيق Vue.js بسيط. انتقل إلى الموقع الرسمي لـ Firebase، ثم من الزاوية اليمنى العليا اختر Sign in لتسجيل الدخول، ستحتاج إلى حساب Google للوصول إلى خدمات Firebase.

بعد تسجيل الدخول بحساب Google ستظهر الصفحة الرئيسية التالية:

1.png

انقر Go to console من الزاوية اليمنى العليا للانتقال إلى صفحة الخدمات، ثم انقر زر Add project (أو Create a project) لإضافة مشروع جديد، سيطلب منك بدايةً اسم المشروع بالإضافة إلى الموافقة على الاتفاقية كما في الشكل التالي:

2.png

لقد اخترت الاسم vue-remote-servers ستضطر بالطبع إلى استخدام اسم آخر لمشروعك لأن الاسم الحالي أصبح محجوزًا. بعد ذلك انقر زر Continue للانتقال إلى المرحلة الثانية التي سيخيرك فيها بتفعيل Google Analytics من أجل هذا المشروع. انقر الزر Continue مجددا للانتقال إلى المرحلة الأخيرة قبل إنشاء المشروع.

في حال كنت قد اخترت تفعيل Google Analytics في المرحلة الثانية، فسيطلب منك في المرحلة الأخيرة تحديد الحساب الذي ترغب باستخدامه مع Google Analytics (سيوفر لك حساب افتراضي اسمه Default Account For Firebase أو سيسمح لك بإنشاء حساب جديد إن أحببت)، بعد تحديد الحساب، سيظهر زر Create project في الاسفل، انقره لإنشاء المشروع. أمّا إذا لم تفعّل Google Analytics من المرحلة الثانية، فسيؤدي ذلك إلى إنشاء المشروع المطلوب فورًا.

ملاحظة إذا كنت تزور Firebase للمرة الأولى، فسيطلب منك في المرحلة الأخيرة تحديد المنطقة (الدولة) الخاصة بـ Google Analytics بالإضافة إلى الموافقة على بنود الاستخدام.

بعد إنشاء المشروع سينتقل المتصفح إلى الصفحة الخاصة به. من القائمة اليسرى انقر الزر Develop ثم انقر Database لأنّنا لن نهتم الآن سوى بقاعدة البيانات.

3.png

بعد النقر على قاعدة البيانات ستحصل على واجهة تسمح لك ببناء قاعدة بيانات جديدة. استخدم شريط التمرير العمودي لكي تنتقل إلى أسفل الصفحة قليلًا حتى تصل إلى القسم الخاص بإنشاء قاعدة بيانات في الزمن الحقيقي (Realtime Database) كما في الشكل التالي:

4.png

انقر زر Create database لإنشاء قاعدة البيانات المطلوبة. ستظهر نافذة صغيرة تعرض فيما إذا كنت تريد أن تجعل قاعدة البيانات مفتوحة للجميع بهدف اختبارها، أم مقفلة تحتاج إلى تسجيل دخول. سنختار أن تكون مفتوحة لجعل الأمر أسهل. اختر Start in test mode:

5.png

لاحظ التحذير الذي سيظهر باللون الأحمر والذي يفيد بأنّ أي شخص سيصبح قادرًا على تعديل قاعدة البيانات أو القراءة منها. بعد النقر على زر Enable ستحصل على شكل شبيه بما يلي:

6.png

أصبحت قاعدة البيانات جاهزة، لاحظ من جديد كيف يظهر تحذير باللون الأحمر يخبرك أنّ قاعدة البيانات مفتوحة ويمكن لأي شخص الوصول إليها. لاحظ أيضًا الرابط الذي يظهر مباشرة فوق التحذير:

https://vue-remote-servers.firebaseio.com/

هذا الرابط هو عنوان نقطة الاتصال لخدمة قاعدة البيانات (Database Service Endpoint) التي أنشأناها توًّا. سنحتاج إلى هذا الرابط لاحقًا في هذا الدرس.

قد تتساءل أيضًا أين الجداول ضمن قاعدة البيانات هذه؟ الحقيقة أنّ قواعد البيانات ضمن Firebase عبارة عن قواعد بيانات NoSQL لمعرفة المزيد حول قواعد البيانات هذه يمكنك زيارة هذه الصفحة.

ملاحظة يمكنك الحصول على المزيد من المعلومات حول Firebase من خلال هذا الرابط الذي يحتوي على الوثائق الرسمية له.

ملاحظة تحدث في بعض الأحيان تحديثات مستمرة على الواجهات الخاصة بـ Google Firebase، لذلك فقد يختلف الشرح الموجود هنا قليلًا عمّا تشاهده في الموقع. مع أنّني أرجو ألّا يحدث ذلك، منعًا لأي التباس قد يعتريك عزيزي القارئ.

تنصيب مكتبة الاتصال بالانترنت vue-resource

يمكن بالطبع استخدام أي طريقة للاتصال بالانترنت من خلال Vue.js، ولكنني سأختار في هذا الدرس أسلوب مخصص لـ Vue.js. سنستخدم مكتبة اسمها vue-resource يمكن من خلالها الاتصال بالخواديم بشكل سهل ومبسّط وبشكل منسجم تقريبًا مع المكتبات الشهيرة المستخدمة لهذا الغرض. يمكنك الوصول إلى المستودع الخاص بهذه المكتبة على Github من خلال هذا الرابط. في الحقيقة، أنصحك بزيارة مستودع هذه المكتبة على Github بعد أن تنتهي من هذا الدرس، لتطلع عليها بشكل جيّد وتتعرف على جميع الإمكانيات التي تقدّمها والتي تسهل عملك كمبرمج. في هذا الدرس، سنتحدّث عن جزء محدود من هذه المزايا.

لتنصيب vue-resource افتح موجّه الأوامر ونفّذ الأمر التالي:

npm install vue-resource

بعد الانتهاء من التنصيب يمكن الآن إضافة هذه المكتبة إلى تطبيق Vue.js وذلك بإضافة السطر التالي إلى قسم المكتبات المستوردة ضمن الملف main.js:

import VueResource from 'vue-resource';

واستخدام التعليمة التالية ضمن الملف main.js أيضًا:

Vue.use(VueResource);

أمّا كيفية الاستخدام الفعلي لهذه المكتبة فسنتعرف عليه في الفقرة التالية.

بناء تطبيق باستخدام Vue.js للقراءة والإضافة من وإلى قاعدة البيانات

كما جرت العادة، سنبني تطبيق عملي جديد باستخدام Vue CLI سنتعلّم من خلاله كيفية الاتصال بقاعدة البيانات التي أنشأناها في الفقرة الأولى من هذا الدرس على Google Firebase.

أنشئ مشروع جديد اسمه vue-remote-servers عن طريق تنفيذ الأمر التالي ضمن موجّه الأوامر:

vue create vue-remote-servers

بعد إنشاء المشروع، افتح المجلّد الخاص به عن طريق Visual Studio Code. احذف الآن الملف HelloWorld.vue ثم احرص على أن تكون محتويات الملف main.js مماثلة لما يلي:

import Vue from 'vue'
import VueResource from 'vue-resource';
import App from './App.vue'
import "bootstrap/dist/css/bootstrap.min.css";

Vue.config.productionTip = false

Vue.use(VueResource);

new Vue({
  render: h => h(App),
}).$mount('#app')

واحرص أيضًا على أن تكون محتويات الملف App.vue مماثلة لما يلي:

<template>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
        <h2>Vue.js Remote Server</h2>
        <div class="form-group">
          <label>Username</label>
          <input type="text" class="form-control" v-model="user.username" />
        </div>
        <div class="form-group">
          <label>First name</label>
          <input type="text" class="form-control" v-model="user.firstname" />
        </div>
        <div class="form-group">
          <label>Last name</label>
          <input type="text" class="form-control" v-model="user.lastname" />
        </div>
        <button class="btn btn-primary" @click="postData()">Add</button>
        <br />
        <br />
        <button class="btn btn-primary" @click="getData()">Retrieve</button>
        <br />
        <br />
        <ul class="list-group">
          <li
            class="list-group-item"
            v-for="usr in users"
            v-bind:key="usr.username"
          >{{usr.username}} - {{usr.firstname}} {{usr.lastname}}</li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      user: {
        username: "",
        firstname: "",
        lastname: "",
      },
      users: [],
    };
  },
  methods: {
    postData: function () {
      this.$http
        .post("https://vue-remote-servers.firebaseio.com/users.json", this.user)
        .then(
          (response) => {
            console.log(response);

            this.user.username = "";
            this.user.firstname = "";
            this.user.lastname = "";
          },
          (error) => {
            console.log(error);
          }
        );
    },
    getData: function () {
      this.$http
        .get("https://vue-remote-servers.firebaseio.com/users.json")
        .then((response) => {
          return response.json();
        })
        .then((data) => {
          const tmpArray = [];

          for (let key in data) {
            tmpArray.push(data[key]);
          }

          this.users = tmpArray;
        });
    },
  },
};
</script>

<style>

</style>

ستعطي الشيفرة السابقة الواجهة التالية:

7.png

الفكرة من هذا التطبيق هي في إمكانية الإضافة المتعددة لمستخدمين افتراضيين. حيث تتكون بيانات كل مستخدم من: اسم المستخدم (Username) والاسم (First name) والكنية (Last name). سترسل بيانات كل مستخدم مفترض إلى قاعدة البيانات على Firebase. بعد ذلك يمكن استرداد بيانات المستخدمين المُضافة مسبقًا إلى قاعدة البيانات هذه، من خلال نقر الزر Retrieve.

ملاحظة سنستخدم في هذا التطبيق تنسيقات Bootstrap على افتراض أنّك قد نصبته مسبقًا. وهذا ما يفسّر وجود السطر التالي ضمن ملف main.js:

import "bootstrap/dist/css/bootstrap.min.css";

إذا أردت أن تتذكر كيفية استخدام تنسيقات Bootstrap ضمن Vue.js يمكنك مراجعة الفقرة: "استخدام إطار العمل Bootstrap" ضمن الدرس: "التعامل مع دخل المستخدم عن طريق نماذج الإدخال".

بالنسبة لآلية عمل التطبيق، لاحظ معي بدايةً أن شيفرة HTML الموجودة ضمن القسم <template> هي بسيطة وواضحة. بالنسبة لحقول البيانات المستخدمة، فهي معرفة ضمن القسم <script> حيث عرّفت جميع الحقول التي تعود للمستخدم المفترض ضمن كائن اسمه user موجود ضمن القسم data ضمن كائن Vue.js. يحتوي هذا الكائن على الحقول التالية: username و firstname و lastname كما هو واضح. كما يحتوي القسم data أيضًا على مصفوفة اسمها users سنخزّن ضمنها بيانات المستخدمين التي سنحصل عليها من قاعدة بيانات Firebase عند نقر زر Retrieve كما سنوضّح ذلك بعد قليل.

أمّا بالنسبة للقسم methods فيحتوي على تابعين فقط: postData و getData وهما معالجين لحدثي النقر على الزرين Add و Retrieve على الترتيب.

بالنسبة للتابع postData تأمل معي الشيفرة البرمجيّة الموجودة ضمنه:

this.$http
        .post("https://vue-remote-servers.firebaseio.com/users.json", this.user)
        .then(
          (response) => {
            console.log(response);

            this.user.username = "";
            this.user.firstname = "";
            this.user.lastname = "";
          },
          (error) => {
            console.log(error);
          }
        );

استخدمت الكائن المبيّت ‎ $http من الكائن this. في الحقيقة أنّ هذا الكائن مُتاح فقط من خلال المكتبة vue-resource التي أضفناها في هذا الدرس. يحتوي الكائن ‎ $http على عدة توابع مفيدة في استقبال وإرسال البيانات عبر الإنترنت من وإلى خواديم أو خدمات (Services) كما هي خدمة Firebase. بما أنّنا نريد إضافة بيانات جديدة، فسنستخدم التابع post كما هو واضح. يقبل هذا التابع وسيطين:

الوسيط الأول هو عنوان نقطة الاتصال للخدمة المراد التواصل معها، وهي في حالتنا هذه عنوان خدمة قاعدة البيانات على Firebase التي أنشأناها في هذا الدرس:

https://vue-remote-servers.firebaseio.com/users.json

الرابط السابق حصلت عليه من الصفحة الخاصة بقاعدة البيانات vue-remote-servers إذا كنت تذكر من الفقرة الأولى من هذا الدرس، ولكن أضفت عليه اسم المصدر users.json. بما أنّني أريد إضافة مستخدمين افتراضيين إلى قاعدة البيانات فمن المنطقي إضافة بيانات هؤلاء المستخدمين إلى جدول (إن صح التعبير) اسمه users. في الحقيقة في قواعد البيانات من النمط NoSQL نسمي مثل هذه الجداول بالمجموعات (Collections). إذًا فالاسم users هو اسم المجموعة التي سنخزّن ضمنها بيانات المستخدمين الافتراضيين، أمّا الامتداد ‎ .json فهو إلزامي.

أمّا الوسيط الثاني فهو عبارة عن البيانات المراد إرسالها. وقد أرسلت الكائن user جملةً واحدة، لأنّه يحتوي على البيانات الفرعية المطلوبة والتي هي مربوطة بدورها عن طريق v-model بعناصر HTML الموافقة.

يُرجع التابع post وعد (Promise) لذلك فإنّنا نُتبع استدعاء التابع post بنقطة مباشرة وبعدها نكتب التابع then كما هو مألوف تمامًا عند العمل مع مكتبات JavaScript الشهيرة الأخرى. يقبل التابع then وسيطين أيضًا: الوسيط الأوّل هو تابع يحتوي على شيفرة برمجيّة تُعالج الرد الذي حصلت عليه مكتبة vue-resource بعد اتصالها مع الخادوم البعيد. انظر إلى الشيفرة التالية:

 (response) => {
            console.log(response);

            this.user.username = "";
            this.user.firstname = "";
            this.user.lastname = "";
          }

مرّرنا التابع السابق كتابع سهم (Arrow Function) يقبل وسيطًا اسمه response وهو يمثّل الرد الذي حصلت عليه المكتبة من الخادوم. ضمن هذا التابع وفي السطر الأوّل منه نلاحظ وجود التعليمة ‎console.log(response) لعرض محتوى البيانات الواردة من الخادوم ضمن الطرفية (Console) الخاصة بأدوات المطوّر ضمن متصفّح الويب. يمكنك إزالة هذا السطر إن أحببت. أمّا التعليمات الثلاث التالية فهدفها تفريغ حقول بيانات المستخدم للإشارة إلى نجاح العملية.

أمّا الوسيط الثاني للتابع then فهو أيضًا تابع آخر، ولكنّه يحتوي على الشيفرة البرمجية التي تعالج حالة وجود خطأ ما في عملية الإرسال.

عند هذه النقطة بالتحديد، أفضّل تشغيل التطبيق لنرى ماذا سيحدث عند إضافة مستخدم جديد. شغل التطبيق عن طريق تنفيذ الأمر التالي ضمن موجّه الأوامر وضمن المجلّد vue-remote-servers الذي يحوي ملفات التطبيق:

npm run serve

زُر الآن الصفحة http://localhost:8080 لتحصل على واجهة التطبيق. أدخل قيم مناسبة لاسم المستخدم و الاسم والكنية، ثم انقر الزر Add. إذا اختفت القيم التي أدخلتها توًّا فهذا دليل على نجاح العملية!

انتقل الآن إلى الصفحة الخاصة بقاعدة البيانات vue-remote-servers (عليك زيارة الموقع ثم نقر زر Go to console وبعد ذلك اختيار المشروع vue-remote-servers ثم نقر زر Database من القائمة اليسرى، وأخيرًا اختيار قاعدة البيانات الحقيقية (Realtime Database) التي تحمل نفس الاسم).

بحسب البيانات التي أدخلتها أنا، حصلت على الشكل التالي:

8.png

لاحظ معي اسم قاعدة البيانات vue-remote-servers يظهر في أعلى الشجرة، ثم يظهر اسم المجموعة users في العقدة التي تليها. يتفرّع عن المجموعة users التي هي بمثابة جدول كما اتفقنا، عقدة تالية تحمل رموزًا مبهمة، يتفرّع عنها من جديد البيانات التي أرسلتها إلى قاعدة البيانات وهي: Husam للاسم و Burhan للكنية و husam79 لاسم المستخدم.

بالنسبة للرموز المبهمة التي تظهر فهي بمثابة مفتاح رئيسي (Primary Key) أو معرّف (ID) للبيانات التي تقع ضمنها. لأنّه من البديهي إرسال العديد من بيانات المستخدمين، فسيعمل Firebase على تخصيص شيفرة فريدة تحوي مثل هذه الرموز المبهمة لكل مستخدم مفترض تتم إضافته إلى قاعدة البيانات. يمكنك إن أحببت إضافة مستخدم آخر لترى كيف يحدث ذلك.

كل الكلام السابق كان للتابع postData. أمّا بالنسبة للتابع getData فها هي الشيفرة البرمجيّة الموجودة ضمنه:

this.$http
        .get("https://vue-remote-servers.firebaseio.com/users.json")
        .then((response) => {
          return response.json();
        })
        .then((data) => {
          const tmpArray = [];

          for (let key in data) {
            tmpArray.push(data[key]);
          }

          this.users = tmpArray;
        });

سنستخدم هذه المرة التابع get من الكائن ‎ $http وذلك للحصول على البيانات المخزّنة ضمن قاعدة البيانات. لا يحتاج هذا التابع كما هو واضح إلّا لوسيط واحد هو نفسه الوسيط الأوّل للتابع post الذي تحدثنا عنه قبل قليل.

يُرجع التابع get أيضًا وعد (Promise)، لذلك وبنفس النقاش السابق فإنّنا نُتبع استدعاء التابع get بنقطة مباشرة وبعدها نكتب التابع then. اكتفيت هنا بكتابة وسيط واحد للتابع then -وهذا جائز تمامًا- وهو عبارة عن تابع سهم كما هو واضح يحتوي على تعليمة برمجية واحدة فقط:

return response.json();

وظيفة هذه التعليمة هي إرجاع تمثيل JSON للرد الذي حصلت عليه المكتبة بعد استدعاء التابع get. وذلك من خلال استدعاء التابع json من الكائن response. وهنا ينبغي التنويه إلى أنّ التابع json يُرجع هو أيضًا وعد (Promise). لذلك فإنّ التعليمة السابقة تُرجع هذا الوعد مما يسمح لنا بوضع نقطة مباشرةً بعد استدعاء التابع then ثم استدعاء تابع then جديد يقبل وسيط وهو بالطبع عبارة عن تابع سهم جديد يقبل وسيطًا واحدًا أيضًا أسميته data يحتوي على البيانات الفعلية التي حصلنا عليها من قاعدة البيانات على الـ Firebase.

ما تبقى ضمن تابع السهم الموجود ضمن تابع then الأخير عبارة عن شيفرة برمجيّة وظيفتها استخلاص البيانات الخام الواردة من المكتبة vue-resource التي تتواصل مع قاعدة البيانات، وتخزينها بشكل مناسب ضمن المصفوفة المؤقتة tmpArray. وبعد انتهاء عملية الاستخلاص، تُسنَد هذه المصفوفة إلى الحقل users وهو مصفوفة بالطبع، لتُعرض البيانات بالشكل المرغوب على المستخدم. انظر هذه الشيفرة:

const tmpArray = [];

for (let key in data) {
    tmpArray.push(data[key]);
}

this.users = tmpArray;

إنّ سبب وجود حلقة for بالشكل السابق، يعود إلى شكل البيانات الواردة من المكتبة vue-resource والتي هي عبارة عن قاموس (Dictionary)، يكون المفتاح (Key) فيه عبارة عن المفتاح الرئيسي (تلك الرموز الغريبة) لأحد المستخدمين، أمّا القيمة (Value) لهذا المفتاح فهي عبارة عن كائن يتضمن بيانات المستخدم (في حالتنا هذه تكون هذه البيانات عبارة عن اسم المستخدم والاسم والكنية كما نعلم). انظر معي إلى البيانات الخام الواردة من المكتبة vue-resource، والتي حصلت عليها بإضافة تعليمة الكتابة إلى الطرفية (Console) عند ورود البيانات من المكتبة:

9.png

في الحقيقة تعمدت إخفاء بعض التوابع الأخرى الموجودة ضمن الكائن للتركيز على ما يهمنا هنا. الشكل السابق هو نتيجة إضافة مستخدمين افتراضيين آخرين إلى قاعدة البيانات، وبعد نقر زر Retrieve بالطبع. انظر إلى الشكل التالي لترى كيف ستبدو نفس البيانات ولكن على صفحة الويب:

10.png

إضافة ميزة تعديل البيانات للتطبيق السابق

من الواضح أنّ تطبيقنا السابق يسمح بإضافة مستخدمين افتراضيين جدد، كما يسمح بقراءة بيانات هؤلاء المستخدمين وعرضهم على الصفحة. ولكن من المفيد أن يسمح التطبيق أيضًا بتعديل بيانات مستخدم افتراضي موجود مسبقًا. يمكن ذلك ببساطة من خلال استخدام التابع put من الكائن ‎ $http حيث يسمح هذا التابع بتعديل بيانات مستخدم موجود مسبقًا ضمن قاعدة البيانات بمجرّد أن نعرف المفتاح الرئيسي (تلك الرموز الغريبة) للمستخدم ضمن قاعدة البيانات.

سأجري في الحقيقة تعديلًا كبيرًا من الناحية الشكلية على التطبيق السابق لكي يسمح بإنجاز هذه الميزة، ورغم التعديلات الكثيرة التي ستجري على الملف App.vue، إلّا أنّها تُعتبر في حقيقة الأمر بسيطة وواضحة خصوصًا بعد أن وصلنا إلى هذه المرحلة في العمل مع Vue.js.

إليك الشيفرة البرمجية الجديدة للملف App.vue:

<template>
  <div class="container">
    <div class="row">
      <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
        <h2>Vue.js Remote Server</h2>
        <div class="form-group">
          <label>Username</label>
          <input type="text" class="form-control" v-model="user.username" />
        </div>
        <div class="form-group">
          <label>First name</label>
          <input type="text" class="form-control" v-model="user.firstname" />
        </div>
        <div class="form-group">
          <label>Last name</label>
          <input type="text" class="form-control" v-model="user.lastname" />
        </div>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-12 col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3">
        <button class="btn btn-primary" @click="postOrPutData()">{{actionButtonTitle}}</button>
        <button class="btn btn-primary float-right" @click="reset()">Reset</button>
      </div>
    </div>
    <hr>
    <div class="row">
      <div class="col-2">
        <button class="btn btn-primary btn-dark" @click="getData()">Retrieve</button>
      </div>
    </div>
    <div class="row" v-for="usr in users" v-bind:key="usr.username">
      <div class="col-2">{{usr.username}}</div>
      <div class="col-4">{{usr.firstname}} {{usr.lastname}}</div>
      <div class="col-1">
        <button class="btn btn-link" @click="prepareToSave(usr.id)">Edit</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "App",
  data() {
    return {
      user: {
        username: "",
        firstname: "",
        lastname: "",
      },
      users: [],
      currentUserIdToSave: "",
      actionButtonTitle: "Add",
    };
  },
  methods: {
    postOrPutData: function () {
      if (this.actionButtonTitle === "Add") {
        this.$http
          .post(
            "https://vue-remote-servers.firebaseio.com/users.json",
            this.user
          )
          .then(
            (response) => {
              console.log(response);

              this.user.username = "";
              this.user.firstname = "";
              this.user.lastname = "";
            },
            (error) => {
              console.log(error);
            }
          );
      } else {
        this.$http
          .put(
            "https://vue-remote-servers.firebaseio.com/users/" +
              this.currentUserIdToSave +
              ".json",
            this.user
          )
          .then(
            (response) => {
              console.log(response);
            },
            (error) => {
              console.log(error);
            }
          );
      }
    },
    getData: function () {
      this.$http
        .get("https://vue-remote-servers.firebaseio.com/users.json")
        .then((response) => {
          return response.json();
        })
        .then((data) => {
          const tmpArray = [];

          for (let key in data) {
            let withId = data[key];
            withId.id = key;
            tmpArray.push(data[key]);
          }

          this.users = tmpArray;
        });
    },
    prepareToSave: function (id) {
      for (var i = 0; i < this.users.length; i++) {
        if (this.users[i].id === id) {
          this.user.username = this.users[i].username;
          this.user.firstname = this.users[i].firstname;
          this.user.lastname = this.users[i].lastname;
          this.currentUserIdToSave = this.users[i].id;

          this.actionButtonTitle = "Save";

          break;
        }
      }
    },
    reset: function () {
      this.actionButtonTitle = "Add";
      this.currentUserIdToSave = "";

      this.user.username = "";
      this.user.firstname = "";
      this.user.lastname = "";
    },
  },
};
</script>

<style>
.row {
  margin-top: 8px;
}
</style>

سأوضّح التعديلات الرئيسية التي طرأت على الشيفرة البرمجية. في البداية أجريت تعديلات تنسيقية على القسم <template> للسماح بإضافة زر جديد اسمه Reset، بالإضافة إلى أنّني غيرت العنصر المسؤول عن عرض بيانات المستخدمين من قاعدة البيانات (كان عبارة عن قائمة غير مرتبة ul)، وذلك لكي أستطيع إضافة زر تعديل Edit بجوار كل مستخدم للسماح بتعديل بياناته في حال الرغبة بذلك.

أضفت أيضًا حقلين جديدين للقسم data، الأول هو currentUserIdToSave ووظيفته الاحتفاظ بالمفتاح الرئيسي للمستخدم الحالي الذي نرغب بتعديل بياناته، أمّا الحقل الثاني فهو actionButtonTitle والذي جعلت قيمته الحالية تظهر مباشرة على الزر Add الذي سيتغير النص الظاهر عليه بحسب السياق. أي عندما نرغب بإضافة مستخدم جديد سيظهر النص Add، أمّا عندما نرغب بتعديل بيانات مستخدم موجود مسبقًا سيظهر النص Save. بمعنى آخر، سيعمل التطبيق على تنفيذ مجموعة مختلفة من التعليمات البرمجية بحسب النص الذي يظهر حاليًا على هذا الزر. لذلك فمن المنطقي تغيير اسم التابع الذي سيُنفّذ عند النقر على هذا الزر، فقد غيرت اسم التابع من postData إلى postOrPutData للإشارة إلى وظيفته الجديدة المتمثلة في الإضافة الجديدة أو التعديل على بيانات موجودة مسبقًا. انظر إلى الشيفرة البرمجية الموجودة ضمن هذا التابع:

if (this.actionButtonTitle === "Add") {
    this.$http
      .post(
        "https://vue-remote-servers.firebaseio.com/users.json",
        this.user
      )
      .then(
        (response) => {
          console.log(response);

          this.user.username = "";
          this.user.firstname = "";
          this.user.lastname = "";
        },
        (error) => {
          console.log(error);
        }
      );
  } else {
    this.$http
      .put(
        "https://vue-remote-servers.firebaseio.com/users/" +
          this.currentUserIdToSave +
          ".json",
        this.user
      )
      .then(
        (response) => {
          console.log(response);
        },
        (error) => {
          console.log(error);
        }
    );
}

لاحظ عبارة if الموجودة في بداية التابع، وانتبه إلى الشرط الموجود ضمنها. يختبر هذا الشرط فيما إذا كانت القيمة الحالية للحقل actionButtonTitle تساوي القيمة Add. فإذا تحقق هذا الشرط، فهذا يعني أننا نريد إضافة مستخدم جديد، فتُنفّذ الكتلة البرمجية المتعلقة بتحقق ذلك الشرط، وهي نفس الكتلة البرمجية التي كانت موجودة ضمن التابع postData قبل التعديل. أمّا إذا لم يتحقق الشرط، فهذا يعني بالتأكيد أنّنا في السياق الذي يسمح بتعديل بيانات موجودة مسبقًا، لذلك تُنفّذ الكتلة البرمجية الموافقة التي تستخدم في هذه المرة التابع put كما هو واضح. يتم التحكّم في قيمة الحقل actionButtonTitle ضمن التابعين الجديدين prepareToSave و reset كما سنرى ذلك بعد قليل. لنركّز الآن قليلًا على التابع put:

this.$http
.put(
    "https://vue-remote-servers.firebaseio.com/users/" +
        this.currentUserIdToSave +
        ".json",
    this.user
    )

يقبل هذا التابع وسيطين. الوسيط الأوّل هو عنوان المصدر الذي سنعمل على تعديل بياناته. لاحظ معي كيف وضعت اسم الجدول (المجموعة) users يليه قيمة المفتاح الرئيسي الذي حصلت عليه من قيمة الحقل currentUserIdToSave ثم وضعت الإمتداد الإلزامي ‎ .json. أمّا الوسيط الثاني فهو بكل بساطة الكائن user الذي من المفترض الآن أن يحمل البيانات المعدّلة لهذا المستخدم. من الممكن أن نجري تعديل على جزء من البيانات أو على جميع البيانات أو أن لا نجري أية تعديلات. في كل حالة من الحالات السابقة سيتم استبدال بالقيم الحالية لبيانات الكائن user البيانات القديمة الموجودة ضمن قاعدة البيانات والتي لها نفس قيمة المفتاح الرئيسي.

كما أوضحت قبل قليل، فقد أضفت تابعين جديدين هما: prepareToSave و reset، كما أجريت تعديلًا بسيطًا للتابع getData. سأبدأ من التعديل البسيط الذي أجريته على التابع getData. أضفت في الحقيقة حقلًا جديدًا إلى كل كائن يمثّل مستخدم مفترض تم جلبه من قاعدة بيانات. انظر معي إلى حلقة for التي حدث فيها التعديل:

for (let key in data) {
    let withId = data[key];
    withId.id = key;
    tmpArray.push(data[key]);
}

أنشأت متغير جديد اسمه withId تنحصر وظيفته في إضافة الحقل id إلى البيانات التي تأتي أصلًا من قاعدة البيانات وذلك بهدف الاحتفاظ بالمفتاح الرئيسي لكل مستخدم مع بياناته الأساسية. سنرى سبب هذا التعديل بعد لحظات.

بالنسبة للشيفرة البرمجية لكل من التابعين prepareToSave و reset فهي بسيطة للغاية. بالنسبة للتابع prepareToSave فيُستدعى عند نقر الزر Edit بجوار مستخدم ما. يقبل هذا التابع وسيطًا واحدًا هو قيمة المفتاح الرئيسي لذلك المستخدم الذي نرغب بتعديل بياناته. بعد ذلك ندخل حلقة for، هدفها الوصول إلى الكائن الذي يمثّل المستخدم المراد تعديل بياناته. استطعت تحديد هذا الكائن، وذلك بمقارنة قيمة الوسيط id (الذي يحمل قيمة المفتاح الرئيسي للمستخدم المراد تعديله) مع قيمة الحقل id لكل مستخدم موجود ضمن المصفوفة users. لاحظ هنا أنّ الحقل id الموجود ضمن المصفوفة users قد أضفناه ضمن التابع getData. بعد الوصول إلى الكائن المطلوب ضمن المصفوفة، تعمل الشيفرة على تحديث بيانات الحقل user وبالتالي تتم تعبئة عناصر الإدخال على الصفحة ببيانات المستخدم المراد تعديله، ثم تُعدَّل قيمة الحقل currentUserIdToSave ليحمل قيمة المفتاح الرئيسي لهذا المستخدم المراد تعديله (لاحظ أنّنا استخدمنا قيمة هذا الحقل ضمن التابع postOrPutData بتمريره للتابع put)، وأيضًا تُعدَّل قيمة الحقل actionButtonTitle لتحمل القيمة Save، وهكذا نكون قد دخلنا في سياق حفظ البيانات، أي نكون جاهزين لتعديل بيانات المستخدم.

وأخيرًا بالنسبة للتابع reset فهو يُعيد الأمور إلى أصلها الأول. أي يعمل على تفريغ حقول الإدخال، ويعمل على إعادة السياق إلى حالة إدخال مستخدم جديد بإسناد القيمة Add ضمن الحقل actionButtonTitle، كما يعمل على تفريغ الحقل currentUserIdToSave لأنّه لا يوجد حاليًا أي مستخدم نرغب بتعديل بياناته كما هو واضح.

إذا شغلت التطبيق بعد هذه التعديلات يُفترض أن تحصل على شكل شبيه بما يلي (لاحظ أنّني قد نقرت أيضًا الزر Retrieve):

11.png

جرب نقر زر Edit بجوار أحد المستخدمين، ثم عدّل بياناته، ثم انقر زر Save، وبعدها انقر زر Retrieve من جديد. يجب أن ترى الآن التعديلات التي أجريتها على ذلك المستخدم. انقر زر Reset للعودة إلى سياق إضافة مستخدم جديد.

ملاحظة: لا يُعتبر الأسلوب الذي اتبعته في تعديل البيانات أسلوبًا عمليًا في التطبيقات الحقيقة. إذ كان من الواجب الانتقال إلى صفحة منفصلة عند النقر على زر Edit لتعديل بيانات المستخدم بشكل مستقل. لكنني آثرت اتباع هذا الأسلوب غير العملي الآن، لأنّ اتباع الأسلوب العملي يقتضي الدخول في بحث جديد تمامًا وهذا ما سأفعله لاحقًا في درس منفصل إن شاء الله.

ملاحظة: من الممكن أيضًا إضافة ميزة حذف مستخدم موجود مسبقًا باستخدام التابع delete مع الكائن ‎ $http مع تمرير وسيط واحد فقط له. هو نفسه الوسيط الأوّل للتابع put.

ختامًا

لقد تعلّمنا الكثير في هذا الدرس! لقد تعلّمنا كيفية إنشاء قاعدة بيانات بسيطة على Google Firebase، كما وتعلّمنا كيفية استخدام المكتبة vue-resource لإكساب تطبيقات Vue.js القدرة على الاتصال بالانترنت، وبنينا أيضًا تطبيق عملي مبسّط يوضّح كيفية التواصل مع قاعدة البيانات على Google Firebase، وبالتالي كيفية إضافة وقراءة وتعديل البيانات الموجودة ضمنها. يُعتبر هذا الدرس مهمًّا بالفعل، فمن خلاله استطعت للمرة الأولى الخروج من "القمقم" والتواصل مع العالم الخارجي. ستحمل ما تبقى من دروس مفاهيم مهمّة أيضًا حول كيفية التعامل مع Vue.js تلك المكتبة القوية والمرنة.

اقرأ أيضًا


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...