A JavaScript, a click esemény, és a buborékozás

By | 2012. március 20.

Aki egy kicsit foglalkozott már JavaScripttel tudja, hogy ha az egér kattintás eseményét szeretném lekezelni, akkor az adott HTML elem onclick metódusát kell implementálnom, azt hívja meg a böngésző. Valószínűleg már kevesebben tudják, hogy mi van akkor, ha nem csak az elemnek, hanem a szülőjének is implementálom az onclick metódusát, akkor melyik hívódik meg? Ha mind a kettő, akkor milyen sorrendben?

A konkrét probléma a következő: a Cukorka oldal készítésénél szerettem volna azt a működést megvalósítani, hogy egy linkre kattintva megjelenik egy ablakocska, máshova kattintva eltűnik. A feladat egyszerűnek tűnik, a megnyitást a link onclick metódusában írom meg, a becsukást pedig a body onclick metódusában. Mivel maga a link is a body elem része, így probléma lehet, ha a linkre kattintva a body onclick metódusa is meghívódik, hiszen az rögtön bezárja az ablakocskát.

A működés egy példán keresztül

A következő példában létrehoztam a body címkén belülre egy bekezdést, és bele egy linket, majd mindegyik elem click eseményére feliratkoztam. Nézzük meg, hogy milyen sorrendben hívódnak meg! (Az alábbi ablak jobb felső részében lehet választani, hogy a JavaScriptet, vagy a HTML-t szeretnénk megnézni, majd a nyilacska ikonnal indítható)

Jól látszik, hogy először a link eseménykezelője hívódik meg, majd lépésről lépésre felfelé haladva a DOM fában terjed tovább. Ezt a folyamatot hívja a szabvány buborékozásnak (Bubbling), ami igen találó név, hiszen akár a buborék alulról felfelé terjed. Másrészről viszont nagyon nehezen kereshető a szó, én is megszenvedtem, mire megtaláltam :).

A buborékozás megakadályozása

Most már ismerjük a működést, viszont így még nem jó, hiszen ahogy az előbb is írtam, így egyből becsukódik az ablakocskám, ahogy megnyitottam. Szerencsére van megoldás, a szabvány lehetőséget biztosít a buborékozás letiltására, egyszerűen úgy, hogy az eseménykezelőből meghívjuk az event objektumon a stopPropagation() metódust (az Internet Explorer régebbi verziói nem tartalmazzák ezt a metódust, ott a “e.cancelBubble = true;” használandó):

Remek, így már nem terjed tovább. A példában bónuszként még azt is megmutatom, hogyan lehet a kattintás alapértelmezett hatását letiltani (link esetén az oldal megnyitását), ez is hasonlóan egyszerű, csak meg kell hívni az event objektum preventDefault() metódusát. (Megjegyzés: a legtöbb böngészőben az is az alapértelmezett működés letiltását eredményezi, ha az eseménykezelő függvény visszatérési értéke false. Bár ez nincs benne a szabványban, Internet Explorer kompatibilitási okokból támogatott.) Ezzel a módszerrel lehet például megvalósítani, hogy egy szövegrészletet ne lehessen kijelölni (a mousedown esemény alapértelmezett működését kell letiltani).

Ezzel véget ért a cikkem fő története, még egy-két érdekességet írok az eseményekkel kapcsolatban.

Egy kis történelem

A DOM események a Netscape Navigator 2.0-ás verziójában jelentek meg először, amikor én még csak első osztályos voltam az általános iskolában (1996), és fogalmam sem volt arról, hogy mi az az internet 🙂 . Akkoriban még nem volt szabványosítva, de ezt a megoldást vette át az Internet Explorer 3-as verziója is. A legelső DOM szabvány csak 1998-ban jelent meg, viszont még nem tartalmazott eseményekkel kapcsolatos részleteket. Erre egészen 2000-ig nem is került sor, amikorra megjelent a DOM Level 2. Addigra viszont már a Netscape népszerűsége hatalmas lejtmenetben volt, és az Internet Explorer vált a domináns böngészővé, így a Microsoftnál megtehették, hogy nem implementálták a szabványt, hanem saját megoldást készítettek. Így volt ez egészen mostanáig, hiszen csak az egy évvel ezelőtt megjelent IE 9 támogatja teljes mértékben a W3C előírásait.

További lehetőségek

Egy DOM eseményekkel foglalkozó cikkben muszáj megemlíteni néhány további jellegzetességet. A szabvány arra is lehetőséget ad, hogy a hagyományos onclick metóduson túl saját függvényeinket is regisztráljuk az eseményhez, így az is meghívódik az onclick mellett a kattintás hatására. Ezt az addEventListener metódus segítségével tehetjük meg. Azonban, ahogy írtam is, az Internet Explorer 9-esnél régebbi verziói még nem támogatják (helyette van azokban az attachEvent), így ennek a használatát nem javaslom. Ezek helyett érdemesebb használni egy JavaScript osztálykönyvtárat/keretrendszert, amely böngészőfüggetlen azáltal, hogy a háttérben az adott böngészőnek megfelelő megoldást használja. Íme egy példa jQuery keretrendszert használva:

+1 érdekesség a végére

A Netscape fejlesztői azt a megoldást támogatták, hogy a buborékozással ellentétesen, felülről lefelé terjedjen az esemény a DOM fában (ezt hívják Capturingnek), az Internet Explorer fejlesztői viszont az alulról felfelé terjedés hívei voltak (ez a már korábban említett Bubbling). A W3C szabvány alkotói nem akartak állást foglalni, így mind a két lehetőséget belevették a szabványba oly módon, hogy két részre osztották az esemény terjedését, egy Capturing szakaszra, és az azt követő Bubbling szakaszra. Az addEventListener alapértelmezett módon a Bubbling szakaszba regisztrálja az eseménykezelőt, de ha harmadik paraméterként átadunk neki egy true értéket, akkor a Capturing szakaszba kerül. Kiváló módja az átláthatatlan alkalmazás készítésének, ha mind a két szakaszt használjuk. 🙂 Általánosságban a buborákozás terjedt el inkább, a legtöbb keretrendszer is ezt használja.

Összefoglalás

Összefoglalásul elmondható, hogy ha alámerülünk a szabvány mélyére, akkor egy egészen komplex esemény modellel találjuk szemben magunkat. Nagyon fontos, és erre az egész cikkben felhívtam a figyelmet, hogy különböző böngészők bizonyos funkciókat eltérően implementálhatnak, így én feltétlenül azt javaslom, hogy használjatok böngészőfüggetlen keretrendszereket.

  • Én is hasonlót szoktam használni, nekem még egy olyan problémám is volt, hogy a megnyíló doboznak nem szabad eltűnnie, ha rákattintasz, ez jQueryben így oldottam meg:

    //Login box
    var box = $('#login');
    var link = $('.sign-in');

    link.click(function(e) {
    box.show();
    e.preventDefault();
    });

    $(document).click(function() {
    box.hide();
    });

    box.click(function(e) {
    e.stopPropagation();
    });

  • szelpe

    Igen, ez a következő lépés, köszi, hogy kiegészítetted! 🙂