حان الوقت الآن لإضافة الوظائف التي تمكّننا من تعديل عناصر المهام الموجودة مسبقًا، لذلك سنستفيد من إمكانات التصيير الشرطي 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.

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