Ebben a fejezetben a kliensoldali dinamizmus egyik kulcselemével, az eseménykezelés további, fontos részleteivek ismerkedünk meg. Ezek az ismeretek elengedhetetlenek hatékony kliensoldali kód írásához.
Az előző fejezetben megismerkedtünk az eseménykezelés alapjaival. Ennek során láthattuk, hogy az események alapvetően valamilyen történés következtében váltódnak ki, áttekintettük a főbb típusait, és megnéztük azt is, hogy hogyan lehet reagálni egy bekövetkezett eseményre programozottan egy eseménykezelő függvénnyel. A feliratkozásnak több módját is láthattuk, amelyek többsége manapság már elavult, egyedül a szabványos módszer használata ajánlott.
Ebben a fejezetben egy kis példán keresztül nézzük meg, milyen további ismeretekre van szükségünk az eseménykezeléssel kapcsolatban.
Adott néhány hivatkozás listába rendezve. Oldjuk meg azt, hogy a SHIFT gomb nyomva tartása mellett egy hivatkozásra kattintva, az ne töltődjön be a böngészőbe – mint ahogy normálisan tenné –, hanem a hivatkozás URL-je a JavaScript konzolon jelenjen meg!
A feladathoz tartozó oldal például így néz ki:
Az oldal HTML szerkezete a következő:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Eseménykezelés - Hivatkozások listája</title> <script type="text/javascript" src="link.js"></script> </head> <body> <ul> <li><a href="http://www.elte.hu">ELTE</a></li> <li><a href="http://www.inf.elte.hu">ELTE Informatika Kar</a></li> <li><a href="http://www.inf.elte.hu/mot">Médiainformatikai Tanszék</a></li> </ul> </body> </html>
Az előző fejezet ajánlását követve a HTML állomány mentes mindenféle beépített JavaScripttől, az oldal viselkedéséért a külső JavaScript állományban megadott kód felel. A feladatban az egyes hivatkozások href attribútumának kiírása a cél.
Az előző fejezetben azt láttuk, hogy minden olyan elemet id attribútummal kell felruházni, amit JavaScriptből el szeretnénk érni. Esetünkben a hivatkozásokat kell egyedi azonosítókkal ellátnunk, hiszen két okból is el kell érnünk a őket: egyrészt egy általuk jelzett eseményre, a rákattintásukra (click), kell feliratkoznunk, másrészt az eseménykezelő függvényen belül a kattintott hivatkozás href attribútumát kell kiolvasnunk. Ennek megfelelően a HTML szerkezet az id attribútumok hozzáadásával változik:
<ul> <li><a id="link1" href="http://www.elte.hu">ELTE</a></li> <li><a id="link2" href="http://www.inf.elte.hu">ELTE Informatika Kar</a></li> <li><a id="link3" href="http://www.inf.elte.hu/mot">Médiainformatikai Tanszék</a></li> </ul>
A link.js állományban először is megjelenik a document.getElementById függvényt rövidítő $ segédfüggvény. Az oldal betöltődése után lefutó init függvényben minden hivatkozás click eseményéhez egy-egy eseménykezelő függvényt rendelünk mutat1, mutat2, mutat3 néven. Mindegyik ugyanúgy néz ki: először ki kell olvasni a hivatkozás URL-jét a href attribútumból (pontosabban ha van ilyen HTML attribútum, akkor van ilyen tulajdonsága a hivatkozásnak megfelelő DOM objektumnak); aztán a kiolvasott értéket meg kell jeleníteni a konzolon. Az egyedüli különbség az egyes eseménykezelő függvényeken belül az adott hivatkozás azonosítója. A függvények végén szereplő alert-re azért van szükség, hogy megállítsa a kattintás következtében betöltődő új oldalt, és meg tudjuk nézni, hogy a JavaScript konzolon valóban az adott hivatkozás URL-je jelent-e meg.
function $(id) { return document.getElementById(id); } function init() { $('link1').addEventListener('click', mutat1, false); $('link2').addEventListener('click', mutat2, false); $('link3').addEventListener('click', mutat3, false); } function mutat1() { var href = $('link1').href; console.log(href); alert('stop'); } function mutat2() { var href = $('link2').href; console.log(href); alert('stop'); } function mutat3() { var href = $('link3').href; console.log(href); alert('stop'); } window.addEventListener('load', init, false);
Az alábbi képernyőképen jól látszik a fenti kód működés közben. Látható, hogy a kattintott hivatkozás URL-je megjelenik a konzolon, és utána egy felugró ablak állítja meg az új oldal betöltődését.
A fenti kódrészletben zavaró lehet, hogy mindhárom mutat függvény az azonosító kivételével ugyanazt a kódrészletet tartalmazza. A közös részeket egy új függvénybe emelhetjük ki (mutatKozos).
function mutatKozos(obj) { var href = obj.href; console.log(href); alert('stop'); } function mutat1() { mutatKozos($('link1')); } function mutat2() { mutatKozos($('link2')); } function mutat3() { mutatKozos($('link3')); }
Sajnos ezzel a megoldással nem csökkentettük az eseménykezelő függvények számát. A mutat1, mutat2 és mutat3 függvény továbbra is megmarad. Ennek az az oka, hogy egyelőre az eseménykezelőkben csak konkrét azonosítóval tudunk hivatkozni: a mutat1-ben a link1 jelenik meg, a mutat2-ben a link2, stb.
A példában szerencsénk van, hogy csak három hivatkozás van. De mit kellene csinálnunk 100 hivatkozás esetén? Jelenlegi tudásunkkal maradna a favágó módszer: mindegyiknek egy-egy id, és egy-egy eseménykezelő függvény. Ez 100 id-t és 100 eseménykezelő függvényt jelentene. Sejthető, hogy ez hosszú távon áttekinthetetlen kódot eredményezne.
Első lépésben próbáljunk megszabadulni a sok eseménykezelő függvénytől. A sok függvényt az okozta, hogy mindegyik függvényben meg kellett jelennie a konkrét azonosítónak. Nem lehetne azonban az eseménykezelő függvényben valahogy az eseményt kiváltó objektumot elérni? Ebben az esetben ugyanis elég lenne mindhárom hivatkozásra kattintáskor ugyanazt a függvényt futtatni, és a függvényen belül lekérdezni, hogy melyik hivatkozás váltotta ki az eseményt.
Az eseménykezelő függvényben lehetőség van az eseményt jelző DOM objektum elérésére a this objektumon keresztül. Ezáltal több elem eseményéhez is rendelhető ugyanaz a függvény, ezen belül pedig elérhető az az objektum, aki az eseményt jelezte, másképpen az eseménykezelőt meghívta.
Ezzel az előzőekben felvetett problémák egy része megoldódik. Mindhárom hivatkozás click eseményéhez elég ugyanazt a mutat függvényt rendelni, amin belül pedig a this tartalmaz referenciát a kattintott linkre.
function init() { $('link1').addEventListener('click', mutat, false); $('link2').addEventListener('click', mutat, false); $('link3').addEventListener('click', mutat, false); } function mutat() { console.log(this); var obj = this; var href = obj.href; console.log(href); alert('stop'); }
Az első console.log kiírja a konzolra a kattintott hivatkozás adatait, ami által láthatjuk, hogy valóban mindig a megfelelő linkre mutat. A második és harmadik sor összevonható, szétválasztásukkal azt szeretnénk kiemelni, hogy a kattintott objektumra valóban a this mutat. A kód többi része pedig az előzőekben megismert feldolgozási logikát tartalmazza.
A megoldásunk eddig nem foglalkozott a feladat egy lényegi kitételével, mégpedig azzal, hogy csak a SHIFT billentyű lenyomásakor kell kiírnunk az URL-t a konzolra. De honnan tudjuk, hogy a SHIFT billentyű le volt-e nyomva kattintás közben? Általában honnan tudom az eseményhez tartozó részleteket, mint például, hogy milyen billentyűt nyomtak le, melyik egérgombot nyomták le, hol volt az egér pozíciója a képernyőn, stb? A válaszhoz ismerkedjünk meg az eseményobjektummal.
Az esemény körülményeit leíró információkat a környezet az ún. eseményobjektumon keresztül teszi elérhetővé. A szabványos eseménykezelési modellben az eseményobjektum az eseménykezelő függvény első paramétereként érhető el.
function fv(e) { //e az eseményobjektum }
Mint minden objektum, ez is számos tulajdonsággal és metódussal rendelkezik, amin keresztül számos paraméter olvasható ki. Ezeket a DOM eseménykezeléssel kapcsolatos jelenlegi és elkövetkező szabványa írja le. Az eseményobjektum többek között információt tárol
A példafeladat megoldása során tehát az eseményobjektumon keresztül tudunk informálódni a SHIFT billentyű állapotáról. A mutat függvényt kiegészítjük egy paraméterrel, amin keresztül automatikusan megkapja az eseményobjektumot, majd ennek a shiftKey tulajdonságát megvizsgálva döntjük el, hogy kell-e a konzolra írni az eredményt.
function mutat(e) { console.log(e); if (e.shiftKey) { var obj = this; var href = obj.href; console.log(href); alert('stop'); } }
A függvény törzsének első lépéseként kiíratjuk a konzolra az eseményobjektumot, így alaposabban megvizsgálhatjuk tulajdonságait.
Ha futtatjuk az alkalmazást, akkor azt tapasztaljuk, hogy SHIFT lenyomása nélkül minden normálisan működik, a böngésző követi a hivatkozást. SHIFT-et lenyomva azonban a feladat kiírásának megfelelően megjelenik az URL, majd az alert által generált felugró ablakot követően a böngésző megint csak követi a linket.
A továbblépéshez tudni kellene, hogyan lehet megakadályozni az oldalbetöltést, pontosabban azt, hogy a hivatkozásra kattintva a böngésző az oldalbetöltést is elvégezze. Ehhez tudni kell azt, hogy bizonyos elemek bizonyos eseményeihez a böngészőben alapértelmezett műveletek vannak rendelve. Ilyen a hivatkozás kattintás eseménye is, amelyhez a hivatkozás URL-jének betöltése van rendelve. Az általános kérdés tehát: Milyen alapértelmezett műveletek vannak, és ezek végrehajtása hogyan akadályozható meg?
A böngészőben bizonyos elemek bizonyos eseményéhez alapértelmezett műveletek tartoznak. Néhány példa ezekre:
Az alapértelmezett műveletek végrehajtása kétféleképpen akadályozható meg. A szabvány szerint az eseményobjektum preventDefault() metódusa éppen erre való, és az eseménykezelő függvényen belül bárhol meghívható:
function fv(e) { e.preventDefault(); //... }
A másik lehetséges módszer nem szabványos ugyan, de mindenhol működik. Eszerint az eseménykezelő függvénynek hamis értékkel kell visszatérnie az alapértelmezett művelet megakadályozása érdekében:
function fv(e) { //... return false; }
A példafeladat megoldásában tehát e módszer valamelyikével kell élnünk, és a böngésző nem követi a linket, ha a SHIFT-et lenyomva tartva kattintunk rá.
function mutat(e) { if (e.shiftKey) { e.preventDefault(); var obj = this; var href = obj.href; console.log(href); //vagy //return false; } }
Ezzel a feladatot megoldottuk. Az eseménykezelő függvény jól látja el a dolgát. Egy kérdésre azonban még nem válaszoltunk fentebb. Mit kell tenni, ha nem három, hanem 100 hivatkozásunk van? A jelenlegi megoldás szerint ugyan egy eseménykezelő függvényünk van, de ehhez 100 db id-t kell felvenni a HTML-ben és 100 db eseménykezelő-regisztrációt az init függvényben. Ez megint csak felesleges kódismétlés, ami hibához és karbantarthatatlan kódhoz vezethet.
Mondhatná valaki, hogy ebben a 100 id-t nem ússzuk meg ugyan, de legalább az eseménykezelő hozzárendelését el lehetne végezni egy ciklusban. Ez csak akkor működik, ha az id megfelelő formátumú, de nem nyújt megoldást arra, ha például egy új link kerül beszúrásra. Ebben az esetben ugyanúgy kézzel kell az eseményt hozzárendelni. Ráadásul JavaScript programozóként ritkán van ráhatásunk a HTML szerkezet összes elemére, főleg akkor, ha azokat nem mi szerkesztjük (hanem pl. adatbázisból generálják).
Olyan megoldás kellene, ahol nem kell egyesével azonosítókat felvenni a hivatkozásokhoz, ami elég rugalmas ahhoz, hogy tetszőleges számú új linket vegyünk fel. A megoldást az események buborékolása adja.
Amikor egy elemen kiváltódik egy esemény, akkor az esemény nemcsak a közvetlenül érintett elemen hívódik meg, hanem mindegyik szülőelemen is szépen egymás után sorban, a faszerkezetben fölfele haladva. Olyan ez, mintha az eredeti esemény egy kis buborék lenne, és a fastruktúrában föllibegne a hierarchia tetejére. Ha tehát a példánkban egy hivatkozásra kattintunk, akkor a click esemény meghívódik az a, utána a li, majd az ul, body és html elemeken sorban egymás után.
Ezt demonstrálja az alábbi ábra, ahol ugyanaz a fastruktúrabeli szülői lánc többféleképpen is látható. A felső 3D-s részen a fastruktúra mélyebb elemei újabb és újabb rétegekként jelennek meg. Az esemény itt egy adott elemről mindig az alatta lévő elemnek adódik át. Középen egy egyszerű elérési útvonal képében látható, hogy az a elemtől milyen út vezet a dokumentum legfelső szintjét képező html-ig. Végül alul a szokásos fastruktúrában látjuk ugyanezt.
Ha valamelyik őselemen kezeljük az eseményt, akkor is lehetőségünk van az eseményt eredetileg kiváltó objektumnak a lekérdezésére az eseményobjektum target attribútumán keresztül.
Azt, amikor az esemény kezelését egy magasabb szintű elemen végezzük el úgy, hogy az eseményt eredetileg kiváltó objektummal dolgozunk, delegálásnak hívjuk. A delegálás egy gyakran alkalmazott programozási minta, általa rövid és hatékony kódok írhatóak. Minden olyan esetben használható, ahol sok, akár változó számú elemen kell valami közöset végrehajtani.
Egy eseménykezelés során kétféle objektumot kérdezhetünk le. A this arra az elemre mutat, amihez az eseménykezelő függvényt rendeltük, az eseményobjektum target-je viszont az eseményt eredetileg kiváltó objektumra mutat. Az előbbit elérhetjük még az eseményobjektum currentTarget tulajdonságán keresztül is.
Delegálás során meg kell győződnünk arról, hogy az eseményt eredetileg jelző objektum az az objektum, amivel dolgozni szeretnénk, hiszen nem biztos, hogy a megfelelő gyerekelemtől jött fel az esemény. Ilyenkor a target objektum valamely tulajdonságát nézhetjük. Igen gyakran arra vagyunk kíváncsiak, hogy mi az elem neve. Ezt az objektum tagName tulajdonságával kérdezhetjük le, ami csupa nagybetűsen adja vissza a HTML tag nevét. Ezzel részletesebben a DOM-ról szóló fejezetben ismerkedünk meg.
Végül még egy fontos információ az események buborékolásával kapcsolatban az, hogy a buborékolás megállítható az eseményobjektum stopPropagation metódusának hívásával.
Példafeladatunkhoz visszatérve, a delegálás azt jelenti, hogy nem feltétlenül kell hivatkozás szintjén kezelnünk a kattintás eseményét. Bármelyik hivatkozásra kattintunk a click esemény előbb-utóbb eljut valamelyik közös ősig. A legközelebbi közös őse mindegyik linknek az ul elem. Ha itt kezeljük az eseményt, akkor egyrészt elég csak egy eseménykezelő függvény hozzárendelését elvégeznünk, ezen belül lekérhető az a hivatkozás, amire kattintottunk, másrészt a programlogikánk független lesz a hivatkozások id-jától, és rugalmas lesz az elemen belül megjelenő hivatkozások számát illetően, ami azt jelenti, hogy akár menet közben bővíthető a hivatkozások száma.
A HTML szerkezetben tehát elég csak az ul-nek id-t adni, a hivatkozásoknál törölni is lehet:
<ul id="lista"> <li><a href="http://www.elte.hu">ELTE</a></li> <li><a href="http://www.inf.elte.hu">ELTE Informatika Kar</a></li> <li><a href="http://www.inf.elte.hu/mot">Médiainformatikai Tanszék</a></li> </ul>
A JavaScript kódban pedig elég csak az ul-hez eseménykezelőt rendelni, a mutat függvényen belül lekérdezhető az eseményt eredetileg kiváltó objektum. Mivel nemcsak az a elemre, hanem a li vagy ul elemre kattintva is ez az eseménykezelő hívódik meg, a függvény elején szükséges ellenőriznünk, hogy a kattintott elem hivatkozás volt-e vagy sem.
function init() { $('lista').addEventListener('click', mutat, false); } function mutat(e) { if (e.target.tagName === 'A') { if (e.shiftKey) { e.preventDefault(); var obj = e.target; var href = obj.href; console.log(href); } } }
Szerencsére az írás időpontjában elérhető böngészők mindegyike támogatja az eseménykezelés szabványos megoldásait, így a fenti kódok minden böngészőben egyformán futnak. Ha azonban szükséges régebbi böngészők támogatása is, akkor meg kell ismerkednünk a böngészők közötti eltérésekkel és ezeket az eltéréseket áthidaló megoldásokkal. Az eltérések jelentős része az Internet Explorer korábbi verzióiban található, történeti okok miatt ugyanis azok eseménykezelési modellje eltért a szabványétól.
Az előző fejezetben már volt szó róla, hogy többféle regisztrálási módja létezik az eseménykezelő függvényeknek. Manapság a legtöbb böngésző egyformán támogatja a szabványos módszert, de régebbi böngészőknél előfordulhat, hogy más módon kell az eseménykezelőket eseményekhez rendelni.
Az egységes megoldás során meg kell vizsgálni, hogy a böngésző támogatja-e az adott módszert. Ennek során egész egyszerűen egy elágazásban megvizsgáljuk, hogy egy elem rendelkezik-e adott nevű tulajdonsággal. Ha nem, akkor biztosan nem támogatja az adott funkciót, ha igen, akkor nagy valószínűséggel az elvárt működést biztosítja. Ebben az esetben az adott metódust használjuk. A módszert tulajdonság-ellenőrzésnek hívjuk (angolul feature detection).
Az egész vizsgálatot érdemes egy csomagolófüggvénybe zárni (kezelotRegisztral) annak érdekében, hogy a regisztrálási logika újrafelhasználható legyen. Először a szabványos módszer addEventListener metódusát nézzük meg, majd a Microsoft módszer attachEvent metódusának támogatását. Ha egyik sem érhető el, akkor a tradicionális módszert alkalmazzuk.
function kezelotRegisztral(elem, tipus, kezelo) { //Szabványos módszer if (elem.addEventListener) { elem.addEventListener(tipus, kezelo, false); } //Microsoft módszer else if (elem.attachEvent) { elem.attachEvent('on' + tipus, kezelo); } //Tradicionális módszer else { elem['on' + tipus] = kezelo; } }
Használata a példakódunkban a következő:
function init() { kezelotRegisztral($('lista'), 'click', mutat); }
Jelentős eltérés van az eseményobjektum megszerzésében a szabványos és a Microsoft módszer között. A szabványos módszer az eseménykezelő függvény első paramétereként teszi elérhetővé az eseményobjektumot. A Microsoft módszerben az eseménykezelő függvényen belül a globális window.event objektum tartalmazza az esemény körülményeit.
Ennek áthidalására az egyik megoldás az eseménykezelőn belül teszi egységessé az eseményobjektum elérését. A megoldás során meg kell vizsgálni, hogy az első paraméter töltve van-e. Ha nincs, azaz az értéke undefined, akkor a window.event objektumon keresztül érhető el. Egy eseménykezelő függvény tehát így kezdődik:
function fv(e) { if (!e) e = window.event; //... e felhasználása }
Ennek a megoldásnak nagy hátránya, hogy minden eseménykezelő függvénynek így kell kezdődnie, ha különböző böngészőket szeretne támogatni. Nem lehetne valahogyan elérni, hogy minden eseménykezelő úgy hívódjon meg, hogy az első paramétere az eseményobjektum? Ehhez az kell, hogy a Microsoft módszerbeli regisztráció során olyan függvényt regisztráljunk, ami a kezelő függvény első paraméterének a window.event objektumot adja meg. Ezt a függvényt nem kell nevesíteni, elég rögtön a felhasználásnál névtelen függvényként megadni. A regisztrálási folyamat a kezelotRegisztral függvényben történt, így ide vezetjük be ezt a változást is:
function kezelotRegisztral(elem, tipus, kezelo) { if (elem.addEventListener) { /* ... */ } else if (elem.attachEvent) { elem.attachEvent('on' + tipus, function () { kezelo(window.event); }); } else { /* ... */ } }
A fenti függvény nagy hátránya, hogy az attachEvent metódusnál a kezelo függvény meghívásakor a this nem az elem-re, hanem a window objektumra fog mutatni. Szerencsére a JavaScriptben megadható, hogy a this hova mutasson egy függvény meghívásánál a call metódus segítségével. Így a kezelotRegisztral a következőképpen nyeri el végleges alakját:
function kezelotRegisztral(elem, tipus, kezelo) { //Szabványos módszer if (elem.addEventListener) { elem.addEventListener(tipus, kezelo, false); } //Microsoft módszer else if (elem.attachEvent) { elem.attachEvent('on' + tipus, function () { return kezelo.call(elem, window.event); }); } //Tradicionális módszer else { elem['on' + tipus] = kezelo; } }
Az eseményobjektum tulajdonságai között igen nagy eltérések lehetnek a böngészőknél, főleg itt is a régebbi Internet Explorerekre kell gondolni. Az egyik ilyen különbség a lenyomott billentyű karakterének a kódja. Szabványos módon a keyCode tulajdonságon keresztül lehet elérni, régebbi Internet Explorerekben azonban a which tulajdonság tartalmazta ezt az értéket. Az egységes megoldás során megint csak ellenőrizni kell, hogy elérhető-e a szabványos tulajdonság. Ha igen, akkor azt használjuk, ha nem, akkor megnézzük, hogy más módszerrel elérhető-e a karakter kódja. Ennek módja például a következő:
function fv(e) { var kod; if (e.keyCode) kod = e.keyCode; else if (e.which) kod = e.which; }
Az eseményt eredetileg kiváltó objektum elérése is más tulajdonságon keresztül történt: a szabványos target mellett bizonyos böngészőkben az srcElement tulajdonság szolgált erre. Az egységes megoldás egymás után végignézi, melyik elérhető, és azt alkalmazza:
function fv(e) { var obj; if (e.target) obj = e.target; else if (e.srcElement) obj = e.srcElement; if (obj.nodeType == 3) { // Safari bug obj = obj.parentNode; } }
A kód végén található elágazás megoldást nyújt régebbi Safari böngészők azon hibájára, miszerint az eseményt a szöveges csomóponton és nem az azt tartalmazó elemen hívták meg. Így ha szöveges csomópontról van szó, akkor annak szülőelemét állítja be az eseményt eredetileg kiváltó objektumnak.
Összegezzük az előző és a mostani fejezetben az eseménykezelésről megszerzett ismereteinket, és nézzük át a fontosabb fogalmakat és tudnivalókat!
//Feliratkozás elem.addEventListener('típus', függvény, false); //Leiratkozás elem.removeEventListener('típus', függvény, false);
function fv(e) { //e az eseményobjektum }
Valahány mozifilmről tároljuk a címét, megjelenési évét, hosszát és rendezőjét. Mivel sok film is lehet, ezért olyan felületet szeretnénk, ahol egy szűrőmezőbe írva csak azok a filmcímek jelennek meg, amelyek tartalmazzák a szűrőmezőbe írt szöveget. A kiválasztott filmcím fölé víve az egeret pedig jelenítsük meg az adott film összes részletét!
A feladat szövegéből kétféle feladat olvasható ki. Az első szerint a felületen megadott részszöveg alapján filmcímeket kell megjelenítenünk. Ez egy kiválogatás programozási tétellel megoldható. A bemeneten filmeknek a sorozatát látjuk, a kimeneten vagy csak filmcímek sorozata jelenik meg, vagy az egész filmrekordot válogatjuk ki. Most ez utóbbit választjuk.
A másik feladat szerint az egeret egy filmcím fölé húzva megjelenik a film összes adata. De honnan tudjuk, melyik film adatát kell megjeleníteni? Egy valós példában minden rekordhoz tartozik egy egyedi azonosító. Ezt az azonosítót kellene a listában elrejteni valahova, ez alapján pedig könnyű és egyértelmű lenne a kiválasztás folyamata. Mivel az ehhez kapcsolódó ismeretek még nincsenek birtokunkban, ezért inkább a film címe alapján választjuk ki a rekordot. Ez egy kiválasztás programozási tétel lesz, hiszen a film címe biztosan szerepel a tömbben.
A feladat központi adatszerkezete a filmrekordokat tároló tömb. Ezt JavaScriptben objektumok tömbjeként fogjuk reprezentálni. A kiválogatás eredményeképpen előálló szűrtFilmek tömb pedig ugyanilyen szerkezetű lesz.
filmek = [ { cim: 'Passió', ev: 2004, hossz: 127, rendezo: 'Mel Gibson' }, { cim: 'Szerelempróba (Tűzálló házasság)', ev: 2008, hossz: 122, rendezo: 'Alex Kendrick' }, { cim: 'Bátorság férfiai', ev: 2011, hossz: 129, rendezo: 'Alex Kendrick' }, { cim: 'Pio atya - A csodák embere', ev: 2000, hossz: 200, rendezo: 'Carlo Carlei' } ];
A specifikációnál ismertetett két feladatot egy-egy függvényként valósítjuk meg. A kiválogatást egy kivalogat, a kiválasztást egy kivalaszt nevű függvény végzi el. A kiválogatás során az indexOf szövegműveletet használjuk, amellyel megnézzük, hogy a tömbbeli filmcímben szerepel-e a szűrőfeltétel részszövegként. Az indexOf -1-et ad vissza, ha nem, egyébként ennél nagyobb értéket. Ha szerepel a részszöveg a címben, akkor a kimeneti szurt tömb végére rakjuk a filmrekordot.
function kivalogatas(filmek, szuro) { var szurt = []; for (var i = 0; i < filmek.length; i++) { if (filmek[i].cim.indexOf(szuro) > -1) { szurt.push(filmek[i]); } }; return szurt; } function kivalaszt(filmek, filmCim) { var film; var i = 0; while (filmek[i].cim !== filmCim) { i++; } return filmek[i]; }
A feladat elég konkrétan meghatározza a felület működését. A szűrőmező egy szöveges beviteli mező lesz (szuro azonosítóval), a filmcímek felsorolásként kerülnek kiírásra egy filmlista azonosítójú ul listában. Végül egy film adatai egy film id-jú div-ben kerülnek kiírásra, egy adat egy bekezdésben.
A kiindulási HTML szerkezet a következő:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Filmlista</title> <script type="text/javascript" src="filmlista.js"></script> </head> <body> <h1>Filmlista</h1> <form> Filmcím szűrése: <input type="text" id="szuro"> <ul id="filmlista"></ul> <div id="film"></div> </form> </body> </html>
A leszűrt filmek címeit a filmlista azonosítójú ul elemben kell megjeleníteni listaelemekként. Ehhez végig kell menni a tömbön, és mindegyik film címét <li></li> elem közé kell tenni. Az így kialakuló HTML-t mint szöveget adja vissza a tombbolLista függvény.
function tombbolLista(szurtFilmek) { var s = ''; for (var i = 0; i < szurtFilmek.length; i++) { s += '<li>' + szurtFilmek[i].cim + '</li>'; }; return s; };
Egy film részleteit megjelenítő HTML-t ugyanúgy szövegként állítja elő a filmbolHTML függvény.
function filmbolHTML(film) { return '<p>Cím: ' + film.cim + '</p>' + '<p>Megjelenés éve:' + film.ev + '</p>' + '<p>Hossza: ' + film.hossz + '</p>' + '<p>Rendező: ' + film.rendezo + '</p>'; };
Sorrendben haladva az első esemény, amire reagálnunk kell, a szűrőmezőbe gépelés. Itt három eseményt is jelez a mező: keydown, keypress, keyup. Ezek közül számunkra az utóbbi lesz a megfelelő, ugyanis csak ennél olvasható ki a beviteli mező értékeként a leütött karakter. A keyup eseményhez egy szur nevű függvényt rendelünk, amely kiolvassa a szöveges beviteli mező értékét, ezzel leszűri a tömböt, majd a kiválogatás eredményét megjeleníti a listában.
function szur() { //Beolvasás var szuro = this.value; //Feldolgozás var szurtFilmek = kivalogatas(filmek, szuro); //Kiírás $('filmlista').innerHTML = tombbolLista(szurtFilmek); }
A másik esemény, amelyhez függvényt kell kapcsolnunk, az, amikor egy filmcím fölé visszük az egeret. Ebben az esetben listaelemenként is kezelhetnénk a mouseover eseményt, de ez egyrészt azt jelentené, hogy a listaelemek létrehozása után azokon végig kellene menni, hogy egyesével eseménykezelőt rendeljünk, másrészt a listaelemeket minden szűrésnél újrageneráljuk. Adja magát, hogy akkor ne listaelemenként, hanem azok közös ősénél, magánál a listánál kezeljük az eseményt, és a kezelését visszadelegáljuk listaelem szintre. Ez utóbbi azt jelenti, hogy meg kell nézni, hogy eredetileg ki is jelezte az egér elem fölé mozgását. Ezt követően már csak ki kell olvasni a listaelemben lévő szöveget, a tömbből ezzel kiválasztani a filmet, és azt megjeleníteni a film azonosítójú div-ben.
function megjelenit(e) { if (e.target.tagName === 'LI') { //Beolvasás var li = e.target; var cim = li.innerHTML; //Feldolgozás var film = kivalaszt(filmek, cim); //Kiírás $('film').innerHTML = filmbolHTML(film); } }
A film részleteit el kell tüntetni akkor, amikor az egeret levisszük egy listaelemről, vagy magáról a listáról:
function torolFilm () { $('film').innerHTML = ''; }
Az eseménykezelők regisztrálása pedig a következőképpen történik:
init = function() { //Eseménykezelők regisztrálása $('szuro').addEventListener('keyup', szur, false); $('filmlista').addEventListener('mouseover', megjelenit, false); $('filmlista').addEventListener('mouseout', torolFilm, false); }; window.addEventListener('load', init, false);
Ha a fenti kódokat egy filmlista.js nevű állományba gyűjtjük össze, akkor az alkalmazás kipróbálható.
Az alkalmazást működés közben az alábbi képernyőkép mutatja.
A fenti JavaScript kód a T.film névtérbe szervezve a következőképpen alakul:
//Névtér létrehozása var T; if (!T) { T = ; }; T.film = ; //Segédfüggvények var $ = T.$ = function(id) { return document.getElementById(id); }; //Adatszerkezetek T.film.filmek = [ { cim: 'Passió', ev: 2004, hossz: 127, rendezo: 'Mel Gibson' }, { cim: 'Szerelempróba (Tűzálló házasság)', ev: 2008, hossz: 122, rendezo: 'Alex Kendrick' }, { cim: 'Bátorság férfiai', ev: 2011, hossz: 129, rendezo: 'Alex Kendrick' }, { cim: 'Pio atya - A csodák embere', ev: 2000, hossz: 200, rendezo: 'Carlo Carlei' } ]; //Feldolgozó függvények T.film.kivalogatas = function(filmek, szuro) { var szurt = []; for (var i = 0; i < filmek.length; i++) { if (filmek[i].cim.indexOf(szuro) > -1) { szurt.push(filmek[i]); } }; return szurt; }; T.film.kivalaszt = function(filmek, cim) { var film; var i = 0; while (filmek[i].cim !== cim) { i++; } return filmek[i]; }; //Kiíró függvények T.film.tombbolLista = function (filmek) { var s = ''; for (var i = 0; i < filmek.length; i++) { s += '<li>' + filmek[i].cim + '</li>'; }; return s; }; T.film.filmbolHTML = function (film) { return '<p>Cím: ' + film.cim + '</p>' + '<p>Megjelenés éve:' + film.ev + '</p>' + '<p>Hossza: ' + film.hossz + '</p>' + '<p>Rendező: ' + film.rendezo + '</p>'; }; //Eseménykezelő függvények T.film.szur = function() { //Beolvasás var szuro = this.value; //Feldolgozás var szurtFilmek = T.film.kivalogatas(T.film.filmek, szuro); //Kiírás $('filmlista').innerHTML = T.film.tombbolLista(szurtFilmek); T.film.torolFilm(); }; T.film.megjelenit = function(e) { if (e.target.tagName === 'LI') { //Beolvasás var li = e.target; var cim = li.innerHTML; //Feldolgozás var film = T.film.kivalaszt(T.film.filmek, cim); //Kiírás $('film').innerHTML = T.film.filmbolHTML(film); } }; T.film.torolFilm = function () { $('film').innerHTML = ''; }; //Eseménykezelők regisztrálása T.film.init = function() { //Eseménykezelők regisztrálása $('szuro').addEventListener('keyup', T.film.szur, false); $('filmlista').addEventListener('mouseover', T.film.megjelenit, false); $('filmlista').addEventListener('mouseout', T.film.torolFilm, false); }; window.addEventListener('load', T.film.init, false);
A tananyag az ELTE - PPKE informatika tananyagfejlesztési projekt (TÁMOP-4.1.2.A/1-11/1-2011-0052) keretében valósult meg.
A tananyag elkészítéséhez az ELTESCORM keretrendszert használtuk.