بدأنا في مقال التعامل مع المتغيرات والخاصيات بتطوير تطبيق قائمة المهام، والهدف الأساسي من هذا المقال هو تعلّم كيفية تقسيم تطبيقنا إلى مكونات يمكن إدارتها ومشاركة المعلومات فيما بينها. سنقسّم تطبيقنا إلى مكونات، ثم سنضيف مزيدًا من الوظائف للسماح للمستخدمين بتحديث المكونات الحالية.
- المتطلبات الأساسية: يوصَى على الأقل بأن تكون على دراية بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة باستخدام سطر الأوامر أو الطرفية، وستحتاج طرفية مثبَّت عليها node و npm لتصريف وبناء تطبيقك.
- الهدف: تعلم كيفية تقسيم تطبيقنا إلى مكونات ومشاركة المعلومات فيما بينها.
يمكنك متابعة كتابة شيفرتك معنا، لذلك انسخ أولًا مستودع github -إذا لم تفعل ذلك مسبقًا- باستخدام الأمر التالي:
git clone https://github.com/opensas/mdn-svelte-tutorial.git
ثم يمكنك الوصول إلى حالة التطبيق الحالية من خلال تشغيل الأمر التالي:
cd mdn-svelte-tutorial/04-componentizing-our-app
أو يمكنك تنزيل محتوى المجلد مباشرةً كما يلي:
npx degit opensas/mdn-svelte-tutorial/04-componentizing-our-app
تذكَّر تشغيل الأمر npm install && npm run dev
لبدء تشغيل تطبيقك في وضع التطوير، وإذا أردت متابعتنا فابدأ بكتابة الشيفرة باستخدام الأداة REPL.
تقسيم التطبيق إلى مكونات
يتكون التطبيق في إطار عمل Svelte من مكوِّن واحد أو من مكونات متعددة، ويُعَدّ المكوِّن كتلةً من الشيفرة البرمجية القابلة لإعادة الاستخدام والمستقلة ذاتيًا والتي تغلّف شيفرة HTML و CSS وجافاسكربت المرتبطة مع بعضها البعض والمكتوبة في ملف .svelte
، كما يمكن أن تكون المكونات كبيرةً أو صغيرةً، لكنها تكون عادةً محدَّدةً بوضوح، فالمكونات الأكثر فاعليةً هي المكونات التي تخدم غرضًا واحدًا واضحًا.
من فوائد تحديد المكونات هو قابلية هذه المكونات للموازنة مع أفضل الممارسات العامة بهدف تنظيم شيفرتك البرمجية ضمن أجزاء يمكن إدارتها، مما يساعدك على فهم كيفية ارتباطها ببعضها البعض ويعزِّز إعادة الاستخدام ويجعل شيفرتك البرمجية أسهل للتفكير بها وصيانتها وتوسيعها.
لا توجد قواعد صارمة لتقسيم المكونات، لذا يفضِّل بعض الأشخاص اتباع نهج بسيط يتمثل بالنظر إلى شيفرة HTML ثم رسم مربعات حول كل مكوِّن ومكوِّن فرعي يبدو أنّ له شيفرته الخاصة، في حين يطبق أشخاص آخرون الأساليب نفسها المُستخدَمة لتحديد ما إذا كان يجب إنشاء دالة أو كائن جديد، وأحد هذه الأساليب هو مبدأ المسؤولية الفردية، أي يجب أن يطبّق المكون شيئًا واحدًا فقط بصورة مثالية، ثم يمكننا تقسيمه إلى مكونات فرعية أصغر إذا لزم الأمر، كما يجب أن يكمل هذا النهجان بعضهما البعض لمساعدتك على تحديد كيفية تنظيم مكوناتك بطريقة أفضل.
سنقسم تطبيقنا إلى المكونات التالية:
-
Alert.svelte
: مربع إشعارات عام لإرسال الإجراءات التي حدثت. -
NewTodo.svelte
: حقل إدخال النص والزر الذي يسمح بإدخال عنصر مهام جديد. -
FilterButton.svelte
: أزرار "كل المهام All" و"المهام النشطة Active" و"المهام المكتملة Completed" التي تسمح بتطبيق المرشّحات Filters على عناصر المهام المعروضة. -
TodosStatus.svelte
: العنوان الذي يعرض العبارة "x out of y items completed" التي تمثّل عدد المهام المكتملة. -
Todo.svelte
: عنصر مهام مفرد، إذ سيُعرَض كل عنصر مهمة مرئي في نسخة منفصلة من هذا المكوِّن. -
MoreActions.svelte
: الزرّان "تحديد الكل Check All" و"احذف المهام المكتملة Remove Completed" الموجودان أسفل واجهة المستخدِم، ويسمحان بتنفيذ مجموعة إجراءات على عناصر المهام.
سنركز في هذا المقال على إنشاء المكونين FilterButton
و Todo
وسنشرح المكونات الأخرى في المقالات القادمة.
ملاحظة: سنتعلم أيضًا في عملية إنشاء أول مكونين تقنيات مختلفة لتواصل المكونات مع بعضها بعضًا، وإيجابيات وسلبيات كل من هذه التقنيات.
استخراج مكون الترشيح
سننشئ أولًا المكون FilterButton.svelte
باتباع الخطوات التالية:
أولًا، أنشئ ملفًا جديدًا components/FilterButton.svelte.
ثانيًا، سنصرّح عن الخاصية filter
في هذا الملف ثم سننسخ شيفرة HTML المتعلقة به من الملف Todos.svelte، لذا أضِف المحتوى التالي إلى هذا الملف:
<script> export let filter = 'all' </script> <div class="filters btn-group stack-exception"> <button class="btn toggle-btn" class:btn__primary={filter === 'all'} aria-pressed={filter === 'all'} on:click={()=> filter = 'all'} > <span class="visually-hidden">Show</span> <span>All</span> <span class="visually-hidden">tasks</span> </button> <button class="btn toggle-btn" class:btn__primary={filter === 'active'} aria-pressed={filter === 'active'} on:click={()=> filter = 'active'} > <span class="visually-hidden">Show</span> <span>Active</span> <span class="visually-hidden">tasks</span> </button> <button class="btn toggle-btn" class:btn__primary={filter === 'completed'} aria-pressed={filter === 'completed'} on:click={()=> filter = 'completed'} > <span class="visually-hidden">Show</span> <span>Completed</span> <span class="visually-hidden">tasks</span> </button> </div>
ثالثًا، ارجع إلى المكوِّن Todos.svelte
، حيث نريد الاستفادة من المكوِّن FilterButton
، إذ يجب استيراده أولًا، لذا أضِف السطر التالي قبل القسم <script>
في المكوِّن Todos.svelte
:
import FilterButton from './FilterButton.svelte'
رابعًا، استبدل الآن العنصر <div> الذي يملك اسم الصنف filters
باستدعاء المكون FilterButton
الذي يأخذ المرشح الحالي بوصفه خاصيةً كما يلي:
<FilterButton {filter} />
ملاحظة: تذكَّر أنه إذا تطابق اسم سمة في لغة HTML مع اسم المتغير، فيمكن استبدالهما بالشكل {variable}
، لذا يمكننا استبدال <FilterButton filter={filter} />
بالشكل <FilterButton {filter} />
.
لنجرب التطبيق الآن، حيث ستلاحظ أنه إذا نقرت على أزرار الترشيح، فستُحدَّد هذه الأزرار وسيُحدَّث التنسيق بطريقة مناسبة، ولكن لدينا مشكلة وهي عدم ترشيح المهام، وسبب هذه المشكلة هو انتقال المتغير filter
من المكوِّن Todos
إلى المكوِّن FilterButton
عبر الخاصية، ولكن لا تنتقل التغييرات التي تحدث في المكوِّن FilterButton
مرةً أخرى إلى المكوِّن الأب، إذ يكون ارتباط البيانات أحادي الاتجاه افتراضيًا.
مشاركة البيانات بين المكونات: تمرير المعالج بوصفه خاصية
تتمثل إحدى طرق السماح للمكونات الأبناء بإعلام المكونات الآباء بأيّ تغييرات في تمرير المعالج بوصفه خاصيةً Prop، حيث سينفّذ المكوِّن الابن المعالج، ويمرّر المعلومات المطلوبة بوصفها معاملًا وسيعدّل المعالج حالة المكوِّن الأب، كما سيتلقّى المكوِّن FilterButton
في حالتنا المعالج onclick
من المكوِّن الأب، فإذا نقر المستخدِم على أيّ زر ترشيح، فسيستدعي المكونُ الابن المعالجَ onclick
ويمرّر المرشّح المحدد على أساس معامل إلى المكوِّن الأب.
سنصرّح فقط عن الخاصية onclick
التي تُسنَد إلى معالِج وهمي لمنع الأخطاء كما يلي:
export let onclick = (clicked) => {}
وسنصرّح عن التعليمة التفاعلية $: onclick(filter)
لاستدعاء المعالِج onclick
كلما جرى تحديث المتغير filter
.
أولًا، يجب أن يبدو القسم <script>
الخاص بالمكوِّن FilterButton
كما يلي:
<script> export let filter = 'all' export let onclick = (clicked) => {} $: onclick(filter) </script>
إذا استدعينا المكوِّن FilterButton
ضمن المكوِّن Todos.svelte
الآن، فيجب تحديد المعالج، لذا عدّله إلى ما يلي:
<FilterButton {filter} onclick={ (clicked) => filter = clicked }/>
إذا نقرت على أيّ زر ترشيح، فسنعدّل المتغير filter
باستخدام المرشّح الجديد وسيعمل المكوِّن FilterButton
مرةً أخرى.
طريقة أسهل لربط البيانات ثنائي الاتجاه باستخدام الموجه bind
أدركنا في المثال السابق أنّ المكوِّن FilterButton
لم يعمل، لأنّ حالة التطبيق تنتقل من المكوِّن الأب إلى المكوِّن الابن من خلال الخاصية filter
، ولكنها لا ترجع مرةً أخرى من المكوِّن الابن إلى المكوِّن الأب، لذلك أضفنا الخاصية onclick
للسماح للمكوِّن الابن بإرسال قيمة الخاصية filter
الجديدة إلى المكوِّن الأب.
يعمل التطبيق جيدًا، ولكن يوفر إطار عمل Svelte طريقةً سهلةً ومباشرةً لتحقيق ربط البيانات ثنائي الاتجاه، إذ تتدفق البيانات عادةً من المكوِّن الأب إلى المكوِّن الابن باستخدام الخاصيات، وإذا أردنا أن تتدفق في الاتجاه الآخر من المكوِّن الابن إلى المكوِّن الأب، فيمكننا استخدام الموجّه bind:
.
سنخبر إطار عمل Svelte باستخدام الموجّه bind
أنّ أيّ تغييرات تجرَى على الخاصية filter
في المكوِّن FilterButton
يجب أن تنتشر إلى المكوِّن الأب Todos
، أي أننا سنربط قيمة المتغير filter
في المكوِّن الأب بقيمته في المكوِّن الابن.
أولًا، عدّل استدعاء المكوِّن FilterButton
في Todos.svelte
كما يلي:
<FilterButton bind:filter={filter} />
يوفِّر إطار عمل Svelte اختصارًا، إذ تعادل التعليمةُ bind:value={value}
التعليمةَ bind:value
، لذلك يمكنك في المثال السابق كتابة <FilterButton bind:filter />
فقط.
ثانيًا، يمكن للمكوِّن الابن الآن تعديل قيمة المتغير filter
الخاص بالمكون الأب، لذلك لم نعد بحاجة إلى الخاصية onclick
، لذا عدّل القسم <script>
الخاص بالمكوِّن FilterButton
كما يلي:
<script> export let filter = 'all' </script>
ثالثًا، جرب تطبيقك مرةً أخرى، وستظل ترى أن المرشّحات تعمل بصورة صحيحة.
إنشاء المكون Todo
سننشئ الآن المكون Todo
لتغليف كل مهمة بما في ذلك مربع الاختيار وشيفرة التعديل لتتمكّن من تعديل مهمة موجودة مسبقًا، وسيتلقى المكوِّن Todo
الكائن todo
بوصفه خاصيةً، لذا لنصرّح عن الخاصية todo
ولننقل الشيفرة البرمجية من المكوِّن Todos
، كما سنستبدل حاليًا استدعاء removeTodo
باستدعاء alert
وسنضيف هذه الوظيفة مرةً أخرى في وقت لاحق.
أنشئ ملف مكوِّن جديد components/Todo.svelte
، وضَع بعد ذلك المحتويات التالية ضمن هذا الملف:
<script> export let todo </script> <div class="stack-small"> <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={() => todo.completed = !todo.completed} checked={todo.completed} /> <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn"> Edit <span class="visually-hidden">{todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={() => alert('not implemented')}> Delete <span class="visually-hidden">{todo.name}</span> </button> </div> </div>
يجب الآن استيراد المكوِّن Todo
إلى Todos.svelte
، لذا انتقل إلى هذا الملف الآن وأضف تعليمة الاستيراد import
التالية بعد تعليمة الاستيراد الموجودة مسبقًا:
import Todo from './Todo.svelte'
يجب بعد ذلك تحديث كتلة {#each}
لتضمين المكوِّن <Todo>
لكل مهمة بدلًا من الشيفرة المنقولة إلى Todo.svelte
، ويجب تمرير كائن todo
الحالي إلى المكوِّن بوصفه خاصيةً، لذا عدّل كتلة {#each}
ضمن المكوِّن Todos.svelte
كما يلي:
<ul role="list" class="todo-list stack-large" aria-labelledby="list-heading"> {#each filterTodos(filter, todos) as todo (todo.id)} <li class="todo"> <Todo {todo} /> </li> {:else} <li>Nothing to do here!</li> {/each} </ul>
تُعرَض قائمة المهام على الصفحة، ويجب أن تعمل مربعات الاختيار (حاول تحديد أو إلغاء تحديد مربعات الاختيار، ثم لاحظ أنّ المرشحات لا تزال تعمل كما هو متوقع)، ولكن لن يُحدَّث عنوان الحالة "x out of y items completed" وفقًا لذلك لأن المكوِّن Todo
يتلقى المهام باستخدام الخاصية، لكنه لا يرسل أيّ معلومات إلى المكوِّن الأب، وسنصلح ذلك لاحقًا.
مشاركة البيانات بين المكونات: نمط الخاصيات للأسفل Props-down والأحداث للأعلى Events-up
يُعَد الموجّه bind
واضحًا جدًا ويسمح بمشاركة البيانات بين المكوِّن الأب والمكوِّن الابن، ولكن يمكن أن يكون تتبّع جميع القيم المرتبطة ببعضها بعضًا أمرًا صعبًا عندما ينمو تطبيقك بصورة أكبر وأكثر تعقيدًا، لذا يمكنك استخدام نهج مختلف هو نمط الاتصال "props-down, events-up".
يعتمد هذا النمط على المكونات الأبناء التي تتلقى البيانات من آبائها عبر الخاصيات والمكونات الآباء لتحديث حالتها من خلال معالجة الأحداث التي تطلقها المكونات الأبناء، لذا تتدفق الخاصيات للأسفل Flow Down من المكوِّن الأب إلى المكوِّن الابن وتنتشر Bubble Up الأحداث للأعلى من المكوِّن الابن إلى المكوِّن الأب، إذ ينشئ هذا النمط تدفقًا أسهل ثنائي الاتجاه للمعلومات.
لنلقِ نظرةً على كيفية إصدار أحداثنا لإعادة تطبيق وظيفة زر "الحذف Delete" المفقودة، إذ يمكن إنشاء أحداث مخصصة من خلال استخدام الأداة createEventDispatcher
التي تعيد الدالة dispatch()
التي تسمح بإصدار أحداث مخصصة، فإذا أرسلتَ حدثًا، فيجب تمرير اسم الحدث وكائن اختياري به معلومات إضافية تريد تمريرها إلى كل مستمع، كما ستكون هذه البيانات الإضافية متاحةً في الخاصية detail
لكائن الحدث.
ملاحظة: تشترك الأحداث المخصصة في إطار عمل Svelte بواجهة برمجة التطبيقات نفسها التي تستخدِمها أحداث DOM العادية، كما يمكنك نشر حدث إلى المكوِّن الأب عن طريق تحديد on:event
بدونّ أي معالج.
سنعدّل المكون Todo
لإصدار الحدث remove
عبر تمرير المهمة المحذوفة بوصفها معلومات إضافية.
أضِف أولًا الأسطر التالية إلى الجزء العلوي من القسم <script>
للمكوِّن Todo
:
import { createEventDispatcher } from 'svelte' const dispatch = createEventDispatcher()
عدّل الآن زر "الحذف Delete" في قسم شيفرة HTML بالملف نفسه ليبدو كما يلي:
<button type="button" class="btn btn__danger" on:click={() => dispatch('remove', todo)}> Delete <span class="visually-hidden">{todo.name}</span> </button>
نصدر الحدث remove
من خلال استخدام dispatch('remove', todo)
ونمرِّر المهام todo
المحذوفة بوصفها بيانات إضافية، إذ سيُستدعى المعالج باستخدام كائن الحدث المتوفر مع البيانات الإضافية المتوفرة في الخاصية event.detail
.
يجب الآن الاستماع إلى هذا الحدث من داخل الملف Todos.svelte والتصرف وفقًا لذلك، لذا ارجع إلى هذا الملف وعدّل استدعاء المكوِّن <Todo>
كما يلي:
<Todo {todo} on:remove={e => removeTodo(e.detail)} />
يتلقى معالجنا المعامِل e
(كائن الحدث) الذي يحتفظ بالمهام المحذوفة في الخاصية detail
.
إذا حاولت تجربة تطبيقك مرةً أخرى الآن، فسترى أنّ وظيفة الحذف تعود للعمل، وبذلك نجح حدثنا المخصّص كما توقعنا، كما يرسل مستمع الحدث remove
تغيّر البيانات إلى المكوِّن الأب، لذلك سيُحدَّث عنوان الحالة "x out of y items completed" بصورة مناسبة عند حذف المهام.
سنهتم الآن بالحدث update
بحيث يمكن إعلام المكوِّن الأب بأيّ مهام مُعدَّلة.
تحديث المهام
لا يزال يتعين علينا تنفيذ الوظيفة للسماح بتعديل المهام الحالية، إذ يجب تضمين وضع التعديل في المكوِّن Todo
، كما سنعرض حقل الإدخال <input>
عند الدخول في وضع التعديل للسماح بتعديل اسم المهمة الحالي مع زرين لتأكيد التغييرات أو إلغائها.
معالجة الأحداث
أولًا، سنحتاج متغيرًا واحدًا لتتبّع ما إذا كنا في وضع التعديل أم في وضع آخر لتخزين اسم المهمة المُعدَّلة، لذا أضِف تعريفات المتغيرات التالية في الجزء السفلي من القسم <script>
للمكوِّن Todo
:
let editing = false // تتبّع نمط التعديل let name = todo.name // تخزين اسم المهمة المُعدَّلة
يجب أن نقرِّر ما هي الأحداث التي سيصدرها المكوِّن Todo
كما يلي:
-
يمكننا إصدار أحداث مختلفة لتبديل الحالة وتعديل الاسم مثل
updateTodoStatus
وupdateTodoName
. -
أو يمكننا اتباع نهج أعم وإصدار حدث
update
واحد لكلتا العمليتين.
سنتخذ النهج الثاني لنتمكن من إظهار طريقة مختلفة، إذ تتمثل ميزة هذا النهج في أنه يمكننا لاحقًا إضافة المزيد من الحقول إلى المهام مع إمكانية معالجة جميع التحديثات باستخدام الحدث نفسه، فلننشئ الدالة update()
التي ستتلقى التغييرات وتصدر حدث تحديث مع المهام المُعدَّلة، لذا أضف ما يلي مرةً أخرى إلى الجزء السفلي من القسم <script>
:
function update(updatedTodo) { todo = { ...todo, ...updatedTodo } // تطبيق تعديلات على المهمة dispatch('update', todo) // إصدار حدث التحديث }
استخدمنا صيغة الانتشار Spread Syntax لإعادة المهمة الأصلية مع التعديلات المُطبَّقة عليها.
سننشئ بعد ذلك دوالًا مختلفةً للتعامل مع كل إجراء للمستخدِم، إذ يمكن للمستخدِم حفظ التغييرات أو إلغائها عندما تكون المهمة في وضع التعديل، ويمكن للمستخدِم حذف المهمة أو تعديلها أو تبديل حالتها بين الحالة المكتملة والنشطة عندما لا تكون في وضع التعديل، لذا أضف مجموعة الدوال التالية بعد آخر دالة للتعامل مع هذه الإجراءات:
function onCancel() { name = todo.name // إعادة المتغير name إلى قيمته الأولية editing = false // والخروج من وضع التعديل } function onSave() { update({ name: name }) // تحديث اسم المهمة editing = false // والخروج من وضع التعديل } function onRemove() { dispatch('remove', todo) // إصدار حدث الحذف } function onEdit() { editing = true // الدخول في وضع التعديل } function onToggle() { update({ completed: !todo.completed}) // تحديث حالة المهمة }
تحديث ملف شيفرة HTML
يجب الآن تحديث شيفرة HTML الخاصة بالمكون Todo
لاستدعاء الدوال السابقة عند اتخاذ الإجراءات المناسبة، إذ يمكنك التعامل مع وضع التعديل من خلال استخدام المتغير editing
الذي له قيمة منطقية، فإذا كانت قيمة هذا المتغير true
، فيجب أن يُعرَض حقل الإدخال <input>
لتعديل اسم المهمة وزرَّي "الإلغاء Cancel" و"الحفظ Save"؛ أما إذا لم تكن في وضع التعديل، فسيُعرَض مربع الاختيار واسم المهمة وأزرار تعديل المهام وحذفها.
يمكن تحقيق ذلك من خلال استخدام كتلة if
التي تصيّر شيفرة HTML شرطيًا، ولكن ضع في الحسبان أنها لن تظهِر أو تخفي شيفرة HTML بناءً على شرط معيّن، وإنما ستضيف وتزيل عناصر نموذج DOM ديناميكيًا اعتمادًا على هذا الشرط.
إذا كانت قيمة المتغير editing
هي true
مثلًا، فسيعرض إطار عمل Svelte نموذج التحديث؛ أما إذا كانت قيمته false
، فسيزيله من نموذج DOM وسيضيف مربع الاختيار، لذا سيكون تعيين قيمة المتغير editing
كافيًا لعرض عناصر HTML الصحيحة بفضل خاصية التفاعل في إطار عمل Svelte.
ستكون كتلة if
كما يلي:
<div class="stack-small"> {#if editing} <!-- markup for editing to-do: label, input text, Cancel and Save Button --> {:else} <!-- markup for displaying to-do: checkbox, label, Edit and Delete Button --> {/if} </div>
يمثِّل الجزء {:else}
أو النصف السفلي من كتلة if
قسم عدم التعديل، كما سيكون مشابهًا جدًا للقسم الموجود في المكوِّن Todos
، ولكن الاختلاف الوحيد بينهما هو أننا نستدعي الدوال onToggle()
و onEdit()
و onRemove()
اعتمادًا على إجراء المستخدِم.
{:else} <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={onToggle} checked={todo.completed} > <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn" on:click={onEdit}> Edit<span class="visually-hidden"> {todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={onRemove}> Delete<span class="visually-hidden"> {todo.name}</span> </button> </div> {/if} </div>
تجدر الإشارة إلى ما يلي:
-
ننفّذ الدالة
onEdit()
التي تضبط المتغيرediting
على القيمةtrue
عندما يضغط المستخدِم على زر "التعديل Edit". -
نستدعي الدالة
onToggle()
التي تنفذ الدالةupdate()
من خلال تمرير كائن مع قيمةcompleted
الجديدة بوصفه معامِلًا عندما ينقر المستخدِم على مربع الاختيار. -
تصدِر الدالة
update()
الحدثupdate
من خلال تمرير نسخة من المهمة الأصلية مع التغييرات المطبَّقة بوصفها معلومات إضافية. -
أخيرًا، تصدِر الدالة
onRemove()
الحدثremove
من خلال تمرير المهمةtodo
المراد حذفها بوصفها بيانات إضافية.
ستحتوي واجهة المستخدِم الخاصة بالتعديل -أي النصف العلوي- على حقل الإدخال <input>
وزرين لإلغاء التغييرات أو حفظها كما يلي:
<div class="stack-small"> {#if editing} <form on:submit|preventDefault={onSave} class="stack-small" on:keydown={e => e.key === 'Escape' && onCancel()}> <div class="form-group"> <label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label> <input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" /> </div> <div class="btn-group"> <button class="btn todo-cancel" on:click={onCancel} type="button"> Cancel<span class="visually-hidden">renaming {todo.name}</span> </button> <button class="btn btn__primary todo-edit" type="submit" disabled={!name}> Save<span class="visually-hidden">new name for {todo.name}</span> </button> </div> </form> {:else} [...]
إذا ضغط المستخدِم على زر "التعديل Edit"، فسيُضبَط المتغير editing
على القيمة true
وسيزيل إطار عمل Svelte شيفرة HTML في الجزء {:else}
من نموذج DOM وسيستبدله بشيفرة HTML الموجودة في القسم {#if}
.
ستكون الخاصية value
الخاصة بالعنصر <input>
مرتبطةً بالمتغير name
، وستستدعي أزرار إلغاء التغييرات وحفظها الدالتين onCancel()
و onSave()
على التوالي كما يلي، وقد أضفنا هاتين الدالتين سابقًا:
-
إذا استُدعيت الدالة
onCancel()
، فستُعاد الخاصيةname
إلى قيمتها الأصلية عند تمريرها بوصفها خاصيةً Prop وسنخرج من وضع التعديل عن طريق ضبط المتغيرediting
على القيمةfalse
. -
إذا استُدعيت الدالة
onSave()
، فسنشغّل الدالةupdate()
من خلال تمرير الخاصيةname
المُعدَّلة، وسنخرج من وضع التعديل.
كما نعطّل زر "الحفظ Save" عندما يكون حقل الإدخال <input>
فارغًا باستخدام السمة disabled={!name}
، كما نسمح للمستخدِم بإلغاء التعديل باستخدام المفتاح Escape
كما يلي:
on:keydown={e => e.key === 'Escape' && onCancel()}.
كما نستخدِم الخاصية todo.id
لإنشاء معرّفات فريدة لعناصر التحكم بحقل الإدخال والتسميات Labels الجديدة.
تبدو شيفرة HTML المعدَّلة الكاملة للمكون Todo
كما يلي:
<div class="stack-small"> {#if editing} <!-- markup for editing todo: label, input text, Cancel and Save Button --> <form on:submit|preventDefault={onSave} class="stack-small" on:keydown={e => e.key === 'Escape' && onCancel()}> <div class="form-group"> <label for="todo-{todo.id}" class="todo-label">New name for '{todo.name}'</label> <input bind:value={name} type="text" id="todo-{todo.id}" autoComplete="off" class="todo-text" /> </div> <div class="btn-group"> <button class="btn todo-cancel" on:click={onCancel} type="button"> Cancel<span class="visually-hidden">renaming {todo.name}</span> </button> <button class="btn btn__primary todo-edit" type="submit" disabled={!name}> Save<span class="visually-hidden">new name for {todo.name}</span> </button> </div> </form> {:else} <!-- markup for displaying todo: checkbox, label, Edit and Delete Button --> <div class="c-cb"> <input type="checkbox" id="todo-{todo.id}" on:click={onToggle} checked={todo.completed} > <label for="todo-{todo.id}" class="todo-label">{todo.name}</label> </div> <div class="btn-group"> <button type="button" class="btn" on:click={onEdit}> Edit<span class="visually-hidden"> {todo.name}</span> </button> <button type="button" class="btn btn__danger" on:click={onRemove}> Delete<span class="visually-hidden"> {todo.name}</span> </button> </div> {/if} </div>
ملاحظة: يمكننا أيضًا تقسيم هذا المكوِّن إلى مكوِّنين مختلفين أحدهما لتعديل المهام والآخر لعرضها، إذ يتلخص الأمر في مدى شعورك بالراحة في التعامل مع هذا المستوى من التعقيد باستخدام مكوِّن واحد، لذا يجب التفكير فيما إذا كان تقسيمه سيمكّنك أكثر من إعادة استخدام هذا المكوِّن في سياق مختلف.
يجب معالجة الحدث update
من المكوِّن Todos
لتشغيل وظيفة التحديث، لذا أضف معالِج الأحداث التالي في القسم <script>
:
function updateTodo(todo) { const i = todos.findIndex(t => t.id === todo.id) todos[i] = { ...todos[i], ...todo } }
نجد المهمة todo
باستخدام معرِّفها id
في مصفوفة المهام todos
ونحدّث محتواها باستخدام صيغة الانتشار، وقد كان بإمكاننا أيضًا استخدام todos[i] = todo
في هذه الحالة، ولكن هذا التطبيق أفضل، مما يسمح للمكوِّن Todo
بإعادة الأجزاء المُعدَّلة فقط من المهام.
يجب بعد ذلك الاستماع إلى الحدث update
في استدعاء المكون <Todo>
، وتشغيل الدالة updateTodo()
عند حدوث ذلك لتغيير المتغير name
والحالة completed
، لذا عدّل استدعاء المكوِّن <Todo>
كما يلي:
{#each filterTodos(filter, todos) as todo (todo.id)} <li class="todo"> <Todo {todo} on:update={e => updateTodo(e.detail)} on:remove={e => removeTodo(e.detail)} /> </li>
جرب تطبيقك مرةً أخرى وسترى أنه يمكنك حذف وإضافة وتعديل وإلغاء تعديل وتبديل حالة اكتمال المهام، وسيُعدَّل عنوان الحالة "x out of y items completed" بطريقة مناسبة عند اكتمال المهام.
يُعَدّ تطبيق نمط "props-down, events-up" في إطار عمل Svelte سهلًا، ولكن يمكن أن يكون الموجّه bind
اختيارًا جيدًا للمكونات البسيطة، وسيتيح لك إطار Svelte الاختيار.
ملاحظة: يوفِّر إطار Svelte آليات أكثر تقدمًا لمشاركة المعلومات بين المكونات، وهي واجهة Context API والمخازن Stores، إذ توفِّر Context API آليةً للمكونات وأحفادها للتواصل مع بعضها البعض دون تمرير البيانات والدوال بوصفها خاصيات، أو إرسال الكثير من الأحداث، في حين تتيح المخازن Stores مشاركة البيانات التفاعلية بين المكونات غير المرتبطة بطريقة هرمية.
يمكنك الوصول إلى نسختك من مستودعنا على النحو التالي لمعرفة حالة الشيفرة كما يجب أن تكون في نهاية هذا المقال:
cd mdn-svelte-tutorial/05-advanced-concepts
أو يمكنك تنزيل محتوى المجلد مباشرةً باستخدام الأمر التالي:
npx degit opensas/mdn-svelte-tutorial/05-advanced-concepts
تذكر تشغيل الأمر npm install && npm run dev
لبدء تشغيل تطبيقك في وضع التطوير، فإذا أردت متابعتنا فابدأ بكتابة الشيفرة باستخدام الأداة REPL.
الخلاصة
أضفنا جميع الوظائف المطلوبة لتطبيقنا، إذ يمكننا عرض المهام وإضافتها وتعديلها وحذفها وتمييزها على أنها مكتملة وترشيحها حسب الحالة، كما غطينا في هذا المقال المواضيع التالية:
- استخراج وظائف مكوِّن جديد.
- تمرير المعلومات من المكوِّن الابن إلى المكوِّن الأب باستخدام معالج يُستقبَل بوصفه خاصيةً.
-
تمرير المعلومات من المكوِّن الابن إلى المكوِّن الأب باستخدام الموجّه
bind
. -
عرض كتل شيفرة HTML المشروطة باستخدام كتلة
if
. - تطبيق نمط الاتصال "props-down, events-up".
- إنشاء الأحداث المخصصة والاستماع إليها.
سنواصل في المقال التالي من جزئية svelte من هذه السلسلة تقسيم تطبيقنا إلى مكونات ونتعرف على بعض التقنيات المتقدمة للعمل مع نموذج DOM.
ترجمة -وبتصرُّف- للمقال Componentizing our Svelte app.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.