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

العرض الشرطي في إطار العمل Vue.js


Ola Abbas

حان الوقت الآن لإضافة الوظائف التي تمكّننا من تعديل عناصر المهام الموجودة مسبقًا، لذلك سنستفيد من إمكانات التصيير الشرطي Conditional Rendering في إطار العمل Vue مثل v-if و v-else للسماح بالتبديل بين عرض عناصر المهام الموجودة مسبقًا وعرض التعديل حيث يمكنك تعديل عناوين أو تسميات labels عناصر المهام، كما سنتعرّف على إضافة وظيفة لحذف عناصر المهام.

  • المتطلبات الأساسية: الإلمام بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة استخدام سطر الأوامر أو الطرفية، إذ تُكتَب مكونات Vue بوصفها مجموعةً من كائنات جافاسكربت التي تدير بيانات التطبيق وصيغة القوالب المستنِدة إلى لغة HTML المرتبطة مع بنية DOM الأساسية، كما ستحتاج إلى طرفية مثبَّت عليها node و npm لتثبيت Vue ولاستخدام بعض ميزاته الأكثر تقدمًا مثل مكوّنات الملف المفرد Single File Components أو دوال التصيير Render.
  • الهدف: تعلّم كيفية استخدام التصيير الشرطي في إطار العمل Vue.

إنشاء مكون التعديل

يمكننا البدء بإنشاء مكوِّن منفصل للتعامل مع وظيفة التعديل، لذا أنشئ ملفًا جديدًا بالاسم ToDoItemEditForm.vue في المجلد components وانسخ الشيفرة التالية في هذا الملف:

<template>
  <form class="stack-small" @submit.prevent="onSubmit">
    <div>
      <label class="edit-label">Edit Name for "{{label}}"</label>
      <input :id="id" type="text" autocomplete="off" v-model.lazy.trim="newLabel" />
    </div>
    <div class="btn-group">
      <button type="button" class="btn" @click="onCancel">
        Cancel
        <span class="visually-hidden">editing {{label}}</span>
      </button>
      <button type="submit" class="btn btn__primary">
        Save
        <span class="visually-hidden">edit for {{label}}</span>
      </button>
    </div>
  </form>
</template>
<script>
export default {
  props: {
    label: {
      type: String,
      required: true
    },
    id: {
      type: String,
      required: true
    }
  },
  data() {
    return {
      newLabel: this.label
    };
  },
  methods: {
    onSubmit() {
      if (this.newLabel && this.newLabel !== this.label) {
        this.$emit("item-edited", this.newLabel);
      }
    },
    onCancel() {
      this.$emit("edit-cancelled");
    }
  }
};
</script>
<style scoped>
.edit-label {
  font-family: Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #0b0c0c;
  display: block;
  margin-bottom: 5px;
}
input {
  display: inline-block;
  margin-top: 0.4rem;
  width: 100%;
  min-height: 4.4rem;
  padding: 0.4rem 0.8rem;
  border: 2px solid #565656;
}
form {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
}
form > * {
  flex: 0 0 100%;
}
</style>

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

تضبط الشيفرة السابقة أساس وظيفة التعديل، إذ ننشئ نموذجًا يحتوي على حقل إدخال <input> لتعديل اسم المهمة، ويوجد زر "حفظ Save" وزر "إلغاء Cancel":

  • إذا نقرت على زر "الحفظ Save"، فسيصدِر المكوِّن التسمية الجديدة باستخدام الحدث item-edited.
  • إذا نقرت على زر "الإلغاء Cancel"، فسيشير المكوِّن إلى ذلك عن طريق إصدار الحدث edit-cancelled.

تعديل المكون ToDoItem

يجب إجراء بعض التعديلات على المكوِّن ToDoItem قبل التمكّن من إضافة المكوِّن ToDoItemEditForm إلى تطبيقنا، إذ يجب إضافة متغير لتعقّب تعديل العنصر، وزر لتبديل هذا المتغير، كما سنضيف زر حذف Delete لأن الحذف وثيق الصلة بالتعديل، أي عدّل قالب المكون ToDoItem كما يلي:

<template>
  <div class="stack-small">
    <div class="custom-checkbox">
      <input type="checkbox" class="checkbox" :id="id" :checked="isDone"
             @change="$emit('checkbox-changed')" />
      <label :for="id" class="checkbox-label">{{label}}</label>
    </div>
    <div class="btn-group">
      <button type="button" class="btn"  @click="toggleToItemEditForm">
        Edit <span class="visually-hidden">{{label}}</span>
      </button>
      <button type="button" class="btn btn__danger" @click="deleteToDo">
        Delete <span class="visually-hidden">{{label}}</span>
      </button>
    </div>
  </div>
</template>

أضفنا عنصر <div> الذي يغلّف القالب بأكمله لأغراض التنسيق، وأضفنا زرَّي "تعديل Edit" و "حذف Delete":

  • إذا نقرت على زر "التعديل Edit"، فسيبدّل عرض مكوِّن ToDoItemEditForm لنتمكن من استخدامه لتعديل عنصر المهام باستخدام دالة معالج حدث تسمى toggleToItemEditForm()‎ التي ستضبط الراية isEditing على القيمة true، لكن يجب أولًا تعريف هذه الدالة ضمن الخاصية data()‎.
  • إذا نقرت على زر "الحذف Delete"، فسيُحذَف عنصر المهام باستخدام دالة معالج حدث تسمى deleteToDo()‎، إذ سنصدِر في هذا المعالج الحدث item-deleted إلى المكوِّن الأب، مما يؤدي إلى تحديث القائمة.

لنعرِّف الآن معالجات النقرات والراية isEditing، لذا أضِف الخاصية isEditing بعد الخاصية isDone كما يلي:

data() {
  return {
    isDone: this.done,
    isEditing: false
  };
}

أضِف الآن توابعك ضمن الخاصية methods وبعد الخاصية data()‎ مباشرةً:

methods: {
    deleteToDo() {
      this.$emit('item-deleted');
    },
    toggleToItemEditForm() {
      this.isEditing = true;
    }
  }

عرض المكونات شرطيا باستخدام v-if و v-else

لدينا الآن الراية isEditing التي يمكننا استخدامها للإشارة إلى أن العنصر مُعدَّل أم لا، فإذا كانت للراية isEditing القيمة true، فيجب استخدام هذه الراية لعرض المكوِّن ToDoItemEditForm بدلًا من مربع الاختيار، إذ سنستخدِم الموجِّه v-if في إطار العمل Vue.

لن يصيِّر الموجِّه v-if كتلةً ما إلا إذا كانت القيمة المُمرَّرة إليه true، وهذا مشابه لكيفية عمل التعليمة if في لغة جافاسكربت، إذ يحتوي الموجّه v-if على موجّهات مقابلة هي v-else-if و v-else لتوفير ما يعادلها في لغة جافاسكربت مثل else if و else ضمن قوالب Vue.

يجب أن تكون كتل v-else و v-else-if الشقيق الأول لكتل v-if و v-else-if، وإلا فلن يتعرَّف Vue عليها، كما يمكنك استخدام الموجّه v-if مع الوسم <template> إذا كنت بحاجة إلى تصيير قالب كامل شرطيًا.

أخيرًا، يمكنك استخدام الموجّهَين v-if و v-else مع جذر المكوِّن لعرض إحدى الكتل فقط أو الكتلة الأخرى، لأنّ Vue لن يصيّر سوى كتلةً واحدةً من هذه الكتل في كل مرة، لذا سنطبّق ذلك في تطبيقنا مما يسمح باستبدال الشيفرة التي تعرض عنصر المهمة في نموذج التعديل.

أضِف v-if="!isEditing"‎ إلى عنصر <div> الجذر في المكون ToDoItem كما يلي:

<div class="stack-small" v-if="!isEditing">

أضف بعد ذلك السطر التالي بعد وسم إغلاق <div>:

<to-do-item-edit-form v-else :id="id" :label="label"></to-do-item-edit-form>

كما يجب استيراد المكوِّن ToDoItemEditForm وتسجيله لنتمكّن من استخدامه ضمن هذا القالب، لذا أضف السطر التالي قبل العنصر <script>:

import ToDoItemEditForm from "./ToDoItemEditForm";

أضِف الخاصية components قبل الخاصية props ضمن كائن المكوِّن:

components: {
  ToDoItemEditForm
},

إذا انتقلتَ الآن إلى تطبيقك ونقرت على زر "تعديل Edit" عنصر المهام، فيجب أن ترى مربع الاختيار مستبدَلًا بنموذج التعديل.

01_todo-edit-delete.png

لكن لا توجد طريقة حاليًا للعودة، لذلك يجب إضافة مزيد من معالجات الأحداث إلى المكوِّن.

الرجوع من وضع التعديل

يجب أولًا إضافة التابع itemEdited()‎ إلى الخاصية methods في المكوِّن ToDoItem، إذ يأخذ هذا التابع عنوان label العنصر الجديد بوصفه وسيطًا ويرسل الحدث itemEdited إلى المكوِّن الأب ويضبط isEditing على القيمة false، لذا أضِف هذا التابع الآن بعد التوابع الحالية كما يلي:

itemEdited(newLabel) {
  this.$emit('item-edited', newLabel);
  this.isEditing = false;
}

سنحتاج بعد ذلك إلى التابع editCancelled()‎، ولا يأخذ هذا التابع أي وسائط ويعمل فقط على ضبط isEditing مرةً أخرى على القيمة false، لذا أضِف هذا التابع بعد التابع السابق:

editCancelled() {
  this.isEditing = false;
}

أخيرًا، سنضيف معالِجات الأحداث للأحداث الصادرة من المكوِّن ToDoItemEditForm، وسنربط التوابع المناسبة لكل حدث، لذا عدِّل الاستدعاء <to-do-item-edit-form> ليبدو كما يلي:

<to-do-item-edit-form v-else :id="id" :label="label"
                      @item-edited="itemEdited"
                      @edit-cancelled="editCancelled">
</to-do-item-edit-form>

تعديل وحذف عناصر المهام

يمكننا الآن التبديل بين نموذج التعديل ومربع الاختيار، ولكن لم نعالِج تحديث المصفوفة ToDoItems مرةً أخرى في المكوِّن App.vue، ويمكن إصلاح ذلك من خلال الاستماع إلى الحدث item-edited وتحديث القائمة وفقًا لذلك، كما يجب التعامل مع حدث الحذف لنتمكّن من حذف عناصر المهام.

أضف التوابع الجديدة التالية إلى كائن مكوِّن App.vue بعد التوابع الموجودة مسبقًا ضمن الخاصية methods:

deleteToDo(toDoId) {
  const itemIndex = this.ToDoItems.findIndex(item => item.id === toDoId);
  this.ToDoItems.splice(itemIndex, 1);
},
editToDo(toDoId, newLabel) {
  const toDoToEdit = this.ToDoItems.find(item => item.id === toDoId);
  toDoToEdit.label = newLabel;
}

سنضيف بعد ذلك مستمعي الأحداث للحدثين item-deleted و item-edited، بحيث:

  • يجب تمرير item.id إلى التابع بالنسبة للحدث item-deleted.
  • يجب تمرير item.id والمتغير الخاص ‎$event بالنسبة للحدث item-edited، إذ يُعَدّ المتغير ‎$event هو متغير خاص بإطار العمل Vue ويُستخدَم لتمرير بيانات الحدث إلى التوابع، فإذا استخدمتَ أحداث HTML الأصيلة Native مثل الحدث click، فسيمرّر هذا المتغير كائن الحدث الأصيل إلى تابعك.

عدّل الاستدعاء <to-do-item></to-do-item> ضمن قالب App.vue ليبدو كما يلي:

<to-do-item :label="item.label" :done="item.done" :id="item.id"
            @checkbox-changed="updateDoneStatus(item.id)"
            @item-deleted="deleteToDo(item.id)"
            @item-edited="editToDo(item.id, $event)">
</to-do-item>

يجب أن تكون الآن قادرًا على تعديل العناصر وحذفها من القائمة.

إصلاح خطأ باستخدام الحالة isDone

يبدو كل شيء رائعًا حتى الآن، ولكننا أحدثنا خطأً عن طريق إضافة وظيفة التعديل، لذا جرّب تنفيذ ما يلي:

  1. تحديد أو إلغاء تحديد أحد مربعات اختيار المهام.
  2. الضغط على زر "التعديل Edit" لعنصر المهام نفسه.
  3. إلغاء التعديل بالضغط على زر "الإلغاء Cancel".

لاحظ حالة مربع الاختيار بعد الضغط على زر الإلغاء، فلم ينسَ التطبيق حالة مربع الاختيار فقط، وإنما أحدث خللًا أيضًا في حالة الاكتمال done لعنصر المهام المقابل، فإذا حاولت تحديده أو إلغاء تحديده مرةً أخرى، فسيتغير عدد المهام المكتملة بعكس ما تتوقعه لأنّ الخاصية isDone ضمن data تُعطَى القيمة this.done عند تحميل المكوِّن، ويمكن إصلاح ذلك عن طريق تحويل عنصر بيانات isDone إلى الخاصية computed، إذ سنستخدِم ميزةً أخرى للخاصيات computed هي أنها تحافظ على التفاعل، مما يعني أنّ حالتها تُحفَظ عندما يتغير القالب.

أزِل السطر التالي من الخاصية data()‎:

isDone: this.done,

أضِف الكتلة التالية بعد كتلة data() { }‎:

computed: {
  isDone() {
    return this.done;
  }
},

ستجد أنّ المشكلة قد حُلَّت الآن عند الحفظ وإعادة التحميل، إذ سيجري الاحتفاظ بحالة مربع الاختيار عند التبديل بين قوالب عناصر المهام.

فهم تشابك الأحداث

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

فمثلًا في الملف App.vue:

  • يستمع العنصر <to-do-form> إلى:
    • الحدث todo-added الصادر عن التابع onSubmit()‎ ضمن المكوِّن ToDoForm عند إرسال النموذج، والنتيجة هي استدعاء التابع addToDo()‎ لإضافة عنصر مهمة جديد إلى المصفوفة ToDoItems.
  • يستمع العنصر <to-do-item> إلى:
    • الحدث checkbox-changed الصادر عن مربع الاختيار في العنصر <input> ضمن المكوِّن ToDoItem عند تحديده أو إلغاء تحديده، والنتيجة هي استدعاء التابع updateDoneStatus()‎ لتحديث حالة الاكتمال done لعنصر المهمة المقابل.
    • الحدث item-deleted الصادر عن التابع deleteToDo()‎ ضمن المكوِّن ToDoItem عند الضغط على زر "الحذف Delete"، والنتيجة هي استدعاء التابع deleteToDo()‎ لحذف عنصر المهمة المقابل.
    • الحدث item-edited الصادر عن التابع itemEdited()‎ ضمن المكوِّن ToDoItem عند الاستماع بنجاح إلى الحدث item-edited باستخدام التابع onSubmit()‎ ضمن المكوِّن ToDoItemEditForm، إذ يمثّل ذلك سلسلةً من حدثَي item-edit مختلفين، والنتيجة هي استدعاء التابع editToDo()‎ لتحديث عنوان label عنصر المهمة المقابل.

في الملف ToDoForm.vue:

  • يستمع العنصر <form> إلى الحدث submit، والنتيجة هي استدعاء التابع onSubmit()‎ الذي يتحقَّق من أنّ العنوان label الجديد ليس فارغًا، ثم يصدر الحدث todo-added الذي يمكن الاستماع إليه لاحقًا ضمن الملف App.vue كما ذكرنا سابقًا، ثم يمسح عنوان العنصر <input> الجديد.
  • في الملف ToDoItem.vue:
  • يستمع عنصر مربع الاختيار checkbox في العنصر <input> إلى الحدث change، والنتيجة هي إصدار الحدث checkbox-changed عند تحديد أو إلغاء تحديد مربع الاختيار الذي يمكن الاستماع إليه لاحقًا في المكوِّن App.vue كما ذكرنا سابقًا.
  • يستمع زر <button> "التعديل Edit" إلى الحدث click، والنتيجة هي استدعاء التابع toggleToItemEditForm()‎ الذي قيمة this.isEditing مبدَّلة إلى true والتي تعرض بدورها قالب تعديل عنصر المهام عند إعادة التصيير.
  • يستمع زر <button> "الحذف Delete" إلى الحدث click، والنتيجة هي استدعاء التابع deleteToDo()‎ الذي يصدر الحدث item-deleted، والذي يمكن الاستماع إليه لاحقًا ضمن المكوِّن App.vue كما ذكرنا سابقًا.
  • يستمع المكوِّن <to-do-item-edit-form> إلى:
    • الحدث item-edited الصادر عن التابع onSubmit()‎ ضمن المكوِّن ToDoItemEditForm عند إرسال النموذج بنجاح، والنتيجة هي استدعاء التابع itemEdited()‎ الذي يصدر الحدث item-edited الذي يمكن الاستماع إليه لاحقًا في المكوِّن App.vue كما ذكرنا سابقًا، كما يعيد ضبط this.isEditing على القيمة false، وبالتالي لن يظهر نموذج التعديل عند إعادة التصيير.
    • الحدث edit-cancelled الصادر عن التابع onCancel()‎ ضمن المكوِّن ToDoItemEditForm عند النقر على زر "الإلغاء Cancel"، والنتيجة هي استدعاء التابع editCancelled()‎ الذي يعيد ضبط this.isEditing على القيمة false، وبالتالي لن يظهر نموذج التعديل عند إعادة التصيير.

في الملف ToDoItemEditForm.vue:

  • يستمع العنصر <form> إلى الحدث submit، والنتيجة هي استدعاء التابع onSubmit()‎ الذي يتحقق مما إذا كانت قيمة العنوان الجديدة غير فارغة وليست مماثلةً للقيمة القديمة، فإذا كان الأمر كذلك، فسيصدر الحدث item-edited الذي يمكن الاستماع إليه لاحقًا ضمن المكوِّن ToDoItem.vue كما ذكرنا سابقًا.
  • يستمع زر <button> "الإلغاء Cancel" إلى الحدث click، والنتيجة هي استدعاء التابع onCancel()‎ الذي يصدر الحدث edit-cancelled الذي يمكن الاستماع إليه لاحقًا في المكون ToDoItem.vue كما ذكرنا سابقًا.

الخلاصة

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

ترجمة -وبتصرّف- للمقال Vue conditional rendering: editing existing todos.

اقرأ أيضًا


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

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

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



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

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

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

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   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.


×
×
  • أضف...