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