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

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

  • المتطلبات الأساسية: الإلمام بأساسيات لغات HTML وCSS وجافاسكربت JavaScript، ومعرفة استخدام سطر الأوامر أو الطرفية، إذ يُعَدّ فهم ميزات جافاسكربت الحديثة مثل الأصناف Classes، والوحدات Modules، وما إلى ذلك مفيدًا للغاية لأن إطار العمل Ember يستخدمها بكثرة.
  • الهدف: معرفة كيفية إنشاء أصناف المكوّنات واستخدام الأحداث للتحكم في التفاعل وتتبع حالة التطبيق باستخدام خدمة.

إضافة التفاعل

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

إنشاء المهام

نريد أن نتمكن من إرسال المهمة المكتوبة في حقل إدخال المهام عندما نضغط على مفتاح Enter لتظهر في قائمة المهام، ويجب أن نكون قادرين على التقاط النص المكتوب في حقل الإدخال لتعرف شيفرة جافاسكربت ما كتبناه، كما يمكننا حفظ مهامنا وتمرير هذا النص إلى مكوّن قائمة المهام لعرضه، ويمكننا التقاط الحدث keydown باستخدام المُعدِّل on الذي يُعَدّ صيغةً مبسَّطةً في Ember للتابعَين addEventListener و removeEventListener.

أضف السطر الجديد الموضَّح أدناه إلى الملف header.hbs:

<input
  class='new-todo'
  aria-label='What needs to be done?'
  placeholder='What needs to be done?'
  autofocus
  {{on 'keydown' this.onKeyDown}}
>

توضَع هذه السمة الجديدة بين أقواس مزدوجة معقوصة، ويدل ذلك على أنها جزء من صيغة قالب آلي في إطار عمل Ember، فالوسيط الأول المُمرَّر إلى on هو نوع الحدث الذي يجب الاستجابة له keydown، والوسيط الأخير هو معالِج الحدث أي الشيفرة المُشغَّلة استجابةً لإطلاق الحدث keydown، في حين تشير الكلمة المفتاحية this إلى سياق Context أو نطاق Scope المكوّن الذي سيختلف بين مكوّن وآخر.

يمكننا تحديد ما هو متاح ضمن this من خلال إنشاء صنف مكوّن ليتماشى مع مكوّنك، وهو صنف بلغة جافاسكربت الصرفة Vanilla JavaScript وليس له معنًى خاصًا في إطار عمل Ember باستثناء وراثة أو توسعة الصنف الأب Component.

يمكنك إنشاء الصنف header ليتوافق مع مكوّن الترويسة من خلال كتابة الأمر التالي في الطرفية:

ember generate component-class header

مما يؤدي إلى إنشاء ملف الصنف الفارغ التالي في الملف todomvc/app/components/header.js:

import Component from '@glimmer/component';

export default class HeaderComponent extends Component {

}

سننفّذ في هذا الملف شيفرة معالج الحدث، لذلك عدّل محتواه إلى ما يلي:

import Component from '@glimmer/component';
import { action } from '@ember/object';

export default class HeaderComponent extends Component {

  @action
  onKeyDown({ target, key }) {
    let text = target.value.trim();
    let hasValue = Boolean(text);

    if (key === 'Enter' && hasValue) {
      alert(text);

      target.value = ''
    }
  }
}

يُعَدّ المزخرِف ‎@action الشيفرة الوحيدة الخاصة بإطار العمل Ember هنا بغض النظر عن وراثة الصنف الأب Component والعناصر الخاصة بإطار العمل Ember التي نستوردها باستخدام صيغة وحدة جافاسكربت، بينما باقي الملف مكتوب بلغة جافاسكربت الصرفة Vanilla JavaScript ويمكن أن يعمل في أيّ تطبيق آخر، كما يصرِّح المزخرِف ‎@action عن أن الدالة هي إجراء action، مما يعني أنها نوع من الدوال التي ستُستدعَى من حدث وقع في القالب، كما أنّ ‎@action يربط this الخاص بالدالة بنسخة من الصنف.

ملاحظة: يُعَدّ المزخرِف Decorator دالة تغليف تغلِّف وتستدعي دوالًا أو خاصيات أخرى مما يوفر وظائفًا إضافيةً، إذ يشغّل المزخرِف ‎@tracked مثلًا الشيفرة المُطبَّقة عليه، ويتتبّعه ويحدّث التطبيق تلقائيًا عند تغيير القيم.

عُد إلى تبويب متصفحك مع تشغيل التطبيق، إذ يمكننا كتابة ما نريد وستظهر رسالة تنبيه تخبرنا بما كتبناه بالضبط عندما نضغط على مفتاح Enter.

01_todos-hello-there-alert.png

نحتاج الآن لمكان لتخزين المهام لتتمكن المكوّنات الأخرى من الوصول إليها.

تخزين المهام باستخدام خدمة

يملك إطار عمل Ember إدارة حالة مبنية مسبقًا على مستوى التطبيق يمكننا استخدامها لإدارة تخزين مهامنا، والسماح لكل مكوّن من مكوّناتنا الوصول إلى البيانات من تلك الحالة على مستوى التطبيق، إذ يستدعي إطار Ember خدمات البناء التي تبقى قيد التشغيل طوال عُمر الصفحة، إذ سيؤدي تحديث الصفحة إلى مسحها.

شغّل الأمر التالي في الطرفية لإنشاء خدمة لتخزين بيانات قائمة المهام:

ember generate service todo-data

يعطي تشغيل الأمر السابق خرجًا يشبه الخرج التالي:

installing service
  create app/services/todo-data.js
installing service-test
  create tests/unit/services/todo-data-test.js

مما يؤدي إلى إنشاء الملف todo-data.js ضمن المجلد todomvc/app/services لاحتواء خدمتنا، كما يحتوي هذا الملف في البداية على عبارة استيراد وصنف فارغ كما يلي:

import Service from '@ember/service';

export default class TodoDataService extends Service {

}

يجب أولًا تتبّع كل من نص المهمة وما إذا كانت مكتملة أم لا، لذا أضف تعليمة الاستيراد التالية بعد تعليمة الاستيراد الموجودة مسبقًا:

import { tracked } from '@glimmer/tracking';

أضف الصنف التالي بعد السطر السابق الذي أضفته:

class Todo {
  @tracked text = '';
  @tracked isCompleted = false;

  constructor(text) {
    this.text = text;
  }
}

يمثل الصنف السابق مهمَّة، لأنه يحتوي على الخاصية ‎@tracked text التي تحتوي على نص المهمَّة، ويحتوي على الخاصية ‎@tracked isCompleted التي تحدِّد ما إذا كانت المهام مكتملةً أم لا، فإذا أنشأنا نسخةً من هذا الصنف، فسيكون للكائن Todo قيمة text أولية تساوي النص المعطَى له عند إنشائه، وتُعطَى الخاصية isCompleted القيمة false.

الجزء الوحيد الخاص بإطار عمل Ember من هذا الصنف هو المزخرِف ‎@tracked الذي يرتبط بنظام التفاعل ويسمح لإطار Ember بتحديث ما تراه في تطبيقك تلقائيًا إذا تغيرت الخاصيات المُتتبَّعة tracked، وحان الوقت الآن لإضافة شيء ما إلى جسم الخدمة، لذا أضِف أولًا تعليمة استيراد import أخرى بعد التعليمة السابقة لإتاحة الإجراءات actions ضمن الخدمة:

import { action } from '@ember/object';

عدّل الكتلة export default class TodoDataService extends Service { … }‎ كما يلي:

export default class TodoDataService extends Service {
  @tracked todos = [];

  @action
  add(text) {
    let newTodo = new Todo(text);

    this.todos = [...this.todos, newTodo];
  }
}

ستحتفظ الخاصية todos في الخدمة بقائمة مهامنا الموجودة ضمن مصفوفة، وسنميّزها بالمزخرِف ‎@tracked لأننا نريد تحديث واجهة المستخدِم أيضًا عند تحديث قيمة الخاصية todos.

يُضاف المزخرِف ‎@action إلى الدالة add()‎ التي يستدعيها القالب لربطها بنسخة الصنف. يمكن أن يكون ذلك مألوفًا في لغة جافاسكربت، ولكن لاحظ استدعاء التابع pushObject()‎ في المصفوفة todos، والسبب أن إطار عمل Ember يوسّع نموذج المصفوفات في لغة جافاسكربت افتراضيًا، مما يمنحنا توابعًا ملائمة لضمان معرفة نظام التعقّب في إطار Ember بهذه التغييرات. هناك العشرات من هذه التوابع مثل pushObject()‎ أو insertAt()‎ أو popObject()‎ التي يمكن استخدامها مع أيّ نوع وليس مع الكائنات فقط. يمنحنا التابع ArrayProxy الخاص بإطار عمل Ember توابعًا سهلة الاستخدام مثل isAny()‎ وfindBy()‎ وfilterBy()‎ لتسهيل الأمور.

استخدام الخدمة من مكون الترويسة

يمكننا الآن بعد أن تحديد طريقة لإضافة المهام التفاعل مع هذه الخدمة من مكوّن الإدخال في الملف header.js لبدء إضافتها فعليًا، إذ يجب أولًا حقن الخدمة في القالب باستخدام المزخرِف ‎@inject الذي سنعيد تسميته إلى ‎@service.

أضف تعليمة الاستيراد التالية إلى الملف header.js بعد تعليمتَي الاستيراد الموجودتَين مسبقًا:

import { inject as service } from '@ember/service';

يمكننا الآن توفير الخدمة todo-data ضمن الصنف HeaderComponent عبر الكائن todos باستخدام المزخرِف ‎@service، لذلك أضف السطر التالي بعد سطر التصدير export‎ الأول مباشرةً:

@service('todo-data') todos;

يمكن الآن استبدال سطر النص البديل alert(text);‎ باستدعاء الدالة add()‎ الجديدة كما يلي:

this.todos.add(text);

إذا جربنا ذلك في تطبيق المهام في متصفحنا من خلال كتابة الأمر npm start ثم الانتقال إلى المضيف المحلي localhost:4200، فلن يحدث أيّ شيء بعد الضغط على مفتاح Enter، إذ يُعَدّ بناء التطبيق بدون أيّ أخطاء علامةً جيدةً، ولكن يمكننا رؤية إضافة مهامنا باستخدام الفاحص Ember Inspector كما يلي:

02_todos-in-ember-inspector.gif

عرض المهام

يجب أن تكون هناك طريقة لوضع المهام التي ننشئها فعليًا مكان مهامنا الساكنة "Buy Movie Tickets"، إذ يجب في المكوّن TodoList إخراج المهام من الخدمة وتصيير Render المكوّن Todo لكل مهمة، كما يمكن استعادة المهام من الخدمة، ولكن يحتاج المكوّن TodoList أولًا إلى صنف داعم للمكوّن لاحتواء هذه الوظيفة، لذلك اضغط على الاختصار Ctrl + C لإيقاف خادم التطوير وأدخِل الأمر التالي في الطرفية:

ember generate component-class todo-list

يؤدي ذلك إلى إنشاء صنف المكوّن الجديد في المجلد todomvc/app/components/todo-list.js.

املأ هذا الملف بالشيفرة التالية بحيث يمكن لقالبنا الوصول إلى الخدمة todo-data باستخدام الخاصية todos، ويمكن الوصول إليها باستخدام this.todos ضمن كل من الصنف والقالب:

import Component from '@glimmer/component';
import { inject as service } from '@ember/service';

export default class TodoListComponent extends Component {
  @service('todo-data') todos;
}

تتمثّل إحدى المشاكل هنا في أن الخدمة تسمَّى todos وتُسمَّى قائمة المهام todos أيضًا، لذلك يمكننا حاليًا الوصول إلى البيانات باستخدام this.todos.todos. لا يُعَد هذا أمرًا سهلًا، لذلك سنضيف تابعًا جالبًا getter إلى الخدمة this.todos.todos بالاسم all سيمثِّل جميع المهام.

ارجع إلى الملف todo-data.js وأضِف ما يلي بعد التعليمة ‎@tracked todos = [];‎:

get all() {
  return this.todos;
}

يمكننا الآن الوصول إلى البيانات باستخدام this.todos.all، إذ يُعَدّ ذلك أسهل. انتقل إلى المكوّن todo-list.hbs، وضع مكان استدعاءات المكوّن الساكنة التالية:

<Todo />
<Todo />

كتلة ‎#each الآلية، وهي صيغة مُبسطَّة من تابع جافاسكربت forEach()‎، إذ تنشئ كتلة ‎#each المكوّنَ <Todo /‎> لكل مهمة متوفرة في قائمة المهام التي يعيدها التابع الجالب all()‎ الخاص بالخدمة:

{{#each this.todos.all as |todo|}}
  <Todo @todo={{todo}} />
{{/each}}

لنتعرّف على محتويات الشيفرة السابقة:

  • this: سياق التصيير أو نسخة المكوّن.
  • todos: خاصية this التي عرّفناها في المكوّن todo-list.js باستخدام التعليمة ‎@service('todo-data') todos;‎، وهي مرجع إلى الخدمة todo-data، مما يسمح بالتفاعل مع نسخة الخدمة مباشرةً.
  • all: جالب الخدمة todo-data الذي يعيد جميع المهام.

جرّب تشغيل الخادم مرةً أخرى وانتقل إلى التطبيق، وستجده يعمل، ولكن كلما أدخلت عنصر مهمة جديد، فسيظهر عنصر قائمة جديد تحت حقل إدخال النص، وستظهر العبارة "Buy Movie Tickets" دائمًا للأسف، لأن عنوان النص في كل عنصر قائمة مضمَّنٌ في ذلك النص كما هو موضَّح في الملف todo.hbs:

<label>Buy Movie Tickets</label>

عدِّل السطر السابق كما يلي لاستخدام الوسيط ‎@todo الذي سيمثل المهمة التي مررناها إلى المكوّن عند استدعائه ضمن الملف todo-list.hbs في السطر <Todo @todo={{todo}} /‎>:

<label>{{@todo.text}}</label>

جرّبه مرةً أخرى، ويجب أن تجد الآن أنّ النص المُرسَل من حقل الإدخال <input> يظهر بصورة صحيحة في واجهة المستخدِم كما يلي:

03_todos-being-appended-with-correct-text.gif

الخلاصة

يمكننا الآن إضافة عناصر المهمات إلى تطبيقنا، كما يمكن تتبّع حالة البيانات باستخدام الخدمة، وسننتقل في المقال التالي إلى تشغيل وظائف التذييل Footer بما في ذلك عدّاد المهام، كما سنتعرّف على التصيير الشرطي وتصميم المهام بصورة صحيحة عند تحديدها، وسنفعّل زر "مسح المهام المكتملة Clear completed".

ترجمة -وبتصرّف- للمقال Ember interactivity: Events, classes and state.

اقرأ أيضًا


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

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

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



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

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

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

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


×
×
  • أضف...