١٧ مايو ٢٠٢٣

تفويض الحدث

يسمح لنا التقاط والتدفق بتنفيذ أحد أنماط معالجة الأحداث الأكثر قوة المسمى تفويض الحدث.

الفكرة هي أنه إذا كان لدينا الكثير من العناصر المعالجة بطريقة مماثلة ، فبدلاً من تعيين معالج لكل منها – نضع معالجًا واحدًا على سلفهم المشترك.

في المعالج نحصل على event.target لمعرفة المكان الذي حدث فيه الحدث فعليًا ومعالجته.

لنرى مثالًا – رسم با-غوا يعكس الفلسفة الصينية القديمة.

ها هو:

HTML مثل هذا:

<table>
  <tr>
    <th colspan="3">جدول <em>Bagua</em>: الاتجاه ، العنصر ، اللون ، المعنى</th>
  </tr>
  <tr>
    <td class="nw"><strong>الشمال الغربي</strong><br>معدن<br>فضة<br>كبار السن</td>
    <td class="n">...</td>
    <td class="ne">...</td>
  </tr>
  <tr>...2 سطرين إضافيين من هذا النوع ...</tr>
  <tr>...2 سطرين إضافيين من هذا النوع ...</tr>
</table>

الجدول يحتوي على 9 خلايا ، لكن يمكن أن يكون هناك 99 أو 9999 ، لا يهم.

مهمتنا هي تسليط الضوء على الخلية <td> عند النقر.

بدلاً من تعيين معالج onclick لكل <td> (يمكن أن يكون كثيرًا) – سنقوم بإعداد المعالج “الشامل” على عنصر <table>.

سيستخدم event.target للحصول على العنصر المُنقر عليه وتسليط الضوء عليه.

الشفرة:

let selectedTd;

table.onclick = function(event) {
 let target = event.target; // أين كان النقر؟

 if (target.tagName != 'TD') return; // ليس على TD؟ إذن لسنا مهتمين

 highlight(target); // تسليط الضوء عليه
};

function highlight(td) {
  if (selectedTd) { // إزالة التسليط الحالي إن وجد
    selectedTd.classList.remove('highlight');
  }
  selectedTd = td;
  selectedTd.classList.add('highlight'); // تسليط الضوء على td الجديد
}

لا يهتم هذا الكود بعدد الخلايا الموجودة في الجدول. يمكننا إضافة / إزالة <td> ديناميكيًا في أي وقت وسيظل التسليط يعمل.

مع ذلك ، هناك عيب.

قد يحدث النقر ليس على <td> ، ولكن داخله.

في حالتنا إذا نظرنا داخل HTML ، يمكننا رؤية علامات متداخلة داخل <td> ، مثل <strong>:

<td>
  <strong>الشمال الغربي</strong>
  ...
</td>

بطبيعة الحال ، إذا حدث نقر على هذا <strong> فإنه يصبح قيمة event.target.

في المعالج table.onclick يجب أن نأخذ هذا event.target ونعرف ما إذا كان النقر داخل <td> أم لا.

هذه هي الشفرة المحسنة:

table.onclick = function(event) {
  let td = event.target.closest('td'); // (1)

  if (!td) return; // (2)

  if (!table.contains(td)) return; // (3)

  highlight(td); // (4)
};

تفسيرات:

  1. يعيد الأسلوب elem.closest(selector) السلف الأقرب الذي يطابق المحدد. في حالتنا نبحث عن <td> في الطريق لأعلى من العنصر المصدر.
  2. إذا لم يكن event.target داخل أي <td> ، فإن الاستدعاء يعود فورًا ، لأنه لا يوجد شيء يمكن القيام به.
  3. في حالة الجداول المتداخلة ، قد يكون event.target هو <td> ، ولكن يكمن خارج الجدول الحالي. لذلك نتحقق مما إذا كان هذا هو <td> لجدولنا فعلاً.
  4. وإذا كان كذلك ، فتسلط الضوء عليه.

وبالتالي ، لدينا رمز تسليط سريع وفعال ، لا يهتم بإجمالي عدد <td> في الجدول.

مثال التفويض: الإجراءات في الترميز

هناك استخدامات أخرى لتفويض الحدث.

لنقل ، نريد عمل قائمة بأزرار “حفظ” و “تحميل” و “بحث” وما إلى ذلك. وهناك كائن به طرق save و load و search … كيفية مطابقتها؟

قد تكون الفكرة الأولى هي تعيين معالج منفصل لكل زر. لكن هناك حلاً أكثر أناقة. يمكننا إضافة معالج للقائمة بأكملها وسمات data-action للأزرار التي لديها الطريقة المطلوب استدعاؤها:

<button data-action="save">انقر للحفظ</button>

يقرأ المعالج السمة وينفذ الطريقة. ألق نظرة على المثال العامل:

<div id="menu">
  <button data-action="save">حفظ</button>
  <button data-action="load">تحميل</button>
  <button data-action="search">بحث</button>
</div>

<script>
  class Menu {
    constructor(elem) {
      this._elem = elem;
      elem.onclick = this.onClick.bind(this); // (*)
    }

    save() {
      alert('جاري الحفظ');
    }

    load() {
      alert('جاري التحميل');
    }

    search() {
      alert('جاري البحث');
    }

    onClick(event) {
      let action = event.target.dataset.action;
      if (action) {
        this[action]();
      }
    };
  }

  new Menu(menu);
</script>

يرجى ملاحظة أن this.onClick مرتبط بـ this في (*). هذا مهم ، لأنه وإلا فإن this داخله سيشير إلى عنصر DOM (elem) ، وليس كائن Menu ، ولن يكون this[action] هو ما نحتاجه.

إذن ، ما هي المزايا التي يمنحنا التفويض هنا؟

  • ليس علينا كتابة الكود لتعيين معالج لكل زر. فقط قم بعمل طريقة وضعها في الترميز.
  • هيكل HTML مرن ، يمكننا إضافة / إزالة الأزرار في أي وقت.

يمكننا أيضًا استخدام الفئات .action-save و .action-load ، ولكن السمة data-action هي أفضل من الناحية الدلالية. ويمكننا استخدامه في قواعد CSS أيضًا.

نمط “السلوك”

يمكننا أيضًا استخدام تفويض الحدث لإضافة “سلوكيات” إلى العناصر بشكل تعريفي ، مع سمات وفئات خاصة.

النمط له جزأين:

  1. نضيف سمة مخصصة إلى عنصر يصف سلوكه.
  2. يتتبع معالج على مستوى المستند الأحداث ، وإذا حدث حدث على عنصر مسمى – يؤدي الإجراء.

السلوك: العداد

على سبيل المثال ، هنا تضيف السمة data-counter سلوكًا: “زيادة القيمة عند النقر” على الأزرار:

العداد: <input type="button" value="1" data-counter>
عداد آخر: <input type="button" value="2" data-counter>

<script>
  document.addEventListener('click', function(event) {

    if (event.target.dataset.counter != undefined) { // إذا كانت السمة موجودة ...
    event.target.value++;
    }

  });
</script>

إذا قمنا بالنقر على زر – يتم زيادة قيمته. ليس الأزرار ، ولكن النهج العام مهم هنا.

يمكن أن يكون هناك العديد من السمات مع data-counter كما نريد. يمكننا إضافة جديدة إلى HTML في أي لحظة. باستخدام تفويض الحدث ، “قمنا بتوسيع” HTML ، وأضفنا سمة تصف سلوكًا جديدًا.

لمعالجات المستندات المستوى – دائمًا addEventListener

عندما نعين معالج حدث على كائن document ، يجب علينا دائمًا استخدام addEventListener ، وليس <document.on<event ، لأن الأخير سيسبب تعارضات: المعالجات الجديدة تكتب فوق القديمة.

بالنسبة للمشاريع الحقيقية ، فمن الطبيعي أن يكون هناك العديد من المعالجات على document التي تم تعيينها بواسطة أجزاء مختلفة من الكود.

السلوك: المفتاح

مثال آخر على السلوك. سيعرض / يخفي النقر على عنصر بسمة data-toggle-id العنصر بالمعرف المعطى:

<button data-toggle-id="subscribe-mail">
 أظهر نموذج الاشتراك
</button>

<form id="subscribe-mail" hidden>
 بريدك: <input type="email">
</form>

<script>
  document.addEventListener('click', function(event) {
    let id = event.target.dataset.toggleId;
    if (!id) return;

    let elem = document.getElementById(id);

    elem.hidden = !elem.hidden;
  });
</script>

لنلاحظ مرة أخرى ما فعلناه. الآن ، لإضافة وظيفة التبديل إلى عنصر – لا حاجة لمعرفة JavaScript ، فقط استخدم السمة data-toggle-id.

قد يصبح ذلك مريحًا حقًا – لا حاجة لكتابة JavaScript لكل عنصر من هذا النوع. فقط استخدم السلوك. يجعل المعالج على مستوى المستند عمله لأي عنصر في الصفحة.

يمكننا دمج سلوكيات متعددة على عنصر واحد أيضًا.

يمكن أن يكون نمط “السلوك” بديلاً للشظايا الصغيرة من JavaScript.

ملخص

تفويض الحدث رائع حقًا! إنه واحد من أكثر الأنماط المفيدة لأحداث DOM.

غالبًا ما يتم استخدامه لإضافة نفس المعالجة للعديد من العناصر المتشابهة ، ولكن ليس فقط لذلك.

الخوارزمية:

  1. ضع معالج واحد على الحاوية.
  2. في المعالج – تحقق من عنصر المصدر event.target.
  3. إذا حدث الحدث داخل عنصر يهمنا ، فقم بمعالجة الحدث.

الفوائد:

  • يبسط التهيئة ويوفر الذاكرة: لا حاجة لإضافة العديد من المعالجات.
  • أقل كود: عند إضافة أو إزالة عناصر ، لا حاجة لإضافة / إزالة المعالجات.
  • تعديلات DOM: يمكننا إضافة / إزالة عناصر بكتلة مع innerHTML وما شابه.

بالطبع للتفويض قيوده:

  • أولاً ، يجب أن يكون الحدث فقاعيًا. بعض الأحداث لا تتدفق. أيضًا ، يجب على المعالجات المنخفضة المستوى عدم استخدام ()event.stopPropagation.
  • ثانيًا ، قد يضيف التفويض حملًا على وحدة المعالجة المركزية ، لأن معالج المستوى الحاوية يتفاعل مع الأحداث في أي مكان من الحاوية ، بغض النظر عما إذا كانت تهمنا أم لا. ولكن عادة ما يكون الحمل ضئيلًا ، لذلك لا نأخذه في الاعتبار.

مهمه

الأهمية: 5

هناك قائمة بالرسائل مع أزرار إزالة [x]. اجعل الأزرار تعمل.

مثل هذا:

ملاحظة: يجب أن يكون هناك مستمع واحد فقط للحدث على الحاوية ، استخدم تفويض الحدث.

افتح sandbox للمهمه.

الأهمية: 5

قم بإنشاء شجرة تعرض / تخفي عقد الأطفال عند النقر:

المتطلبات:

  • معالج واحد فقط للحدث (استخدم التفويض)
  • يجب ألا يؤدي النقر خارج عنوان العقدة (على مساحة فارغة) إلى أي شيء.

افتح sandbox للمهمه.

الحل له جزأين.

  1. قم بلف كل عنوان عقدة شجرة في <span>. ثم يمكننا تصميم CSS عليها على :hover والتعامل مع النقرات بالضبط على النص ، لأن عرض <span> هو بالضبط عرض النص (على عكس بدونه).
  2. قم بتعيين معالج لعقدة الجذر tree وتعامل مع النقرات على تلك العناوين <span>.

افتح الحل في sandbox.

الأهمية: 4

اجعل الجدول قابلًا للفرز: يجب أن تقوم النقرات على عناصر <th> بفرزه حسب العمود المقابل.

كل <th> لديه النوع في السمة ، مثل هذا:

<table id="grid">
  <thead>
    <tr>
      <th data-type="number">العمر</th>
      <th data-type="string">الاسم</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>5</td>
      <td>جون</td>
    </tr>
    <tr>
      <td>10</td>
      <td>آن</td>
    </tr>
    ...
  </tbody>
</table>

في المثال أعلاه ، يحتوي العمود الأول على أرقام ، والثاني – على سلاسل. يجب أن تتعامل وظيفة الفرز مع الترتيب وفقًا للنوع.

يجب دعم أنواع "string" و "number" فقط.

المثال العامل:

ملاحظة: يمكن أن يكون الجدول كبيرًا ، بأي عدد من الصفوف والأعمدة.

افتح sandbox للمهمه.

الأهمية: 5

قم بإنشاء كود JS لسلوك التلميح.

عندما يأتي الماوس فوق عنصر مع data-tooltip ، يجب أن يظهر التلميح فوقه ، وعندما يذهب يختفي.

مثال على HTML المشروح:

<button data-tooltip="التلميح أطول من العنصر">زر قصير</button>
<button data-tooltip="تلميح HTML<br>">زر آخر</button>

يجب أن يعمل هكذا:

في هذه المهمة نفترض أن جميع العناصر مع data-tooltip لديها نص فقط بداخلها. لا توجد علامات متداخلة (بعد).

التفاصيل:

  • يجب أن يكون المسافة بين العنصر والتلميح 5px.
  • يجب أن يكون التلميح مركزًا بالنسبة للعنصر ، إذا كان ذلك ممكنًا.
  • يجب ألا يتجاوز التلميح حواف النافذة. عادةً ما يكون فوق العنصر ، ولكن إذا كان العنصر في أعلى الصفحة ولا يوجد مساحة للتلميح ، فأدناه.
  • يتم إعطاء محتوى التلميح في سمة data-tooltip. يمكن أن يكون HTML تعسفيًا.

ستحتاج إلى حدثين هنا:

  • يُطلق الحدث mouseover عندما يأتي مؤشر فوق عنصر.
  • يُطلق الحدث mouseout عندما يغادر المؤشر عنصرًا.

يرجى استخدام تفويض الحدث: قم بإعداد معالجين على document لتتبع جميع “overs” و “outs” من العناصر مع data-tooltip وإدارة التلميحات من هناك.

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

ملاحظة: قد يظهر تلميح واحد فقط في وقت واحد.

افتح sandbox للمهمه.

خريطة الدورة التعليمية

التعليقات

إقرأ هذا قبل أن تضع تعليقًا…
  • إذا كان لديك اقتراحات أو تريد تحسينًا - من فضلك من فضلك إفتح موضوعًا فى جيتهاب أو شارك بنفسك بدلًا من التعليقات.
  • إذا لم تستطع أن تفهم شيئّا فى المقال - وضّح ماهو.
  • إذا كنت تريد عرض كود استخدم عنصر <code> ، وللكثير من السطور استخدم <pre>، ولأكثر من 10 سطور استخدم (plnkr, JSBin, codepen…)