Скривени кључ за динамичке веб интеракције

Током мојих првих корака у свету веб развоја, наишао сам на један посебно занимљив феномен – ширење догађаја. У почетку ми се чинило необичним, али када се замислите над тиме, схватите да је потпуно логично. Као веб програмер, неминовно ћете се срести са овим механизмом. Дакле, шта је то уствари?

JavaScript користи догађаје како би омогућио интеракцију корисника са веб страницама. Догађај представља појаву или радњу која се може детектовати и на коју се може реаговати путем кода. Примери догађаја су кликови мишем, притисци на тастере, подношење форми и многи други.

JavaScript користи слушаоце догађаја да би детектовао и реаговао на догађаје. Слушалац догађаја је функција која „слуша“ или чека да се одређени догађај догоди на страници. На пример, догађај клика на дугме. Када слушалац догађаја региструје догађај на који је „намештен“, он реагује извршавањем кода који је повезан са тим догађајем. Цео овај процес хватања и одговарања на догађаје назива се руковање догађајима.

Замислимо сада да имамо три елемента на страници: div, span и button. Елемент button је угнежђен унутар елемента span, а елемент span је угнежђен у div. Илустрација тога је приказана испод:

Ако сваки од ових елемената има слушаоца догађаја који чека на догађај клика и исписује поруку у конзоли када се клик деси, шта се дешава када кликнете на дугме?

Да бисте сами тестирали, направите фолдер и у њему креирајте HTML фајл назван index.html, CSS фајл назван style.css и JavaScript фајл назван app.js.

У HTML фајл додајте следећи код:

<html lang="en">
<head>
  <title>Event bubbling</title>
  <link rel="stylesheet" href="https://wilku.top/the-hidden-key-to-dynamic-web-interactions/style.css">
</head>
<body>
  <div>
    <span><button>Click Me!</button></span>
  </div>
  <script src="app.js"></script>
</body>
</html>

У CSS фајл додајте следећи код да бисте стилизовали div и span елементе.

div {
  border: 2px solid black;
  background-color: orange;
  padding: 30px;
  width: 400px;
}
span {
  display: inline-block;
  background-color: cyan;
  height: 100px;
  width: 200px;
  margin: 10px;
  padding: 20px;
  border: 2px solid black;
}

У JavaScript фајл додајте следећи код, који додаје слушаоце догађаја div, span и button елементима. Сви слушаоци догађаја чекају на догађај клика.

const div = document.querySelector('div');
div.addEventListener('click', () => {
  console.log("You've clicked a div element")
})
const span = document.querySelector('span');
span.addEventListener('click', () => {
  console.log("You've clicked a span element")
})
const button = document.querySelector('button');
button.addEventListener('click', () => {
  console.log("You've clicked a button")
})

Сада отворите HTML фајл у прегледачу. Прегледајте страницу, а затим кликните на дугме. Шта примећујете? Резултат клика на дугме је приказан испод:

Кликом на дугме покреће се слушалац догађаја који чека на клик на дугме. Међутим, покрећу се и слушаоци догађаја на span и div елементима. Зашто је то тако, питате се?

Кликом на дугме покреће се слушалац догађаја везан за дугме, што се исписује у конзоли. Међутим, пошто је дугме угнежђено унутар span елемента, клик на дугме технички значи да кликћемо и на span елемент, и зато се покреће и његов слушалац догађаја.

Пошто је span елемент такође угнежђен у div елементу, клик на span елемент истовремено значи да кликћемо и на div елемент, па се због тога покреће и његов слушалац догађаја. То је оно што је познато као ширење догађаја (event bubbling).

Ширење догађаја (Event Bubbling)

Ширење догађаја је процес у коме се догађај који је покренут у угнежђеном скупу HTML елемената проширује или „избија“ из унутрашњег елемента где је покренут и креће се уз DOM стабло до основног елемента, покрећући све слушаоце догађаја који чекају на тај догађај.

Слушаоци догађаја се покрећу одређеним редоследом који одговара начину на који се догађај проширује кроз DOM стабло. Размотрите DOM стабло приказано испод, које представља структуру HTML-а која се користи у овом чланку.

DOM стабло приказује дугме, угнежђено унутар span, које је угнежђено у div, који је угнежђен унутар body, а body је угнежђен унутар HTML елемента. Пошто су елементи угнежђени један унутар другог, када кликнете на дугме, догађај клика ће покренути слушаоца догађаја везаног за дугме.

Међутим, пошто су елементи угнежђени, догађај ће се померити нагоре кроз DOM стабло (bubble up) до span елемента, затим div, затим body и на крају до HTML елемента, покрећући све слушаоце догађаја који чекају на догађај клика тим редоследом.

Због тога се извршава слушалац догађаја приложен span и div елементима. Да смо имали слушаоце догађаја који чекају на клик на body и HTML елементу, и они би се такође покренули.

DOM чвор у коме се дешава догађај назива се циљ (target). У нашем случају, пошто се клик дешава на дугме, елемент дугмета је циљ догађаја.

Како зауставити ширење догађаја

Да бисмо спречили да се догађај прошири кроз DOM, користимо метод под називом stopPropagation(), који је доступан на објекту догађаја. Размотрите пример кода испод, који смо користили да додамо слушаоца догађаја елементу дугмета.

const button = document.querySelector('button');
button.addEventListener('click', () => {
  console.log("You've clicked a button");
})

Код доводи до догађаја који се шири кроз DOM стабло када корисник кликне на дугме. Да бисмо спречили ширење догађаја, позивамо метод stopPropagation() као што је приказано испод:

const button = document.querySelector('button');
button.addEventListener('click', (e) => {
  console.log("You've clicked a button");
  e.stopPropagation();
})

Руковалац догађаја (event handler) је функција која се извршава када се кликне на дугме. Слушалац догађаја аутоматски прослеђује објекат догађаја руковаоцу догађаја. У нашем случају, овај објекат догађаја је представљен променљивом e, која се прослеђује као параметар у руковаоца догађаја.

Овај објекат догађаја, e, садржи информације о догађају и такође нам даје приступ разним својствима и методама које се односе на догађаје. Један такав метод је stopPropagation(), који се користи да спречи ширење догађаја. Позивање stopPropagation() у слушаоцу догађаја дугмета спречава да се догађај шири кроз DOM стабло од елемента дугмета.

Резултат клика на дугме након додавања метода stopPropagation() је приказан испод:

Користимо stopPropagation() да спречимо да се догађај прошири са елемента на коме га користимо. На пример, ако желимо да се догађај клика шири од дугмета до span елемента, а не даље кроз DOM стабло, користили бисмо stopPropagation() на слушаоцу догађаја span елемента.

Хватање догађаја (Event Capturing)

Хватање догађаја је супротно од ширења догађаја. Приликом хватања догађаја, догађај се „спушта“ од најудаљенијег елемента до циљног елемента као што је илустровано испод:

На пример, у нашем случају, када кликнете на елемент дугмета, при хватању догађаја, слушаоци догађаја на div елементу ће бити први који ће се покренути. Након тога следе слушаоци на span елементу и коначно, слушаоци на циљном елементу ће се покренути.

Ширење догађаја је подразумевани начин на који се догађаји проширују у Document Object Model (DOM). Да бисмо променили подразумевано понашање са ширења на хватање догађаја, прослеђујемо трећи аргумент нашим слушаоцима догађаја да бисмо поставили хватање догађаја на true. Ако не проследите трећи аргумент слушаоцима догађаја, хватање догађаја је постављено на false.

Узмите у обзир слушаоца догађаја испод:

div.addEventListener('click', () => {
  console.log("You've clicked a div element")
})

Пошто нема трећег аргумента, хватање је постављено на false. Да бисмо поставили хватање на true, прослеђујемо трећи аргумент, логичку вредност true, што поставља хватање на true.

div.addEventListener('click', () => {
  console.log("You've clicked a div element")
}, true)

Алтернативно, можете проследити објекат који поставља capture на true, као што је приказано испод:

div.addEventListener('click', () => {
  console.log("You've clicked a div element")
}, {capture: true})

Да бисте тестирали хватање догађаја, у свој JavaScript фајл додајте трећи аргумент свим слушаоцима догађаја као што је приказано:

const div = document.querySelector('div');
div.addEventListener('click', () => {
  console.log("You've clicked a div element")
}, true)
const span = document.querySelector('span');
span.addEventListener('click', (e) => {
  console.log("You've clicked a span element")
}, true)
const button = document.querySelector('button');
button.addEventListener('click', () => {
  console.log("You've clicked a button");
}, true)

Сада отворите свој прегледач и кликните на елемент дугмета. Требало би да добијете следећи излаз:

Обратите пажњу на то да, за разлику од ширења догађаја, где је излаз са дугмета прво исписан, код хватања догађаја, први излаз је са најудаљенијег елемента, div.

Ширење и хватање догађаја су главни начини на које се догађаји проширују у DOM-у. Међутим, ширење догађаја је оно што се обично користи за проширивање догађаја.

Делегирање догађаја (Event Delegation)

Делегирање догађаја је дизајнерски образац где је један слушалац догађаја везан за заједнички родитељски елемент, на пример елемент <ul>, уместо да има слушаоце догађаја на сваком од подређених елемената. Догађаји на подређеним елементима се затим проширују до родитељског елемента, где се њима рукује преко руковаоца догађаја на родитељском елементу.

Стога, у случају када имамо родитељски елемент са подређеним елементима унутар њега, родитељском елементу додајемо само један слушалац догађаја. Овај руковалац догађаја ће управљати свим догађајима у подређеним елементима.

Можда се питате како ће родитељски елемент знати на који подређени елемент је кликнуто. Као што је раније поменуто, слушалац догађаја прослеђује објекат догађаја руковаоцу догађаја. Овај објекат догађаја има методе и својства која нуде информације о одређеном догађају. Једно од својстава у објекту догађаја је својство target (циљ). Target својство указује на одређени HTML елемент где се догађај десио.

На пример, ако имамо неуређену листу са ставкама листе и прикачимо слушаоца догађаја на елемент <ul> када се догађај деси на ставци листе, target својство у објекту догађаја ће указивати на одређену ставку листе где се догађај десио.

Да бисте видели делегирање догађаја на делу, додајте следећи HTML код у постојећи HTML фајл:

<ul>
    <li>Toyota</li>
    <li>Subaru</li>
    <li>Honda</li>
    <li>Hyundai</li>
    <li>Chevrolet</li>
    <li>Kia</li>
  </ul>

Додајте следећи JavaScript код да бисте користили делегирање догађаја како бисте употребили један слушалац догађаја на родитељском елементу да бисте слушали догађаје у подређеним елементима:

const ul = document.querySelector('ul');
ul.addEventListener('click', (e) => {
  // target element
  targetElement = e.target
  // log out the content of the target element
  console.log(targetElement.textContent)
})

Сада отворите прегледач и кликните на било коју ставку на листи. Садржај елемента треба да буде исписан у конзоли као што је приказано испод:

Користећи један слушалац догађаја, можемо руковати догађајима у свим подређеним елементима. Много слушалаца догађаја на страници утиче на њене перформансе, троши више меморије и доводи до спорог учитавања и приказа страница.

Делегирање догађаја нам омогућава да избегнемо све ово минимизирањем броја слушалаца догађаја које треба да користимо на страници. Делегирање догађаја се ослања на ширење догађаја. Стога, можемо рећи да ширење догађаја може помоћи у оптимизацији перформанси веб страница.

Савети за ефикасно руковање догађајима

Као програмер, када радите са догађајима у Document Object Model-у, размислите о коришћењу делегирања догађаја уместо да имате много слушалаца догађаја на елементима на вашој страници.

Када користите делегирање догађаја, не заборавите да приложите слушаоца догађаја најближем заједничком претку подређених елемената којима је потребно руковање догађајима. Ово помаже у оптимизацији ширења догађаја и такође минимизира путању коју догађај мора да пређе пре него што се њиме обради.

Када рукујете догађајима, користите објекат догађаја који обезбеђује слушалац догађаја у своју корист. Објекат догађаја садржи својства као што је target, која су корисна при руковању догађајима.

Да бисте имали ефикасније веб локације, избегавајте прекомерну манипулацију DOM-ом. Догађаји који покрећу честе DOM манипулације могу негативно утицати на перформансе ваше веб локације.

Коначно, у случају угнежђених елемената, будите веома опрезни када елементима прилажете угнежђене слушаоце догађаја. Ово не само да може да утиче на перформансе, већ ће руковање догађајима учинити веома сложеним, а ваш код ће бити тешко одржавати.

Закључак

Догађаји су моћан алат у JavaScript-у. Ширење догађаја, хватање догађаја и делегирање догађаја су важни алати за руковање догађајима у JavaScript-у. Као веб програмер, користите чланак да бисте се упознали са концептима како бисте могли да направите интерактивније, динамичније и ефикасније веб локације и апликације.