JavaScript eseménykezelés ajax következményekkel

ajaxFogjunk egy elemet (mondjuk egy linket) és JavaScripttel adjuk meg számára a click eseményhez egy saját eseménykezelőt, mondjuk a linkClick() függvényt hívjuk meg ha a user rákattan egy linkre. Ettől függetlenül mondjuk legyen a egy click kezelőnk a li elemekre is, mondjuk a liClick() függvény. Történetünk akkor kezd bonyolódni amikor egy olyan linkkel találjuk szembe magunkat ami egy li leszármazottja. Melyik függvény fog lefutni, illetve milyen sorrendben fognak futni, és mi ennek az egésznek a hatása? Na erről lesz most szó.

Capturing vs Bubbling

Persze a kérdésre más-más emberkéknek más-más volt a kézenfekvő válasz, így most nagy örömünkre különböző megvalósításokkal találjuk magunkat szembe az egyes böngészőkben. A capturing elv szerint a liClick() fut le előszőr, és ezután a linkClick(). Vagyis a DOM fában lefelé fut az eseménykezelés, előszőr a DOM fában magasabban elhelyezkedő elemé hajtódik végre, majd az alatta lévő(k)é.

A bubbling megvalósítás szerint ennek pont az ellenkezője, azaz a DOM fában felfelé fut az eseménykezelés, előszőr a linkClick(), majd a liClick() hajtódik végre.

A W3C kivételesen nem foglalt állást egyik megoldás mellett sem, hanem mindkettőt integrálta. A szabvány szerint előszőr az esemény a capture elv szerint lefut a DOM fában míg el nem éri a célját, aztán ott megfordul és felfut a DOM fában a bubble szerint.

A dilemma feloldásaként általában a javascript libraryk adnak valamilyen megoldást arra, hogy a fejlesztő választhasson, hogy a lehetséges megközelítések közül melyiket válassza. Például a prototype js library az Event.observe metódusában használ paraméterként egy useCapture változót, mellyel megadhatjuk, hogy az alapértelmezett bubbling eljárást, vagy a capture-t akarjuk használni.

Alapértelmezett eseménykezelő regisztrálás

Ritkán fordul elő, hogy a fenti pédában felvázolt esemény ütközéssel találnánk szembe magunkat, azonban a fentieknek van egy sokat használható következménye, mely egyszerűbbé, rövidebbé, gyorsabbá és átláthatóbbá teheti a kódunkat. Ez pedig az, hogy alapértelmezett eseménykezelőket regisztrálunk.

A fentiekből az következik, hogy ha a document elemre megadok egy általános eseménykezelőt, mondjuk legyen a defaultClick() függvény, akkor ez mindenféleképpen végre fog hajtódni, a böngészőnk akár a bubble, akár a capture akár a W3C elvet is valósította meg.

Persze ebből meg az következik, hogy általánosságban nekünk az volna a legjobb, ha valahogyan megakadályoznánk, hogy egy kattintás arra ösztökélje a JavaScript értelmezőt, hogy ide-oda rohangáljon a DOM-ban. Ha erre szükségünk van (lentebb látható lesz, hogy ajax alkalmazások esetében általában nincs) a cancelBubble tulajdonságot kell true-ra állítanunk.

Egy gyakorlati példa

A magam részéről az egész probléma és elv ajax deluxe alkalmazások létrehozásakor jött elő. Mondjuk van egy alkamazásom, amiben szerepel egy lista, az elvégzendő feladataimról. Az alkamazással tudok új feladatokat létrehozni, módosítani, törölni. Ezek közül a módosítást úgy tudom elindítani ha rákattintok a feladat szövegére.

Magát a html kódot úgy építem fel, hogy a feladatok egy rakás egymásba ágyazott ul > li listában vannak elhelyezve és maguk a feladatok linkek.

Ebben a felállásban egy feladat linkjére való kattintáskor azt szeretnénk, hogy meghívja a feladatokat szerkesztő függvényünket és hogy a linkre való kattintás ne töltse újra az oldalt, mint ahogy az egy nem ajax alkalmazás esetén történne. Ehhez valami ilyesmi kódot kellene írnunk (prototype library használat esetén):

var task = {
  init : function(){
    document.links.each(function(aLink){
      Event.observe(aLink, 'click', task.clickKezelo.bindAsEventListener(aLink));
      });
    },

Létrehozzuk az ojjektumot, és az összes linken végigsétálva csatoljuk a click eseményhez a saját eseménykezelőnket.

  clickKezelo : function(e){
    task.szerkeszto();
    if(e) Event.stop(); return false;
    },

Ez a függvény a saját eseménykezelőnk. Az első sor meghívja a szerkesztést végző függvényünket, az utolsó sor miértjét meg már leírtam itt. Lényeg, hogy itt a böngésző számára meg nem történtté tesszük a kattintást, vagyis se nem rohangál föl alá tovább a DOM-ban, se nem tölti újra az oldalt.

  szerkeszto : function(){
    //mindenféle ügyes cuccok, amik lekezelik a szerkesztést
    }
  }

Event.observe(window, 'load', task.init);

Végül jön a szerkesztést végző függvény mint a task objektumunk utolsó metódusa. Az utlsó sorban pedig arról gondoskodunk, hogy ha az oldal betöltődött és a DOM már él akkor lefusson a task.init() függvény.

Ezzel a megoldással (többek között) az lesz a gondunk, hogy ha az alkamazás beilleszt egy új feladatot, akkor az egy új linket fog jelenteni html szempontból, amihez nekünk kell külön kézzel hozzácsatolnunk a click kezelést. Ez kényelmetlen, nehézkes és sok hibalehetőséget rejt magában.

Ha azonban kihasználjuk az alapértelmezett eseménykezelő regisztrálás lehetőségét, akkor az új elemekhez automatikusan csatolódni fog a click kezelése. Ebben az esetben valami ilyesmi kódot kapunk.

var task = {
  init : function(){
    Event.observe(document, 'click', task.clickKezelo);
    },

Nem foglalkozunk külön a linkekkel, hanem egyszerűen magához a documentumhoz csatolunk egy eseménykezelőt.

  clickKezelo : function(e){
    if(e.target.nodeName == 'A'){
      task.szerkeszto();
      if(e) Event.stop(); return false;
      }
    },

Lássuk az eseménykezelőnket. Az e.target az a html elem lesz amire ténylegesen rákattintottunk. Azért vizsgálom, hogy ez egy link volt-e, mert akár egy select elemet is lenyithattam a kattintással, vagy egy input mezőre is kattinthattam éppen. Ha tényleg egy linkre kattintottam, akkor meghívom a szerkesztő metódust és felfüggesztem az esemény további futását. Ez ugyan nincs a kódban, de ha az if nem teljesül, akkor a kattintás a böngésző alapértelmezett eseménykezelőjét fogja meghívni, azaz ha mondjuk egy üres részre kattintottam akkor nem fog semmi történni, ha egy selectre akkor az le fog nyílni.

  szerkeszto : function(){
    //mindenféle ügyes cuccok, amik lekezelik a szerkesztést
    }
  }

Event.observe(window, 'load', task.init);

A kód további részében nincs változás.

Az egész hókuszpókusszal annyit nyerünk (ami itt nem látszik), hogy az új taskokat hozzáadó metódusunk lényegesen egyszerűsödhet. Egy ilyen egyszerű példánál nem olyan szembeötlő a különbség, de a tényleges alkalmazásban az alapértelmezett eseménykezelők regisztrálása 10%-kal csökkentette a végső kód méretét, és feleslegessé tett 3 különböző objektum létrehozását és kezelését, ami felszabaduló memóriát, és sebességnövekedést eredményezett.

A tanulság

A leírtaknak több tanulsága is van. Számomra az egyik az, hogy néha hosszabb idő is eltelik úgy hogy az ember használ egy programozási nyelvet, és egyszer csak szembetalálkozik egy megoldással ami lényegesen egyszerűbbé tette volna az életét és a kódjait ha hamarabb ismerkedik meg vele. A másik az, hogy a tisztán programozás elméleti tudásnak is lehetnek időnként gyakorlati hasznai.

Megjegyzés

A kód első változatát nem teszteltem sehol, előfordulhat, hogy van benne valami szintaktikai hiba. A második változat működik FF alatt, de IE alatt nem teszteltem, előfordulhat, hogy az okoz valami meglepetést. Ha igen nyugodtan JólMegMondjátok a magatokét! 🙂

One thought on “JavaScript eseménykezelés ajax következményekkel

  1. Pingback: WebMánia » Ajax alkalmazások építése - lépésről lépésre

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöljük.