تعلّم jquery أحداث jQuery وكيفيّة تفويضها


محمد فوّاز عرابي

تُسهّل jQuery الاستجابة لتفاعل المُستخدم مع صفحات الويب. معنى هذا أنّ بإمكانك تنفيذ أمرٍ ما عندما ينقر المُستخدم على جزءٍ مُعيّن من الصّفحة، أو عندما يُحرّك مؤشّر الفأرة فوق عنصر في نموذج مثلاً. في المثال التّالي، لدينا أمر يُنفَّذ عندما ينقر المُستخدم فوق أيّ عنصر قائمة في الصّفحة:

$( 'li' ).click(function( event ) {
  console.log( 'clicked', $( this ).text() );
});

جرّب المثال في ساحة التّجربة  (تأكد من ضغط الزرّ Run with JS في هذ المثال وكلّ الأمثلة التّالية)

يُحدّد النّصّ السّابق كلّ عناصر القائمة ويُسند إليها دالّة تتولّى حدث النّقر على كلّ عنصر، باستخدام وظيفة ‎.click()‎ في jQuery.

توفّر jQuery وظائف مُختصرةً عديدةً لربط الأحداث، وكلّ من هذه الوظائف يوافق حدث DOM أصليًّا:

اسم الحدث الأصليّ                    الوظيفة المُختصرة
‏click‎.click()
‏keydown.keydown()
‏keypress.keypress()
‏keyup.keyup()
‏mouseover.mouseover()
‏mouseout.mouseout()
‏mouseenter.mouseenter()
‏mouseleave.mouseleave()
‏scroll.scroll()
‏focus.focus()
‏blur.blur()
‏resize.resize()

تستخدم هذه الوظائف المُختصرة الوظيفة ‎.on()‎ ما وراء الكواليس، وهي وظيفة يمكنك استخدامها بنفسك لمرونةٍ أكبر. وعند استخدامك لها، فإنّك تُمرّر اسم الحدث الأصليّ كمُعامل أوّل للوظيفة، ثمّ دالّة تتولى الحدث كمعامل ثانٍ:

$( 'li' ).on( 'click', function( event ) {
  console.log( 'clicked', $( this ).text() );
});

جرّب المثال في ساحة التّجربة

ما إن "تربط" مُتولّي الحدث بعنصرٍ من العناصر، فبإمكانك إثارة هذا الحدث بـjQuery أيضًا:

$( 'li' ).trigger( 'click' );

وإن كان للحدث الّذي تُريد إثارته وظيفةٌ مختصرة (كما ورد في الجدول السّابق)، فبإمكانك إثارة الحدث باستدعاء الوظيفة المختصرة ذاتها:

$( 'li' ).click();

ملاحظة: عندما تُثير حدثًا باستخدام ‎.trigger()‎، فإنّك تستدعي مُتولّيات الأحداث الّتي أُنشئت في JavaScript فقط ولا تستدعي السّلوك الافتراضيّ للحدث. فمثلًا إن أثرت حدث النّقر على رابط (عنصر <a>) فلن ينتقل المتصفّح إلى الرّابط المُسند إليه في صفة href (مع أنّ بإمكانك كتابة أوامر تُنفّذ هذه الغاية).

بعد أن تربط حدثًا بُعنصر، بإمكانك فكّ هذا الارتباط باستخدام الوظيفة ‎.off()‎ الّتي تُزيل أية مُتولّيات ارتبطت بهذا الحدث:

$( 'li' ).off( 'click' );

حصر الأحداث ضمن فضاء أسماء

من المزايا الّتي تُتيحها ‎.on()‎ إمكانيّة حصر الأحداث ضمن "فضاء أسماء". قد تتساءل عن الحاجة لذلك. افترض مثلًا أنّك تريد ربط بعض الأحداث بعنصر ما، ثمّ إزالة بعض المُتولّيات، يمكنك أن تفعل ما فعلناه في الفقرة الماضية:

تحذير: أسلوب برمجيّ غير مُفضّل

$( 'li' ).on( 'click', function() {
  console.log( 'a list item was clicked' );
});

$( 'li' ).on( 'click', function() {
  registerClick();
  doSomethingElse();
});

$( 'li' ).off( 'click' );

إلّا أنّ هذا الأسلوب سيُزيل كلّ مُتولّيات النقر على كلّ عناصر القوائم، وليس هذا ما نريد. إذا ربطت متولّيًا للأحداث محصورًا ضمن فضاء أسماء، فبإمكانك استهدافه بدقّة:

$( 'li' ).on( 'click.logging', function() {
  console.log( 'a list item was clicked' );
});

$( 'li' ).on( 'click.analytics', function() {
  registerClick();
  doSomethingElse();
});

$( 'li' ).off( 'click.logging' );

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

بإمكاننا استخدام ميزة حصر الأحداث لإثارة أحداثٍ مُحدّدة:

$( 'li' ).trigger( 'click.logging' );

ربط أحداث مُتعدّدة في وقت واحد

ميزة أخرى تُوفّرها ‎.on()‎، وهي إمكانيّة ربط أحداث مُتعدّدة في وقتٍ واحد. افترض مثلًا أنّك تريد تنفيذ أمرٍ مُعيّن عندما يُمرّر المستخدم الصّفحة أو يغيّر قياس النّافذة. فهذه الوظيفة تُتيح لك تمرير الحدثين معًا مفصولين بمسافة في سلسلة نصيّة، يتبعهما الدّالّة الّتي تريد أن تتولّى الحدثين:

$( 'input[type="text"]' ).on('focus blur', function() {
  console.log( 'The user focused or blurred the input' );
});

$( window ).on( 'resize.foo scroll.bar', function() {
  console.log( 'The window has been resized or scrolled!' );
});

جرّب المثال في ساحة التّجربة

تمرير دوالّ مُسمّاة كُمتولّيات الأحداث

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

var handleClick = function() {
  console.log( 'something was clicked' );
};

$( 'li' ).on( 'click', handleClick );
$( 'h1' ).on( 'click', handleClick );

كائن الحدث

في كلّ مرّة يُثار فيها حدثٌ ما، تستقبل الدّالّة المُتولّية للحدث مُعاملًا واحدًا، وهو كائن الحدث الّذي يتبع معايير مُتّفقًا عليها بين كلّ المُتصفّحات. ولهذا الكائن خصائص مُفيدة كثيرة، منها:

$( document ).on( 'click', function( event ) {
  console.log( event.type );    // نوع الحدث، مثل: "click"
  console.log( event.which );   // الزرّ أو المفتاح الّذي ضغط
  console.log( event.target );  // العنصر الّذي انطلق منه الحدث
  console.log( event.pageX );   // موقع مؤشّر الفأرة على المحور X
  console.log( event.pageY );   // موقع مؤشّر الفأرة على المحور Y
});

جرّب المثال في ساحة التّجربة

داخل مُتولّي الأحداث

عندما تُحدِّد الدّالة المُتولّية لحدث ما، فإنّه يُتاح لهذه الدّالّة وصول إلى عنصر DOM الخام الّذي أطلق الحدث كسياق الدّالّة this، فإن أردت استخدام jQuery للتّعامل مع الحدث، فأحطه بـ‎$()‎:

$( 'input' ).on( 'keydown', function( event ) {
  // this: العنصر الخام الّذي أطلق الحدث
  // event: كائن الحدث

  // غير لون الخلفية إلى أحمر إذا ضغط زر مسح الحرف، أو إلى أخضر فيما سوى ذلك
  $( this ).css( 'background', event.which === 8 ? 'red' : 'green' );
});

جرّب المثال في ساحة التّجربة

منع السّلوك المبدئيّ

قد ترغب أحيانًا في منع السّلوك المبدئيّ لحدثٍ ما، كأن ترغب في تولّي النّقر فوق رابط باستخدام AJAX، بدلًا من بدء إعادة تحميل كاملةٍ للصّفحة (وهو السّلوك المبدئيّ). يصل العديد من المُطوّرين إلى هذه الغاية بإعادة false من مُتولّي الحدث، ولكنّ لهذا تأثيرًا جانبيًّا آخر: فهو يمنع تفشّي الحدث (propagation) أيضًا (سنشرحه بعد قليل)، ممّا قد يعطي نتائج غير مرغوبة. الطّريقة السّليمة لمنع السّلوك المبدئيّ لحدث تكون باستدعاء ‎.preventDefault()‎ على كائن الحدث:

$( 'input' ).on( 'keydown', function( event ) {
  // this: العنصر الخام الّذي أطلق الحدث
  // event: كائن الحدث

  // غير لون الخلفية إلى أحمر إذا ضغط زر مسح الحرف، أو إلى أخضر فيما سوى ذلك
  $( this ).css( 'background', event.which === 8 ? 'red' : 'green' );
});

جرّب المثال في ساحة التّجربة

صعود الأحداث (Event bubbling)

تمعّن النّص البرمجيّ التّالي:

$( 'a' ).on( 'click', function( event ) {
  // امنع الحدث المبدئيّ.
  event.preventDefault();
  // سجّل ما حدث.
  console.log( 'I was just clicked!' );
});

جرّب المثال في ساحة التّجربة

يربط هذا النّص مُتولّيًا للنقر على كلّ العناصر في الصّفحة (وهو أمر يجب ألّا تفعله نهائيًّا في المواقع الحقيقيّة)، بالإضافة إلى عنصري النّافذة والمُستند. ولكن ما الّذي يحدث عندما تنقر على عنصر <a> مُدرج داخل عناصر أخرى؟ الحقيقة أنّ الحدث سيُثار على العنصر <a> وعلى كلّ العناصر الّتي تُحيط به صعودًا حتّى الوصول إلى العنصرين document وwindow.

يُسمّى هذا السّلوك "صعود الأحداث"، فالحدث يُثار على العنصر الّذي نقر عليه المُستخدم، ثمّ ينتقل صاعدًا إلى كلّ العناصر الّتي تحويه وصولًا إلى أعلى DOM، إلّا إن استدعيت الوظيفة ‎.stopPropagation()‎ على كائن الحدث.

بإمكانك فهم ذلك بسهولة أكبر في هذا المثال:

<a href="#foo"><span>I am a Link!</span></a>
<a href="#bar"><b><i>I am another Link!</i></b></a>
$( 'a' ).on( 'click', function( event ) {
  event.preventDefault();
  console.log( $( this ).attr( 'href' ) );
});

عندما تنقر على "I am a Link!‎"، فإنك لا تنقر فعليًّا على العنصر <a>، بل على العنصر <span> داخله، ولكن الحدث "يصعد" نحو العنصر <a> ويُثير حدث النّقر المُرتبط به.

تفويض الأحداث (Event delegation)

يسمح لنا مفهوم "صعود الأحداث" بإنجاز فكرة "تفويض الأحداث"، أي ربط الأحداث بعناصر في مستوى أعلى، ثمّ اكتشاف أيّ عنصر فرعيّ ضمنها أثار الحدث. بإمكاننا مثلًا ربط حدث بقائمة غير مُرتّبة، ثمّ تحديد أيّ العناصر أثار الحدث:

$( '#my-unordered-list' ).on( 'click', function( event ) {
  console.log( event.target ); // يُسجّل العنصر الّذي أثار الحدث
});

جرّب المثال في ساحة التّجربة

بالطّبع ستتعقّد الأمور إذا احتوت عناصر القائمة على عناصر فرعيّة ضمنها، ولهذا تُقدّم jQuery وظيفةً مُساعدة تسمح لنا بتحديد أي العناصر نهتمّ بها، مع الاحتفاظ بالحدث مُرتبطًا بالعنصر ذي المُستوى الأعلى:

$( '#my-unordered-list' ).on( 'click', 'li', function( event ) {
  console.log( this ); // يُسجّل عنصر القائمة الّذي أثار الحدث
});

جرّب المثال في ساحة التّجربة

لتفويض الأحداث فائدتان اثنتان: أولاهما أنّه يسمح بربط عددٍ أقلّ من مُتولّيات الأحداث مقارنة بالعدد الّذي نحتاجه لو قرّرنا ربط الأحداث بالعناصر المُنفردة، وهذا يُحسّن الأداء في الصّفحة بصورة كبيرة. وثاني الفائدتين أنّه يسمح لنا بربط الأحداث بالآباء (كالقائمة غير المرتّبة في مثالنا)، مع اطمئنانا إلى أنّ الأحدث ستُثار حتّى وإن تغيّرت مُحتويات العنصر الأب.

هذا النّصّ مثلًا، يُضيف عنصر قائمةٍ جديدًا بعد تفويض الحدث إلى العنصر الأب، والنّقر فوق هذا العنصر سيُثير الحدث كما ينبغي، دون الحاجة لربط أيّة أحداث جديدة:

$( '#my-unordered-list' ).on( 'click', 'li', function( event ) {
  console.log( this ); // يُسجّل عنصر القائمة الّذي نُقر عليه
});

$( '<li>a new list item!</li>' ).appendTo( '#my-unordered-list' );

جرّب المثال في ساحة التّجربة

خاتمة

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

ترجمة (بشيء من التصرف) للجزء الرابع من سلسلة  jQuery Fundamentals لمؤلّفتها Rebecca Murphey.





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


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



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن