היכרות ראשונית עם אירועים - Events

אירועים (Evants) אלו פעולות או התרחשויות אשר קורות במערכת שאנחנו מתכנתים בה, והמערכת אומרת לנו  בדרך מסויימת על ההתרחשות שלהם, כך שאנחנו יכולים להגיב להתרחשות של האירוע המסויים בדרך מסויימת שנרצה. לדוגמא אם משתמש לחץ על כפתור באתר, אולי נרצה בתגובה לטפל באירוע הלחיצה הזה על ידי הצגת הודעה כלשהי. במאמר זה אנחנו נדון בעקרונות החשובים של events ונסתכל כיצד הם עובדים בדפדפדנים. אנחנו לא נרחיב יותר מדי, אלא רק מה שתצטרכו לדעת בשלב זה.

ידע מוקדם: הכרות בסיסית עם המחשב ועם הבסיס של HTML ו- CSS, וכן סיום במלאו של מודול צעדים ראשונים ב-JavaScript.
מטרה: להבין את התיאוריה הבסיסית של אירועים וכיצד הם עובדים בדפדפנים וכיצד אירועים יכולים להיות שונים בסביבות פיתוח שונות.

מה הם אירועים? 

כפי שרשמנו למעלה, אירועים, events, הם פעולות או התרחשויות שקורות במערכת שאנחחנו מתכנתים עליה - המערכת שולחת סימן מסויים שהאירוע מתרחש וגם מספקת לנו מכניזם מסויים שעל פיו כלומר, קוד מסויים ירוץ בתגובה להתרחשות אירוע מסויים. כמו בדוגמא עם לחיצת הכפתור שפירטנו למעלה. כאשר האירוע של ״לחיצת כפתור״ התרחש, הדפדפן שלח לנו סימן מסויים שהתרחש אירוע וטיפלנו בו באמצעות הקפצת חלונית.

במקרה שלנו, אירועים מתרחשים בתוך חלון הדפדפן, ונוטים להיות מחוברים לפריט מסויים שבו הם התרחשו או עליו הם קרו - זה יכול להית אלמנט אחד, מספר אלמנטים, אירוע של טעינה דף HTML בלשונית מסויימת או טעינתו של חלון הדפדפן כולו.

יש הרבה סוגים של אירועים שיכולים להתרחש. הנה דוגמא קטנה שלהם:

  • המשתמש לחץ על אלמנט מסויים או עבר עם העכבר על אלמנט מסויים.
  • המשתמש לחץ על כפתור במקלדת.
  • המשתמש הגדיל או הקטין את חלון הדפדפן.
  • דף אינטרנט סיים להיטען במלואו.
  • טופס נשלח.
  • וידאו החל להתנגן או הושהה או סיים לנגן.
  • התרחשה שגיאה.

תוכלו לראות בדף MDN בנושא Event reference שיש לא מעט סוגי אירועים שניתן לטפל בהם - כלומר להגיב להתרחשותם.

לכל אירוע זמין יש ״מטפל אירוע״ - event handler, שזה בעצם בלוק של קוד שבדרך כלל זו פונקציה שאנחנו נגדיר בעצמנו ושירוצו כאשר האירוע החל להתרחש. שימו לב שלפעמים event handlers נקראים event listeners - ״מאזיני אירוע״ בעיקרון, הם כמעט אותו דבר מבחינתנו כרגע, אבל יש הבדלים ביניהם, הם עובדים יחד: מאזין אירוע - event listeners - מאזין להתרחשות האירוע, ואילו מטפל האירוע - event handlers, מטפל באותו אירוע שהתרחש - (הקוד שמורץ בתגובה להתרחשות של אותו אירוע).

לתשומת לב: חשוב לדעת כי אירועי web אינם חלק מהבסיס של JavaScript - הם מוגדרים כחלק מה-API של JavaScript המובנים בתוך הדפדפן.

דוגמא

נסתכל על דוגמא פשוטה על מנת להסביר את הנושא. ראינו כבר אירועים ו-event handlers בהרבה מהדוגמאות במהלך הקורס אבל בואו נרענן את הזיכרון.

הסתכלו בדוגמא הבאה, יש לנו אלמנט <button> אחד, אשר כאשר הוא נלחץ, הוא שנה את הצבע של הרקע לצבע אקראי:

<button>Change color</button>

קוד ה-JavaScript שלנו נראה כך:

var btn = document.querySelector('button');

function random(number) {
  return Math.floor(Math.random()*(number+1));
}

btn.onclick = function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

בדוגמא למעלה, אחסנו הפניה לאלמנט <button> בתוך משתנה שנקרא btn, באמצעות פונקציית Document.querySelector(). בנוסף ,הגדרנו פונקציה שמחזירה לנו מספר אקראי. החלק השלישי של הקוד הוא מטפל האירוע - event handler. המשתנה btn מצביע על אלמנט  <button> ולסוג הזה של האובייקט יש מספר מסוים של אירועים שיכולים להתרחש עליו, ולכן יש מספר מטפלי אירוע זמינים עבורו.

בדוגמא זו אנחנו מקשיבים בעצם לאירוע/התרחשות של לחיצת עכבר באמצעות השמת ערך לתכונה (property) של event handler השווה לפונקציה אנונימית. פונקציה אנונימית זו מכילה קוד שמייצר צבע רנדומלי בפורמט rgb וקובעת את ה-background-color לצבע זה.

הקוד שבפונקציה האנונימית ירוץ בכל פעם שאירוע הלחיצה על האלמנט <button> יתרחש, כלומר בכל פעם שהמשתמש לחץ על האלמנט הזה.

התוצאה של הדוגמא תהא כך:

זה לא רק בדפי האינטרנט

דבר נוסף שנרצה להזכיר בנקודה זו הוא שאירועים הם לא מיוחדים ל-JavaScript - רוב שפות התכנות מכילות מודל כלשהו של אירועים, והדרך שבה מודל זה עובד בהם, יהיה לרוב שונה מהדרך שבה מודל זה עובד ב-JavaScript. האמת, מודל האירועים ב-JavaScript עבור דפי אינטרנט, שונה ממודל האירועים עבור JavaScript כאשר היא בסביבות אחרות.

לדוגמא, Node.js היא סביבה המאפשרת לנו הרצה ושימוש ב-JavaScript בצד השרת. מודל האירועים של Node.js מתבסס על מאזינים (listeners) אשר מאזינים לאירועים ועל פולטים (emitters) אשר פולטים אירועים בתדירות מסויימת - זה לא נשמע שונה, אבל הקוד הוא שונה, ושם מתבצע שימוש בפונקציות כמו ()on על מנת לרשום מאזין אירוע ו-()once על מנת לרשום מאזין אירוע שיאזין באופן חד פעמי ויתבטל לאחר שהורץ פעם אחת. דוגמא לכך ניתן לראות ב-HTTP connect event docs.

דוגמא נוספת למודל אירועים שונים אנחנו יכולים לראות כאשר משתמשים ב-JavaScript על מנת לבנות תוספים לדפדפנים (מעין שיפורים לדפדפנים) - באמצעות טכנולוגיה שנקראת WebExtensions. מודל האירועים שם הוא דומה למודל האירועים של ה-Web, אבל שונה מעט - מאזיני האירועים הם camel-cased - כלומר, onMessage ולא onmessage, והם צריכים להיות ביחד עם פונקצית addListener. לדוגמאות ראו runtime.onMessage page.

אינכם צריכים להבין עכשיו שום דבר על הסביבות הללו, רק רצינו להסביר לכם שאירועים ומודל האירועים יכול להיות בסביבות פיתוח שונות.

דרכים לשימוש ב-web events

יש מספר דרכים שונות שבהם אנחנו יכולים להוסיף מאזין אירוע -event listener לדפי הרשת על מנת שהוא יריץ את ה-event handler כאשר יתרחש האירוע הרלוונטי (event registration). בחלק זה, אנחנו נסקור את הדרכים השונות ונדון באילו מהן אנו צריכים להשתמש.

דרך I: הוספת תכונה של מטפל אירוע - Event handler properties

דרך אחת היא בעצם לתת לאלמנט מסויים תכונה של מאזין אירוע והערך שלה יהיה מטפל אירוע. אנו נקרא לה ״תכונת מטפל אירוע״. נתקלנו בזה במהלך הקורס לא מעט. אם נחזור לדוגמא למעלה:

var btn = document.querySelector('button');

btn.onclick = function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

התכונה onclick היא תכונת מטפל האירוע (מאזין האירוע) הרלוונטית כרגע לדוגמא זו. היא תכונה בדיוק כמו שיש ל-btn תכונות נוספות שזמינו תעבורו כמו btn.textContent, או btn.style, אבל היא תכונה מסוג מיוחד - כאשר אנחנו משימים בה ערך ששווה לקוד מסויים, הקוד הזה ירוץ בכל פעם שהאירוע יתרחש על הכפתור (או על האלמנט אשר נתנו לו את התכונה הזו).

אנחנו גם יכולים לתת למטפל אירוע שכזה שיהיה שווה לשם של פונקציה מסויימת, כמו שראינו במאמר בניית פונקציות משלנו. הקוד הבא יעבוד בצורה מצויינת:

var btn = document.querySelector('button');

function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

btn.onclick = bgChange;

יש הרבה סוגים של תכונות מטפלות אירוע שזמינות. ננסה כמה מהם:

ראשית, שמרו לכם עותק מקומי של random-color-eventhandlerproperty.html, ופיתחו אותו בדפדפן. זהו עותק של הדוגמא הפשוטה של בחירת הצבע שראינו בעבר. עכשיו שנו את ה- btn.onclick לערכים הבאים, כל אחד בתור, וראו מה קורה :

  • btn.onfocus ו- btn.onblur — הצבע ישתנה כאשר הפוקוס יהיה על הכפתור וכאשר הוא יוצא ממנו - באמצעות מקש tab. אלו משמשים אותנו להצגת מידע כיצד למלא שדות בטופס שלנו או על מנת להציג הודעת שגיאה כאשר הכניסו בשדה ערך שגוי.
  • btn.ondblclick — כאן הצבע ישתנה רק כאשר נלחץ עליו פעמיים - דאבל קליק.
  • window.onkeypress, window.onkeydown, window.onkeyup — כאן הצבע ישתנה כאשר המשתמש לחץ על כפתור במקלדת אשר יכול להפיק תו ושחרר אותו. keypress הוסר מהסטנדרטים המקובלים ולא מומלץ להשתמש באירוע מסוג זה מכיוון שהתמיכה בו נעלמת. keydown and keyup לא מדובר באירוע של לחיצה על לחצני החצים - אלא מדובר באירוע שמתרחש ברגע שהכפתור נלחץ ואירוע אחר שמתרחש ברגע שהפסיק ללחוץ על הכפתור. שימו לב שזה לא עובד אם נרשום את המטפל אירוע הזה על הכפתור צמו - אנחנו צריכים לרשום אותו על אובייקט window שמייצג את החלון הדפדפן כולו. 
  • btn.onmouseover ו- btn.onmouseout — כאן הצבע ישתנה כאשר סמן העכבר עובר מעל הכפתור וכאשר הוא יוצא ממנו, בהתאמה.

ישנם אירועים שהם מאד כלליים והם זמינים כמעט בכל מקום או עבור כל אלמנט - לדוגמא - אפשר להאזין לאירוע onclick כמעט על כל אלמנט, בעוד יש כאלו שהם יותר ספציפיים ושימושיים בסיטואציות מסויימת. לדוגמא, הגיוני לנו ש-onplay יהיה זמין רק לאלמנטים מסוג מסויים כמו <video>.

דרך II: לידיעה בלבד - לא להשתמש בהם - Inline event handlers

יכול להיות שראיתם קוד שנראה כך:

<button onclick="bgChange()">Press me</button>
function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}

לתשומת לב: אתם יכולים לראות את קוד המקור לדוגמא זו ב- GitHub או בדף אינטרנט.

בעבר, הדרך לרשום (register) מטפלי אירוע הייתה באמצעות event handler HTML attributes הידועים גם כ- inline event handlers, כמו שרשום בדוגמא למעלה. הערך של אותו attribute הוא ממש קוד JavaScript שאנחנו רוצים שירוץ כאשר האירוע מתרחש. הדוגמא למעלה מפעילה פונקציה שמוגדרת בתוך האלמנט <script> אבל באותה הדרך אנחנו יכולים להכניס פונקציה שלמה לתוך אותו attribute:

<button onclick="alert('Hello, this is my old-fashioned event handler!');">Press me</button>

אל תשתמשו בדרך שכזו על מנת לטפל באירוע, זה נחשב כ- bad practice (פרקטיקה שאינה מקובלת בתעשייה). זה אולי נשמע יותר פשוט, אבל מהר מאוד זה הופך למשהו שלא ניתן לעקוב אחריו ולא יעיל.

בנוסף, זה לא רעיון טוב לערבב בין HTML לבין JavaScript מאחר שזה נהיה קשה לעיבוד - היצמדו לכך שכל ה-JavaScript נמצא במקום אחד.

אפילו כאשר מדובר בקובץ אחד, inline event handlers הם לא רעיון טוב. כאשר יש כפתור אחד זה עוד סביר, אבל מה אם היו לנו 100 כפתורים? היינו צריכים להוסיף 100 attributes לתוך הקובץ. מהר מאוד זה הופך לסיוט מבחינת תחזוקה של הקוד. עם JavaScript, אנחנו יכולים בקלות להוסיף מטפל אירוע לכל הכפתורים, לא משנה כמה יש כאלו, באמצעות קוד כזה:

var buttons = document.querySelectorAll('button');

for (var i = 0; i < buttons.length; i++) {
  buttons[i].onclick = bgChange;
}

לתשומת לב: הפרדה בין הקוד שלנו לבין התוכן גם הופך את האתר שלנו ליותר ידידותי למנועי החיפוש.

דרך addEventListener() : III  ו- ()removeEventListener

הסוג החדש ביותר של מנגנון אירועים הוגדר ב- Document Object Model (DOM) Level 2 Events של ארגון W3C. מסמך זה הגדיר דרך טובה יותר לנהל אירועים, בין היתר באמצעות אובייקט מיוחד בשם  EvantTarget אשר נוצר בדפדפן כאשר אירוע מתרחש. לאובייקט זה יש שלוש מתודות ואחת מהן היא ()addEventListener. מתודה זו דומה לדרך הראשונה של event handler properties, אבל הסינטקס שונה. אנחנו יכולים לכתוב את הדוגמא שלנו עם הצבעים האקראיים בצורה הזו:

var btn = document.querySelector('button');

function bgChange() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
}   

btn.addEventListener('click', bgChange);

לתשומת לב:תוכלו למצוא את קוד המקור של דוגמא זו ב- GitHub או ראו כ- דף אינטרנט).

בתוך מתודת ()addEventListener, אנחנו מציינים שני פרמטרים - אחד בעבור אותו האירוע, אותו ה-event אשר אנחנו רוצים שיאזינו והפרמטר השני זה פונקציה של הקוד שאנחנו רוצים שירוץ כמטפל באותו אירוע. שימו לב שזה בסדר גמור לשים את כל הקוד שיטפל באירוע כפונקציה אנונימית, בשונה מהדוגמא למעלה:

btn.addEventListener('click', function() {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  document.body.style.backgroundColor = rndCol;
});

לשיטה הזו יש יתרונות ברורים על השיטות הישנות שדנו בהם למעלה. ראשית, יש מתודה נוספת בשם ()removeEventListener אשר מאפשרת לנו להסיר מאזיני אירוע. לצורך העניין, הקוד הבא יסיר את מאזין האירוע שקבענו למעלה:

btn.removeEventListener('click', bgChange);

זה אמנם לא כזה משמעותי עבור תוכניות קטנות או פשוטות, אבל בעבור תוכניות גדולות או מורכבות, זה יכול להגביר את היעילות ומאפשר לנקות מטפלי אירוע שאינם בשימוש עוד. בנוסף, זה מאפשר לנו שלאותו אלמנט - לאותו הכפתור - יהיו מספר מטפלי אירועים אשר יפעלו בצורה אחרת באירועים שונים - כל מה שאנחנו צריכים לעשות זה להוסיף/להסיר מטפלי אירוע בהתאם.

בנוסף, אנחנו גם יכולים לרשום מספר מטפלי אירוע לאותו אירוע - הקוד הבא לדוגמא לא יכול להחיל שני מטפלי אירוע:

myElement.onclick = functionA;
myElement.onclick = functionB;

זאת מכיוון שהשורה השנייה דורסת את הערך שנקבע ל-onclick של השורה הראשנה. על מנת להוסיף מטפל אירוע נוסף מבלי לדרוס את מטפל את האירוע הקיים, אנחנו יכולים לעשות משהו כזה:

myElement.addEventListener('click', functionA);
myElement.addEventListener('click', functionB);

במקרה זה, שתי הפונקציות ירוצו כאשר ילחצו על האלמנט.

כמו כן, יש מספר אפשרויות משמעותיות ותכונות זמינות עבור מנגנון ירועים בדרך הזו. אנחנו לא נרחיב עליהם, אך אם תרצו להרחיב את ידיעתכם בנושא, היכנסו לדפי ההסבר של MDN בנושא ()addEventListener ובנושא ()removeEventListener.

באיזה מנגנון אירועים להשתמש?

מתוך שלושת המנגנונים הללו, בוודאות אל תשתמשו במנגנון HTML event handler attributes - זה מנגנון ישן, שלא נמצא כבר בשימוש ונחשב כ- bad practice להשתמש בו.

השניים האחרים הם יחסית די חלופיים כאשר מדובר בתוכנית פשוטה או כאשר מדובר בשימוש פשוט שנרצה לעשות באירועים:

  • ל- Event handler properties יש פחות אפשרויות ופחות כוח אך תמיכת הדפדפנים רחבה יותר (כולל Internet Explorer 8).
  • DOM Level 2 Events - כגון addEventListener() ו-()removeEventListener ועוד, מאפשרים לנו הרבה יותר אפשרויות, אבל יכולים להיות מורכבים ובעלי פחות תמיכת דפדפנים (נתמך החל מגרסת Internet Explorer 9). השתדלו לנסות ולהשתמש בהם בכך מתי שניתן. 

כמו שאמרנו, היתרון העיקרי של המנגנון השלישי הוא שאתם יכולים להסיר קוד שמטפל באירוע, באמצעות ()removeEventListener, ואתם יכולים להוסיף מספר מאזיני אירוע לאותו אלמנט. לדוגמא, אנחנו יכולים לקרוא ל-({ ... }()addEventListener('click', function על אלמנט מסויים מספר רב של פעמים, כאשר בכל פעם תהיה פונקציה אחרת בארגומנט השני. אופציה זו לא אפשרית ב- event handler properties כי כל הגדרה נוספת של property תמחוק את ההגדרה הקודמת שלו:

element.onclick = function1;
element.onclick = function2;
etc.

לתשומת לב: אם אתם נדרשים לתמוך בדפדפנים ישנים מאינטרנט אקפלורר 8, אתם עלולים להיתקל בקשיים שכן דפדפדנים ישנים אלו משתמשים במודל אירועים שונה מדפדפנים חדשים, אך אל פחד. מרבית ספריות JavaScript (כמו לדוגמא jQuery) מכילות פונקציות מובנות שמבטלות כביכול את ההבדלים בין הדפדנים השונים. אין לכם צורך לדאוג לנושא זה בתחילת הלמידה שלכם.

עקרונות נוספים בנושא אירועים

בחלק זה, אנחנו נגע בקצרה בנושאים מתקדמים הקשורים לאירועים. אין זה הכרחי להבין נושאים אלו לעומק בשלב זה, אך זה עלול לסייע לכם להבין תבניות קוד שתיתקלו בהם מדי פעם.

Event objects

לפעמים בתוך פונקצייה המטפלת באירוע (event handler) - אתם תראו פרמטר המצויין בתך הסוגריים, הנושא שם כמו event, evt, או סתם e. זה נקרא event object - האובייקט של האירוע עצמו, והוא מועבר בצורה אוטומטית למטפלי האירוע על מנת לספק אפשרויות נוספות ומידע נוסף. לדוגמא, נרשום את דוגמת הצבעים שלנו מחדש בצורה קצת שונה:

function bgChange(e) {
  var rndCol = 'rgb(' + random(255) + ',' + random(255) + ',' + random(255) + ')';
  e.target.style.backgroundColor = rndCol;
  console.log(e);
}  

btn.addEventListener('click', bgChange);

לתשומת לב: תוכלו למצוא את קוד המקור ב-GitHub וגם לראות את הדוגמא כדף אינטרנט.

כאן אנחנו יכולים לראות שאנחנו כוללים את האובייקט של האירוע - e, בתוך הפונקציה, ובתוך הפונקציה אנחנו קובעים צבע רקע ל- e.target — שזה בעצם הכפתור עצמו. המאפין target של event object הוא תמיד הפנייה לאלמנט שהאירוע התרחש עליו. כך, בדוגמא שלנו, אנחנו קובעים בעצם צבע רקע אקראי לכפתור עצמו שהאירוע הלחיצה התרחש עליו ולא לדף.

לתשומת לב: אתם יכולים להשתמש באיזה שם שנרצה עבור event object — רק השתמשו בשם שיהיה ברור שזה הפניה ל- event object. השמות המקובלים הם e/evt/event מכיוון שהם קצרים וקל לזכור את ההקשר שלהם.

e.target הוא שימושי להפליא כשאנחנו רוצים לקבוע את אותו event handler על מספר אלמנטים ולעשות משהו לכולם כאשר אותו אירוע מתרחש. לדוגמא, נניח ויש לנו סט של 16 אריחים שנעלמים כאשר הם נלחצים. זה נוח יותר כאשר מתאפשר לנו לקבוע שאלמנט יעלם כשאנחנו נלחץ עליו באמצעות שימוש ב- e.target , מאשר לבחור אותו בצורה מורכבת אחרת. בדוגמאות הבאות למשל ב-useful-eventtarget.html - קוד המקור (ניתן לראות גם ב-דף האינטרנט) יצרנו 16 אלמנטים מסוג <div> באמצעות JavaScript, ואנחנו בוחרים את כולם באמצעות document.querySelectorAll() ואז עוברים על כל אחד מהם באמצעות לולאה ומוסיפים מאזין אירוע ומטפל אירוע של onclick לכל אחד מהם כך שכאשר כל אחד מהאלמנטים הללו נלחץ על ידי המשתמש, צבע אקראי אחר ניתן לאותו אלמנט:

var divs = document.querySelectorAll('div');

for (var i = 0; i < divs.length; i++) {
  divs[i].onclick = function(e) {
    e.target.style.backgroundColor = bgChange();
  }
}

הפלט נראה כך. נסו לשחק איתו:

הרבה ממאזיני האירועים/מטפלי האירועים שניתקל בהם הם בעלי סט סטנדרטי של properties ופונקציות - או יותר נכון מתודות, אשר זמינים עבור ה-event object - ראו את הדף בנושא אובייקט Event לרשימה המלאה.

יש מטפלי אירועים יותר מורכבים, המכילים properties מיוחדים בעלי מידע ספציפי שהם צריכים על מנת לתפקד כמטפל אירוע. לדוגמא ל-Media Recorder API, יש סוג אירוע - dataavailable - שמתרחש כאשר אודיו או וידאו הוקלטו והם זמינים כך שניתן לבצע עליהם פעולות מסויימות - לדוגמא לשמור אותם או לנגן אותם שוב. למאזין האירוע/מטפל אירוע התואם ondataavailable יש property שזמין בשם data עבורו המכיל את האודיו/וידאו שהוקלט ומאפשר לנו לגשת אליו ולעשות איתו דברים.

מניעת התנהגות ברירת מחדל

לפעמים אנחנו ניתקל בסיטואצית שאנחנו נרצה למנוע מאירוע לבצע את מה שהוא אמור לעשות כברירת מחדל. הדוגמא הכי נפוצה היא טופס באינטרנט. כאשר אנחנו נמלא את הפרטים ונלחץ על כפתור ה-Submit , ההתנהגות ברירת המחדל תהיה לשלוח את המידע שהוכנס בטופס לדף מסויים בשרת לשם עיבוד, והדפדפן יועבר למעין דף של ״הפרטים נשלחו בהצלחה״ או משהו דומה.

הבעיה מתעוררת כאשר המשתמש לא הזין את המידע כראוי - כמפתחים, אנחנו נרצה למנוע שליחה של המידע השגוי לשרת ולתת למשתמש הודעת שגיאה שאומרת להם מה הייתה הטעות ומה הם צריכים לעשות. חלק מהדפדנים תומכים בולידציה של מידע בטפסים, אך מכיון שהרבה לא ומסיבות נוספות, אנחנו ממליצים לא להסתמך על ולידציה של דפדפנים אלא לבנות את הולידציה בעצמכם. לדוגמא:

נתחיל עם טופס HTML פשוט דורש מאיתנו להכניס שם פרטי ושם משפחה:

<form>
  <div>
    <label for="fname">First name: </label>
    <input id="fname" type="text">
  </div>
  <div>
    <label for="lname">Last name: </label>
    <input id="lname" type="text">
  </div>
  <div>
     <input id="submit" type="submit">
  </div>
</form>
<p></p>

כעת קצת JavaScript - כאן החלנו בדיקה פשוטה מאוד בתוך מטפל האירוע onsubmit (האירוע של submit מתרחש כאשר הטופס נשלח).

הבדיקה שלנו בודקת הם שדות הטקסט ריקים. אם כן, אנחנו קוראים לפונקציית ()preventDefault על ה- event object - כלומר על האלמנט שעליו התרחש האירוע. פונקצייה זו מונעת מהטופס להישלח - ומציגה הודעת שגיאה בפסקה מתחת לטופס שלנו, על מנת להציג למשתמש מה השתבש:

var form = document.querySelector('form');
var fname = document.getElementById('fname');
var lname = document.getElementById('lname');
var submit = document.getElementById('submit');
var para = document.querySelector('p');

form.onsubmit = function(e) {
  if (fname.value === '' || lname.value === '') {
    e.preventDefault();
    para.textContent = 'You need to fill in both names!';
  }
}

ברור שזו ולידציה פשוטה וחלשה מאוד - זה לא ימנע מהמשתמש להזין רווחים או מספרים לתוך שדות הטקסט, אך זה מספיק לשם הדוגמא. הפלט ייראה כך:

לתשומת לב: ראו את קוד המקור פה preventdefault-validation.html או דף אינטרנט.

event bubbling ו- capture

נושא נוסף שאנחנו נרצה לדון הוא משהו שאמנם לא ניתקל בו הרבה, אך אם לא נכיר אותו ונבין אותו, יכול לעשות לנו כאב ראש לא קטן.

event bubbling ו- capture אלו שני מנגנונים אשר מסבירים מה קורה כאשר שנים או יותר מטפלי אירוע של אותו אירוע מופעלים על אלמנט אחד. לצורך ההסבר, פתחו את show-video-box.html בלשונית חדשה בדפדפן ואת קוד המקור בלשונית אחרת. ניתן גם לראות את הדוגמא כאן למטה:

זו דוגמא פשוטה שמראה ומסתירה אלמנט <div> שמכיל אלמנט <video> בתוכו:

<button>Display video</button>

<div class="hidden">
  <video>
    <source src="rabbit320.mp4" type="video/mp4">
    <source src="rabbit320.webm" type="video/webm">
    <p>Your browser doesn't support HTML5 video. Here is a <a href="rabbit320.mp4">link to the video</a> instead.</p>
  </video>
</div>

כאשר אנחנו לוחצים על <button> הוידאו מוצג, באמצעות שינוי ה-class של <div> מ- hidden ל- showing (ה-css בקובץ מכיל בין היתר שני classes אשר מציגים את הקופסא במסך או מסתירים אותה, בהתאמה) :

btn.onclick = function() {
  videoBox.setAttribute('class', 'showing');
}

לאחר מכן אנחנו מוסיפים עוד שני מטפלי אירוע של onclick - הראשון ל- <div> והשני ל - <video>. הרעיון הוא שכאשר האזור של ה- <div> מחוץ לוידאו מקבל לחיצה, הקופסא אמורה להיעלם שוב; כאשר הוידאו עצמו נלחץ, הוידאו אמור לפעול שוב:

videoBox.onclick = function() {
  videoBox.setAttribute('class', 'hidden');
};

video.onclick = function() {
  video.play();
};

אבל ישנה בעי כרגע, כאשר אנחנו לוחצים על הוידאו הוא מתחיל להתנגן, אבל גורם גם ל- <div> להיות מוסתר באותו הזמן. זה מכיוון שהוידאו שבתוך ה-<div> - הוא חלק ממנו - אז כאשר אנחנו לוחצים על הוידאו - אנחנו בעצם גם מפעילים את האירוע על ה-<div> עצמו, ושני מטפלי האירוע נכנסים לפעולה.

bubbling ו- capturing

כאשר אירוע מתרחש על אלמנט שיש לו אלמנט אב - לדוגמא האלמנט <video> שלנו, לדפדפנים המודרניים יש שני תהליכים שונים שהם מבצעים: שלב ה- capturing ושלב ה- bubbling.

בשלב - capturing:

  • הדפדפן בודק האם אלמנט האב הראשי (<html>) מכיל event handler של onclick הרשום בשלב capturing ואם כן הוא מריץ אותו.
  • לאחר מכן, ובכל מקרה, הוא עובר לאלמנט הבן שנמצא בתוך - (<html>) - ובודק שוב את הדבר, וכך הלאה, עד אשר הוא מגיע לאלמנט שבפועל לחצנו עליו.

בשלב - bubbling קורה בדיוק ההפך:

  • הדפדפן בודק האם האלמנט שלחצו עליו בפועל מכיל event handler של onclick הרשום עליו בשלב bubbling ואז מריץ אותו אם כן.
  • לאחר מכן הוא ממשיך בכל מקרה לאלמנט האב הישיר שלו ומבצע את אותו דבר וכך הלאה, עד שהוא מגיע לאלמנט <html>.

לחצו על התמונה על מנת להגדיל אותה.

בדפדפנים מודרניים, ברירת המחדל היא שכל ה-event handlers נרשמים בשלב ה-bubbling. בדוגמא שלנו למשל, כאשר לחצנו על הוידאו, האירוע מסוג לחיצה ״בועבע״ מאלמנט <video> הלאה לאלמנט <html>, כאשר במהלך הדרך :

  • הוא מצא מטפל אירוע - video.onclick... והריץ אותו ולכן הוידאו החל להתנגן.
  • לאחר מכן הוא מצא מטפל אירוע  videoBox.onclick... והריץ אותו גם, ולכן הסתיר את הוידאו (או יותר נכון הסתיר את ה-div שהכיל את הוידאו).

שימו לב שמה שבועבע הוא התרחשות האירוע עצמו, ולא מטפל האירוע (אותו דבר קורה גם בשלב ה-capturing). 

תיקון הבעיה עם ()stopPropagation

על מנת לתקן את ההתנהגות הזו, לאובייקט האירוע עצמו - event object - יש מתודה זמינה עבורו שנקראת ()stopPropagation, כאשר היא מופעלת על מטפל האירוע של אובייקט האירוע, היא תריץ את מטפל האירוע, אבל תמנע מהאירוע עצמו להמשיך לבעבע במעלה השרשרת לאלמנטי האב שלו (וכנ״ל גם בשלב ה- capturing) וכך מטפלי אותו אירוע של אלמנטי האב לא ירוצו.

כך, על מנת לתקן את הבעיה בדוגמא שלנו, אנחנו נשנה את הפונקציה המטפלת באירוע כך:

video.onclick = function(e) {
  e.stopPropagation();
  video.play();
};

אתם יכולים ליצור עותק מקומי של קוד המקור של show-video-box.html ולתקן זאת בעצמכם או לראות הקובץ המתוקן כאן ואת קוד המקור כאן.

הערה חשובה: אתם בטח שואלים את עצמכם מדוע לטרוח וליצור שני שלבים  - גם capturing וגם bubbling? התשובה נעוצה בהיסטוריה ובימי העבר כאשר התאימות בין דפדפנים הייתה שונה מאוד ולא כפי שהיא היום.

בימים ההם Netscape השתמש ב-event capturing בלבד ואילו Internet Explorer השתמש ב - event bubbling בלבד. כאשר ארגון W3C החליט לנסות וליצור סטדרט אחיד, הם החליטו על מנגנון שיכלול את שני השלבים - וכל הדפדפנים אימצו את שני השלבים.

לתשומת לב: יחד עם זאת, כברירת מחדל, כל מטפלי האירועים נרשמים בשלב bubbling ולא בשלב capturing וברוב המוחלט של הפעמים זה עושה שכל. אם אנחנו ממש רוצים לרשום אירוע בשלב ה-capturing, אנחנו יכולים לעשות זאת באמצעות הוספת true כפרמטר שלישי בתוך ()addEventListener. היכנסו לקישור זה אם תרצו להרחיב בנושא. 

Event delegation

bubbling מאפשר לנו להשתמש ביתרונות של event delegation — רעיון זה מתבסס על העובדה שאם אנחנו רוצים להריץ קוד מסויים כאשר אנחנו לוחצים על אלמנט ילד אחד שהוא חלק ממספר אלמנטים ילדים של אלמנט אב, אנחנו יכולים לקבוע את מאזין האירוע על אלמנט האב, ואז כאשר האירוע מתרחש על אחד מאלמנטים הילדים, האירוע ״יבועבע״ לאלמנט ההורה שלהם.

כתיבה שכזו עדיפה על רישום מאזין אירוע לכל אחד מהילדים בנפרד. להזכירכם - בשלב ה-bubbling נבדק האם האלמנט שהאירוע התרחש עליו מכיל מטפל אירוע לאירוע שהתרחש ומתקדם הלאה לאלמנט ההורה ומבעבע אליו את האירוע ובודק האם הוא מכיל מטפל אירוע לאירוע שהתרחש וכך הלאה.

דוגמא טובה לכך היא רשימה של פריטים - אם אנחנו רוצים שכל פריט מהרשימה יקפיץ הודעה כאשר לוחצים עליו, אנחנו יכולים לקבוע מאזין אירוע על האלמנט ההורה - במקרה שלנו <ul>, וכל לחיצה על פריט מהרשימה (<li>) -  כלומר כל התרחשות אירוע שכזה על פריט מהרשימה - אירוע הלחיצה יבעבע מאלמנט <li> לאלמנט האב - <ul>.

רעיון זה מוסבר בפירוט רב בבלוג של David Walsh עם דוגמאות נוספות. ראו את המאמר שלו בנושא How JavaScript Event Delegation Works.

לסיכום

במאמר זה ניסינו להסביר כל מה שאתם צריכים לדעת על אירועי Web בשלב זה. כפי שנאמר למעלה, אירועים הם לא חלק מה-core של JavaScript - הם מוגדרים ב-Web APIs של הדפדפן.

חשוב להבין בנוסף שיש הבדלים בין מודלי האירועים של JavaScript בסביסות השונות שהיא מורצת - החל מ- Web APIs ועד לסביבות אחרות כמו Browser WebExtensions ו- Node.js. לא מצפים מכם שכרגע תבינו את כל הסביבות הללו, אבל זה עוזר לכם להבין את הבסיס ככל ותתקדמו בלמידה.

אם משהו לא ברור, הרגישו חופשי לקרוא שוב את המאמר או ליצור עמנו קשר.

See also

  • Event order  - מאמר מפורט בנושא capturing and bubbling שנכתב על ידי Peter-Paul Koch.
  • Event accessing  - מאמר מפורט בנושא event object שנכתב גם הוא על ידי Peter-Paul Koch.
  • Event reference

במודול זה