JavaScript - Event Propagation


Razumijevanje širenja događaja (Event Propagation)

Širenje događaja je mehanizam koji definše kako se događaji šire ili putuju kroz DOM stablo da bi došli do cilja i što mu se nakon toga događa. Shvatimo to uz pomoć primjera. Pretpostavimo da ste dodijelili obrađivač događaja klika hipervezi (tj. <a> elementu) koja je ugnježdena unutar paragrafa (tj. <p> elementa). Ako kliknete na tu vezu, izvršitelj će se izvršiti. Ali, umjesto veze, ako dodijelite obrađivač događaja klika paragrafa koji sadrži vezu, čak i u ovom slučaju, klikovi na vezu i dalje će pokrenuti rukovatelj. To je zato što događaji ne utiču samo na ciljni element koji je generisao događaj - oni putuju gore-dole kroz DOM stablo da bi dostigli svoj cilj. Ovo je poznato kao širenje događaja (Event Propagation). U modernom pretraživaču širenje događaja odvija se u dvije faze: capturing i bubbling. Prije nego što nastavimo dalje, pogledajte sljedeću ilustraciju:



Gornja slika pokazuje kako događaj putuje u DOM stablu tokom različitih faza širenja događaja kada se događaj aktivira na element koji ima nadređene elemente. Koncept širenja događaja uveden je da bi se bavio situacijama u kojima višestruki elementi u DOM hijerarhiji s odnosom roditelj-dijete imaju obrađivače događaja za isti događaj, kao što je klik mišem. Sada je pitanje, koji će se događaj klika na elementu prvo obraditi kada korisnik klikne na unutrašnji element, događaj klika vanjskog elementa ili unutrašnjeg elementa. U sljedećim dijelu ove lekcije detaljnije ćemo razmotriti svaku fazu širenja događaja i saznati odgovor na ovo pitanje.



Faza hvatanja (Capturing Phase)

U fazi hvatanja događaji se šire od objekta Window prema dole kroz DOM stablo do ciljnog čvora. Na primjer, ako korisnik klikne hipervezu, taj događaj klika prošao bi kroz element <html>, element <body> i <p> koji sadrži vezu. Takođe ako bilo koji predak ciljnog elementa i sam cilj, ima posebno registrovan slušatelj hvatanja događaja za tu vrstu događaja, ti se slušatelji izvršavaju tokom ove faze. Pogeldajmo sljedeći primjer:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Capturing Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showTagName() {
        alert("Capturing: "+ this.tagName);
    }
    
    var elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showTagName, true);
    }
</script>
</body>
</html>

Evo jednostavne demonstracije koju smo kreirali koristeći gornji primjer kako bismo vam pokazali kako sve funkcioniše. Kliknite bilo koji element i promatrajte kojim redosljedom se pojavljuju popup prozori s upozorenjima. POGLEDAJ IZGLED PRIMJERA. Hvatanje događaja (Event capturing) nije podržano u svim pretraživačima i rijetko se koristi. Na primjer, Internet Explorer prije verzije 9.0 ne podržava hvatanje događaja (Event capturing).

Takođe, hvatanje događaja radi samo s rukovaocima događajaja registrovanih metodom addEventListener() kada je treći argument postavljen na true. Tradicionalni metod dodjeljivanja obrađivača događaja, poput korištenja onclick, onmouseover itd., ovdje neće raditi. Pogledajte lekciju o slušateljima događaja JavaScript da biste saznali više o slušateljima događaja.



Faza Bubbling

U fazi bubbling događa se upravo suprotno. U ovoj fazi događaj se širi ili stvara mjehuriće sigurnosne kopije DOM stabla, od ciljnog elementa do objekta Window, posjećujući sve pretke ciljnog elementa jednog po jednog. Na primjer, ako korisnik klikne hipervezu, taj događaj klika prošao bi kroz element <p> koji sadrži vezu, element <body>, element <html> i čvor dokumenta. Takođe, ako bilo koji predak ciljnog elementa i samog cilja ima obrađivače događaja dodijeljene za tu vrstu događaja, ti se obrađivači izvršavaju tokom ove faze. U modernim pretraživačima svi su obrađivači događaja prema zadanim postavkama registrovani u fazi bubbling. Pogledajmo primjer:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Bubbling Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div onclick="alert('Bubbling: ' + this.tagName)">DIV
    <p onclick="alert('Bubbling: ' + this.tagName)">P
        <a href="#" onclick="alert('Bubbling: ' + this.tagName)">A</a>
    </p>
</div>
</body>
</html>

Evo jednostavne demonstracije koju smo kreirali koristeći gornji primjer kako bismo vam pokazali kako bubbling događaja funkcioniše. Kliknite bilo koji element i promatrajte kojim redosljedom se pojavljuju popup prozori s upozorenjima. POGLEDAJ IZGLED PRIMJER. Bubbling događaji podržani su u svim pretraživačima i rade za sve rukovaoce, bez obzira na to kako su registrovani, npr. koristeći onclick ili addEventListener() (osim ako nisu registrovani kao preslušavači hvatanja događaja).



Pristup ciljnom elementu

Ciljni element je DOM čvor koji je generisao događaj. Na primjer, ako korisnik klikne hipervezu, ciljni element je hiperveza. Ciljni element je dostupan kao event.target, ne mijenja se kroz faze širenja događaja. Uz to, ključna riječ this predstavlja trenutni element (tj. element koji ima trenutno pokrenuti obrađivač). Pogledajmo primjer:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Target Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;          
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    // Odabir div elementa
    var div = document.getElementById("wrap");

    // Pričvršćivanje rukovatelja događaja onclick
    div.onclick = function(event) {
        event.target.style.backgroundColor = "lightblue";

        // Neka pretraživač završi prikazivanje boje pozadine prije prikazivanja upozorenja
        setTimeout(() => {
            alert("target = " + event.target.tagName + ", this = " + this.tagName);
            event.target.style.backgroundColor = ''
        }, 0);
    }
</script>
</body> 
</html>

Evo jednostavne demonstracije koju smo kreirali koristeći gornji primjer. Kliknite bilo koji element i on će vam pokazati ime oznake ciljnog elementa i trenutnog elementa. POGLEDAJ IZGLED PRIMJERA. Znak strelice (=>) koji smo koristili u gornjem primjeru izraz je funkcije strelice. Ima kraću sintaksu od izraza funkcije i učinio bi da se ova ključna riječ ponaša ispravno. Pogledajte lekcije o ES6 karakteristikama da biste saznali više o funkciji strelice.



Zaustavljanje širenja događaja (Event Propagation)

Takođe možete zaustaviti širenje događaja u sredini ako želite spriječiti da bilo koji obrađivač događaja elementa pretka bude obaviješten o događaju. Na primjer, pretpostavimo da imate ugnježdene elemente i da svaki element ima onclick rukovatelj događajima koji prikazuje dijaloški okvir upozorenja. Obično, kada kliknete na unutrašnji element, svi obrađivači izvršavaće se odjednom, jer se bubble događa do DOM stabla.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Event Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showAlert() {
        alert("You clicked: "+ this.tagName);
    }

    var elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showAlert);
    }
</script>
</body>
</html>

Evo jednostavne demonstracije koju smo kreirali koristeći gornji primjer. Ako kliknete na bilo koji podređeni element, izvršava se i rukovatelj događajima na roditeljskim elementima i možda ćete vidjeti više okvira upozorenja. POGLEDAJ IZGLED PRIMJERA. Da biste spriječili ovu situaciju, možete zaustaviti bubbling događaj DOM stabla pomoću metode event.stopPropagation(). U sljedećem primjeru slušatelj događaja klika na nadređenim elementima neće se izvršiti ako kliknete na podređene elemente.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Zaustavljanje Event Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div id="wrap">DIV
    <p class="hint">P
        <a href="#">A</a>
    </p>
</div>

<script>
    function showAlert(event) {
        alert("You clicked: "+ this.tagName);
        event.stopPropagation();
    }

    var elems = document.querySelectorAll("div, p, a");
    for(let elem of elems) {
        elem.addEventListener("click", showAlert);
    }
</script>
</body>
</html>

Evo ažurirane demonstracije. Ako kliknete bilo koji podređeni element, pojavit će se samo jedno upozorenje. POGLEDAJ IZGLED PRIMJERA. Uz to, čak možete spriječiti izvršavanje bilo kog drugog slušatelja pridruženog istom elementu za isti tip događaja metodom stopImmediatePropagation(). U sljedećem primjeru ćemo hipervezi pridružili više slušatelja, ali samo jedan slušatelj hiperveze će se pokrenuti kad kliknete na vezu i vidjećete samo jedno upozorenje.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Zaustavljanje Immediate Propagation Demo</title>
<style type="text/css">
    div, p, a{
        padding: 15px 30px;
        display: block;
        border: 2px solid #000;
        background: #fff;
    }
</style>
</head>
<body>
<div onclick="alert('Kliknuli ste: ' + this.tagName)">DIV
    <p onclick="alert('Kliknuli ste: ' + this.tagName)">P
        <a href="#" id="link">A</a>
    </p>
</div>

<script>
    function sayHi() {
        alert("Ćao!");
        event.stopImmediatePropagation();
    }
    function sayHello() {
        alert("Hello World!");
    }
    
    // Povezivanje više obrađivača događaja na hipervezu
    var link = document.getElementById("link");
    link.addEventListener("click", sayHi);  
    link.addEventListener("click", sayHello);
</script>
</body>
</html>


Sprječavanje zadane radnje

Neki su događaji povezani sa zadanom radnjom. Na primjer, ako kliknete na pretraživač veze, vodi vas do cilja veze, kada kliknete na dugme za slanje obrazca, pretraživač pošalje obrazac itd. Takve zadane radnje možete spriječiti metodom prevenDefault() objekta događaja. Međutim, sprječavanje zadanih radnji ne zaustavlja širenje događaja. Događaj se nastavlja širiti na DOM stablo kao i obično. Pogeldajmo primjer:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JavaScript Sprječavanje zadane radnje Demo</title>
</head>
<body>
<form action="/examples/html/action.php" method="post" id="users">
    <label>Upiši ime:</label>
    <input type="text" name="first-name" id="firstName">
    <input type="submit" value="Pošalji" id="submitBtn">
</form>

<script>
    var btn = document.getElementById("submitBtn");
    
    btn.addEventListener("click", function(event) {
        var name = document.getElementById("firstName").value;
        alert("Izvini, " + name + ". preventDefault() neće vam dopustiti da pošaljete ovaj obrazac!");
        event.preventDefault(); // Sprječava predaju obrasca
    });
</script>
</body>
</html>